SEN2201 · Week 6 · The von Neumann Model (I/O basics)

I/O basics: memory-mapped I/O and polling

How the keyboard and the display are connected to the LC-3: the KBSR/KBDR/DSR/DDR registers, the ready bit, the polling loop, the difference from interrupts and an introduction to TRAP service routines.

input/output (I/O)memory-mapped I/Odevice registerKBSRKBDRDSRDDRready bitpollinginterrupt-driven I/OasynchronoussynchronousTRAPservice routinetrap vector table

A computer that only did calculations would be useless: it has to get data from somewhere and give the result somewhere. The input and output parts of the von Neumann model do this job. On this page you will learn how the LC-3 talks to the keyboard and the display. The mechanism is simple: the device has two registers, one holds the answer to the question “are you ready?”, the other holds the character itself.

This topic uses the LC-3 instructions of weeks 7-8 (LDI, STI, BR). If you have not seen those instructions yet, reread this page after them; here we explain what each instruction does in a single sentence.

Device registers

Every I/O device has at least two registers:

  • Data register: the data transferred (the character).
  • Status register: the state of the device, especially the “ready” information.

The registers of the keyboard and display in the LC-3:

RegisterAddressContents
KBSR (keyboard status)xFE00bit 15: is there a new character
KBDR (keyboard data)xFE02bits 7:0: the ASCII code of the key pressed
DSR (display status)xFE04bit 15: is the display ready to accept a new character
DDR (display data)xFE06bits 7:0: the character to be written to the display

Memory-mapped I/O

Some processors have special instructions for I/O (like IN, OUT). The LC-3 does not do this: the device registers are given memory addresses. The processor accesses them with ordinary load and store instructions like memory. When the memory interface sees that the address is in the xFE00 region, it routes the request to the device, not to memory. This is called memory-mapped I/O.

The result: “read from the keyboard” = “read address xFE02”. “Write to the screen” = “write to address xFE06”.

Why LDI and STI?

The LD and ST instructions can access an address near the PC: PC + offset, with a 9-bit offset, that is -256..+255. The program is around x3000. xFE00 is too far. The solution: put the device’s address next to the program as a pointer and access it indirectly.

KBSRP   .FILL xFE00     ; the address of KBSR, inside the program
...
        LDI  R1, KBSRP  ; R1 ← M[M[KBSRP]] = M[xFE00] = KBSR

LDI R1, KBSRP reads in two steps: first it takes the value in KBSRP (xFE00), then it puts the contents of that address in R1. STI does the same for writing.

Synchronization: the ready bit

The keyboard is asynchronous: it is not known when the user will press a key. The processor, meanwhile, executes billions of instructions per second. For the two to work together a handshake is needed. The ready bit provides it.

Keyboard side:

  1. The user presses a key. The keyboard puts the ASCII code in KBDR and sets KBSR[15] = 1.
  2. The processor reads KBDR. At the moment of reading, KBSR[15] automatically becomes 0.
  3. While KBSR[15] = 0, the keyboard does not write a new character to KBDR; this way an unread character is not overwritten.

Display side:

  1. If DSR[15] = 1, the display is ready.
  2. The processor writes the character to DDR. DSR[15] becomes 0.
  3. After the display has shown the character, DSR[15] becomes 1.

TIP · Why BRzp? The ready bit is bit 15, that is, the sign bit of the 16-bit number. When ready, the value loaded into the register is negative (N). When not ready it is 0 or positive (Z or P). “Wait as long as it is not ready” = loop back with BRzp.

Polling

The processor reading the ready bit again and again in a loop is called polling.

Read a character from the keyboard:

POLL    LDI  R1, KBSRP   ; R1 ← KBSR
        BRzp POLL        ; bit 15 = 0 → not ready, ask again
        LDI  R0, KBDRP   ; R0 ← KBDR (the character)

Write a character to the screen:

POLL2   LDI  R1, DSRP    ; R1 ← DSR
        BRzp POLL2       ; the display is busy, wait
        STI  R0, DDRP    ; DDR ← R0

Worked example: echo

Read a character from the keyboard and write it to the screen.

        .ORIG x3000
START   LDI  R1, KBSRP
        BRzp START
        LDI  R0, KBDRP
ECHO    LDI  R1, DSRP
        BRzp ECHO
        STI  R0, DDRP
        HALT
KBSRP   .FILL xFE00
KBDRP   .FILL xFE02
DSRP    .FILL xFE04
DDRP    .FILL xFE06
        .END

