Lab 7 - RISC-V I/O System

Lab 7 - RISC-V I/O System

For this laboratory you will integrate your processor into an I/O system and download your processor onto the FPGA board. Your processor will be able to control several I/O interfaces on the FPGA board.

Avg Hours: 4.0 (Winter 2022)

Learning Outcomes

  • Complete full synthesis and implementation of processor
  • Create assembly language programs that run on your RISC-V processor
  • Generate a bitstream that could be downloaded onto the FPGA

Preliminary

The RISC-V processor that you have developed over the semester will be inserted into a top-level design that includes other logic that will be provided for you. This logic will allow your processor to interact with the various input/output devices on the FPGA board such as the LEDs and switches. Your processor will be able to control and interact with these devices. A high-level overview of your processor system is shown below:

As shown in this figure, the top-level design will include the instruction memory, data memory and a variety of I/O devices. Your RISC-V assembly language programs will be inserted into the instruction memory and the data memory will contain any constants needed by your program and will hold the system stack. In addition to memory, the top-level design includes a variety of I/O devices that your processor can control. These I/O devices include the 16 LEDs, 16 switches, the five buttons, the seven-segment display, the UART, and the VGA display (we will not use the VGA display for this lab). A summary of this I/O system will be provided below in the preliminary and a more detailed description of this system can be found here.

The system that has been created for you has several different address regions, each with a specific and important purpose. It is essential that you understand this address space as you will need to create assembly language programs that properly interact with this system. The purpose of these memory regions is summarized below:

  • Instruction Memory (.text): This is the region that contains the instructions of the program that is being executed.
  • Data Memory (.data): This is the region that contains the global data memory. The top of this region is used for the stack and the bottom of this region is used for global data memory.
  • Memory Mapped IO (mmio): This region is allocated for specific I/O devices such as the LEDs. The memory mapping of these devices and the control of these devices will be explained in detail below.
  • Memory Mapped VGA Display (vga): This region is allocated for the VGA character display. The mapping of this memory space to actual VGA locations will be described in a later lab.

The address range and size of each of these memory regions is summarized below:

Address Range Size (bytes) Purpose
0x00000000 - 0x00001fff 8192 Instruction Memory (.text)
0x00002000 - 0x00003fff 8192 Data Memory (.data)
0x00007f00 - 0x00007fff 256 Memory Mapped I/O
0x00008000 - 0x0000bfff 16384 VGA Space

Match addresses with the appropriate memory region.

Memory Mapped I/O

The I/O devices that have been added to this system can be controlled and monitored by writing data to and reading data from reserved memory locations that are dedicated to I/O devices in the system. This approach for controlling I/O is called “Memory Mapped I/O”. Memory Mapped I/O includes dedicated decoding logic within that monitors the address and control signals coming out of the RISC-V processor. When this decoding logic observes a write operation (caused by the ‘sw’ instruction) at the specific location of a I/O device, it intercepts this write and sends the data to the appropriate I/O device. The I/O devices act just like a memory in that they accept data from the ‘sw’ instruction and provide data back to the processor from the ‘lw’ instruction.

256 bytes of address space has been reserved for the I/O devices in this system. The I/O space is located at 0x0000_7f00 (i.e., 0x0000_7f00 - 0x0000_7fff). To access the I/O memory, you will need to load a register with the base address of the I/O memory space (0x0000_7f00).

Each individual I/O device in this system has a specific address reserved within this range and is represented as an 8-bit address. For example, the I/O device with an offset of 0x18 is located at 0x000_7f18. The I/O space reserved for the system you are using is summarized in the table below:

I/O Offset Read Write
0x00 (0) Read value of LEDs Write LED values
0x04 (4) Read Switch status N/A
0x18 (24) Seven Segment Display Value Seven Segment Display Value
0x24 (36) Read Buttons status N/A
0x30 (48) Timer (ms) New Timer value

The function of each of these I/O devices and how they are mapped to this I/O address space will be described below. Several assembly language code examples will be given to demonstrate how to use these I/O devices.

I/O Base Address

