Section 8: ISA Simulators
In this discussion section, we will learn how to use a Verilog functional-level (FL) model. This is a completely new kind of Verilog modeling which only models the functional-level behavior of a module and is not meant to be synthesizable. A FL model is a kind of "executable specification" which can be very useful for verification, since we can compare the behavior of our gate-level or RTL model to the FL model to ensure correctness.
We will be exploring a FL model of a TinyRV1 processor; these kind of models are often called "instruction set architecture simulators" or "ISA simulators" since they model just the ISA without any of the microarchitectural implementation details. Go ahead and open up the TinyRV1 ISA manual here:
1. Logging Into ecelinux with VS Code
Follow the same process as previous discussion sections. Find a free workstation and log into the workstation using your NetID and standard NetID password. Then complete the following steps (described in more detail in the last discussion section):
- Start VS Code
- Install the Remote-SSH, Verilog, and Surfer extensions
- Use View > Command Palette to execute Remote-SSH: Connect Current Window to Host...
- Enter
netid@ecelinux-XX.ece.cornell.eduwhere XX is anecelinuxserver number - Install the Verilog extension on the server
- Use View > Explorer to open your home directory on
ecelinux - Use View > Terminal to open a terminal on
ecelinux
There is no need to fork the repo for today's discussion section. Simple clone the repo as follows.
% source setup-ece2300.sh
% mkdir -p ${HOME}/ece2300
% cd ${HOME}/ece2300
% git clone git@github.com:cornell-ece2300/ece2300-sec08-isa-sim sec08
% cd sec08
% tree
The repo includes the following files:
Makefile.in: Makefile for the build systemconfigure: Configure script for the build systemconfigure.ac: Used to generate the configure scriptscripts: Scripts used by the build systemece2300: ECE 2300 unit testing library and miscellaneous macroslab4/lab4.mk: Tells build system about files in this subprojectlab4/tinyrv1.v: Verilog helper module for the TinyRV1 ISAlab4/test/ProcFL.v: Verilog functional-level model for the TinyRV1 ISAlab4/sim/proc-isa-sim.v: Interactive TinyRV1 ISA simulatorlab4/asm/prog1.asm: Simple TinyRV1 assembly program
Go ahead and create a build directory, run configure to generate a Makefile, and build the ISA simulator
To make it easier to cut-and-paste commands from this handout onto the
command line, you can tell Bash to ignore the % character using the
following command:
Now you can cut-and-paste a sequence of commands from this tutorial
document and Bash will not get confused by the % character which begins
each line.
2. Assembling a Simple TinyRV1 Program
Recall from lecture that a processor executes machine programs where a machine program is a sequence of machine instructions. Each machine instruction is a sequence of ones and zeros that tells the processor what to do. Writing machine programs by hand is quite tedious, so usually we write assembly programs which are sequence of assembly instructions. Each assembly instruction is a string that specifies the kind of instruction and all of the instruction operands. There is a one-to-one mapping between machine instructions and assembly instructions. We use two tools to converte between machine and assembly programs:
- Assembler translates assembly programs into machine programs
- Disassembler translates machine programs into assembly programs
Go ahead and open the provide assembly program in VS Code.
It should look like this:
There is one assembly instruction per line, and comments are denoted with
the #. This simple program calculates 2+2 and stores the result in
register x3.
Let's use a TinyRV1 assembler to translate this assembly program into a machine program.
The result should look like this:
00000000_00100000_00000000_10010011
00000000_00100000_00000001_00010011
00000000_00100000_10000001_10110011
There are three instructions and each instruction translates into a 32-bit machine instruction. Take a few minutes to look at ISA manual and convince yourself that the assembler has transated these three assembly instructions into the correct machine instructions.
We can save the machine instructions into a "binary file" with then -o
command line option like this:
% cd ${HOME}/ece2300/sec08/build
% ../scripts/tinyrv1-assemble -o prog1.bin ../lab4/asm/prog1.asm
% cat prog1.bin
We can use a TinyRV1 disassembler to translate the binary file which contains the machine program back into the corresponding assembly instrucitons.
3. Simulating TinyRV1 Arithmetic Instructions
Let's now use an ISA simulator to simulate this simple TinyRV1 program. Start by looking at the Verilog FL model for the TinyRV1 instruction set using VS Code.
It should be clear that this kind of Verilog modeling is very different from what we have used in the past! This Verilog FL model is not synthesizable. It is not meant to model the details of the hardware but is instead meant to provide a high-level functional level model which we can use for verification.
Let's run our TinyRV1 program binary on the ISA simulator.
Press enter to execute instruction one at a time. Press q and then enter to quit the simulation. The trace output should look like this:
cycle pc inst wreg wdata
------------------------------------------------------
0: 0x00000000 addi x1, x0, 0x002 1 0x00000002
1: 0x00000004 addi x2, x0, 0x002 2 0x00000002
2: 0x00000008 add x3, x1, x2 3 0x00000004
3: 0x0000000c illegal inst
4: 0x00000010 illegal inst
5: 0x00000014 illegal inst
The trace output shows the cycle number, program counter (PC),
instruction, what register the instruction is writing (wreg), and what
data is being written to that register (wdata).
The TinyRV1 ISA manual says that the PC is reset to 0 so the very first instruction executed is at memory address 0x000. Remember that the 0x prefix means the number is in hexadecimal. The trace output shows that the first instruction writes the value 2 to register x1, the second instruction writes the value 2 to the register x2, and the third instruction writes the value 4 to the register x3.
We can also use a text user interface (TUI) mode to better visualize the execution of the ISA simulator much in the same way as we use worksheets in lecture.
The TUI output should look like this:
> addi x1, x0, 0x002 Memory
addi x2, x0, 0x002 .------------.
add x3, x1, x2 0x1fc | 0xxxxxxxxx |
illegal inst ... | .... |
illegal inst 0x130 | 0xxxxxxxxx |
illegal inst 0x12c | 0xxxxxxxxx |
illegal inst 0x128 | 0xxxxxxxxx |
illegal inst 0x124 | 0xxxxxxxxx |
illegal inst 0x120 | 0xxxxxxxxx |
illegal inst 0x11c | 0xxxxxxxxx |
illegal inst 0x118 | 0xxxxxxxxx |
illegal inst 0x114 | 0xxxxxxxxx |
illegal inst 0x110 | 0xxxxxxxxx |
illegal inst 0x10c | 0xxxxxxxxx |
illegal inst 0x108 | 0xxxxxxxxx |
illegal inst 0x104 | 0xxxxxxxxx |
0x100 | 0xxxxxxxxx |
Prog Counter .. | .... |
.------------. 0x03c | 0xxxxxxxxx |
| 0x00000000 | 0x038 | 0xxxxxxxxx |
'------------' 0x034 | 0xxxxxxxxx |
0x030 | 0xxxxxxxxx |
Registers 0x02c | 0xxxxxxxxx |
.------------. 0x028 | 0xxxxxxxxx |
x31 | 0xxxxxxxxx | 0x024 | 0xxxxxxxxx |
... | .... | 0x020 | 0xxxxxxxxx |
x7 | 0xxxxxxxxx | 0x01c | 0xxxxxxxxx |
x6 | 0xxxxxxxxx | 0x018 | 0xxxxxxxxx |
x5 | 0xxxxxxxxx | 0x014 | 0xxxxxxxxx |
x4 | 0xxxxxxxxx | 0x010 | 0xxxxxxxxx |
x3 | 0xxxxxxxxx | 0x00c | 0xxxxxxxxx |
x2 | 0xxxxxxxxx | 0x008 | 0x002081b3 |
x1 | 0xxxxxxxxx | 0x004 | 0x00200113 |
x0 | 0x00000000 | 0x000 | 0x00200093 |
'------------' '------------'
Notice that the instructions are stored in the first three words in
memory! 0x002000093 is the hexadecimal representation of the first
instructions (addi x1, x0, 2). The instructions are always stored in
memory starting from address 0x000. The little > indicates which
instruction will be executed next. The program counter, registers, and
memory all represent the state before this instruction is executed.
Press enter to execute the first instruction. You should see the >
advance to the next instruction, the PC updated to 0x00000004, and the
value 2 in register x1. Step through all three instructions to verify
that the registers end up with the correct values. Press q and then enter
to quit the simulation.
Section Task: Experiment with Arithmetic Instructions
Modify the prog1.asm assembly program to do one of two tasks: (1)
generate the Fibonacci sequence in registers x1 through x7; or (2)
write x2 with the cube of the value in register x1. Assemble the
assembly program into a machine program, and then run the binary on
the ISA simulation using tracing and TUI mode.
4. Simulating TinyRV1 Memory Instructions
Let's now experiment with the simple program we looked at the end of the
last lecture. Modify the prog1.asm assembly program as follows:
addi x1, x0, 256 # R[x1] = 256
addi x2, x0, 9 # R[x2] = 9
sw x2, 0(x1) # M[ R[x1] ] = R[x2]
lw x3, 0(x1) # R[x3] = M[ R[x1] ]
Note that the decimal number 256 is 0x100 in hexadecimal. Try running this on the ISA simulator using TUI model. Make sure the values in the memory and registers match your expectations.
You can initialize data in memory before the program executes using the
.data and .word assembler directives. So the following program
initializes an array of four words in memory and then loads the first
word into register x2.
The .data section always starts at address 0x100 (decimal value 256) so the first word will be at address 0x100, second word will be at 0x104, and so on.
Section Task: Experiment with Arithmetic Instructions
Modify the prog1.asm assembly program to do add together all four
elements of the array and store the result back to memory at address
0x110.