Lab 6 - RISC-V Control

Lab 6 - RISC-V Control

For this laboratory you will design the control unit for a simplified, multi-cycle, non-pipelined datapath you designed in the previous laboratory. Your simple RISC-V processor will be able to execute simple assembly language programs.

Avg Hours: 6.4, 5.6 (Winter 2022, Winter 2021)

Learning Outcomes

  • Implement a multi-cycle control unit for the RISC-V datapath in SystemVerilog
  • Verify the control unit of the RISC-V processor by executing a simple program

Preliminary

A simple single-cycle control implementation of the RISC-V processor is described in detail in Sections 4.4 (pages 269-283) of the textbook. You will need to understand this section of the book in great detail to complete this lab. You will likely need to refer to these sections of the textbook as you complete this lab. Make sure you have access to your textbook while completing this lab. You will be creating a controller for your datapath to implement the following 21 instructions:

Type Instruction
Arithmetic/Logic add, sub, slt, sll, srl, sra
Arithmetic/Logic Immediate addi, slti, slli, srli, srai
Logical and, or, xor
Logical Immediate andi, ori, xori
Branch beq
Memory lw, sw

For this lab, you will be creating a multi-cycle controller that executes each instruction in five clock cycles. You will implement a state machine that sequences between the following five states for instruction execution:

State Purpose
IF Instruction fetch: provide the PC to the instruction memory
ID Instruction Decode: decode the received instruction and start register access
EX Execute: execute the operation in the ALU
MEM Memory: perform memory access (for ld/sd)
WB Write Back: write new data into register file

A state diagram demonstrating these instruction steps is shown below:

The primary purpose of this lab is to implement the state machine and generate the control signals for the datapath unit. The following discussion and set of questions will help you understand the logic you need to create the control signals.

ALUCtrl

‘ALUCtrl’ is a four-bit control signal that dictates the operation of the ALU. As described in the book and in the class lecture, generating the ‘ALUCtrl’ signal requires multi-level decoding involving the ‘opcode’ field of an instruction as well as the ‘funct3’ and ‘funct7’ bits. The decoding logic should set the ALU operation based on the instruction as follows:

  • load and store: addition
  • branch: subtraction
  • ALU operations: operation based on funct3 and funct7 (including shift instructions)

To test your understanding of the decoding necessary for this signal, determine the value of the ALUCtrl signal for each of the following instructions:

32-bit instruction ALUCtrl
0x004b2283 0010
0x403100b3  
0x0ff0c193  
0x04510463  
0x01830493  
0x0078e533  
0x0062a233  
0x0028a623  
0x01d3f2b3  
0x00350533  
0x0163f293  
0x0ff9a693  

ALUCSrc

When ‘ALUSrc’== 0, the register read 2 output is used as the input to operand 2 of the ALU. When ‘ALUSrc’== 1, immediate data for the input to operand 2 of the ALU (see Figure 4.19 in the text). Immediate data is needed for ‘load’, ‘store’, and ‘ALU Immediate’ instructions. You can determine the value of ‘ALUSrc’ directly from the opcode bits of the instruction.

Determine the value of the ‘ALUSrc’ signal for each of the following instructions (for instructions in which it doesn’t matter, enter a ‘0’ or a ‘1’).

32-bit instruction ALUSrc
0x004b2283 1
0x403100b3  
0x0ff0c193  
0x04510463  
0x01830493  
0x0078e533  
0x0062a233  
0x0028a623  
0x01d3f2b3  
0x00350533  
0x0163f293  
0x0ff9a693  

MemRead and MemWrite

Two signals control the memory interface: ‘MemRead’ and ‘MemWrite’. The ‘MemRead’ signal is set for ‘load’ instructions and the ‘MemWrite’ signal is set for ‘store’ instructions. Unlike the previous signals, these signals should only be set during the ‘MEM’ stage of the instruction state machine.

Determine which of the following instructions should set the ‘MemRead’ signal and ‘MemWrite’ signals during the ‘MEM’ instruction stage:
(0x004b2283, 0x403100b3, 0x0ff0c193, 0x04510463, 0x01830493, 0x0078e533, 0x0062a233, 0x0028a623, 0x01d3f2b3, 0x01d3f2b3, 0x00350533).

MemtoReg and RegWrite

The ‘RegWrite’ signal is used to control when values are written to the register file. For this five-cycle execution sequence, ‘RegWrite’ should only be set to ‘1’ during the ‘WB’ state of the instruction state machine. Some instructions do not write back to the register file so this signal will stay low for the entire five clock cycle instruction sequence.