To access the I/O memory, you will need to load a register with the base address of the I/O memory space (0x0000_7f00). If we had access to the full RISC-V instruction space, we could simply use a lui instruction followed by a addi instruction to load a register with this address (i.e., the li pseudo instruction). Unfortunately we do not have access to this instruction at this point (we will add it later in the semester) and we will need to initialize a register with this address in a more awkward way.

The approach shown below will initialize register x3 by loading 0x7f into x3 and then “shifting” x3 to the left 8 places.

    # Loading 0x7f into x3
    addi x3, x0, 0x7f
    # Shift x3 8 times
    slli x3, x3, 8
    # x3 should have 0x7f00

At this point, x3 should have the base address of the I/O address space and you can use this register to access the I/O modules described below. All of the code examples shown below assume that this base address is stored in register x3.

LEDs

Your RISC-V processor is able to control the 16 LEDs that are provided on the Basys 3 board. You can turn these LEDs on by writing data to address 0x0000_7f00 (0x0000_7f00 + 0). Writing to this memory location using a store word (‘sw’) instruction will set the value of the 16 LEDs. Bit 0 of the word corresponds to LED 0, bit 1 corresponds to LED 1, and so on. Note that since there are 16 LEDs and 32 bits in a word, the upper 16 bits written to this address location will be ignored. The bit value ‘1’ corresponds with turning ‘on’ the corresponding LED.

The following code example demonstrates how to write the value 0xa5 to the LED registers.

    # Assumes that x3 contains the I/O address space
    .eqv LED_OFFSET 0x0
    addi x6, x0, 0xa5
    sw x6, LED_OFFSET(x3)

This example will turn ON the following LEDs: LED7, LED5, LED2, and LED0. All other LEDs will be turned off.

What is the 16-bit value that should be used to turn ON the following LEDs: LED14, LED13, LED10, LED5, LED4, LED2, and LED1.

Note that you can also read from this memory location using the load word (‘lw’) instruction to determine which LEDs are currently turned on (as dictated by a previous ‘sw’ instruction to the LEDs).

Switches

The memory mapped address 0x0000_7f04 (0x0000_7f00 + 0x04) corresponds to the slide switches. Reading from this memory location will indicate which switches are in the “on” (up) position. Bit 0 of the resulting word corresponds to the status of SW0 (right-most switch), bit 1 corresponds to SW1, and so on. Since there are only 16 switches, the top 16 bits of this word will always be zero. Writing a value to this memory location will not do anything. The following code example demonstrates how to read the switches:

    # Read the switches from 0x00007f04 by providing an offset of 4 to t0
    # (t0 + 4 = 0x00007f04)
    .eqv SWITCH_OFFSET 0x4
    lw t1, SWITCH_OFFSET(x3)
    # The value of the switches is now in t1

Indicate what the following Example #1 assembly language program will do?

    # Example #1
    lw t1, 4(x3)
    xori t1, t1, -1
    sw t1, 0(x3)

Buttons

The address 0x0000_7f24 (0x0000_7f00 + 0x24) corresponds to the push buttons. Reading from this memory location will indicate buttons which are pressed. When a button is pressed, the bit corresponding to the button will have the value ‘1’. The relationship between bit location and button is summarized in the table below:

4 3 2 1 0
UP RIGHT DOWN LEFT CENTER
BTNU BTNR BTND BTNL BTNC

The following example demonstrates how to read the buttons, branch back when the ‘down’ button (BTND) NOT pressed, and then fall through when the ‘down’ button IS pressed:

    # It is better to use constants when programming I/O
    .eqv BUTTON_OFFSET 0x24
    .eqv BUTTON_D_MASK 0x04
    # Assume x3 has the I/O base address
wait_for_btnd:
    # Read the buttons 
    lw t1, BUTTON_OFFSET(x3)
    # Mask the buttons for button D
    andi t1, t1, BUTTON_D_MASK
    # If button d is not pressed, branch back
    beq t1, x0, wait_for_btnd:
button_d_pressed:
    # fall through after button d is pressed

Determine what the following Example #2 code sequence does.

    # Example #2
    .eqv BUTTON_OFFSET 0x24
    .eqv BUTTON_D_MASK 0x04
