Skip to content

CS 21 25.2 Laboratory Exercise 8-9

Memory-Mapped I/O: Software and Hardware

Overview

You will be using both Ripes and Logisim to implement a simple graphical game using a graphical display and hardware-based player input.

Individual or pair activity

This activity is intended to be done either alone or with a partner. You are allowed to choose a partner from any CS 21 25.2 lab section.

To confirm your group (either solo or pair), create a group via https://classroom.github.com/a/Vp1gzQ_y (once per pair). For pairs, have the other member join the group created.

This activity involves two subtasks:

  1. Simple graphical game in RISC-V
  2. Logisim-based single-cycle processor with I/O devices

Part 1: Simple Graphical Game in RISC-V

Overview

For this subtask, you are to implement a simple RISC-V-based game that employs the LED matrix and directional pad devices provided by Ripes.

The game is set in a 10 x 10 grid in which the player controls a single entity that occupies one cell and starts at the top-left-most cell of the grid.

Ripes setup

The program is to assume that Ripes is set up as follows:

  • LED matrix:
    • Base address of LED matrix (LED_MATRIX_0_BASE): 0xf0000000
    • Size of LED matrix (LED_MATRIX_0_SIZE): 0x190
    • Width of LED matrix (LED_MATRIX_0_WIDTH): 0xa
    • Height of LED matrix (LED_MATRIX_0_HEIGHT): 0xa
  • Directional pad:
    • Base address of directional pad (D_PAD_0_BASE): 0xf0000190
    • Size of directional pad (D_PAD_0_SIZE): 0x10
    • Address of up register (D_PAD_0_UP_OFFSET): 0xf0000190
    • Offset of address of up register (D_PAD_0_UP_OFFSET): 0x0
    • Address of down register (D_PAD_0_DOWN_OFFSET): 0xf0000194
    • Offset of address of down register (D_PAD_0_DOWN_OFFSET): 0x4
    • Address of left register (D_PAD_0_LEFT_OFFSET): 0xf0000198
    • Offset of address of left register (D_PAD_0_LEFT_OFFSET): 0x8
    • Address of right register (D_PAD_0_RIGHT_OFFSET): 0xf000019c
    • Offset of address of right register (D_PAD_0_RIGHT_OFFSET): 0xc

To ensure that Ripes is set up as described above, do the following:

  1. Open the I/O tab of Ripes (see panel on left side of Ripes window)
  2. Close all I/O device windows (e.g., subwindows with titles such as LED Matrix 0 and D-Pad 0); if done correctly, the I/O exports section should show only the following text:
    #ifndef RIPES_IO_HEADER
    #define RIPES_IO_HEADER
    #endif // RIPES_IO_HEADER
    
  3. Double-click the LED Matrix item under the Devices section; an LED matrix window should appear together with information about the LED matrix at the right-hand side of the screen
  4. In the said information panel, do the following:
    1. Double-click the value 25 to the right of Height, replace it with 10, then hit Enter to confirm; the LED matrix displayed should adjust its height accordingly
    2. Double-click the value 25 to the right of Width, replace it with 10, then hit Enter to confirm; the LED matrix displayed should adjust its width accordingly
    3. Double-click the value 8 to the right of LED size, replace it with 20, then hit Enter to confirm; the LEDs in the LED matrix should appear much larger
  5. Double-click the D-Pad item under the Devices section; a directional pad window should appear together with information about the directional pad at the right-hand side of the screen
  6. If done correctly, the I/O exports window should show exactly the following:
    #ifndef RIPES_IO_HEADER
    #define RIPES_IO_HEADER
    // *****************************************************************************
    // * LED_MATRIX_0
    // *****************************************************************************
    #define LED_MATRIX_0_BASE   (0xf0000000)
    #define LED_MATRIX_0_SIZE   (0x190)
    #define LED_MATRIX_0_WIDTH  (0xa)
    #define LED_MATRIX_0_HEIGHT (0xa)
    
    
    // *****************************************************************************
    // * D_PAD_0
    // *****************************************************************************
    #define D_PAD_0_BASE    (0xf0000190)
    #define D_PAD_0_SIZE    (0x10)
    #define D_PAD_0_UP_OFFSET   (0x0)
    #define D_PAD_0_UP  (0xf0000190)
    #define D_PAD_0_DOWN_OFFSET (0x4)
    #define D_PAD_0_DOWN    (0xf0000194)
    #define D_PAD_0_LEFT_OFFSET (0x8)
    #define D_PAD_0_LEFT    (0xf0000198)
    #define D_PAD_0_RIGHT_OFFSET    (0xc)
    #define D_PAD_0_RIGHT   (0xf000019c)
    
    
    #endif // RIPES_IO_HEADER
    

