COMP 311: Lab 5

Pull the new starter code!

The starter code for each subsequent lab will be added directly into the same course repository. To update your individual repo, navigate to your workspace on the GitHub website and you should see a “Sync Fork” button. Click this and then “Update Branch.” Now if you run git pull in your terminal, you will have all the materials you need.

OR

If you went through the additional git set-up steps for lab 1, you should be able to run

git pull starter main --rebase

Then all the new code for lab 5 should be there!


Datapath

In Labs 3 and 4, you designed the ALU and the Register File. Now, it’s time to put these components together to design a functioning simple datapath that supports the following RV32I instructions:

  1. add
  2. and
  3. or
  4. slt
  5. sub
  6. addi
  7. slti
  8. andi

All of these are standard RISC-V RV32I integer ALU or ALU-immediate instructions.


Task 0. ALU Modifications

Copy your circuits from Lab 3 and Lab 4 into your Lab 5 repo.

The diagram below shows the interface of the ALU you designed in Lab 3.

Your ALU had separate control inputs for sub, Bool, and Op. In this task, you will modify the interface of the ALU so that it takes in a 4-bit ALUOperation control signal that encodes the sub, Bool, and Op control signals. (If your earlier ALU had Zero, Negative, or other flags, you may keep them, but they are not required in this lab.) Also, since we are not supporting shift instructions, you can delete the barrel shifter. The new expected interface is shown below:

A[31:0]  ────────────────▶     ┌──────────────────────┐
                               |                      |
B[31:0]  ────────────────▶     │                      │───▶  Result[31:0]
                               │         ALU          │
ALUOperation[3:0] ─────────▶   │                      │
                               └──────────────────────┘

The 4-bit ALUOperation control signal is shown in a table below. This is taken from the Appendix of optional textbook #1.

Function ALU Operation
AND 0000
OR 0001
add 0010
sub 0110
slt 0111

Testing

Your final design should be in alu.dig. Replace the test cases in your local ALU test with the test cases in alu-test.txt. Run the tests as usual.


Task 1. Control Signals (RISC-V)

The control unit is a circuit that generates the control signals for the current instruction based on the RISC-V instruction fields: opcode, funct3, and (for R-type) funct7. To begin designing the control unit, we first need to define the control signals for each instruction.

You will fill out the CSV file, control-signals.csv, with the control signals for each instruction.

Control Signals Used in This Lab

For this subset, our microcode for each instruction consists of:

So each instruction’s control word has 8 bits total:

RegWrite ALUSrc ImmSrc[1] ImmSrc[0] ALUOp[3] ALUOp[2] ALUOp[1] ALUOp[0]

In control-signals.csv, the columns are:

The DataValueForROM column is the 8 control bits concatenated together, written in binary and prefixed with 0b so Digital interprets it correctly.

Example: add

The RISC-V add instruction:

So the control bits are:

Concatenated:

1 0 0 0 0 0 1 0 = 0b10000010

The add row is provided as an example in control-signals.csv.

What You Should Do

  1. For each of the 8 supported instructions (add, sub, and, or, slt, addi, slti, andi), determine the values of RegWrite, ALUSrc, ImmSrc1, ImmSrc0, ALUOp3..0.
  2. Write those values into control-signals.csv.
  3. For each row, compute the binary DataValueForROM by concatenating the bits into the pattern:
    RegWrite ALUSrc ImmSrc1 ImmSrc0 ALUOp3 ALUOp2 ALUOp1 ALUOp0
    and write it as something like 0b10000010.

It is recommended that you complete this task using Excel or Google Sheets.


Task 2. Microcode (ROM)

The set of control signals for each instruction is known as microcode. We store the microcode in a small memory unit in our datapath called a ROM. ROM stands for read-only memory. As the name implies, it’s memory that is read-only. Why is it read only? Once your datapath is complete, we should never need to update the control signals, so there is no need to write to this memory.

You can think of the ROM as a table that stores our control signals. Each row of the ROM stores the 8-bit microcode for one instruction. The address at which you will store each instruction’s control signals is shown below.

Address Instruction
0 add
1 sub
2 and
3 or
4 slt
5 addi
6 slti
7 andi

Address 0 will store the control bits for add. Address 1 will store the control bits for sub, etc. (The instructions could have been stored in any order; the order above just matches our spreadsheet.)

Component: ROM

You can find the ROM in Components → Memory → ROM.

Place the ROM in a new file called: control-unit.dig. If you pulled the lab within the first 20 mins of it being posted (good job!!!) Kaki included a wrong control-unit. Delete it and start fresh.

ROM Inputs

ROM Outputs

Example Usage

Configuration

Let’s configure our ROM!

Upon opening up the ROM you see this window:

It will be your job to set the configuration:

Now, we want to copy and paste the DataValueForROM column from control-signals.csv into the ROM. To edit the contents of the ROM:

  1. Right-click on the ROM component and click Edit.
  2. Make sure that your values in the spreadsheet are prefixed with 0b (e.g., 0b10000010) so that they are stored correctly in the ROM.
  3. Copy that column from your CSV and paste it into the ROM editor.

Testing

There are no automated tests for this task. You should manually check:


Task 3. Control Unit (RISC-V Decode)

In this task, you will complete your implementation of the control unit. Below you will find the interface of the control unit:

Your control unit will:

  1. Use the RISC-V instruction fields opcode, funct3, and funct7 to determine which instruction is being executed.
  2. Produce a one-hot set of signals, one per instruction (e.g., is_add, is_sub, is_and, …).
  3. Feed those signals into a priority encoder to select the ROM address.
  4. Use that address to read the correct microcode from the ROM.
  5. Output the microcode bits (RegWrite, ALUSrc, ImmSrc, ALUOperation) as control signals to the rest of the datapath.