L1:
    lw t1, BUTTON_OFFSET(x3)
    andi t1, t1, BUTTON_D_MASK
    beq t1, x0, L1
L2:
    lw t1, BUTTON_OFFSET(x3)
    andi t1, t1, BUTTON_D_MASK
    xori t1, t1, -1
    andi t1, t1, BUTTON_D_MASK
    beq t1, x0, L2
L3:

Seven-Segment Display

Address 0x0000_7f18 (0x0000_7f00 + 0x18) corresponds to the seven-segment display. Writing to this memory location will cause the lower 16-bits of the word being written to at this address to be displayed in hex on the four digits of the seven segment display. Like the LEDs, you can read the value of this memory location to see what value is currently being displayed on the seven-segment display. The following code segment demonstrates a loop that continuously updates the value of the seven segment display:

    .eqv SEVEN_SEGMENT_DISPLAY_OFFSET 0x18
    addi t1, x0, 0
loop:
    sw t1, SEVEN_SEGMENT_DISPLAY(x3)
    addi t1, t1, 1
    beq x0, x0, loop

Determine what the following Example #3 code sequence does.

    # Example #3
    .eqv BUTTON_OFFSET 0x24
    .eqv BUTTON_D_MASK 0x04
    .eqv SEVEN_SEGMENT_DISPLAY_OFFSET 0x18
    addi t2, x0, 0
L1:
    lw t1, BUTTON_OFFSET(x3)
    andi t1, t1, BUTTON_D_MASK
    beq t1, x0, L1
L2:
    sw t2, SEVEN_SEGMENT_DISPLAY(x3)
    addi t2, t2, 1
    beq x0, x0, L1

Timer

A simple timer has been added to the system to provide a time stamp to facilitate timing accurate interactions. When the timer is read (0x0000_7f30), it will return the number of milliseconds since the processor was first turned on or since it was last reset. The timer can be loaded with any value by writing to the same address location (this is a convenient way to reset the timer). The following code example demonstrates how to wait for 10 ms:

    .eqv TIMER 0x30
    sw x0, TIMER(x3)      # Clear timer to zero
    addi t1, x0, 10       # Set t1 to 10 (10 milliseconds)
wait:
    lw t2, TIMER(x3)
    beq t2, t1, after_wait
    beq x0, x0, wait
after_wait:

Determine what the following Example #4 code sequence does.

    # Example #4
    .eqv BUTTON_OFFSET 0x24
    .eqv BUTTON_D_MASK 0x04
    .eqv SEVEN_SEGMENT_DISPLAY_OFFSET 0x18
    .eqv TIMER 0x30
    addi t3, x0, 0
    sw t3, SEVEN_SEGMENT_DISPLAY(x3)
    addi t1, x0, 500
L1:
    lw t0, BUTTON_OFFSET(x3)
    andi t0, t0, BUTTON_D_MASK
    beq t0, x0, L1
L2:
    sw x0, TIMER(x3)
L3:
    lw t2, TIMER(x3)
    beq t2, t1, L4
    beq x0, x0, L3
L4:
    addi t3, t3, 1
    sw t3, SEVEN_SEGMENT_DISPLAY(x3)
    beq x0, x0, L2

The I/O sub-system also includes the UART and the VGA controller but these will be not be used in this lab (they are described here and will be used in a future lab).

Exercises

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

Exercise #1 - I/O System Project

In this first exercise you will simulate your processor from the previous lab executing within the I/O system described above. A set of Verilog files has been created that implements the I/O functions described above. In addition, a constraints file has been created for you that maps the top-level I/O signals to the pins on the BASYS3 board. You will insert your multi-cycle processor into this system to provide your processor with the memories and I/O you need. Begin the exercise by updating the starter code and merging the updated code in your repository.

Create a new project for this lab by executing the create_multicycle_project.tcl script that is in your ‘lab07’ repository directory (i.e., ‘source’ this file in the Vivado tcl shell from within your lab07 directory: source create_multicycle_project.tcl ). These commands will create a new project and integrate your code with the I/O system code to create a top-level system. You may want to perform the synthesis step on your full processor system to make sure everything is hooked up properly and that there are no major errors.

