Homework 7: Making it count! (or SUMmarizing and re-STATEing )

Goals

The goals of this assignment are to summarize and combine multiple aspects of the course:

  • Take another look at a common approach to state machines
  • Continue to explore the design of computers by taking a deep look at the control and datapath for a RISC-V single-cycle microarchitecture.
  • Look at one way computers can interface with the real world (screens, keys, etc.)
  • Ensure everyone has an opportunity to take a digital design and generate real-world hardware that implements that design.

(All of the above are overall learning outcomes for the course)

The goals are:

  1. Create a short example program, which will need certain instructions to run.
  2. Update a RISC-V model to support a needed instruction, lui.
  3. Actually implement it with our hardware!

The goal is a very simple state machine that controls a counter. The counter will either count up or count down. The count will be shown on a display, two LEDs will also be used to show which mode it’s in (counting up or counting down), and buttons will be used to switch it between counting modes. (Often when the word “mode” is used in electronics and computing/software, it’s referring to the state of a state machine).

When done it should behave like this:

Part 0: Setup

Link to get the repository / codespace: link

Part 1: Coding a State Machine

Background: Input/Output

We have covered how CPUs interact with memories, but we haven’t covered how they get input from the outside world or how they provide output, the combination commonly referred to as “I/O”. Without I/O computers wouldn’t have much impact.

A form of I/O that’s a variation on things we’ve already seen is “Memory Mapped I/O”. Memory mapped I/O is connected like the RAM. Interaction of the device is just reading from and writing to the RAM locations that correspond to the device. In this case we will designate memory locations for the elements of the Key and LED module:

