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, or Custom for any from 1 to 32. How large one word is drawn. Default 1 by 1.
  2. Display width and height in pixels: 64, 128, 256, 512, 1024, or Custom for any from 1 to 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.

Pictures in Files

A program can draw a picture it reads from a File of the project, such as a sprite sheet. Open a BMP File and choose Pixels under the editor to draw in it, or make one with New picture in the Explorer. A new picture is 32-bit and stored top-down, the layout of this screen: the pixel at column x and row y is the word at pixels + 4 × (y × width + x), and that word is already the screen's color, so drawing the picture is a copy. Read the File into memory with the file services; its pixels start at the offset held in its bytes 10 to 13, which is 54 in the 32-bit picture New picture makes unless told otherwise.

Each stroke updates the picture in your project when you release the mouse button or lift your finger. With autosave on, the change is saved immediately. With autosave off, save the project manually to keep your changes. Use Undo in the Pixel editor to undo a stroke. The program reads the picture as you last drew it at its next run. Other BMP layouts open in Pixels too, and keep their layout: a 24-bit picture keeps its three bytes a pixel and its rows stored bottom-up, which the program then has to convert.

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, any from 1 to 1024.

  • height: the display height in pixels, any from 1 to 1024.

  • unit: how many pixels wide and high one word is drawn, any from 1 to 32. unitWidth and unitHeight set the two separately.

  • MARS's and RARS's own tool only offers 64, 128, 256, 512, 1024 for the display and 1, 2, 4, 8, 16, 32 for the unit, so a program that also runs there keeps to those. Here # @screen width=224 height=288 gives an arcade machine's screen.

  • 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 outside the ranges is replaced by the nearest one inside them, 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 accepts typed keys and scripted Testcase input. It 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 measured in milliseconds since 1970-01-01 UTC; subtract two readings to measure 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 2000-01-01 UTC 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.