A simple test I/O program, multicycle_iosystem.s, has been written that operates on the I/O system described above. This program uses the buttons, switches, LEDs, and seven-segment display. Carefully review this program and answer the questions on Learning Suite about its functionality.

Answer the questions about the test program in Learning Suite

You need to assemble this program with the RARS assembler and create a ‘.mem’ memory file for use by the Vivado tools. The following command demonstrates how to assemble this file and generate the memory file for Vivado. Run the command from within your lab07 directory.

java -jar ../resources/rars1_4.jar mc CompactTextAtZero a dump \
  .text HexText multicycle_iosystem_text.mem multicycle_iosystem.s

The arguments to this executable are as follows:

  • mc CompactTextAtZero This indicates the “memory configuration” and specifies that the .text segment should start at address zero
  • a dump .text HextText multicycle_iosystem_text.mem This indicates that the .text segment should be dumped in ascii form to the file named multicycle_iosystem_text.mem
  • multicycle_iosystem.s This is the input assembly filename

After the memory file has been successfully created, you need to add the memory file to your project and set the property of your top-level design with the name of your newly compiled memory file.

add_files multicycle_iosystem_text.mem
set_property generic TEXT_MEMORY_FILENAME=multicycle_iosystem_text.mem [get_filesets sim_1]

If your project is created properly and your processor binary files are properly added to the project, then you can simulate this program executing on your processor within the top-level I/O system. Open the simulator with your project and execute the template tcl file, iosystem_template.tcl, to start simulation. When simulating this system with the precompiled program, you will receive messages indicating reads and writes to the I/O ports. The following example demonstrates the output of this simulation.

5540000:Writing 0x00000000 to Seven Segment Display
5690000:Writing 0x00000000 to Timer
5870000:Reading 0x00000000 from Buttons
6020000:Reading 0x00000000 from Switches
6470000:Reading 0x00000000 from Timer
6590000:Writing 0x00000000 to Seven Segment Display

You can emulate the pressing of buttons and changing the switches by modifying this .tcl script to change these top-level inputs. Create your own tcl file named iosystem.tcl and extend this tcl script to simulate the following conditions:

  • Change the value of the switches, observe impact on LEDs
  • Press BTNR, observe impact on LEDs
  • Press BTNL, observe impact on LEDs
  • Press BTNU, observe impact on LEDs
  • Press BTND, observe impact on LEDs
  • Make sure the simulation executes for at least 1 ms to see the timer reach a value of 1 ms
  • Press BTNC and observe the impact on the timer and seven segment display

Note that when you simulate a button press, you need to hold the value of the button for about 10 us so that the debouncer properly registers the button press. Also, provide a delay of at least 1 us between the releasing of a button and the pressing of a new button.

You will need to include this .tcl file as part of your lab submission

Exercise #2 - Testbench Simulation

After convincing yourself that your processor is hooked up properly and that the program you have been given is working, you will need to test your processor with a testbench. This testbench will perform a few simple operations with the buttons and switches and check to see that the processor is responding properly. To perform this testbench check, create a second simulation set (labeled sim_2) for this testbench and add the testbench file to the new simulation set with the following tcl commands within the Vivado tcl shell:

create_fileset -simset sim_2
add_files -fileset sim_2 -norecurse tb_multicycle_io.sv

Simulate this ‘sim_2’ simulation set by making sim_2 the active fileset and running the simulation (run all). Note that this simulation set ‘sim_2’ should not have the TEXT_MEMORY_FILENAME generic set as it was for simulation set ‘sim_1’.

Answer the following questions about the testbench in Learning Suite

At what time do the LEDs first change from all zeros to a non-zerio value? Provide your answer in decimal microseconds (us)
At what time do the LEDs change back to all zeros from a non-zerio value?
At what time do the LEDs change to 0xffff (i.e., all on)?

Exercise #3 - Synthesize, Implement, and Generate Bitstream

