RISC-V Screen and Memory-Mapped I/O

RISC-V programs draw and read input through memory, not through system calls: the screen is a grid of words anywhere in memory, and the keyboard and the console are four words at 0xffff0000. Both are RARS's own tools, the bitmap display and the keyboard and display simulator, with the same parameters and the same register layout, so a program written for RARS runs here unchanged.

Bitmap display

One word of memory is one pixel. Its low 24 bits are the color, red in bits 23-16, green in 15-8 and blue in 7-0; the top byte is ignored. Words run left to right and then top to bottom, so the pixel below a word is one row of words further on.

The screen panel's Display button configures it, with RARS's own five parameters, and a program can ask for them itself with the @screen comment below. The parameters are saved with the project, and testcases run with them too.

1 Unit width and unit height in pixels: 1, 2, 4, 8, 16, 32. How large one word is drawn. Default 1 by 1.
2 Display width and height in pixels: 64, 128, 256, 512, 1024. Default 512 by 256. Divided by the unit size, they give the word grid: the default is 512 by 256 words, one megabyte of memory.
3 Base address for display: where the grid starts. 0x10000000 (global data), 0x10008000 ($gp), 0x10010000 (static data), 0x10040000 (heap), 0xffff0000 (memory map). Default 0x10010000 (static data), which is where .data puts your first label.
        .data
display:.space  262144          # 256 * 256 words

        .text
main:
        la      t0, display
        li      t1, 0x00ff8000  # low 24 bits: red 0xff, green 0x80, blue 0x00
        sw      t1, 0(t0)       # the pixel at the top left
        sw      t1, 1024(t0)    # 256 words further on: the one below it

Reserve the memory the grid covers, with .space or a label of your own: the screen shows whatever those words hold, and a program that writes past what it reserved is writing over something else. Undo walks the picture back with the code, because the picture is the memory the emulator rolled back.

Configuring the screen from the program

A comment line naming @screen sets those five parameters at every Build, before the first instruction runs, so opening a program and building it is all it takes. It is a comment, so the same file still assembles in RARS, where you set the parameters in the tool's window as usual.

# @screen unit=1 width=256 height=256 base=display

        .data
display:.space  262144          # 256 * 256 words

        .text
main:
        la      t0, display
width

The display width in pixels, one of 64, 128, 256, 512, 1024.

height

The display height in pixels, one of 64, 128, 256, 512, 1024.

unit

How many pixels wide and high one word is drawn, one of 1, 2, 4, 8, 16, 32. unitWidth and unitHeight set the two separately.

base

Where the grid starts: a label the program defines, which is the point of it — the program never has to know the address — or an address such as 0x10010000 or 268500992. A label not on a word boundary is rounded down to one.

  • Order and spacing do not matter, commas are allowed between settings, and unitWidth, unit-width and unitwidth are the same name.
  • What the directive leaves out keeps the value it had, and a program with no @screen line changes nothing at all: the configuration stays yours.
  • A value RARS has no entry for, an unknown setting or a label that does not exist is a warning on the directive's line, never an error: a comment cannot stop a program from assembling. A size off the list is replaced by the nearest one on it, and anything else is left as it was.
  • Changing a parameter in the Display popover afterwards wins, until the next Build reads the comment again.

Keyboard and display registers

Four words carry one character each way. Click the screen panel to give the program the keyboard; the ring around it says the editor's own shortcuts are off while it has focus. What the program transmits is appended to the console transcript, the same one print services write to, which is also what testcases assert on.

0xffff0000 Receiver control

Bit 0 is Ready: a typed character is waiting in the receiver data register. The device sets it and clears it; a program only reads it.

0xffff0004 Receiver data

The typed character, in the low byte. Reading it takes that character; the next one, if any, appears at once and Ready stays set until the queue is empty.

0xffff0008 Transmitter control

Bit 0 is Ready, and here it is always set: the console never makes a program wait to print.

0xffff000c Transmitter data

Storing a character in the low byte prints it on the console. ASCII 12, a form feed, clears the console instead.

        li      s0, 0xffff0000
poll:
        lw      t0, 0(s0)       # receiver control
        andi    t0, t0, 1       # the Ready bit
        bnez    t0, take
        li      a7, 32          # nothing typed yet: a wait costs no instructions
        li      a0, 10
        ecall
        j       poll
take:
        lw      t1, 4(s0)       # receiver data, one character
        sw      t1, 12(s0)      # transmitter data: print it

The receiver never loses a keystroke: what does not fit in the data register waits in a queue behind it, and Ready (1) stays set until that queue is empty. Bit 1 of either control register, RARS's interrupt-enable bit (2), stops the program with an error: this editor polls, it does not deliver device interrupts.

Program time

Service 30 (a7 before a ecall) answers with the time in milliseconds, low word in a0 and high word in a1, and service 32 waits for the milliseconds in a0. Time is counted from the start of the run rather than from 1970, so a program differences two reads exactly as it did before, and a wait costs no instructions — a program idling on the keyboard never reaches the execution limit. In a testcase both run on a virtual clock that starts at zero and only advances through the program's own waits, so a five second wait finishes at once and elapsed-time output is the same on every machine.

Differences from RARS

  • The display is always there: it is a panel next to the memory view rather than a tool you connect to the program before running it.
  • Interrupt-driven I/O is not supported. Setting the interrupt-enable bit of a control register stops the program with an error naming the feature; poll the Ready bit instead.
  • Time comes from the start of the run, and a testcase runs on a virtual clock. RARS answers with the host's wall clock.
  • There is no mouse: neither simulator's tools have one.
  • Programs live in examples/risc-v/ in the repository, one per feature, each naming the display it wants in an @screen comment; RARS has no such directive and ignores the line.