Directed Questions

Background

The RISC-V Instruction Set Architecture

The processor cores featured in this lab implement the RISC-V ISA, developed at UC Berkeley for use in education, research, and industry.

A complete software toolchain is pre-installed on the lab machines. Note that the GNU utilities are prefixed with the target triplet1 (riscv32-unknown-elf2) but otherwise function similarly to their native Binutils and GCC counterparts, which may be familiar to you. The components most relevant to this lab are:

  • riscv32-unknown-elf-gcc: GNU cross-compiler for C

Chisel

Students will not be required to write Chisel code as part of this lab beyond adding and modifying parameters as directed.

You must complete Lab 0 Exercise 1 before starting this lab.

Directed Portion (30% of lab grade)

Setup

This lab uses the same Chipyard tree you built in Lab 0. In each new shell:

source ~/cs152-env.sh

The remainder of this exercise will use ${CHIPYARDROOT} to denote the path of the working tree. Its directory structure is outlined below:

${CHIPYARDROOT}/
├── generators/                    # Chisel source code for cores/caches/peripherals/etc.
│   └── riscv-sodor/               # Sodor sources and utilities
│       ├── src/main/scala/sodor/
│       │   ├── common/            # Common source code shared between all Sodor cores
│       │   ├── rv32_1stage/        # Source code for the 1-stage core
│       │   ├── rv32_2stage/        # Source code for the 2-stage core
│       │   ├── rv32_3stage/        # Source code for the 3-stage core
│       │   ├── rv32_5stage/        # Source code for the 5-stage core
│       │   └── rv32_ucode/         # Source code for the microcoded core
│       ├── riscv-bmarks/           # Pre-compiled benchmark binaries
│       └── scripts/                # Python scripts for analyzing Sodor traces
├── test/
│   ├── custom-tests/              # Stub for open-ended question 4.2
│   └── custom-bmarks/             # Stub for open-ended question 4.1
├── scripts/                        # Contains repo initialization script
└── sims/
    └── verilator/                  # Verilator simulation directory
        ├── generated-src/          # Generated Verilog after Chisel elaboration
        └── output/                 # Simulation traces are logged here

Of particular note is that the Chisel source code for the processors can be found in $. While you do not need to understand the code to do this assignment, it may be interesting to examine the internals of a processor. Although it is not recommended that you alter any of the processors while collecting data from them in the directed lab portion (except as instructed), feel free in your own time (or perhaps as part of the open-ended portion) to modify the processors as you see fit.

Building and Simulating the Sodor Cores

The lab repository contains five different cores: 1-, 2-, 3-, and 5-stage pipelines and a microcoded processor. You built and ran the 1-stage core in Lab 0; the same make CONFIG= workflow selects any of the others. See Lab 0, Exercises 2 and 3, if you need a refresher.

Dumping Waveforms for Debugging