When the user types k:

k

When the program ends, R0 = x006B (the ASCII code of ‘k’), R1 = x8000 (the last DSR read, ready), condition code N.

The difference between polling and interrupts

PollingInterrupt-driven
Who asks?The processor keeps reading the ready bitThe device sends a signal to the processor when ready
The processor while waitingSpins uselessly in a loopRuns another program
EfficiencyLow (most loops are wasted)High
SimplicityVery simpleAn interrupt mechanism is needed

A person presses perhaps 5 keys per second, and in that time the processor can execute billions of instructions. Polling wastes almost all of this time. With interrupts, the processor does its own work, a short service routine runs when the device is ready, and the processor returns to where it left off.

TRAP: leave it to the operating system

Writing these polling loops in every program is both tedious and dangerous (you do not want a user program to play with the device registers). In the LC-3, this job is left to service routines in the operating system. The user program calls them with the TRAP instruction.

TRAPAliasWhat does it do?
x20GETCRead a character, put it in R0 (does not echo to the screen)
x21OUTWrite the character in R0[7:0] to the screen
x22PUTSWrite the string starting at the address in R0 up to x0000
x23INPrint a prompt, read a character, echo it to the screen too, put it in R0
x25HALTStop the machine

When TRAP x23 runs:

  1. The return address is saved: R7 ← PC (the already incremented PC).
  2. PC ← M[x0023]. The range x0000-x00FF is the trap vector table; each row holds the starting address of a service routine.
  3. The service routine does its job with its own polling loop.
  4. At the end it returns to the user program with RET (= JMP R7).

Worked example: read a digit from the keyboard and print its double.

        GETC                ; R0 ← '3' = x33
        LD   R1, NEG30      ; R1 ← -48
        ADD  R2, R0, R1     ; R2 ← 3 (the number)
        ADD  R0, R2, R2     ; R0 ← 6
        LD   R1, POS30      ; R1 ← 48
        ADD  R0, R0, R1     ; R0 ← '6'
        OUT
        HALT
NEG30   .FILL #-48
POS30   .FILL #48

If the user types 3:

6

PITFALL · overwriting R7. TRAP writes the return address into R7. If your program keeps an important value in R7, it is lost after a TRAP. Save it somewhere else first.

Common mistakes

Wrong ideaThe truth
The ready bit is bit 0Bit 15 (the sign bit)
When ready the value is positiveBit 15 = 1 → negative, N
The wait loop uses BRnLoop back while not ready: BRzp
LD R1, xFE00 worksoffset9 does not reach that far; LDI + .FILL
Reading KBDR does not change KBSRWhen read, KBSR[15] = 0
Write to DDR without looking at DSRIf the display is busy the character is lost
Polling frees the processorThe processor spins uselessly in the loop
TRAP pushes the return address on the stackIt writes it to R7

Test yourself

Exam-style questions. Solve them on paper first, then reveal the answer.

Question 1easy

What is memory-mapped I/O?

Show answer
Device registers are assigned memory addresses and accessed with load/store instructions

Device registers (KBSR, KBDR, DSR, DDR) are placed at memory addresses. The processor does not use separate I/O instructions; it accesses these addresses with the same LD/ST family (LDI, STI). When the address reaches xFE00 and above, the memory interface routes the request to the device, not to memory.

Question 2easy

Give the LC-3 memory-mapped addresses of KBSR, KBDR, DSR and DDR.

Show answer
xFE00, xFE02, xFE04, xFE06

Keyboard: status xFE00, data xFE02. Display: status xFE04, data xFE06. In the status registers you find the ready bit, in the data registers the character (bits [7:0]).

Question 3easy

Which bit of KBSR tells the processor that a new character is available?

Show answer
Bit 15

KBSR[15] is the ready bit. When a key is pressed, the keyboard puts the character in KBDR and sets KBSR[15] = 1. When the processor reads KBDR, this bit is automatically cleared to 0.

Question 4medium

A polling loop is: POLL LDI R1, KBSRP followed by BRzp POLL. Why is BRzp the right branch?

Show answer
Ready bit is bit 15, so the value is negative when ready; loop while zero or positive

LDI loads KBSR into R1 and sets the condition codes. If bit 15 = 1, the 16-bit value is negative in 2's complement (N). If not ready, the value is 0 or positive (Z/P). BRzp loops back as long as it is not ready; when it is ready, it falls through to the next instruction.

Question 5medium

Why does the program use LDI R1, KBSRP (with KBSRP .FILL xFE00) instead of LD R1, xFE00?

