Lab 7: The data path - putting everything together
Welcome back to CS2100DE! We're nearing the final destination. We've built the critical components of our CPU, namely the instruction decoder, immediate extension unit, and the ALU. Now, it's time to wire everything up and get ready to see it working. By the end of this lab, we should be able to assemble all the components of our CPU together, and get it ready for the final task next week, when we will see the CPU run real programs.
Project Work
This manual is part of the final project. You do not need to write a report for this. You should work in your project groups.
Introduction
The datapath for our RISC-V CPU
Over the past few weeks, we've built three of the main components of our CPU. Let's explore the functions that these components perform inside our CPU in a bit more detail:
-
The
Decoder
module takes the instruction itself as an input, and it produces a number of control signals as outputs. These are:PCS
goes to the PC Logic block. It determines what the PC should add to the current PC value: either the constant 4 or an offset (for branching).mem_to_reg
controls a multiplexer to select whether the ALU result or the data read from memory should be stored in a register.mem_write
controls the write enable for the data memory. We do not always want to write to memory, only on store instructions.alu_control
chooses the operation the ALU should perform.alu_src_b
controls a multiplexer that chooses whether the second operand of the ALU should be an immediate or a register value.imm_src
tells the Extender module the immediate encoding format to expect.reg_write
controls the write enable for the register file. We do not always want to write to the register file, for example for branch or store instructions.
-
The
Extend
module takes theimm_src
input from theDecoder
and part of theinstr
input to the CPU. The output goes into the first of the two multiplexers we have (the one controlled byalu_src_b
). It may be used as an input to the ALU, if the instruction is not an R-type. -
The
ALU
module takes two inputs:alu_source_a
always comes from the register file.alu_source_b
may come from the register file (R-type) or the immediate extender (all other types).
It also has two outputs: 1.
alu_flags
should go into the PC logic module. These help us determine whether or not a branch in our code should be taken. 2.alu_result
should be used to address our data memory. It should also be connected to the second multiplexer, controlled bymem_to_reg
, which determines whether the data to be written to the register file comes directly from the ALU, or from the data read from the data memory (mem_read_data
).
There are some other components of the CPU, namely the register file, the PC logic, and the PC itself. We can consider these black boxes, and do not need to understand the details of how they work. However, the code is, of course, available to peruse. For the purposes of this course, it will suffice to just understand each input and output to these modules, and to connect the outputs from each module correctly following the microarchitecture diagram.
Here is a very brief description of what each of the provided modules does:
-
RegFile
is the register file for our CPU. It takes in two source numbers, and returns the value stored in the register indexed by those source numbers. For example, ifrs1
is00011
,RD1
will contain the value stored in registerx3
. It also takes inrd
andWD
, which choose the index of the register to be written to, and the data to be written to that register, respectively.WE
is used as a write enable, to control whether registerrd
is written to. -
PC_Logic
takes in thePCS
from the instruction decoder and thealu_flags
. It uses this information to determine whether branches should be taken or not, and controls whether the PC should be incremented by 4 (branch not taken) or by some other value determined by the encoded immediate (branch taken). -
ProgramCounter
takes in the clockclk
(to count up on positive edges), the reset signalrst
(to reset to 0 when the CPU_RESET button is pressed), and the new value that PC should takepc_in
. This can either be the current PC value plus 4, or the current PC value plus the immediate offset. Of course, the outputpc
determines the current value of the program counter.
We can assume that the "Instr Memory" and "Data Memory" are outside the CPU itself, and these will be provided later. Indeed, in a regular Von Neumann architecture, the memory lives outside the CPU, so this is somewhat accurate. Notice that the input to the Instruction Memory (PC
) is an output of the CPU, while the ouput of the Instruction Memory (Instr
) is an input to the CPU. The same is true for Data Memory; outputs of the CPU are inputs to the memory and vice versa.
Wiring everything up
Today, we will be filling out the RISCV_MMC.sv
file. (2100, our course code, is MMC in Roman numerals.) This is our actual CPU. For today, we can just focus on connecting everything here. Next time, we will have the rest of the scaffolding needed to make the CPU tick, and we will be able to write and run our own programs on it.
Today's task is reasonably simple. We can follow the microarchitecture diagram from Lecture 7, Slide 20. Here are the steps we need to follow to do this:
-
Create the wires/logic elements that we need to connect all the components together. Following the microarchitecture diagram should give us all the necessary ones. If we miss any, it should become quite obvious when we try to start connecting things together, so it's not really a big deal.
-
Instantiate the modules we worked on in the previous two weeks, namely the instruction decoder
Decoder
, the immediate extension unitExtend
, and the ALUALU
. -
Download and instantiate the modules provided for this week, namely the Register File
RegFile
, the PC LogicPC_Logic
, and the Program CounterProgramCounter
itself. -
There are some discrepancies with the schematic from the schematic in the slides, namely:
- The
rst
input to the CPU should be connected to theRESET
signal of the program counter. - Our instruction decoder takes the full 32 bit instruction, instead of having
funct3
,opcode
andfunct7
as explicit inputs.
- The
This week, we will not be simulating the code. Next week, we will have a fairly complex framework to run programs and extensively debug our design.
As usual, here is an unordered collection of tips for writing the code:
-
Use a consistent naming scheme where possible. Neil personally prefers to use
PascalCase
for module names, andsnake_case
for inputs, outputs, module instances, and logic elements. This is the convention used in most of the source files. -
Remember to use the correct bit width for each wire. This is a common issue, and it causes us many headaches that are completely avoidable.
-
Ensure that all the inputs and outputs of each module are connected, and accounted for. Use the RTL Schematic to verify this. In particular, verify that
alu_result
, which is used by two components, is correctly connected. -
Remember, multiplexers can easily be instantiated in your CPU itself using the ternary operator
assign some_wire = (condition) ? value_if_true : value_if_false;
Concluding Remarks
Nice work! We've built our CPU, and we're ready to see it in action next week.
What we should know
- How the datapath of our RISC-V CPU looks like.
- How to go from a schematic to implementation in SystemVerilog.
- What all the components and signals inside our CPU do, and why they exist.