Z80 Input/Output
A Z80 has no system calls. Programs reach the outside world with in and out, which address one of 256 ports: out (n), a sends A to port n, in a, (n) reads a byte back. The (c) forms (out (c), r and in r, (c)) take the port number from C,
which lets a program compute it, and put B on the high byte of the address bus, which is how
a read carries a parameter: the key code, the mouse view, the byte of the clock, the length
of a wait.
The emulator connects the ports below to the terminal, the screen, the keyboard, the mouse
and the clock. Every other port behaves like an empty bus: writes are dropped and reads
answer 0xFF. Reading a connected port with nothing to read pauses the program
until there is something, so in never fails, it only waits.
Console 0x10 - 0x14
The Terminal: one port per output format, and the input reads that pause the program until a line (or a keystroke on the Screen) is available. What is printed here is also drawn at the Screen’s text cursor, in 8 by 8 cells.
0x10 Character
Prints the byte as a character (Latin-1) and draws it at the Screen’s text cursor. 0x0A prints a newline.
Returns the next character of the input line, pausing for input when the line has been consumed. The line ends with a newline character (0x0A). Once the program has touched a Screen, Keyboard or Mouse port the characters come from the focused Screen instead, one keystroke at a time, and are echoed at the text cursor.
.org 0x8000
ld hl, msg
loop: ld a, (hl)
or a
jr z, done
out (0x10), a
inc hl
jr loop
done: halt
msg: .asciz "Hello!", 10 ; 10 is the newline: strings keep \n literally
0x11 Unsigned number
Prints the byte as an unsigned decimal number, 0 to 255.
Reads a line, parses it as a decimal number and returns its low byte. Stops the program with an error when the line is not a number.
.org 0x8000
in a, (0x11) ; ask for a number
add a, a ; double it
out (0x11), a ; print it
halt
0x12 Signed number
Prints the byte as a signed decimal number, -128 to 127.
Same as the unsigned number port.
.org 0x8000
ld a, 5
sub 10
out (0x12), a
halt
0x13 Hexadecimal
Prints the byte as two upper case hexadecimal digits.
Reads a line, parses it as a hexadecimal number (0x, $ prefix or h suffix accepted) and returns its low byte.
.org 0x8000
ld a, 255
out (0x13), a
halt
0x14 16 bit number
Prints the 16 bit number made of the high byte of the port address (register B when using out (c),r) and the byte written, as an unsigned decimal number.
Same as the unsigned number port.
.org 0x8000
ld hl, 1000
ld b, h ; high byte goes on the address bus
ld c, 0x14 ; port number
out (c), l ; prints HL
halt
Screen 0x20 - 0x2A
Drawing. Set the colors and the coordinates, then write one command to the command port; every coordinate is a byte, so the Screen is at most 256 by 256 pixels and is 256 by 192 until the program resizes it. Colors are one 3-3-2 byte.
Commands
Written to the command port, 0x27. One write runs one operation
on the coordinates and colors already set.
One pixel at (X, Y) in the pen color. The pen width does not apply.
A line from (X, Y) to (X2, Y2) in the pen color, leaving the drawing position at (X2, Y2).
A line from the drawing position to (X, Y), which becomes the new position: a polyline costs one command per point.
Moves the drawing position to (X, Y) without drawing.
A rectangle from (X, Y) to (X2, Y2), filled with the fill color and outlined with the pen. The right and bottom edges are excluded, as they are in EASy68K.
The same rectangle, outline only.
The ellipse inscribed in that rectangle, filled and outlined like it.
The same ellipse, outline only.
Spreads the fill color from (X, Y) over every pixel of the color that was there, four ways.
Fills the whole Screen with the fill color, homes the text cursor, and adopts that color as the background, so scrolled text rows and a later resize match what is on screen.
Resizes the Screen to X by Y pixels and clears it; a coordinate of 0 means 256, the largest size a byte cannot hold.
Double buffering on: drawing goes to an off-screen copy of the visible image until PRESENT, so an animation never shows a half-drawn frame.
Double buffering off: drawing appears immediately. The off-screen image is dropped without being shown.
Shows the off-screen image. With double buffering off it only asks for a repaint.
The TRS-80's memory-mapped display: the Screen becomes 64 by 16 character cells and shows the kilobyte of memory at 0x3C00, one byte per cell. Characters 128 to 191 are 2 by 3 blocks, for 128 by 48 chunky pixels, and the keyboard matrix at 0x3800 answers reads. The drawing commands above are not available in this mode; a program draws by storing bytes. The same mode a ; @screen trs80 comment asks for before the program starts.
Back to the drawing commands above. The image stays as it is until something draws on it, and memory at 0x3C00 becomes ordinary RAM again.
The TRS-80 display
Command 14 switches the screen to the memory-mapped display of
the TRS-80, the machine this Z80 emulator descends from — the one graphics
interface here that programs written elsewhere already target. A program can
also ask for it before it starts, with a ; @screen trs80 comment, which is what a program brought in
from outside needs. The drawing commands above are not available in this
mode, and console output goes only to the terminal: on this machine,
printing is storing a byte.
One byte per character cell, 64 columns by 16 rows, read left to right and top to bottom: the cell at column X of row Y is 0x3C00 + Y*64 + X. Store a byte, see a character. It is ordinary RAM as well, so a program can read back what it drew, and a whole screen can be block copied into it with one ldir.
Eight rows of eight keys. The low byte of the address selects rows — bit 0 is row 0 — and a read answers every selected row OR’ed together, so ld a,(0x38FF) scans the whole keyboard at once. A bit is 1 while its key is held. The four banks of 256 mirror each other.
Port 0x00 is the machine's joystick, and the reason the ports
above start at 0x10: nothing of this editor's is mapped there,
so a program's joystick poll reads an empty bus floating high — 0xFF, exactly what the machine answers with none attached.
While the character port lived at 0x00 that poll stopped the
program to wait for a line nobody was typing. Everything else the machine
decodes is at 0x75 or above, clear of this map entirely.
Characters 0x20 to 0x7F are text. Characters 128 to 191 are 2 by 3 blocks of chunky pixels — the low six bits
are the blocks, bit 0 top-left then across and down — so the screen is also
a 128 by 48 pixel grid. 191 is solid and 128 is blank.
Colors
A color is one byte: three bits of red in bits 7-5, three of green in bits
4-2 and two of blue in bits 1-0. Each field is stretched over the screen's
eight bits by repeating it, so 0xFF is white and 0xE0 pure red.
0x00 black0x03 blue0x1C green0x1F cyan0xE0 red0xE3 magenta0xFC yellow0xFF white0xF0 orange0x92 gray0x49 dark gray0x20 Pen color
Sets the color of pixels, lines, outlines and text, as one 3-3-2 byte (RRRGGGBB).
The current pen color, back in 3-3-2.
.org 0x8000
ld a, 0xE0 ; 111 000 00: pure red
out (0x20), a ; pen color
ld a, 100
out (0x23), a ; X
ld a, 50
out (0x24), a ; Y
xor a ; command 0: pixel
out (0x27), a
halt
0x21 Fill color
Sets the color the inside of a filled shape, a flood fill and a clear use, as one 3-3-2 byte.
The current fill color, back in 3-3-2.
.org 0x8000
ld a, 0x1C ; 000 111 00: pure green
out (0x21), a ; fill color
ld a, 0xFF
out (0x20), a ; white pen for the outline
ld a, 20
out (0x23), a
out (0x24), a ; from (20, 20)
ld a, 80
out (0x25), a
out (0x26), a ; to (80, 80), right and bottom excluded
ld a, 4 ; command 4: filled rectangle
out (0x27), a
halt
0x22 Pen width
Sets how many pixels wide lines and outlines are, at least 1. A width of 0 is read as 1.
The current pen width.
.org 0x8000
ld a, 5
out (0x22), a ; five pixels wide
ld a, 10
out (0x23), a
out (0x24), a ; from (10, 10)
ld a, 200
out (0x25), a
ld a, 150
out (0x26), a ; to (200, 150)
ld a, 1 ; command 1: line
out (0x27), a
halt
0x23 X
The first X coordinate: the pixel, the start of a line, the left edge of a shape, the width of a resize.
The byte last written.
.org 0x8000
ld a, 128
out (0x23), a
ld a, 96
out (0x24), a ; (128, 96), the middle of the default Screen
ld a, 3 ; command 3: move to
out (0x27), a
ld a, 200
out (0x23), a
ld a, 20
out (0x24), a
ld a, 2 ; command 2: line to
out (0x27), a
halt
0x24 Y
The first Y coordinate, the companion of the X port, and the height of a resize.
The byte last written.
0x25 X2
The second X coordinate: the end of a line, the right edge of a shape. Unused by the one-point commands.
The byte last written.
0x26 Y2
The second Y coordinate, the companion of the X2 port.
The byte last written.
0x27 Command
Runs one drawing operation on the coordinates, colors and pen width already set. The commands are listed above.
The number of the last command run, 0 before the first one.
.org 0x8000
ld a, 0x03 ; 000 000 11: pure blue
out (0x21), a ; fill color
ld a, 9 ; command 9: clear to the fill color
out (0x27), a
ld a, 0xFC ; 111 111 00: yellow
out (0x21), a
ld a, 60
out (0x23), a
ld a, 40
out (0x24), a
ld a, 196
out (0x25), a
ld a, 152
out (0x26), a
ld a, 6 ; command 6: filled ellipse
out (0x27), a
halt
0x28 Pixel color
Ignored: the pixel is drawn with command 0, in the pen color.
The color of the pixel at (X, Y) as a 3-3-2 byte, read from the image being drawn on — the off-screen one while double buffering. Outside the Screen it reads as the background color.
.org 0x8000
ld a, 0xE0
out (0x20), a ; red pen
ld a, 8
out (0x23), a
out (0x24), a ; (8, 8)
xor a ; command 0: pixel
out (0x27), a
in a, (0x28) ; read the color back
out (0x13), a ; print it as hexadecimal
halt
0x29 Text cursor column
Moves the text cursor to a column, in 8 by 8 character cells: 0 to 31 on the default Screen. Out of range values are clamped.
The column the text cursor is on.
.org 0x8000
ld a, 12
out (0x29), a ; column 12
ld a, 8
out (0x2A), a ; row 8
ld hl, msg
loop: ld a, (hl)
or a
jr z, done
out (0x10), a ; the character port draws at the cursor
inc hl
jr loop
done: halt
msg: .asciz "HELLO"
0x2A Text cursor row
Moves the text cursor to a row, in 8 by 8 character cells: 0 to 23 on the default Screen. Out of range values are clamped.
The row the text cursor is on. Text scrolls the whole Screen up one cell row at the bottom, graphics included.
Keyboard 0x30 - 0x33
Input from the focused Screen, polled: whether a typed character is waiting (read it from the character port), whether a given key is held, and the last key pressed and released. Key codes are EASy68K’s, the same table every environment here uses: letters and digits are the ASCII code of their capital, 0x25 to 0x28 are the arrows left, up, right and down, 0x20 is the space bar and 0x0D is Enter.
0x30 Typed input available
Ignored.
1 when a character typed on the focused Screen is waiting to be read from the character port, else 0. During a testcase it answers for the scripted input instead, so a program that polls before reading works in both.
.org 0x8000
wait: in a, (0x30) ; anything typed?
or a
jr z, wait ; poll until there is
in a, (0x10) ; take the character
out (0x10), a ; print it
halt
0x31 Key state
Ignored.
1 while the key whose code is in B is held down, else 0. This is how a game reads WASD or the arrows: it never consumes anything, and a key held over several reads answers 1 every time.
.org 0x8000
ld c, 0x31 ; the key state port
ld b, 0x27 ; the right arrow
wait: in a, (c)
or a
jr z, wait ; poll until it is held
ld a, 'R'
out (0x10), a
halt
0x32 Last key pressed
Ignored.
The code of the last key pressed on the Screen, 0 before the first press. It persists, so a program that polls slowly still sees the key.
.org 0x8000
loop: in a, (0x32) ; the last key pressed
or a
jr z, loop
out (0x13), a ; print its code in hexadecimal
halt
0x33 Last key released
Ignored.
The code of the last key released, 0 before the first release. With the last-pressed port it is the pair EASy68K’s task 19 answers with.
Mouse 0x40 - 0x43
Pointing input over the Screen, polled, in the same logical pixels drawing uses. B selects which of the three views a read answers with: the current state, the state at the last button release, or the state at the last button press.
Views
Put one of these in B before reading a mouse port.
0x40 Mouse X
Ignored.
The X of the view selected by B (0 current, 1 last release, 2 last press), in logical Screen pixels from the left edge, independently of how the panel is zoomed.
.org 0x8000
ld c, 0x42 ; the buttons port
ld b, 0 ; view 0: the current state
wait: in a, (c)
and 1 ; the left button
jr z, wait
ld c, 0x40
in a, (c) ; X
out (0x11), a
ld a, ','
out (0x10), a
ld c, 0x41
in a, (c) ; Y
out (0x11), a
halt
0x41 Mouse Y
Ignored.
The Y of the view selected by B, in logical Screen pixels from the top edge.
0x42 Mouse buttons
Ignored.
The buttons and modifiers of the view selected by B: bit 0 left, bit 1 right, bit 2 middle, bit 3 the double-click flag (only in the last-press view), bit 4 Shift, bit 5 Alt, bit 6 Ctrl.
0x43 Mouse event count
Ignored.
For the current view, how many mouse events have happened, as a byte that wraps around; for the two snapshot views, the count at the moment of the snapshot, 0 when it has not happened yet. Comparing it with the previous read is how a program tells a new click from the one it already handled.
Program time 0x50 - 0x52
Waiting and elapsed time. A wait and a frame sync are reads that suspend the program without blocking the editor: the machine re-executes the in when the time has passed, so Stop still answers and the Screen still repaints. Testcases run on a virtual clock, where waits complete at once and time starts at zero.
0x50 Wait
Ignored.
Waits B hundredths of a second, then answers 0. The editor stays responsive and Stop still works: the machine simply re-executes the in when the time is up. In a testcase the wait completes at once.
.org 0x8000
ld c, 0x50
ld b, 25 ; a quarter of a second
in a, (c)
ld a, '!'
out (0x10), a
halt
0x51 Frame sync
Ignored.
Waits for the next animation frame, then answers 0. One read per frame is how an animation runs at the display’s own pace instead of as fast as the host can go; with double buffering, present the frame first and sync afterwards.
.org 0x8000
ld b, 10 ; ten frames
loop: push bc
in a, (0x51) ; wait for the next frame
ld a, '.'
out (0x10), a
pop bc
djnz loop
halt
0x52 Elapsed time
Ignored.
One byte of the number of hundredths of a second since the run started, selected by B: 0 the lowest byte, 3 the highest. A testcase reads a virtual clock that starts at zero and only moves when the program waits, so a test is reproducible.
.org 0x8000
ld c, 0x50
ld b, 10 ; wait a tenth of a second
in a, (c)
ld c, 0x52
ld b, 0 ; the lowest byte of the elapsed hundredths
in a, (c)
out (0x11), a
halt