Show answer
xFE00 is out of range of the 9-bit PC-relative offset

The address of LD is PC + offset9, that is, within -256..+255 of the PC. The distance from x3001 to xFE00 is 52735, which does not fit. So the address is written to a nearby location with .FILL and read indirectly with LDI.

Question 6easy

When the keyboard is ready and KBSR = x8000, what condition code is set by LDI R1, KBSRP?

Show answer
N

x8000 = 1000 0000 0000 0000, MSB 1 → negative. According to the loaded value N = 1, Z = P = 0. When not ready, KBSR = x0000 → Z.

Question 7medium

Why must the program check DSR[15] before writing a character into DDR?

Show answer
The display is slower; writing before it is ready would lose characters

The processor is much faster than the display. While the character written to DDR is being displayed, DSR[15] = 0. If you write a new character before it finishes, it overwrites the previous one and the character is lost. When DSR[15] = 1, it is safe to write.

Question 8medium

What is the difference between polling and interrupt-driven I/O?

Show answer
Polling: CPU repeatedly checks the ready bit; interrupt: the device signals the CPU when ready

In polling the processor reads the ready bit continuously in a loop and cannot do other work in the meantime (wasted time). In interrupt-driven I/O the processor continues its own work; when the device is ready it interrupts the processor, which runs a short service routine and returns to where it left off.

Question 9easy

Is keyboard input synchronous or asynchronous? Why?

Show answer
Asynchronous

The user presses keys not according to the processor's clock but at his own pace. It is not known when a character will arrive. That is why a handshake mechanism like the ready bit is needed.

Question 10medium

The program below reads two characters and prints each one plus 1. If the user types HI, what is printed?

        AND  R2, R2, #0
        ADD  R2, R2, #2
AGAIN   LDI  R1, KBSRP
        BRzp AGAIN
        LDI  R0, KBDRP
        ADD  R0, R0, #1
WAIT    LDI  R1, DSRP
        BRzp WAIT
        STI  R0, DDRP
        ADD  R2, R2, #-1
        BRp  AGAIN
        HALT
Show answer
IJ

R2 is the counter (2). In each round one character is read, its ASCII code is incremented by 1, and it is written to the screen. 'H' = x48 → x49 = 'I'. 'I' = x49 → x4A = 'J'. When R2 reaches 0, BRp falls through, HALT.

Question 11hard

In the program above, why is the counter decremented right before BRp AGAIN and not earlier?

Show answer
BRp tests the condition codes of the last instruction that wrote a register, the ADD R2

The condition codes are determined by the last instruction that wrote to a register. Because ADD R2, R2, #-1 comes right before, BRp looks at the new value of R2. If a load like LDI R1 were in between, BRp would decide based on R1 and the loop would break.

Question 12hard

A user program executes TRAP x23. Where does the processor find the starting address of the service routine?

Show answer
In memory location x0023 (trap vector table)

The TRAP instruction zero-extends the 8-bit trapvect to 16 bits and loads the value at that address into the PC: PC ← M[x0023]. The range x0000-x00FF is the trap vector table. Also the return address is saved in R7 (R7 ← incremented PC).

Verification: The polling programs and the TRAP example were run on our own LC-3 simulator (with the keyboard and display registers mapped to addresses xFE00-xFE06); the outputs, register values and condition codes were taken from there (verify/SEN2201/06.mjs).

Review card

Memory-mapped I/O: device registers = memory addresses, access with LDI/STI KBSR xFE00 | KBDR xFE02 | DSR xFE04 | DDR xFE06 Ready bit = bit 15. When ready the value is NEGATIVE (N) Keyboard: KBSR[15]=1 -> the character is in KBDR[7:0]; when KBDR is read KBSR[15]=0 Display: DSR[15]=1 -> write to DDR; after writing DSR[15]=0 Read: POLL LDI R1,KBSRP / BRzp POLL / LDI R0,KBDRP Write: POLL2 LDI R1,DSRP / BRzp POLL2 / STI R0,DDRP KBSRP .FILL xFE00 (LD cannot reach that far, offset9 = -256..255) Polling: the CPU keeps asking (wasted time) | Interrupt: the device interrupts the CPU Keyboard is asynchronous -> handshake (ready bit) TRAP x20 GETC | x21 OUT | x22 PUTS | x23 IN | x25 HALT TRAP: R7 <- PC, PC <- M[trapvect] (table x0000-x00FF); return: RET (JMP R7)