Tip: Automatic Ripes assembler constants

All definitions above (e.g., D_PAD_0_BASE) can be used in your program as though they were defined via .equ (i.e., Ripes automatically has .equ D_PAD_0_BASE 0xf0000190 set).

As an example, li t0, D_PAD_0_BASE will work automatically in Ripes without any having any .equ for D_PAD_0_BASE as long as the I/O panel has the appropriate #define.

Note that you may press the button inside each I/O device subwindow to make them appear even if the current Ripes tab is changed to anything other than the I/O tab.

Movement

Pressing a directional pad button determines the movement of the player entity across the grid. Pressing multiple buttons at the same time has undefined behavior.

The player entity is not allowed to move past the edge of the grid (i.e., attempting to go past any edge will result in the player entity not moving).

Directional pad-based movement should be implemented via polling. While there is no expected speed at which the player entity moves across the grid, it must be possible to realistically finish the game.

Food objects

At the start of the game, seven food objects must be in the grid at the following locations (rows and columns are zero-indexed):

  1. Row 0, Column 9
  2. Row 9, Column 0
  3. Row 9, Column 9
  4. Row 2, Column 2
  5. Row 6, Column 7
  6. Row 1, Column 8
  7. Row 6, Column 3

Each food object must appear as a white dot (i.e., with color 0x00ffffff) in their specified location.

Consuming food objects

When the player entity collides with a food object (i.e., they share the same grid location), the food object must disappear. Additionally, the player entity must change color based on the number of consumed food objects:

  • 1 consumed food object: 0x00ff0000 (red)
  • 2 consumed food objects: 0x00ff7f00 (orange)
  • 3 consumed food objects: 0x00ffff00 (yellow)
  • 4 consumed food objects: 0x0000ff00 (green)
  • 5 consumed food objects: 0x000000ff (blue)
  • 6 consumed food objects: 0x007f00ff (violet)
  • 7 consumed food objects: 0x00ff00ff (pink)

Initially, the player entity must have the color 0x00cccccc (gray).

When all the food objects have been consumed, the game must still continue (i.e., there is no indication that all food objects have been consumed).

Data segment constraints

Your implementation must not initialize anything via .data. You are to use the address range starting 0x10000 as a replacement for the data segment. If there are initial values needed, they must be manually initialized by the program.

Instruction constraints

Your implementation must use only the following instructions:

  • lw
  • sw
  • add
  • sub
  • and
  • or
  • slt
  • addi
  • beq
  • jal
  • lui
  • jalr

While you are allowed to use pseudoinstructions such as li, you must ensure that their equivalent basic instructions fall within the allowed subset above.

Multiplication via repeated addition

You are expected to perform multiplication via repeated addition (ideally via a loop).

Deliverable

Create a RISC-V program Lab 8-9.s that implements the game described above, subject to the listed constraints.

Ensure that this is properly pushed to your Github Classroom repository.

Part 2: Logisim-Based Single-Cycle Processor with I/O Devices

Overview

A Logisim-Evolution template for a working RISC-V single-cycle processor can be downloaded via the link in the previous section. This processor implementation supports the following instructions:

  • lw
  • sw
  • add
  • sub
  • and
  • or
  • slt
  • addi
  • beq
  • jal
  • lui

Unlike the single-cycle processor in Lab 5, the single-cycle processor in this implementation has its instruction memory and data memory factored out.

In other words, the single-cycle processor is simply a subcircuit called processor that takes in CLK, ReadData, and Instr as inputs, and produces PCCurrent, MemWrite, Address, and WriteData as outputs. The main circuit connects a processor instance with the factored-out instruction and data memories.

main includes two I/O devices: a directional pad and an RGB video component (taken to be an LED matrix).

Address decoder

To provide access to I/O devices (which includes data memory), memory-mapped I/O is employed by using addressdecoder to partition the address space into the following mappings:

  • 0x00000000-0xefffffff: Data memory
  • 0xf0000000-0xf000018f: RGB video component
  • 0xf0000190-0xf000019f: Directional pad
  • 0xf00001a0-0xffffffff: Data memory