The ‘MemtoReg’ signal is based on the current instruction and is set to ‘1’ only when executing a ‘load’ instruction. This signal does not depend on the current state (it doesn’t matter what this signal is during all state except the ‘WB’ state).

Determine which instructions should set the ‘RegWrite’ signals during the ‘WB’ instruction stage.

loadPC and PCSrc

The ‘loadPC’ signal is used to update the PC with a new value. The ‘loadPC’ signal should be set to ‘1’ during the ‘WriteBack’ state of every instruction (thus loading a new PC value before entering the next IF state).

The ‘PCSrc’ signal is used to indicate which value to load to the PC: ether PC+4 for regular instructions or PC+branch_offset for branch taken operations. This signal is based on both the current instruction and on the value of the ‘Zero’ signal. This signal should be set to ‘1’ when the following conditions are present: (1) current instruction is a “BEQ” function, and (2) the Zero flag is high.

Assuming the Zero flag is high, determine which of the following instructions should set the ‘PCSrc’ signal to ‘1’.

Exercises

Before proceeding with your laboratory exercises, update your repository with the latest lab starter code.

Exercise #1 - Verilog RISC-V Processor

For this exercise you will implement the basic RISC-V processor as described in the textbook. Begin your exercise by creating an empty Verilog module named riscv_multicycle.sv with the following top-level ports.

Module Name: riscv_multicycle      
Parameter   Width Default Value
INITIAL_PC   32 0x00400000
Port Name Direction Width Function
clk Input 1 Global clock
rst Input 1 Synchronous Reset
instruction Input 32 Instruction data from Instruction Memory
dReadData Input 32 Data read from data memory
PC Output 32 Program Counter
dAddress Output 32 Address for data Memory
dWriteData Output 32 Data to write into data memory
MemRead Output 1 Control signal indicating memory read
MemWrite Output 1 Control signal indicating memory write
WriteBackData Output 32 Data being written to registers (for debug)

Like the datapath module in the previous lab, your module should have a parameter for the INITIAL_PC. The following steps will guide you through the process of creating this module.

1. Datapath

The first step is to instance the datapath circuit you created in the previous lab. When you instance your datapath circuit, you should pass the INITIAL_PC parameter of this module to your datapath module. This parameter indicates the initial value in memory where your ‘.text’ segment will execute. The organization of the datapath circuit in relation to the control logic and the top-level ports is shown in the figure below:

Attach the following ports in the datapath module to their corresponding top-level port within this new module:

  • PC
  • instruction
  • dAddress
  • dReadData
  • dWriteData

The rest of the logic within this module will generate the control signals that control the datapath module and will be described in more detail below.

2. State Machine

Create a five-state state machine that sequences through the following five steps of executing an instruction:

  • IF: Instruction Fetch
  • ID: Instruction Decode
  • EX: Execute
  • MEM: Memory Access
  • WB: Write Back

The state diagram for this state machine is shown below. The state machine should return to the ‘IF’ state when the synchronous reset is provided.

3. Control Signals

With the state machine created, create the control signals that will control the datapath.

  • The signals ALUSrc, ALUCtrl, and MemtoReg, depend on the current instruction value only. You do not need to use the current processor state to set these signals.
  • The PCSrc depends on the current instruction and the zero signal. It also does not need to use the current processor state.
  • The signals RegWrite, MemRead, and MemWrite should only be asserted during the appropriate processor state.
  • The loadPC depends only on the current processor state (it operates the same for all processor instructions).

As you create the logic for your controller, you may need additional constants in your SystemVerilog code. If additional constants are needed, add them to the riscv_datapath_constants.sv file in the include directory of your project.

Exercise #2 - Testbench Simulation

For this exercise, you will simulate your multicycle processor using a testbench that uses the simple_datapath.s test program used in the previous lab. The testbench is named tb_multicycle.sv and requires a number of files to properly execute. Create a makefile rule named sim_tb_multicycle to run your test bench. Implement the following in your makefile to create the testbench:

  • Generate the simple_datapath_text.mem and simple_datapath_data.mem memory files from the simple_datapath.s in lab 5.
  • Generate a log file named sim_tb_multicycle.log.
  • Add the --include ../include flag to xvlog so that the files in the ../include directory are found
  • Compile the file ../include/riscv_memory.sv. This is a memory module that will be used for both the instruction and data memories.
  • Compile the tb_riscv_pkg.sv before the tb_multicycle.sv file.
  • Add the flag -timescale 1ns/100ps to your xelab command for simulation.