In the very unlikely scenario that you need to debug what you suspect to be an RTL bug, VCD-formatted waveforms can be obtained by running make run-binary-debug instead of the usual make run-binary command. Open the resulting files in a waveform viewer such as GTKWave (http://gtkwave.sourceforge.net/).

Tracing Instruction Mixes Using the 1-Stage Processor

In this lab, benchmarks are small, self-contained RISC-V programs—in other words, compiled test workloads that you run on your processor to measure how it behaves. For this section, you will look at the instruction mixes of several such programs. The $BMARKS directory contains a variety of RISC-V benchmarks: dhrystone, median, multiply, qsort, rsort, towers, and vvadd. Make sure you read each section carefully so that you only run the required benchmarks. We wish to avoid overloading the eda servers with unnecessary simulations.

Lab 0 introduced the trace format and the ${SCRIPTS}/tracer.py script, including the two fixes to instructions.py and tracer.py that the tracer needs in order to report accurate statistics. Those fixes are already applied in your tree; do not reapply them.

You will also be modifying tracer.py in Problem 3.8, so while you run the rest of your simulations, take some time to review the structure of instructions.py and tracer.py.

Part 1

Run the simulations and record the statistics and instruction mix for the following benchmarks only: median, multiply, towers, and vvadd. Remember: do not provide raw dumps. A good way to visualize this kind of data would be a bar graph. Then, answer the following questions:

  • Which benchmark has the highest arithmetic intensity?

  • Which benchmark seems most likely to be memory bound?

  • Which benchmark seems most likely to be dependent on branch predictor performance?

CPI Analysis Using the 1-Stage Processor

Consider the results gathered from the RV32 1-stage processor. Suppose you were to design a new machine such that the average CPI of loads and stores is 2 cycles, integer arithmetic instructions take 1 cycle, and other instructions take 1.5 cycles on average.

Part 2

What is the overall CPI of the machine for each benchmark?

What is the relative performance for each benchmark if loads/stores are sped up to have an average CPI of 1? Is this still a worthwhile modification if it means that the cycle time increases by 30%? Is it worthwhile for all benchmarks or only a subset? Explain.

CPI Analysis Using the 5-Stage Processor

For this section, we will analyze the effects of branching and bypassing in a 5-stage processor.3

The 5-stage processor has been parameterized to support both a fully bypassed configuration (which must still stall for load-use hazards) and a fully interlocked configuration. The fully interlocked variant performs no bypassing and instead must stall (interlock) the instruction fetch and decode stages until all hazards have been resolved.

First, verify that full bypassing is enabled in the design. Navigate to the Chisel source code:

cd ${CHIPYARDROOT}/generators/riscv-sodor/src/main/scala/sodor/rv32_5stage
vim consts.scala

The consts.scala file defines constants and compile-time parameters for the processor. Observe that the parameter on line 21 is val USE_FULL_BYPASSING = true. You can see how this parameter changes the pipeline by referring to the data path in dpath.scala (lines 269-301) and the control path in cpath.scala (lines 226-245). The data path instantiates the bypass muxes when full bypassing is activated. The control path contains the stall logic, which must account for more situations when no bypassing is selected.

As we did for the 1-stage processor in Lab 0, build the processor with the default behavior of bypassing enabled.4

cd ${CHIPYARDROOT}/sims/verilator
make CONFIG=Sodor5StageConfig

Part 3.1

Run the simulations (make sure to use the Sodor5StageConfig) and record the instruction mix for the following benchmarks only: median, multiply, towers, and vvadd.

Before proceeding, move the benchmark .out files into a new folder, as they will be overwritten in the next step. You will need the bypassed output files for Problem 3.8.

cd ${CHIPYARDROOT}/sims/verilator
mv output/chipyard.harness.TestHarness.Sodor5StageConfig/ output/chipyard.harness.TestHarness.Sodor5StageConfigBypassed/

Now disable full bypassing in consts.scala, and rerun the build (check that your Chisel code recompiles).

Part 3.2

Record the new instruction mix and CPI values for the median, multiply, towers, and vvadd benchmarks. How does full bypassing perform compared to full interlocking? If adding full bypassing would hurt the cycle time of the processor by 25%, would it be worth it? Argue your case quantitatively.

Before proceeding to the next step, make sure to reset val USE_FULL_BYPASSING = true.

Bypass Path Analysis

In this section, we will analyze the effect of bypass paths on CPI. As mentioned in the previous section, the code for enabling bypassing is located in the Scala files at ${CHIPYARDROOT}/generators/riscv-sodor/src/main/scala/sodor/rv32_5stage.

You will need to modify both the data path in dpath.scala (lines 269-301) and the control path in cpath.scala (lines 226-245). After modifying the pipeline, ensure that your processor passes the assembly tests!

To run assembly tests, you can replace make run-binary with make run-asm-tests. For example, to run the assembly tests on the Sodor 5-stage configuration, use make run-asm-tests CONFIG=Sodor5StageConfig. This may take a while, so you can also run a single assembly test.

Analyze the CPI effects of removing the exe_alu_out $\rightarrow$ dec_op1_data bypass path and the exe_alu_out $\rightarrow$ dec_op2_data bypass path. After modifying the Scala code to remove each bypass path, make sure to run the following benchmarks: median, multiply, towers, and vvadd.

Part 4

After collecting the instruction mix and CPI data for median, multiply, towers, and vvadd, answer the following questions:

  • Which bypass path had the greatest negative impact on CPI when removed? Compare it to the other bypass path.

  • Why do you think this bypass path has the greater impact on CPI? Think about the order of instructions in common operations in RISC-V assembly code.

  • What are the potential advantages and disadvantages of removing a bypass path in the real world?

In your report, include the modified section of Chisel source code.

Design Problem Using the 5-Stage Processor

Imagine that your employer has asked you to evaluate a potential modification to the design of a 5-stage RISC-V pipeline. The proposed modification is to merge the Execute/Address Calculation stage and the Memory Access stage into a single pipeline stage. In this combined stage, the ALU and memory will operate in parallel. Data access instructions will use memory while leaving the ALU idle, and arithmetic instructions will use the ALU while leaving memory idle. These changes are beneficial in terms of area and power efficiency. Consider why this is the case, and if you are still unsure, ask about it in a discussion section or during office hours.

In RISC-V, the effective address of a load or store is calculated by summing the contents of one register (rs1) with an immediate value (imm).

The problem with the new design is that there is now no way to perform any address calculation in the middle of a load or store instruction because loads and stores no longer have access to the ALU. Proponents of the new design advocate changing the ISA to allow only one addressing mode: register-direct addressing. Only one source register is used, and the value it contains is the memory address to be accessed. No offset can be specified.

In RISC-V, register-direct addressing can be performed only as a register-immediate address calculation with imm == 0.

With the proposed design, any load or store instruction that uses register-immediate addressing with imm != 0 will take two instructions. First, the register and immediate values must be summed with an add instruction, and then this calculated address can be loaded from or stored to in the next instruction. Load and store instructions that currently use an offset of zero will not require extra instructions on the new design.

Your job is to determine the percentage increase in the total number of instructions that would have to be executed under the new design. This will require a more detailed analysis of the different types of loads and stores executed by our benchmark programs.

In order to track more specific statistics about the instructions being executed, you will need to modify the Python script at {CHIPYARDROOT}/generators/riscv-sodor/scripts/tracer.py.

Modify the tracer to detect the percentage of instructions that are loads and stores with non-zero offsets. Follow the existing framework to accomplish this task. The file contains code that you can adapt for your modifications.

Consult the RISC-V Green Card (https://inst.eecs.berkeley.edu/~cs152/archive/sp22/greencard.pdf) to determine which instruction bits correspond to which fields.

After modifying tracer.py, rerun the tracer on all of the output files from the bypassed 5-stage simulations to gather results.

cd ${CHIPYARDROOT}/sims/verilator/output/chipyard.harness.TestHarness.Sodor5StageConfigBypassed
${SCRIPTS}/tracer.py towers.out

Part 5

What percentage of the instruction mix does each type of load and store instruction represent? Evaluate the new design in terms of the percentage increase in the number of instructions that will have to be executed. Which design would you advise your employer to adopt? Justify your position quantitatively.

Feedback Portion

In order to improve the labs for the next offering of this course, we would like your feedback. Please append your feedback to your individual report for the directed portion. These questions are also placed at the bottom of the open-ended part of the lab for your convenience.

  • How many hours did the directed portion take you?

  • How many hours did you spend on the open-ended portion?

  • Was this lab boring?

  • What did you learn?

  • Is there anything that you would change?

Feel free to write as much or as little as you prefer (a point will be deducted only if you leave this section completely empty).

Team Feedback

In addition to feedback on the lab itself, please answer a few questions about your team:

  • In one short paragraph, describe your contributions to the project.

  • Describe the contributions of each of your team members.

  • Do you think that every member of the team contributed fairly? If not, why?

Notes for this lab

  1. A canonical name for the system type that follows the nomenclature cpu-vendor-os. ↩

  2. You will need to modify the PATH variable to access the RISC-V tools. See Problem 4.1. ↩

  3. The 2-stage and 3-stage processors will not be explicitly used in this lab, but they exist to demonstrate how pipelining in a relatively simple microarchitecture is implemented. ↩

  4. Should you encounter a java.lang.OutOfMemoryError exception, repeat the make command. ↩