RISC-V Instruction Patterns (for this lab)

For reference, the RV32I encodings for the instructions we support are:

You can use comparators to match these bit patterns and produce one boolean signal per instruction.

Example for add:

Do the same for each instruction (is_sub, is_and, is_or, is_slt, is_addi, is_slti, is_andi).

Component: Priority Encoder

You will need to use a priority encoder to complete this task.

You can find the priority encoder at Components → Plexers → Priority Encoder.

We will use an 8-input priority encoder where:

The priority encoder sets the num output to the binary value of the highest-numbered input that is active (high). The highest priority input is in7 and the lowest priority input is in0. If multiple inputs are high (which should not happen if your decode is correct), the encoder will choose the one with the highest index.

The following truth table illustrates the operation of an 8-bit priority encoder:

in0 in1 in2 in3 in4 in5 in6 in7 num any
0 0 0 0 0 0 0 0 0b000 0
1 0 0 0 0 0 0 0 0b000 1
X 1 0 0 0 0 0 0 0b001 1
X X 1 0 0 0 0 0 0b010 1
X X X 1 0 0 0 0 0b011 1
X X X X 1 0 0 0 0b100 1
X X X X X 1 0 0 0b101 1
X X X X X X 1 0 0b110 1
X X X X X X X 1 0b111 1

Wiring the Control Unit

  1. The comparators decode the RISC-V instruction into is_add, is_sub, …, is_andi.
  2. These signals feed the priority encoder’s inputs (in0..in7).
  3. The encoder’s num output is the ROM address (A).
  4. The ROM’s D output is the 8-bit microcode for that instruction.
  5. Split those 8 bits into the individual control wires (RegWrite, ALUSrc, ImmSrc, ALUOperation) and send them to the datapath.

Testing:

An example test case for the add instruction is below:

opcode   funct3  funct7    RegWrite ALUSrc ImmSrc1 ImmSrc0 ALUOp3 ALUOp2 ALUOp1 ALUOp0
0b0110011 0b000  0b0000000 0b1      0b0    0b0     0b0     0b0    0b0    0b1    0b0

It is your job to: - Add additional tests for the remaining instructions to verify that your control unit correctly decodes them and produces the right control outputs. —

Task 4. Instruction Memory and Program Counter

The Instruction Memory and Program Counter have been set up for you in cpu.dig.

The instruction memory is a ROM. We want the instruction memory to be able to store 1024 instructions, so the ROM should have 10 address bits. Each instruction is 32 bits, so the size of each entry in the instruction memory should be 32 bits. You can see the configuration of the ROM below.

In RV32I, the total addressable memory space is 4 GB or 232 bytes. The instruction memory for this lab is actually a portion of the entire memory (specifically addresses 0x00003000 to 0x00003FFC). The size of our instruction memory is 212 bytes (1024 instructions × 4 bytes per instruction).

Our instruction memory component has 1024 entries, so we need to address it with log21024 = 10 bits instead of 32 bits.

Which bits of the program counter do we use to address the instruction memory?

There is nothing for you to complete for this task. Just make sure you understand the explanation above.


Task 5. Everything Else (RISC-V Datapath)

Complete cpu.dig by adding your:

Important RISC-V details:

When you add the Register File, the input/output names may be overlapping. To fix this, open your register-file.dig and widen the component by clicking Edit → Circuit specific settings and changing the width field.

To confirm that your circuit is functioning correctly, the provided tests verify the values of ReadReg1 and ReadReg2. To check these signals against expected values, we have to make them outputs of cpu.dig. The output pins have already been provided. Ensure you connect these output pins to their corresponding signals.


Testing (RISC-V)

Overview of Provided Files

Directions

1. Read Over the Provided Instructions

Open basic-tests.s to view the RISC-V instructions used to test your CPU. The program uses only:

and sets up a few registers to known values so we can check your datapath.

2. Convert the Instructions to Machine Code

We will use the miniRISC-V simulator/assembler.

  1. Open basic-tests.s and take a look at the RISC-V assembly instructions we’ve given you to start.
  2. In the miniRISC-V simulator, you can clear the whole window, paste in one instruction, and hit ``assemble’’. Then go to Dump Memory. It will show you the hexadecimal representation of each instruction in memory.
  3. Save this output in a new .mem file. Each line in this file should contain one machine code instruction.

3. Format the Instructions for Digital

The instructions need to be formatted to be compatible with Digital’s instruction memory.

Use format.py to convert the .mem file into a .csv file containing a single 0b... 32-bit binary word per line:

python format.py <input_file> <output_file>

Replace with the name of your .mem file.

Replace with the desired name for your output .csv file.

For example

python format.py basic-tests.mem basic-tests.csv

Load Instructions into Digital

  1. Open the generated .csv file in Excel and copy the contents.
  2. Paste the contents into the instruction memory in Digital. You can also hit Edit –> File –> Load, to load in a file!

Set Up Testing in Digital

  1. Copy and paste the contents of basic.txt into the Digital testing unit.
  2. Run the tests as normal in Digital.

Extend the Tests

Once the basic tests pass, you will need to write additional tests for the remaining instructions. Note, you will only pass as many tests as you have instructions loaded up in memory. So if you just put the first instruction in memory (addi x4, x0, -20), you will only pass the first test.

Submission Instructions

Assignments are submitted through Gradescope.