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: 2.7 (Winter 2021) Note: An additional exercise has been added since Winter 2021

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


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 (see note about the need for a VGA Display 1).

If you are working in the digital lab then you can connect your board to the monitor in the lab. If you are not working in the digital lab, you will need to obtain access to a VGA monitor or use a conventional monitor with a VGA adapter.

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 the 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). Each individual I/O device in this system has a specific address reserved within this range and is represented as an 8-bit offset. 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 registter 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 immediate 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 “shifting” x3 to the left 8 places. We don’t have a shift instruction yet so we will have to perform a shift by one by adding x3 to itself eight times.

    # Loading 0x7f into x3
    addi x3, x0, 0x7f
    # Add x3 to itself 8 times
    addi x5, x0, 8
    add x3, x3, x3
    addi x5, x5, -1
    beq x5, x0, done_shifting
    beq x0, x0, shift_by_1
    # 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.


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).


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 assembly language program will do?

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


The address 0x0000_7f24 (0x0000_7f00 + 0x24) corresponds to the push buttons. Reading from this memory location will indicate butons 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

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
    # 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:
    # fall through after button d is pressed

Determine what the following code sequence does.

    # Example #2
    .eqv BUTTON_OFFSET 0x24
    .eqv BUTTON_D_MASK 0x04
    lw t1, BUTTON_OFFSET(x3)
    andi t1, t1, BUTTON_D_MASK
    beq t1, x0, L1
    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

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:

    addi t1, x0, 0
    addi t1, t1, 1
    beq x0, x0, loop

Determine what the following code sequence does.

    # Example #3
    .eqv BUTTON_OFFSET 0x24
    .eqv BUTTON_D_MASK 0x04
    addi t2, x0, 0
    lw t1, BUTTON_OFFSET(x3)
    andi t1, t1, BUTTON_D_MASK
    beq t1, x0, L1
    addi t2, t2, 1
    beq x0, x0, L1


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)
    lw t2, TIMER(x3)
    beq t2, t1, after_wait
    beq x0, x0, wait

Determine what the following code sequence does.

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

The I/O sub-system also includes the UART and the VGA controller but these will be described in a future lab.


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: 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 compile 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.

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

The arguments to this

  • 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 messagese 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 th 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 file as part of your lab submission

Exercise #2 - 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


Download the bitstream to the Basys3 board and verify that it operates as you expect.

Exercise #3 - 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.

Use the file multicycle_iosystem.s as a start for your code. Note: The assembly language coding standard has been updated. Make sure you review this 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 #2. 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 \

Create a new simulation set named ‘sim_2’ so you can simulate your new program (the other simulation set,’sim_1’, has the original assembly language program). Having two simulation sets allows you to switch between the two programs in the same project. The following commands demonstrate how to create new simulation set and how to specify the instruction memory used for this set.

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

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]

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. Tag your repository with the string lab7_submission and push your repository back to the origin. Test your submission by running the 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.

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.

  1. You will need to connect your FPGA board to a VGA display for this lab. 

Last Modified: 2022-05-31 12:23:24 -0500