Lab 0: Chipyard Setup

Introduction and Goals

Welcome to CS 152/252A! If Chipyard, Chisel, or RTL simulation are new to you, you are in the right place. Lab 0 is a short tour of the tools we will use throughout the semester.

By the end of this lab, you will be able to:

  • Enter the course environment from a new terminal

  • Build a simulator for a small RISC-V processor

  • Run a program on that simulated processor

  • Recognize the major steps that turn a hardware design into a simulator

The setup you complete here will be reused in every later lab, so take your time and read each step. Later labs will assume that this workflow is familiar, but they will provide any new commands or concepts they require.

One pair of terms will appear often: the host is the real computer doing the simulation, while the target is the computer being simulated. In this lab, the instructional server is the host, the Sodor RISC-V processor is the target, and the benchmark is target software. Even though everything appears in the same terminal window, keeping these roles separate will make the rest of the toolchain much easier to understand.

Graded Items

Complete the Gradescope quiz and submit your results from Exercise 3 and Exercise 5.

Background

Chipyard

Modern chips usually contain much more than a processor core. A system-on-chip (SoC) can combine processor cores, memories, accelerators, and input/output devices into one system. Connecting all of those pieces and preparing them for simulation or fabrication requires a large collection of tools.

Chipyard brings many of those pieces together. It is not a single processor. Think of it as a hardware-development workspace: it contains reusable hardware designs, ways to configure and connect them, and tools for turning the resulting system into something that can be simulated, placed on an FPGA, or eventually manufactured.

For the next several labs, we will use only a small part of Chipyard: Sodor, a family of compact RISC-V processor cores designed for teaching. At this point, you can think of Chipyard as the environment we use to select, build, and simulate a Sodor core. A configuration such as Sodor1StageConfig acts like a recipe that selects a particular Sodor design.

Later in the semester, we will use more of what Chipyard provides and introduce more complex processors such as BOOM (Berkeley Out of Order Machine). There is no need to learn about those yet; we will introduce them when we need them.

Chipyard is a large repository, and you are not expected to understand all of it. For now, the important idea is simply:

Chipyard lets us choose a hardware design, build a simulator for it, and run RISC-V programs on the simulated system.

Chisel

Before introducing Chisel, it helps to understand what we are trying to describe. Register-transfer level (RTL) is a way of describing a digital circuit in terms of the values stored in registers and the logic that computes new values between clock ticks. Unlike an ordinary program, which describes a sequence of instructions for a processor to execute, RTL describes hardware whose components operate together. A cycle-accurate RTL simulator reproduces these clock-by-clock state changes in software.

Chisel is a hardware description language embedded in Scala, a general-purpose programming language. Designers use Chisel to write hardware generators: designs that can produce different circuits when given different parameters. That is how one codebase can describe a family of related processors instead of requiring a separate copy for every design choice.

When we build a simulator, the Chisel code runs on the host during a step called elaboration. Elaboration constructs the selected circuit and emits Verilog RTL. Verilator then turns that RTL into a C++ model, which is compiled into an executable that runs on the host.

The simulator is a normal host program, but the clock cycles, instructions, and register values it reports belong to the simulated target. You do not need prior Chisel or Scala experience for this lab; focus on the role each stage plays, and we will introduce more detail when it becomes useful.

Exercise 1: One-Time Environment Setup

Many of the following commands take several minutes to run. We suggest students run them in tmux or screen and find something else to do while they complete. Some benefits of using tmux include having multiple “panes” open in a single window and safely detaching from terminal sessions without ending programs -— you can read more about it here.

Step 1: Log Into an Instructional Machine

To obtain an instructional account for this class, visit https://acropolis.cs.berkeley.edu/~account/webacct/. ssh into an instructional server with the instructional computing account provided to you. The lab infrastructure runs on the eda-{1..4}.eecs.berkeley.edu machines. Check https://hivemind.eecs.berkeley.edu/ to pick a machine with lower load. If you’ve never done this before or need a refresher, you can take a look at CS 61C’s guide.

You must be using the Campus VPN to connect to these machines. Follow the instructions here to install and set up the bSecure Remote Access VPN. Make sure there are no errors during your installation process.

You can add the following snippet to your ~/.ssh/config for ease of use:

Host eda-*
   HostName %h.eecs.berkeley.edu
   User cs152-XXX        # insert your username here
   ForwardAgent yes
   IdentityFile          # insert path to your ssh private key here

Run ssh-copy-id eda-X and enter your password. Now, you should be able to simply run ssh eda-X to access any of the eda machines without your password!

All work can be done in your ~/ folder. However, you also have the option of working in the /scratch directory. Commands generally run faster from here, but this folder is not sync’d across the eda{1..4} machines, meaning you would need to repeat this setup if you switch machines at any time.