Consistency with Ripes

Note that the memory-mapped address ranges above are consistent with the Ripes setup described in the previous section.

The ReadDataSel output of the address decoder is responsible for determining the I/O device source of ReadData which is to be fed to the processor. Of the three I/O devices in main, only data memory and the directional pad are capable of being read from.

For example, Address = 0xf0000194 falls under the segment for the directional pad, so ReadData will be whatever dpadcontroller emits (more on this later).

The MemWrite signal, if set to 1, translates to one of the writeable I/O devices being enabled for writing. The outputs WEMem and WERGB correspond to the write enable signals for data memory and the RGB video component, respectively.

If MemWrite is 1, which write enable signal will be set depends on where Address falls in the memory map stated earlier. As an example, Address = 0xf00000004 falls under the RGB video component, so WERGB must be set to 1 (assuming MemWrite is also 1).

RGB video component

The RGB video component in main is a 10x10 matrix of RGB pixels. Each pixel is described by a 24-bit value 0xRRGGBB where 0xRR, 0xGG, and 0xBB correspond to the intensities of the red, green, and blue components of the resulting color, each as an 8-bit unsigned value (i.e., 0 to 255).

Data persistence outside controller

Note that unlike the LED matrix described in the lecture, the expected controller of the RGB vide omponent should not have registers to store the current color of each pixel.

Instead, the RGB video component itself is able to store the current colors of all pixels.

The rgbcontroller subcircuit is in charge of two things:

  1. Translating Address into the unsigned 4-bit values XRGB and YRGB which correspond to zero-indexed column (X) and row (Y) indices, respectively
  2. Taking the lower 24 bits of the 32-bit WriteData and emitting them as the WriteDataRGB output

Assuming WERGB is set to 1, exactly one pixel can be set to the value of WriteDataRGB during each positive edge of CLK. Which pixel is set is determined by the XRGB and YRGB values set by rgbcontroller.

Each pixel occupies four byte addresses (despite being only a 24-bit value). The first pixel (i.e., row 0, column 0) corresponds to address 0xf0000000. The next pixel (i.e., row 0, column 1) corresponds to address 0xf0000004. The last pixel (i.e., row 9, column 9) corresponds to address 0xf000018c.

Supposing Address is set to 0xf0000188, WriteData is 0x00014421, and WERGB is 1, the pixel in row 9, column 8 will be set to the color with 0x01 for red, 0x44 for green, and 0x21 for blue (i.e., UP Forest Green) during the next positive edge.

Directional pad

The directional pad in main is comprised of four input Pin components (one for each cardinal direction) and switches from 0 to 1 (or vice versa) with the Poke tool.

For each pin, a value of 1 represents the state in which the user is pressing on the corresponding button, while 0 means it is not being pressed.

Each direction corresponds to an address in the segment mapped to the directional pad:

  • Up: 0xf0000190
  • Down: 0xf0000194
  • Left: 0xf0000198
  • Right: 0xf000019c

The dpadcontroller subcircuit is in charge of two things:

  • Stores the latest value of each direction (i.e., either 0 or 1) during each positive edge of CLK
  • Sets ReadDataDPad to the 32-bit value corresponding to which direction Address corresponds to

The least significant bit of ReadDataDPad must be equal to the stored value of the direction Address corresponds to. All other bits of ReadDataDPad must be set to 0.

As ReadDataDPad depends on Address to select which direction value to read, only one direction value can be retrieved during each tick.

Input pin instead of button

While the button component seems to be more appropriate for the directional pad, there is no way to simultaneously actuate several buttons (which is needed to reset the game).

The input pin component allows this, hence its use over buttons.

jalr support

As the given circuit does not currently support jalr, you are also expected to add support for it.

Deliverable

Provide implementations for the addressdecoder, dpadcontroller, and rgbcontroller subcircuits as described above. Ensure your implementation also supports jalr.

Save your work as Lab 8-9.circ. If done correctly, your RISC-V code from Part 1 should run properly on the Logisim circuit.

Ensure that this is properly pushed to your Github Classroom repository.

Submission Details

Two-week deadline and Lab 10

Kindly note that despite the two-week deadline, the timeline for Lab Exercise 10 will be overlapping with that of Lab Exercise 8-9.