# The bitmap display and the keyboard registers

[Courses](https://asm-editor.specy.app/learn/courses.md) › [RISC-V assembly](https://asm-editor.specy.app/learn/courses/risc-v.md) › Talking to the outside world

Web page: https://asm-editor.specy.app/learn/courses/risc-v/talking-to-the-outside-world/the-bitmap-display-and-the-keyboard

> RISC-V uses lw and sw for a bitmap display backed by memory and for four console device registers at 0xffff0000. Configure the Screen panel with @screen, calculate a pixel address, and poll the Ready bit to echo keys.

Overview of this topic: [Assembly basics](https://asm-editor.specy.app/learn/courses/assembly-basics/talking-to-the-outside-world/memory-mapped-io.md)

The same topic in: [M68K](https://asm-editor.specy.app/learn/courses/m68k/talking-to-the-outside-world/screen-keyboard-and-mouse.md), [MIPS](https://asm-editor.specy.app/learn/courses/mips/talking-to-the-outside-world/the-bitmap-display-and-the-keyboard.md), [Z80](https://asm-editor.specy.app/learn/courses/z80/talking-to-the-outside-world/screen-keyboard-and-mouse.md), [x86-64](https://asm-editor.specy.app/learn/courses/x86/talking-to-the-outside-world/ports-devices-and-the-kernel.md)

`ecall` asks the Playground to do work. A screen and a console device can also be reached through
addresses. RISC-V still uses the load and store instructions you already know: `lw` reads a word
from an address and `sw` writes one.

This is **memory-mapped I/O**. The important distinction is what sits behind the address. The
bitmap display uses an ordinary region of memory as its backing store. The console addresses below
belong to device registers, so reading or writing them can have an effect outside the program.

## One word is one pixel

The Screen panel can display a region of memory as a grid of pixels. Each pixel is one four-byte
word. Its low 24 bits are colour bytes:

| bits          | colour byte |
| ------------- | ----------- |
| 23 through 16 | red         |
| 15 through 8  | green       |
| 7 through 0   | blue        |

The high byte is ignored. Thus `0x00FF0000` is red, `0x0000FF00` is green,
`0x000000FF` is blue, and `0x00FFFFFF` is white. This is the familiar `#RRGGBB` order written as
a hexadecimal word.

Pixels occupy consecutive words: left to right across a row, then the next row. Here the grid has
16 words in each row, so `.space 1024` reserves its 256 words. Run the program and inspect the
Screen panel.

```riscv
# @screen unit=16 width=256 height=256 base=display
.data
display: .space 1024        # 16 * 16 words, four bytes each

.text
.globl main
main:
    la t0, display
    li t1, 0x00FF0000       # red: column 0, row 0
    sw t1, 0(t0)
    li t1, 0x0000FF00       # green: one word to the right
    sw t1, 4(t0)
    li t1, 0x000000FF       # blue: one row below the first pixel
    sw t1, 64(t0)
    li t1, 0x00FFFFFF       # white: the final word in the grid
    sw t1, 1020(t0)
    li a7, 10
    ecall
```

The offsets are bytes. `4(t0)` moves by one word and `64(t0)` moves by 16 words, one full row.
The display reads these reserved words; loading or storing them otherwise behaves like loading or
storing any data memory.

## Configuring the Screen panel

The Screen panel's **Display** button lets you choose a display size, pixel-unit size, and backing
address. A complete `@screen` directive gives the same settings in the program. Each Build applies
them before the program begins, which makes the display setup repeatable:

```
# @screen unit=16 width=256 height=256 base=display
```

Here `width=256` and `height=256` make a 256-by-256-pixel display. `unit=16` draws every backing
word as a 16-by-16 screen-pixel square, leaving a 16-by-16 grid of words. RARS offers display
widths and heights of 64, 128, 256, 512, and 1024, and unit dimensions of 1, 2, 4, 8, 16, and 32.
The editor also takes any whole number from 1 to 1024 for the display and from 1 to 32 for a unit,
which RARS cannot show. `unit` sets both dimensions, while `unitWidth` and `unitHeight` can set
them separately.
Finally, `base=display` tells the panel that the word at the label `display` is the first pixel.
An address may be used for `base` too, but a label keeps the program independent of the data
section's numeric address.

## Finding a pixel

For a 16-column display, begin at the base, skip `y` whole rows of 16 words, then skip `x` more
words within that row. Since each word has four bytes, the address is:

```
base + (y * 16 + x) * 4
```

For example, column 15 in row 15 is the final word: skip 15 rows, then 15 words. A simple
blue-and-green ramp can use `x << 4` for the blue byte and `y << 12` for the green byte. At
`x = 15`, `y = 15`, those pieces are `0x000000F0` and `0x0000F000`, giving the colour
`0x0000F0F0`.

This program calculates the address for one chosen pixel. Change `x`, `y`, or `colour`, build, and
run it to place a different pixel.

```riscv
# @screen unit=16 width=256 height=256 base=display
.data
display: .space 1024

.text
.globl main
main:
    li t0, 15                   # x: column
    li t1, 15                   # y: row
    li t2, 0x0000F0F0           # green and blue
    la t3, display              # base address

    slli t4, t1, 4              # y * 16 words
    add  t4, t4, t0             # word number: rows, then x words
    slli t4, t4, 2              # byte offset: four bytes per word
    add  t4, t4, t3             # address of this pixel
    sw   t2, 0(t4)

    li a7, 10
    ecall
```

## The console registers at `0xffff0000`

The console appears as four **device registers**. The two control registers carry status and
control bits; the two data registers carry characters. They are accessed with ordinary `lw` and
`sw` spelling, but they are not a buffer in your data section. In particular, reading receiver data
takes a character from the input queue, and storing transmitter data sends a character to the
console.

|      address | register            | use                                                             |
| -----------: | ------------------- | --------------------------------------------------------------- |
| `0xffff0000` | receiver control    | bit 0 is **Ready** when a typed character is waiting            |
| `0xffff0004` | receiver data       | low byte is the next character; reading it takes that character |
| `0xffff0008` | transmitter control | bit 0 is Ready; it is always 1 here                             |
| `0xffff000c` | transmitter data    | storing a low-byte character prints it on the console           |

For the programs on this page, inspect bit 0 of either control register.

The transmitter is ready to accept a character, so this prints without using an `ecall` printing
service:

```riscv
.eqv MMIO, 0xffff0000

.text
.globl main
main:
    li s0, MMIO
    li t0, 'H'
    sw t0, 12(s0)           # transmitter data register
    li t0, 'i'
    sw t0, 12(s0)
    li t0, '\n'
    sw t0, 12(s0)
    li a7, 10
    ecall
```

The base in `s0` is `0xffff0000`, so the four word registers are at byte offsets 0, 4, 8, and 12.
When the registers panel shows that base as a signed number, it may display a negative value; the
same 32 address bits still select these registers.

## Polling and echoing keys

No instruction announces that a key was pressed. Instead, a program can repeatedly load receiver
control, keep bit 0, and branch back while that bit is zero. This is **polling**. Once Ready is one,
loading receiver data obtains one queued character. The following program sends that character back
through transmitter data and ends when it receives `q`.

Click the Screen panel before typing so that it has keyboard focus.

```riscv
.eqv MMIO, 0xffff0000
.data
banner: .asciz "Click the screen, then type. q ends the program.\n"

.text
.globl main
main:
    li a7, 4
    la a0, banner
    ecall

    li s0, MMIO
    li s1, 'q'
poll:
    lw t0, 0(s0)            # receiver control
    andi t0, t0, 1          # retain Ready, bit 0
    bnez t0, take

    li a7, 32               # wait briefly before checking again
    li a0, 10
    ecall
    j poll
take:
    lw t1, 4(s0)            # receiver data: take one queued character
    andi t1, t1, 0xFF
    sw t1, 12(s0)           # transmitter data: echo it
    beq t1, s1, quit
    j poll
quit:
    li a7, 10
    ecall
```

*Testcase for the program above (JSON):*

```json
{ "runFor": 40000 }
```

In this Playground, service 32 lets an empty polling loop wait without spending its instruction
budget. During an interactive run it waits for the requested time; a testcase advances its virtual
clock immediately. The wait makes the program pleasant to leave idle while it is waiting for a key.

## Arrow keys, held keys and the mouse

The receiver reports typed characters. Arrow keys and Shift do not produce characters, and the
receiver does not report when a key is released. A game that uses arrow keys or moves only while
a key is held can read the **Input controller** at `0xffff0300`. It reports key presses and releases,
held keys, and the mouse position and buttons. See the
[Input controller reference](https://asm-editor.specy.app/documentation/risc-v/input-controller.md) for examples.

## Your turn

The Screen panel is a 16-by-16 word grid whose base label is `display`. Put white at the pixel with
the supplied `x` and `y` values. Derive the row-and-column offset, convert it to bytes, and store
`0x00FFFFFF` at the resulting address.

*Exercise: the code to start from, checked by the testcase after it:*

```riscv
# @screen unit=16 width=256 height=256 base=display
.eqv SIDE, 16
.data
display: .space 1024

.text
.globl main
main:
    li t0, 5                # x
    li t1, 3                # y
    # your code here
```

*Testcase for the program above (JSON):*

```json
{
    "expectedMemory": [
        { "type": "number-chunk", "address": "0x100100D4", "bytes": 4, "expected": ["0x00FFFFFF"] }
    ]
}
```

<details>
<summary>Show solution</summary>

*Solution:*

```riscv
# @screen unit=16 width=256 height=256 base=display
.eqv SIDE, 16
.data
display: .space 1024

.text
.globl main
main:
    li t0, 5                # x
    li t1, 3                # y
    la t2, display
    li t3, SIDE
    mul t4, t1, t3          # y * SIDE rows of words
    add t4, t4, t0          # then x words
    slli t4, t4, 2          # four bytes per word
    add t4, t4, t2
    li t5, 0x00FFFFFF
    sw t5, 0(t4)
    li a7, 10
    ecall
```

</details>

Print `Hi` by storing its character codes in the transmitter data register, without printing
services 4 or 11. You may still use service 10 to end the run.

*Exercise: the code to start from, checked by the testcase after it:*

```riscv
.text
.globl main
main:
    # your code here
```

*Testcase for the program above (JSON):*

```json
{
    "expectedOutput": "Hi"
}
```

<details>
<summary>Show solution</summary>

*Solution:*

```riscv
.text
.globl main
main:
    li s0, 0xffff0000
    li t0, 'H'
    sw t0, 12(s0)           # transmitter data
    li t0, 'i'
    sw t0, 12(s0)
    li a7, 10
    ecall
```

</details>

## Instructions in this Lecture

- RISC-V: [`la`](https://asm-editor.specy.app/documentation/risc-v/instruction/la.md), [`li`](https://asm-editor.specy.app/documentation/risc-v/instruction/li.md), [`sw`](https://asm-editor.specy.app/documentation/risc-v/instruction/sw.md), [`ecall`](https://asm-editor.specy.app/documentation/risc-v/instruction/ecall.md), [`slli`](https://asm-editor.specy.app/documentation/risc-v/instruction/slli.md), [`add`](https://asm-editor.specy.app/documentation/risc-v/instruction/add.md), [`lw`](https://asm-editor.specy.app/documentation/risc-v/instruction/lw.md), [`andi`](https://asm-editor.specy.app/documentation/risc-v/instruction/andi.md), [`bnez`](https://asm-editor.specy.app/documentation/risc-v/instruction/bnez.md), [`j`](https://asm-editor.specy.app/documentation/risc-v/instruction/j.md), [`beq`](https://asm-editor.specy.app/documentation/risc-v/instruction/beq.md), [`mul`](https://asm-editor.specy.app/documentation/risc-v/instruction/mul.md)

Previous: [ecall](https://asm-editor.specy.app/learn/courses/risc-v/talking-to-the-outside-world/ecall.md)

Next: [When an instruction cannot be carried out](https://asm-editor.specy.app/learn/courses/risc-v/talking-to-the-outside-world/exceptions.md)