Simulate and debug your design until you have no errors in the testbench.

Answer the questions in the lab report concerning the testbench simulation

Exercise #3 - Simple Assembly Program

For this exercise you will write your own assembly language program that runs on your multicycle processor. This program should be implemented in a file named bounds.s. The data segment for this program starts at 0x0 (“Compact, Data at Address 0” memory model). The details of the program are as follows:

  • A companion file named bounds_data.s is provided in the starter code. This file contains the test data in the .data segment that your program will operate on. The details of this file are as follows:
    • It contains two constants, MAX_VALUE and MIN_VALUE using the .eqv directive that you will need to use in your program.
    • It contains an array of 32-bit words in the global .data segment that you will
    • The end of the array ends with the value of 0. This value signals the end of the array and you will need to check for it in your program
  • Add the statement .include "bounds_data.s" in your bounds.s file. This statement is similar to an include directive in C that will insert the bounds_data.s code into your code.
  • Create a main function that iterate over each element of the bounds_data array and process each element as follows:
    • If the value is 0, end your iteration over the array and perform the end of program functions discussed below
    • If the value is greater than the MAX_VALUE constant, overwrite the data with the MAX_VALUE constant. In addition, increment the a1 register (this register is used to count the number of array elements that are greater than MAX_VALUE).
    • If the value is less than the MIN_VALUE constant, overwrite the data with the MIN_VALUE constant. In addition, increment the a2 register (this register is used to count the number of array elements that are less than MIN_VALUE).
    • After adjusting the array element with either max or min, add the result to the a0 register (this register is used to hold a running count of the adjusted array sum)
  • At the end of the program after evaluating each array element, execute the following instructions:
    • addi x0, a0, 0
    • addi x0, a1, 0
    • addi x0, a2, 0
    • ebreak
    • nop

Simulate your program in the RARS simulator to make sure it is executing properly. Note: You need to configure the simulator with the “Compact, Data at Address 0” memory configuration when simulating in RARS. This can be done in the GUI by selecting: Settings->Memory Configuration “Compact, Data at Address 0”. Review the values of the a0, a1, and a2 registers to confirm your program is executing properly.

After successfully simulating your program in RARS, simulate your program within the tb_multicycle.sv testbench. Create a makefile rule named sim_tb_multicycle_bounds to run your bounds.s program. Implement the following in your makefile to create the testbench:

  • Generate the files bounds_text.mem and bounds_data.mem memory files from your bounds.s and the included bounds_data.s files.
    • Use the mc CompactDataAtZero option when generating your files to indicate the memory configuration
  • When elaborating the tb_multicycle simulation model using the xelab, add the following flags:
    • -generic "instruction_memory_filename=bounds_text.mem" This causes the testbench to use the bounds_text.mem file instead of the default filename
    • -generic "data_memory_filename=bounds_data.mem"" This causes the testbench to use the bounds_data.mem file instead of the default filename
    • -s tb_multicycle_bounds This flag indicates that the simulation should be named tb_multicycle_bounds to distinguish it from your previous simulation with the default name of tb_multicycle
  • When simulating with the xsim command, use the tb_multicycle_bounds as the argument rather than tb_multicycle (match the -s flag from the elaboration)
  • Generate a simulation log file named sim_tb_multicycle_bounds.log.

Exercise #4 - Synthesis

The final exercise in this lab is to synthesize your single-cycle RISC-V control unit in ‘out_of_context’ mode. Create a makefile rule named riscv_multicycle_synth.dcp that runs this synthesis script. This rule should generate a synthesis log file named riscv_multicycle_synth.log as well as the design checkpoint file riscv_multicycle_synth.dcp. You will not be generating a bitfile for this lab, so you do not need to perform the implementation step

Summarize the estimated resources for your synthesized logic in the table below. Note that you will have a warning that you are using too many I/Os. This is ok since we are not downloading a bitstream for this lab.

Resource Estimation
LUT  
RAM32*  
FD*  

Pass Off

The final step in the laboratory process is to complete the ‘pass off’. Carefully review the instructions for Git Submission as you prepare your submission for this lab. You will need to run the following command successfully to submit your lab:

python3 passoff.py --submit

Include the following information at the end of your laboratory report.

How many hours did you work on the lab?

Provide any suggestions for improving this lab in the future

How did you use AI to help you with this lab



Last Modified: 2026-02-20 02:10:15 +0000