For this exercise, you will synthesize, implement, and download your processor along with the I/O sub-system precompiled with the demonstration program. Before performing synthesis, you need to tell the synthesis tool which instruction memory file to use. The following tcl command demonstrates how to set the instruction binary for the circuit to the memory file you generated in the previous exercise:

set_property -name {STEPS.SYNTH_DESIGN.ARGS.MORE OPTIONS} -value {-generic TEXT_MEMORY_FILENAME=multicycle_iosystem_text.mem} -objects [get_runs synth_1]

Perform the steps of synthesis, implementation, and bitstream generation to generate a bitstream file. Carefully review the synthesis warnings. There will be a number of synthesis warnings even for a properly working system.

Summarize the estimated resources for your implemented design.and the worst negative slack (WNS)

Resource Estimation
LUT  
LUTRAM  
FF  
BRAM  
IO  
BUFG  
MMCM  

Download

Download the bitstream to the Basys3 board and verify that it operates as you expect. Although this bitstream will not be tested during the passoff script, you are strongly encouraged to test this design on your board to make sure it works. If it does not work, resolve any problems before proceeding to the next exercise.

Exercise #4 - Custom Program

For this final exercise, you will create your own program that runs on your multi-cycle RISCV processor. Create a new assembly language program named buttoncount.s that performs the following functions:

  • The value of the switches are continuously copied to the seven segment display
  • Increment the LEDs by 1 when BTNU is pressed
  • Decrement the LEDs by 1 when BTND is pressed
  • Clear the LEDs when BTNC is pressed. Note that you should only increment/decrement once for each button press.

You will need to implement some form of a “one shot” functionality in software to make sure that you only increment/decrement when the button is first pressed. You can do this by creating a section of code that loops to check for the initial pressing of any of the three buttons. Once one of the buttons has been pressed, perform the corresponding function as described above. After performing the given function, jump to a section of code that checks the presence of any button presses. Do not jump back to the “checking for button presses” code until none of the buttons have been pressed (i.e., wait until all of the buttons have been released).

Use the file multicycle_iosystem.s as a start for your code (i.e., copy it to a filed named buttoncount.s) Follow the assembly language coding standard as you write your assembly language code.

After you have created your assembly language program, assemble the program into a hex memory dump file named buttoncount_text.mem using the instructions described in Exercise #1. The following command demonstrates how to generate this file along with a ‘debug’ file that contains the assembled debug output buttoncount_s.txt. This file is useful for determining the address of each instruction when you need to debug your program in the simulator.

java -jar ../resources/rars1_4.jar mc CompactTextAtZero a \
  dump .text HexText buttoncount_text.mem \
  dump .text SegmentWindow buttoncount_s.txt \
  buttoncount.s

The following commands demonstrate how to change the instruction memory used for the first simulation set:

add_files ./buttoncount_text.mem
current_fileset -simset [get_filesets sim_1]
set_property generic TEXT_MEMORY_FILENAME=buttoncount_text.mem [get_filesets sim_1]

A simulation tcl file named buttoncount.tcl has been created for you to stimulate the switches and buttons in your program. Use this stimulus to make sure your program is operating properly.

After you have verified that your program operates correctly, create a new bitfile with this program by changing the synthesis parameter and running the implementation tools:

set_property -name {STEPS.SYNTH_DESIGN.ARGS.MORE OPTIONS} -value {-generic TEXT_MEMORY_FILENAME=buttoncount_text.mem} -objects [get_runs synth_1]

Note: Make sure you test the bitstream generated through this process on your board. The TAs will build and run your bitstream to make sure it works. You will lose significant points on this lab if your generated bitstream does not operate correctly.

Pass Off

To create your submission, make sure the following files are committed in your ‘lab07’ directory:

  • iosystem.tcl
  • buttoncount.s

Make sure you do not add unnecessary files (including Vivado project files) to your repository. Test your submission by running the lab07_passoff.py pass-off script found in the starter code. Review the instructions for submitting and passing off labs to make sure you have completed the lab properly.

Make sure you test the bitstream created in the passoff script on your board - the passoff script will not check this and you can lose significant points on this lab if your bitstream does not operate correctly.

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



Last Modified: 2024-06-01 00:07:11 +0000