Skip to content

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.edu where XX is an ecelinux server 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 system
  • configure: Configure script for the build system
  • configure.ac: Used to generate the configure script
  • scripts: Scripts used by the build system
  • ece2300: ECE 2300 unit testing library and miscellaneous macros
  • lab4/lab4.mk: Tells build system about files in this subproject
  • lab4/tinyrv1.v: Verilog helper module for the TinyRV1 ISA
  • lab4/test/ProcFL.v: Verilog functional-level model for the TinyRV1 ISA
  • lab4/sim/proc-isa-sim.v: Interactive TinyRV1 ISA simulator
  • lab4/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

% cd ${HOME}/ece2300/sec08
% mkdir -p build
% cd build
% ../configure
% make proc-isa-sim

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:

% alias %=""

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.

% cd ${HOME}/ece2300/sec08/build
% code ../lab4/asm/prog1.asm

It should look like this:

  addi x1, x0, 2   # R[x1] = 2
  addi x2, x0, 2   # R[x2] = 2
  add  x3, x1, x2  # R[x3] = R[x1] + R[x2]

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.

% cd ${HOME}/ece2300/sec08/build
% ../scripts/tinyrv1-assemble ../lab4/asm/prog1.asm

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.

% cd ${HOME}/ece2300/sec08/build
% ../scripts/tinyrv1-disassemble prog1.bin

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.

% cd ${HOME}/ece2300/sec08/build
% code ../lab4/test/ProcFL.v

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.

% cd ${HOME}/ece2300/sec08/build
% ./proc-isa-sim +bin=prog1.bin +step

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.

% cd ${HOME}/ece2300/sec08/build
% ./proc-isa-sim +bin=prog1.bin +tui

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.

  addi x1, x0, 0x100
  lw   x2, 0(x1)

  .data
  .word 1
  .word 3
  .word 5
  .word 7

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.