If you choose to use the /scratch directory, you should ls /scratch in eda{1..4} and pick a machine with fewer directories to help with load balancing. Once you’ve selected a machine, you can mkdir -p /scratch/$(whoami) and cd there.

For all following instructions and cs152-env.sh, you would need to replace the ~/ with /scratch/$(whoami)/. Proceed with caution!

Step 2: Install conda

Run the following bash commands.

wget -O Miniforge3.sh "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh"
bash Miniforge3.sh -b -p ~/conda
source ~/conda/etc/profile.d/conda.sh

Step 3: Clone and Build Chipyard

Clone the Chipyard tree used by Labs 0 and 1 and run its setup script (copy the ./build-setup.sh command as one line).

git clone https://github.com/ucb-bar/chipyard
cd chipyard
git checkout main
git reset --hard 150f888
ln -s /home/ff/cs152/fa26/chipyard/.conda-env .conda-env
conda activate .conda-env/
./build-setup.sh riscv-tools --skip-conda --skip-toolchain --skip-circt --skip-firesim --skip-marshal # if you are curious about these, see https://ucb.bar/cystart

Step 4: Set Up Your Shell Environment

Lastly, run these commands to set up your shell environment.

CHIPYARDROOT=$PWD
BMARKS=$CHIPYARDROOT/generators/riscv-sodor/riscv-bmarks
SCRIPTS=$CHIPYARDROOT/generators/riscv-sodor/scripts
source ./env.sh

Step 5: Create Your Environment Script

Run this once to create ~/cs152-env.sh:

cat > ~/cs152-env.sh << 'EOF'
# CS152 lab environment. Usage: source ~/cs152-env.sh [lab-directory]
# With no argument, enters the chipyard tree used by Labs 0 and 1.
source ~/conda/etc/profile.d/conda.sh
cd ~/"${1:-chipyard}"
conda activate .conda-env/
export CHIPYARDROOT=$PWD
export BMARKS=$CHIPYARDROOT/generators/riscv-sodor/riscv-bmarks
export SCRIPTS=$CHIPYARDROOT/generators/riscv-sodor/scripts
source ./env.sh
EOF

Then enter the environment:

source ~/cs152-env.sh

Every Time You Open a New Shell

This is the only setup you will repeat this semester:

source ~/cs152-env.sh

Later labs that live in their own directory take it as an argument, e.g., source ~/cs152-env.sh lab2.

Checkpoint

  1. Your shell prompt is prefixed with the conda environment name.

  2. echo $CHIPYARDROOT prints the path of your chipyard directory.

If a check fails, open a fresh terminal and re-run source ~/cs152-env.sh; if it still fails, ask your TA. The remainder of this lab uses ${CHIPYARDROOT} to denote the path of the Chipyard working tree.

Setup in Later Labs

Most later labs are distributed as branches of the course’s chipyard-cs152 repository. Their one-time setup will be roughly the same, but we will provide more specific instructions at the start of each lab.

After that, entering the lab in a new shell is just source ~/cs152-env.sh lab<N>. A lab that needs special tooling will say so on its own page.

To recap:

How often What
Once Install conda; clone and build Chipyard; create ~/cs152-env.sh
Once per later lab Clone that lab’s branch of the course repo and run its build-setup.sh
Every new shell/login source ~/cs152-env.sh [lab-directory]

Exercise 2: Building Your First Simulator and Binary

Now that you have Chipyard installed and your environment set up, we’ll begin by building a 1-stage Sodor RISC-V processor using the following command.

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

The make command orchestrates the following steps:

  1. Start sbt (the Scala Build Tool), select the Sodor1StageConfig config, and compile and run the Chisel code that generates a Verilog RTL description of the processor. The generated Verilog code can be found in $.

  2. Run Verilator, an open-source tool that converts Verilog into a C++ cycle-accurate simulation model.

  3. Compile the Verilator-generated C++ code into an x86 executable.

By adding arguments, you can specify a software benchmark to execute on your selected processor. Run the following commands to simulate the Sodor 1-stage processor executing the Towers of Hanoi benchmark.

cd ${CHIPYARDROOT}/sims/verilator
make CONFIG=Sodor1StageConfig run-binary BINARY=${BMARKS}/towers.riscv

The simulation should print the cycle count (mcycle) and instruction count (minstret) upon completion. If any benchmarks fail to complete and print mcycle and minstret, verify that you are running on a recommended instructional machine. Otherwise, contact your TA.

Exercise 3: Switching Configs and Parameters

To select a different processor design point, simply change the CONFIG= key of the make command. Valid options for Sodor are listed in the table below.

Sodor1StageConfig
Sodor2StageConfig
Sodor3StageConfig
Sodor5StageConfig
SodorUCodeConfig

Another way to customize the processor you’re running is by changing its Config mixin parameters. Chipyard is highly configurable—you can easily update properties, such as cache size or bypassing settings, for any processor and observe the simulated effects of these changes by rerunning your benchmarks.

