Input/Output & System Calls

Assembly Fundamentals

Chapter 8 · Input/Output & System Calls

HALT has quietly ended nearly every code example since assembly1-1, and assembly1-2's own tip-box promised it would eventually be explained in full. This chapter finally does that — and, along the way, introduces the single instruction behind every bit of keyboard and screen interaction an LC-3 program can perform: TRAP.

The TRAP Instruction — Calling Into the Operating System

TRAP behaves a lot like assembly1-7's own JSR — it saves a return address into R7 and transfers control elsewhere — but instead of jumping to a label you wrote yourself, it jumps to a pre-defined operating system service routine, looked up through a small table of addresses called the trap vector table.

1111opcode
0000unused
trapvect88 bits

The 8-bit trapvect8 field isn't an offset the way BR's or LD's fields were — it's an index into the trap vector table, a small region of memory holding the actual starting address of each OS routine. TRAP x25 doesn't jump to address x25 directly; it looks up whatever address is stored at vector table entry x25, and jumps there.

The OS/Hardware Boundary

This indirection exists for a real reason: your program never needs to know exactly how the keyboard or display hardware actually works, what memory-mapped registers they use, or how their timing quirks are handled. It only needs to know the trap number for "read a character" or "print a string." The operating system's own service routines — sitting behind that vector table — are the only code that actually deals with the hardware directly. TRAP is the boundary between "your program's logic" and "the OS/hardware layer that talks to the real world," and it's a boundary this course has been relying on silently since its very first example.

The Standard TRAP Routines

MnemonicVectorWhat it does
GETCx20Reads one character from the keyboard into R0 — no echo to the screen
OUTx21Writes the character currently in R0 to the display
PUTSx22Writes a null-terminated string, starting at the address in R0, to the display
INx23Prints a prompt, reads one character (with echo this time), stores it in R0
HALTx25Stops the fetch-decode-execute cycle entirely
GETC never echoes what was typed
A very common early mistake: calling TRAP x20 (GETC) to read a character and expecting to see it appear on screen automatically. It doesn't — GETC deliberately reads silently. If you want the typed character to actually show up, you have to explicitly follow it with TRAP x21 (OUT), or use TRAP x23 (IN) instead, which reads and echoes in one call.

HALT, In Full

Recall assembly1-2's own warning: "the CPU has no concept of 'done' — it fetches forever until an instruction like HALT explicitly stops it." Now the mechanism behind that is fully visible: HALT is simply TRAP x25. Its OS service routine doesn't return control back to your program the way GETC or OUT do — instead, it halts the fetch-decode-execute cycle at the hardware level, the one and only way execution actually stops.

A Small Working Example

        TRAP x20        ; GETC — read one character into R0 (no echo)
        TRAP x21        ; OUT — echo it back manually
        LEA  R0, MSG    ; R0 <- address of the message (assembly1-3/assembly1-5's LEA)
        TRAP x22        ; PUTS — print the null-terminated string at that address
        TRAP x25        ; HALT
MSG     .STRINGZ "Thanks!"

This reads one character, echoes it manually (per this chapter's own warn-box), prints a short message using LEA to get the string's address into R0 exactly the way assembly1-5 introduced, and halts.

TRAP inherits JSR's own R7 rules
Because TRAP saves a return address into R7 exactly like JSR, everything assembly1-7 established about nested calls applies here too: if a subroutine you wrote uses a TRAP call and R7 hasn't been saved on the stack first, that TRAP will silently overwrite the subroutine's own pending return address, the same bug from assembly1-7's own warn-box — just triggered by TRAP instead of a nested JSR.

Polling vs. Interrupts — A First Preview

Under the hood, the OS's GETC routine has to somehow know when a key has actually been pressed. The simple technique — and the one this course's examples rely on without you ever needing to think about it — is polling: repeatedly checking a status register in a tight loop until it indicates a key is ready. It works, but it wastes CPU cycles doing nothing but asking "is it ready yet?" over and over.

A more advanced alternative is interrupts: instead of the CPU repeatedly asking, the keyboard hardware itself signals the CPU the instant a key is pressed, letting the CPU do other useful work in between. This course doesn't go further than naming the distinction — interrupt-driven I/O is real, genuinely more efficient LC-3 territory, but it adds real complexity this Fundamentals course deliberately leaves for later, more advanced material, the same way assembly1-1 deferred real-hardware quirks to cpu8bit1 and the future x86-64 course.

Hands-On Exercises

Exercise 1

Explain, using this chapter's own "OS/Hardware Boundary" section, why TRAP looks up an address through a vector table instead of just jumping directly to a fixed address the way BR or JSR does with a PC-relative offset.

📄 View solution
Exercise 2

A learner writes TRAP x20 followed immediately by TRAP x25, expecting to see the character they typed appear on screen before the program halts. Using this chapter's own warn-box, explain what they'll actually observe, and fix their code.

📄 View solution
Exercise 3

A subroutine called via JSR itself calls TRAP x22 (PUTS) to print a status message before returning. Using this chapter's own tip-box and assembly1-7's calling convention, explain what safeguard this subroutine needs around its TRAP call, and why.

📄 View solution

Chapter 8 Quick Reference

  • TRAP — opcode 1111 + an 8-bit trapvect8 index into the trap vector table; saves a return address into R7 like JSR
  • TRAP is the boundary between your program's logic and the OS/hardware layer that actually talks to the keyboard/display
  • GETC (x20) — read a character, no echo · OUT (x21) — print R0's character · PUTS (x22) — print a null-terminated string · IN (x23) — read with prompt+echo · HALT (x25) — stop the fetch-decode-execute cycle for good
  • GETC never echoes automatically — pair it with OUT, or use IN instead
  • HALT is just TRAP x25 — this is the actual mechanism behind assembly1-2's "a program stops via an explicit HALT instruction"
  • TRAP inherits JSR's R7-overwrite risk — save R7 first if a subroutine both calls another routine AND uses TRAP
  • Polling (repeatedly checking a status register) vs. interrupts (hardware signals the CPU) — interrupts are real, more advanced LC-3 material deliberately left for later