Memory Address What Read / Write / Both
0x8000 (32'h8000) 32-bit value that will be displayed on the digits of the display Read or write
0x8004 (32'h8004) 32-bit value; Lowest eight bits control LEDs. Bit 0 is rightmost LED (D8 on the board; LED 1 in the simulator) Read or write; Only lowest 8-bits are writeable
0x8008 (32'h8008) 32-bit value; Lowest eight bits represent the most recent state of the buttons. Bit 0 is rightmost button (SW8 on the board; Button 1 in the simulator) Read only. Writes will be ignored.

RISC-V assembly language uses the 0x notation to indicate a hexidecimal value. That format should be used for this problem.

In our final computer we can use sw to store values at address 0x8000, which will cause the value to be shown on the screen. Correspondingly, we can read from 0x8004 using lw to determine if any buttons are currently pressed.

State Machines

State machines can also be represented with program code, where a variable typically contains the state (the variable is stored in RAM and that variable is just being used like the flip-flops we’ve used to store state). It’s often much easier to write a computer program for a state machine than it is to create a digital circuit.

Problem 1: RISC-V Assembly State Machine

Here’s a description of the state machine:

State Machine

  1. Start by creating a prototype program in the programming language of your choice. You can use whatever technique you want to simulate the “buttons”. (It may be easiest to do something that prompts for the button every time through a loop. Perhaps 0 and 1 could represent the two buttons and if any other key is typed it would indicate neither is pressed)
  2. As in the prior assignment, make a plan to convert your code to assembly language. You’ll need to update your I/O code to use the memory-mapped locations for our display, LEDs, and keys.
  3. Add your code to the counting/counting.s project in the designated places. The TODO items indicate where you should initialize values and where your main code (loop) should be. Although our real key/led board will be memory mapped, the simulated one is not. Instead we are emulating memory mapping by the jal update_memmapped instruction and some additional code. Be sure the that instruction is called everytime you need to update states and output. Each jal update_memmapped will synchronize the values at 0x8000, 0x8004, and 0x8008 with the simulated display. In most cases it should only be needed one place that happens just after outputs are updated and before the next state is computed.

Caution!

Only use the instructions listed in the file (li, lw, sw, add, sub, and, or, slt, addi, andi, ori, slti, beq, and jal)!

The simulator will open a small, simulated board that operates like the Key & LED board provided in your kit. There are just two buttons, two LEDs, and two digits on the display, but they should behave the same as our board. The display will only be showing the last two digits of the value, but the simulation runs slow enough for that to work well.

Your code should behave like:

Part 2: Adding an Instruction

Caution!

This is a real-world example of a significant computer engineering task. You need to have a plan rather than just opening the Verilog file and typing.

The risc-v folder contains a Verilog implementation of a partial RISC-V processor (an implementation that corresponds to our textbook). It is a single-cycle CPU and includes a full datapath and the elements that control the datapath. It supports only the following instructions: lw, sw, add, sub, and, or, slt, addi, andi, ori, slti, beq, and jal

You probably needed to use an li in your code for part 1. Note that li is a pseudo-instruction that is translated into either addi or a combination of both a lui and an addi (Table B.7). In order to work with our display, we need to access memory address 0x8000-0x8008, which requires the version of li that translates to a lui….And the provided processor does not support lui. You will need to update the processor (control and datapath) to support lui.

  1. Start with:
    • a diagram of the single-cycle CPU:

    Single Cycle CPU

    (Figure 7.15 from Digital Design and Computer Architecture, RISC-V Edition by Harris & Harris)

    • Review lui, its meaning/behavior (also referred to as its “semantics”), and its format in Figure B.1 and Table B.1

    • You’ll also need to understand how various parts of this CPU implementation that aren’t fully described in the diagram behave. The parts that aren’t shown in the diagram are the ALU (Table 7.3, page 409) and the Extend unit (Table 7.5, page 412).

  2. Develop a plan for how the existing design can be modified to support lui and annotate it on the diagram. It may help to re-read/review 7.3 of the text.

    Caution!

    If you come to office hours or need help on this part, the first question will be “show us your diagram”. We will need to know what you want to do before we can help.

  3. Code review: The code in risc-v/riscvsingle.sv reprents a working model of a significant part of a RISC-V CPU. It contains a hierarchy of multiple modules. At the top of that hierarchy is the riscvsingle module. It contains the two major aspects of a simple CPU: a controller, which decodes instructions and configures the other element, the datapath. As the name implies, the datapath encompasses all the components that process data.

    • Review the various modules in riscvsingle.sv and update your diagram with details about which modules correspond to which parts of the diagram. For example, there are actually two Verilog modules that correspond to the “Control Unit”. Also, look carefully for the types of signals that are important for your approach to supporting lui.
  4. Identify a plan to update riscvsingle.sv to support lui (there are multiple valid approaches). Update riscvsingle and check your work with the provided testbenches, which are described in the “Testing” section below.

You may find that you want to “Revert” your changes if things aren’t working out. You can easily retrieve the original code via git or GitHub. Here are two approaches:

  1. Go to the repository on GitHub.com, click on the file of interest, then click on the History button, and finally select a specific version to view (if you haven’t pushed updates to GitHub, only the original version will be shown). You can copy/paste the code as needed.
  2. Right-click on a file in the CodeSpace file explorer and select Open Timeline. A TIMELINE view will be shown in the explorer panes and you can select an older version of the work, which will be compared with the current version. The timeline includes both git commits and the times the file has been saved since you opened the Codespace session.

Testing

Three testbenches are configured:

  • risc_v_lui_tb: This just tests a few variations of lui instructions. This will help test that your lui works without consideration for other instructions. If successful, the PROBLEMS panel will show an entry for risc-v-lui-tb.sv that says “All tests passed!”. If not successful, it will contain some details about the specific instruction that failed.
    • The machine code and assembly language being checked is in risc-v-lui-tb/lui-test.rom.txt
  • risc_v_getn_tb: This is a more general test. It tries nearly all instructions once, including the lui. It isn’t a very rigorous test, but it will help confirm that changes to lui probably didn’t interfer with the behavior of other instructions. If successful, the PROBLEMS panel will show an entry for risc-v-gen-tb.sv that says “Simulation succeeded!”.
    • A copy of the assembly language listing for the program being run is in risc-v-gen-tb/risc-v-gen.s. If the testbench fails, you may need to refer to the actual instructions to identify bugs. (See “Debugging the Datapath” below). (Changing risc-v-gen.s won’t have any impact. It was used to create the machine code in risc-v-gen.rom.txt that is actually being use by the testbench)
  • risc_v_counting_tb: Part 1 needs to be completed first and then the counting: rom task should be run to convert code to an UPduino ROM. This test bench can be used to try to verify that your code is running correctly. You can modify the test case in the risc-v-counting-tb.sv file indicated in the TODO notes. The “Debugging” video below shows both hwo this works and how you can use the waveform traces to debug flaws in your design.

    Caution!

    Check to see if the PROBLEMS panel indicates any errors with counting.rom.txt. If it indicates your code uses an unsupported instructions (“Line may not contain an allowed instruction”), review and revise your code as needed.

Debugging the Datapath

The “Surfer” waveform view allows you to see the step-by-step execution of a program and look at all components of device under test (that is, the CPU).

Here’s an example of using waveforms to understand what’s going on:

And another example, focused on the lui testbench and how you may want to use the waveforms to understand and debug Verilog:

Part 3: Making it Real

  1. If you haven’t already done so, do Parts 2-2.1 of Studio 7B. Confirm that the display is connected correctly and works.
  2. You’ll need to convert your code from Part 1 to machine code (binary) and it will be programmed in the ROM. A task has been provided that will do this for you: counting: rom. The task will:
    1. Use a real assembler for RISC-V to translate your code to machine code.
    2. Adjust the format to be suitable to program a “ROM” representation on the iCE40.
    3. Open a window showing the machine code and the original assembly language. Review the code that’s generated. Confirm that:
      1. The two “do not edit” parts, the jal instruction and the code at the bottom of the file, were removed.
      2. You are only using instructions supported by this simplified RISC-V model (li, lw, sw, add, sub, and, or, slt, addi, andi, ori, slti, beq, j, and jal)
    4. Once you are sure that your UPduino is connected correctly, your RISC-V model supports lui correctly, and that your code is appropriate, use the the risc-v: image task to build the image and the web programmer to program it!

Part 4: Reflections & Questions

Answer the questions in questions.md

Submission

You will need to submit your assignment via Gradescope. There will be three distinct dropboxes:

Parts 1-2, 4

As with prior homeworks, work should be submitted via Gradescope. Like in Homework 5, you will need to commit and push work to GitHub and then go to Gradescope to “pull” the work over. You should:

  1. Submit Part 1 under the appropriate dropbox (confirm the results of the unit testing) (confirm the results of the unit testing)
  2. Submit Part 2 under the appropriate dropbox (confirm the results of the unit testing).
  3. Submit Part 4 to the appropriate dropbox
  4. Confirm that questions.md is included and transferred to Gradescope.

Part 3

A demonstration / discussion of your work is required.

You can do a “demo” (brief overiew/discussion of your work) during TA or instructor office hours starting Tuesday, Dec. 3.

Submission time will be based on Gradescope submissions, not the demo time. There will be additional office hour times for demos between Dec. 3 and Dec. 6, some time during the class session on Thursday, Dec. 5th, and during reading days (~10am-5pm the 9th-11th).