As a simple example, execute the following commands to see parameters for the Sodor5StageConfig:

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

There is a single parameter specified on line 21 to toggle USE_FULL_BYPASSING. You will change this value in Lab 1 to simulate the effects of toggling this parameter.

For a more complex example, execute the following commands to see several parameters for BOOM configurations:

cd ${CHIPYARDROOT}/generators/boom/src/main/scala/common
less config-mixins.scala

This file contains various BOOM configs with values that can be changed to tweak register counts, cache entries, and fields for other components. We’ll dive into what some of these fields actually do when we cover out-of-order processors later in the semester.

Select a benchmark in $BMARKS to run on any two Sodor configs listed above. On Gradescope, submit the configs and benchmark used. For each config, determine the resulting cycle and instruction counts.

Exercise 4: Checking a Program with Spike

So far, you have run a RISC-V program on a cycle-accurate simulator for a particular Sodor core. This tells you whether the program completed correctly, but it also models the details of that processor one clock cycle at a time. That detail is useful for studying performance, but it makes RTL simulation relatively slow.

Spike takes a simpler approach. It is a RISC-V instruction set simulator: it models what each instruction must do according to the RISC-V ISA without modeling a particular processor pipeline cycle-by-cycle. This makes Spike much faster than an RTL simulator, but it also means that Spike’s cycle counts are not useful for measuring the performance of a Sodor core.

Run the same Towers of Hanoi binary from Exercise 2 with Spike:

spike --isa=RV32I ${BMARKS}/towers.riscv

The program’s result should agree with the result from the Sodor simulation. The reported cycle count may not agree, and that is expected. We use Spike as a golden functional reference: a trusted, simpler model against which we can check a more detailed hardware implementation.

This comparison is especially useful while debugging. If a program fails in both Spike and the RTL simulator, the problem is likely in the program or binary. If it works in Spike but fails in the RTL simulator, the hardware implementation or simulation setup deserves a closer look.

Looking Inside the Binary

The .riscv file contains machine code for the target, so opening it directly in a text editor is not very helpful. A disassembler translates those encoded bits back into human-readable assembly instructions. Run:

riscv32-unknown-elf-objdump --disassemble=main ${BMARKS}/towers.riscv

The riscv32-unknown-elf prefix tells us that this tool operates on 32-bit RISC-V target files rather than host executables. In each instruction line of the output, you will see the instruction’s address, its machine-code encoding, and the disassembled RISC-V instruction. Take a moment to identify each of these three fields for one instruction in main.

Disassembly does not recover the original C source, variable names, or comments. It shows the instructions that the target actually executes, which will be useful when we begin reading processor traces.

Exercise 5: Read a Commit Log with tracer.py

A cycle count (mcycle) tells you how long the program took in clock cycles, and dividing it by minstret gives you the average cycles per instruction (CPI). Every Sodor simulation writes a commit log that can be used to see what the processor did during each cycle. Open the trace from your towers run:

cd ${CHIPYARDROOT}/sims/verilator
less output/chipyard.harness.TestHarness.Sodor1StageConfig/towers.out

Every line has the same format:

Cyc=       3 [0] pc=[00000000] W[r 0=00000000][1] Op1=[r 0][00000000] Op2=[r 0][00000000] inst=[00004033] xor zero, zero, zero
  • Cyc=: cycle number
  • [0]: commit flag: [1] means an instruction committed this cycle; [0] is a bubble
  • pc=: program counter of the committed instruction
  • W[r 0=00000000][1]: writeback, destination register = value, then its write-enable bit
  • Op1 / Op2: the two source operands (register number and value)
  • inst=: raw instruction bits, followed by what instruction it disassembles to

Reading thousands of these lines can take a long time, so we have a script called tracer.py that helps us see the CPI and instruction mix.

sed -i '914s/SLLI_RV32/SLLI/; 914s/SRLI_RV32/SRLI/; 914s/SRAI_RV32/SRAI/' ${SCRIPTS}/instructions.py
sed -i '146s/- start_cycle$/- start_cycle + 1/' ${SCRIPTS}/tracer.py

Then run it on your trace:

${SCRIPTS}/tracer.py output/chipyard.harness.TestHarness.Sodor1StageConfig/towers.out

You should see something like:

Stats:

CPI          : 1.000
IPC          : 1.000
Cycles       : 24180
Instructions : 24180
Bubbles      : 0

Instruction Breakdown:
% Arithmetic  : 38.209 %
% Ld/St       : 42.994 %
% Branch/Jump : 18.218 %
% Misc.       : 0.579 %

Select a benchmark (aside from towers) from {$BMARKS} to simulate on Sodor1StageConfig. Generate the .out file using the command from Exercise 3, and use tracer.py to print stats for your selected trace. Submit the statistics collected to Gradescope.