Solutions for the RISC-V Exercises

Overview

You can download a source code archive with the completed logisim circuits.

R-Type Instructions

ALU

Here is a working solution for the ALU:

ALU

It is basically a big multiplexer that selects the adequate result among the different computations. Each computation has been realised by a basic gate or a block from the logisim arithmetic library. Note that we have extracted the lower five bits of the second input for the shift operations.

Decode Unit

Here is a working decode unit for R-type instructions:

Decode unit for R-type instructions

For R-type instructions, there is actually not much to do, since most outputs are just redirections of wires from the instruction word. We have extracted bit 30 (cf label sub), which is used together with the funct3 field to construct the ALU operation output. For now, both write and ALUsrc outputs can be hard wired to the value 1: All instructions write the register file, and all of them use the second register for ALU computations.

We have already prepared the immediate constant, even if it is not used by R-type instructions. We make use of the bit extender module of the logisim library for sign extension from 12 to 32 bits.

Immediate Instructions

Here is the new version of the decode unit:

Decode unit for R-type and immediate instructions

There are two changes with respect to the previous version:

  1. We now use bit 5 of the opcode to decide on the second ALU source
  2. The same bit needs to be taken into account to determine the ALU operation

For the first part (ALU source), note that the opcodes for R-type instructions (0110011) and immediate instructions (0010011) only differ in bit 5. We can therefore directly use this bit (extracted to the label sel_reg) to select the ALU source, where 1 corresponds to the second regiser, and 0 to the sign extended immediate value.

For the second part, note that the only R-type instruction making use of bit 30 for the ALU operation is sub. For all other R-type instructions, this bit is zero, and therefore also the most significant bit of the ALUop output. Note also that there is no subtract immediate instruction, all immediate instructions will set the MSB of ALUop to zero. We need to be careful not to use bit 30 (label sub) for immediate instructions, since bit 30 is part of the immediate constant encoded in the instruction word. It could be 1 or 0, depending on the value of the constant. This is why we need to add an AND gate in order to set the MSB of ALUop to zero for all immediate instructions.

Load instruction

Data Path

Here is the data path for the load word instruction lw:

Data path for load

Let's zoom in on the interesting part:

Data path for load

The address of the data memory (first register plus constant offset) will be computed by the ALU, this corresponds actually to the same computation as for the addi instruction. For the moment, we can leave the data input of the memory unconnected, we will need it later to store data. Don't forget to connect the clock and to connect the write input to 0 (ground symbol).

We now have two different possibilities for the data written back to the register file: It could be the ALU result (for R-type and immediate instructions) or the data read from the data memory (for load instructions). We need to add another multiplexer in order to choose among these two options. The select input is connected to the new control signal load.

Decode Unit

Here is the new version of the decode unit, taking into account lw:

Decode unit for load instructions

There are two changes with respect to the previous version:

  1. We need to compute the new load signal
  2. We need to update the computation of the ALUop signal

For the first part, we can simply use a comparator to check if the opcode corresponds to the lw instruction (0000011 or 3 in decimal).

Remember that we want the ALU to perform an addition of the first register and the immediate constant from the instruction word. So we need to make sure that

  • The ALUsrc chooses the immediate value for the second operand (0)
  • The ALUop is set to addition (0)

Looking at the lw opcode, we notice that we can reuse bit 5 (label sel_reg) for the ALU source, since it is 0. Unfortunately, we cannot reuse the funct3 field for the ALU operation, since for load instructions, this field indicates the size of the loaded data (we only do word size loads here). So instead we add a multiplexer that will set the ALU operation to zero if we are decoding a load instruction. For the select input of the multiplexer, we can simply use the newly created load signal.

Store Instruction

Data Path

There are two changes in the data path:

  • We need to connect the new store signal to the write control input of the data memory
  • We need to connect the second register output to the Wdata input of the data memory

Decode Unit

Here is a working implementation of the decode unit for store instructions:

Decode unit for load instructions

There are several changes:

  1. The new store control signal
  2. The immediate value
  3. The ALU operation
  4. The ALU source
  5. The write signal

We can compute the store control signal in the same way as the load signal, by comparing the opcode with the value for store instructions (0100011 or 23 in hexadecimal). The store signal will be useful for the computation of the remaining control signals.

The immediate value needs to be adapted since it is encoded differently for store instructions. The reason is that we have two source registers, but no destination register. The design decision of the RISC-V creators was to always keep register addresses at the same place in the intruction word. Therefore, the five lower bits of the immediate value cannot be stored in bit 20 to 24, since this is the place for the second source register address. Instead, we use the place freed by the destination register (bits 7 to 11). In order to distinguish between these two immediate encodings, we add a multiplexer with the store signal as select input.

The ALU operation is the same as for load instructions (0), since we need to compute the sum of the first register and the immediate value. We just add a condition to the already existing multiplexer by adding an OR gate, taking load and store as inputs.

Regarding the ALU source, unfortunately, the store instruction breaks the simple logic that worked for all previous instructions, since the opcode bit number 5 (labeled sel_reg) is 1 for store, but ALUsrc must be 0 in order to select the immediate value as second operand. We can fix this by adding an AND gate with sel_reg and the negation of store as inputs. In this way, ALUsrc is set to 0 whenever we decode a store instruction.

Finally, we need to adapt the write signal, since store instructions do not write the register file (they write to the memory instead). The solution is to define write as the negation of store.