Skip to content

Champ-Goblem/2020-2021-Dissertation-RISC-V-Emulator

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

59 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

C++ RISC-V Emulator/Simulator

A C++ RISC-V RV32I instruction set simulator with support for extensions and multiple instruction set architectures (ISA)s. This was originally designed and built for a dissertation thesis in simulating RISC-V instruction sets to assist further work in developing a cryptographic hardware extension for RISC-V CPUs.

Building

Requires GCC version 10

A dockerfile is the recommended way to build this project.

A makefile is provided that aids with building the code and also running individual and end-to-end tests. Running make should be sufficient to create the build directories and output the final binary to obj/build. To remove all build objet files, the main binary and all files resulting from building tests run make clean.

The full set of tests can be built and run automatically with make testall and built with make testbuild. The test directory can also be cleaned with make testclean.

Running

As a minimum the program requires a flat binary to run, which can be specified by the --binary argument. There are also a number of arguments to choose from:

Arg Description Available Choices
--base-isa= The base architecture to use for parsing instructions RV32I
--extensions= A set of extensions to choose from T
--memory-size= The maximum size of memory to allocate for the emulator
--hardware-threads= The number of HARTs to emulate within the processor
--binary= Path to a pre-built flat binary to execute
--pause Whether or not to pause the program on startup
--runningOutput Sets if the UI will update live during the running of the processor (will slow execution dramatically)
--halt= An address to halt the processor at if reached
-h --help Show the help dialog

Some example programs can be found in the directory tests/binaries.

The General Design

HART

The class Hart is the main body of the emulator, and is the namesake of the hardware thread (HART) in the reference material for RISC-V. It is the HARTs job to control the execution of the pipeline and react to any issues that occur during the lifecycle of the program being executed, such as stalls, flushes, failed predictions and halting. The code that performs the execution of each individual instruction is abstracted away from the implementation of the HART and instead deals with the fives stages of the pipeline:

  • Fetch
  • Decode
  • Execute
  • Memory Access
  • Register Writeback

The method Hart::tick controls a single cycle of the HART, progressing an instruction in the pipeline by one stage on each call.

With pipelines comes hazards, and these are detected by the Pipeline Hazard Controller. It records the current instructions in the pipeline and determines if a register value is stale based on the the destination registers of the instructions further into the pipeline. If this is determined to be the case then the pipeline can stall, causing a momentary pause until the value for the source register is calculated. The same class is also used as an intermediary for instructions fetching from registers, and allows forwarding values from instructions that have not yet committed to registers or memory.

Processor

The class Processor is the controller for the HARTs in a system and performs the setup of each of these and main memory. The main terminal UI (TUI) directly interfaces with this class allowing it to perform pause, stop, step and resume operations on the processor. This class is also responsible for loading the initial flat binary into memory on setup.

Memory

Main memory is represented by the Memory class, it is simply a one-dimensional array of bytes, stored in a vector. A set of accessors are provided that operate on different lengths of data within memory, the current implementations are:

  • HWord -> Half word (2 bytes)
  • Word -> Word (4 bytes)
  • DWord -> Double Word (8 bytes)
  • QWord -> Quad Word (16 bytes)

It also provides some debugging methods to print or get a region of memory determined by a start position and length.

Register File

The set of registers are contained within the Register File class. This simply holds 16 or 32 registers depending on the underlying architecture, and provides accessors to these through the get and set methods.

Branch Prediction

The design of this emulator allows for fully configurable branch prediction, by providing an abstract interface to build off (AbstractBranchPredictor). This interfaces with each HART through the handleFlush, getNextPC and checkPrediction functions. The peak method is used by the TUI to show the next program counter (PC) to the user.

The current implementation supports only "simple" branch prediction, based on the implementation specified in the RISC-V specification. It states that if the sign bit of the immediate of a jumping instruction is negative, then this jump is always taken, otherwise the prediction continues onto the next instruction succeeding the jump. This is analogous to a form of loop in a high level language.

If wanted this can be expanded on in the future to provide more heuristic-based prediction.

Terminal UI

The rendering of the user interface is controlled by the Screen class. This makes use of the ftxui library to display the current program counters in the pipeline, memory output, registers output and a small console.

Instruction Set Architecture and Instructions

Each instruction once decoded is represented by the Abstract Instruction class. This holds the registers being accessed, fully decoded instruction, the results after execution and the pointers to the methods used to control execution, memory access and writeback. This allows the implementation of each specific instruction to be abstracted out of the HART and instead accessed through the pointers set in AbstractInstruction::execute, AbstractInstruction::registerWriteback and AbstractInstruction::memoryAccess for the corresponding pipeline stages.

A number of instruction types are based upon the abstract implementation mentioned above, and these are:

The meaning for each of these can be found in the reference documentation for RISC-V.

The only currently implemented ISA is RV32I or the 32-bit base instruction set. This class holds all of the methods used to perform decoding, execution, memory access and writeback of each instruction where applicable. A map of opcode to decode routine is stored in the OpcodeSpace and the subsequent steps in the pipeline are stored within the Instruction class after decoding is performed.

Design Decisions

Although RV32I is only supported currently, the decision to use vector<byte> for all data stored within the program allows the emulator to be extended to support RV64I and even RV128I on currently available 64-bit machines. This meant the usual mathematical operators had to be rewritten to support operations over a number of bytes and these can be found in bytemanip.h.

Extending the Instruction Set

Extension of the current instruction set is achieved by defining a new extension or base instruction set alongside the instruction opcodes it supports. The new class must also contain the methods for decoding, execution, memory access and register writeback, which are required to be called by the tick function of the HART.

The extension T provides an example for adding extra extensions in the future.

The decode routine is required to initialise the Instruction object of an available type. The binary form of the instruction must then be decoded into the fields of the object and the pointers to the corresponding functions set. In the case of data being fetched, this must be performed through the pipeline hazard controller. A check should always be performed for a stale register, returning a No-Operation instruction and setting the parameter stall to true if this is the case.

For jumps and branches, the calculated address should be verified in the execute stage by calling the checkPrediction method of the branch predictor.

Testing

Tests are implemented in /tests by the cxx-test framework. A set of results for the tests during development are also available in /tests/results.



Author: Cameron McDermott (champ_goblem) 2020

About

A C++ RISC-V emulator

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published