MIPS Screen and Memory-Mapped I/O

Screen, keyboard and console

The simulator includes a screen, keyboard and console for your program to use. These are virtual devices inside the editor: there is no separate hardware to connect or configure before running. The screen is a grid of words in memory, and the keyboard and console are controlled through four memory-mapped words starting at 0xffff0000. A program checks the keyboard's Ready bit, reads a character, and writes characters to the console. The screen panel is always available beside the memory view.

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, and a program can set those same five parameters with the @screen comment below. The parameters are saved with the project, and testcases run with them too. Width and height are physical display pixels; divide each by its matching unit size to get the number of memory words across and down. For example, 512 by 256 pixels with 2 by 4 pixel units gives 256 by 64 words, so the grid occupies 16,384 words (65,536 bytes). The default unit is 1 by 1, so the default 512 by 256 pixel display uses 512 by 256 words.

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

The bitmap example configures its own 256 by 256 word grid with @screen, so its 256-word row stride is 1024 bytes.

# @screen unit=1 width=256 height=256 base=display
        .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 example reserves 256 × 256 words, or 262,144 bytes; the screen shows whatever those words hold, and writing past the reserved range overwrites other memory. 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 it does not change how the assembly source is parsed.

# @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.

  • 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. 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      $v0, 32         # nothing typed yet: a wait costs no instructions
        li      $a0, 10
        syscall
        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. Click the screen panel to send keyboard input to the program; its focus ring shows that the editor's own shortcuts are paused. This simulator polls the devices and does not deliver device interrupts, so setting bit 1 of either control register (2) stops the program with an error. Poll Ready instead.

Program time

Service 30 ($v0 before a syscall) 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, so subtracting two readings measures elapsed time. A wait costs no instructions, which lets a program idle for keyboard input without reaching the execution limit. In a testcase the virtual clock starts at zero and advances only through the program's waits: a five-second wait finishes immediately, and elapsed-time output is repeatable. There is no mouse device in this simulator.