Skip to content

Change the border

Write a Z80 instruction, assemble it, and make a visible prediction.

  • Sinclair ZX Spectrum
  • Assembly

Our first program will turn the Spectrum’s border red. Then we will change one number and ask the machine for a different colour. You do not need BASIC or previous programming experience.

We are using a 48K ZX Spectrum. The computer you are reading this on is the host: it runs the tools. The Spectrum is the target: its Z80 processor runs the program those tools produce. An emulator lets the host act as the target.

Give the processor an instruction

A processor works with numbers. We will write names such as ld and out, then use an assembler to translate that source text into the bytes the processor understands: machine code.

Here is the complete source. Lines beginning with ; are comments for us; the assembler does not turn them into instructions.

first-program.asm
; A visible instruction. Stock 48K Spectrum, loaded from tape.
; The ROM loader supplies the initial machine environment.
; This hold loop is deliberately not a clock.
 org 32768
start:
 ld a,2
 out ($FE),a
hold:
 jr hold
 end start

Read ld a,2 as “load A with 2”. A is a register: a small store inside the processor. It holds one byte, which can represent a whole number from 0 to 255. The comma separates the destination, A, from the value, 2.

out ($FE),a sends A’s value to a hardware port. Ports let the processor communicate with hardware. Here the Spectrum uses the lowest three bits of the value to select its border colour. For values 0–7, that gives:

Value Colour Value Colour
0 Black 4 Green
1 Blue 5 Cyan
2 Red 6 Yellow
3 Magenta 7 White

The $ prefix means a number is written in hexadecimal, or base sixteen. $FE is 254 in decimal. For now, keep that port number as written. The parentheses are part of the out instruction’s syntax; this is a port operation, not a write to a memory address.

Where does it start, and where does it stop?

The Spectrum’s memory has numbered locations called addresses. org 32768 tells the assembler to calculate this program’s addresses starting at 32768. It is an assembler directive, not an instruction the Z80 executes. It does not load the program by itself.

start: gives a name to the address of the following instruction. Such a name is a label. The final directive, end start, finishes the source and supplies the entry address where the program starts.

The processor normally carries on to the next instruction. Finishing our source file does not tell it to stop. hold: names the address of jr hold, and jr means “jump relative”: continue execution at the nearby label. Here it jumps back to itself. We stay inside our program instead of running into whatever bytes follow it.

This loop keeps the processor busy. It does not measure time or set an animation speed.

Try it in the page

Press Assemble and run. The browser starts a fresh Spectrum, places the assembled bytes in memory and asks the ROM—the machine’s built-in software—to call the program. The border should stay red.

Source · view and edit
ZX Spectrum

Edit the source, then assemble.

Edits are saved in this browser. Keep a downloaded copy of work you want to keep.

The editor’s colours help separate comments, instructions and values. The words and punctuation are what the assembler reads. Colour alone does not tell you whether a program is correct.

Replace the 2 in ld a,2 with 4. What should change?

Show the explanation

The border becomes green. We put a different value in A, then send it to the same port. The rest of the screen is unaffected: this program contains no instruction to change it.

Make that edit and run again. Download source saves your edited .asm file. Edits are saved in this browser, with a choice to restore them when you return. Keep a downloaded copy of work you want to keep; browser storage can be cleared. If saving fails, download your edits before leaving. Revert discards your edits and restores the starting source. Each run starts a fresh machine and installs your program, so this is also how we recover from a program that has gone wrong.

From source to bytes: what does defb do?

Our source names instructions. The assembler encodes them as bytes:

Assembly source Bytes in hexadecimal
ld a,2 3E 02
out ($FE),a D3 FE

The first instruction takes two bytes. $3E selects “load A with the following byte”; $02 supplies the value two. The Z80 executes those bytes, not the letters ld a,2.

We can also supply the bytes ourselves. defb means define byte. It is an assembler directive, a request to the tool that builds our program:

Z80 assembly
defb $3E,$02

This emits exactly the same two bytes as ld a,2. In the editor above, restore the starting program and replace only ld a,2 with defb $3E,$02. Predict the border colour, then assemble and run. It should still be red: the running program has not changed. Restore ld a,2 afterwards; its meaning is easier to read.

The Z80 never executes defb. The assembler handles it before the program runs. At runtime, the Z80 fetches the emitted bytes as instructions. We can also use defb to emit picture patterns, text or other data, as the next lesson does. The directive does not mark a byte as code or data; how the program uses it matters.

Where compilation fits

This is native Z80 machine code, whether we ask the assembler to encode ld a,2 or supply $3E,$02 ourselves. A compiler can also produce native Z80 machine code from a higher-level language. It chooses instructions to implement the source; the assembler encodes instructions we chose. Given the same bytes and starting conditions, the Z80 behaves the same whichever route produced them.

Ordinary Spectrum BASIC takes another route. RUN asks the ROM’s BASIC interpreter to read our stored BASIC program and carry out its operations. The interpreter is itself Z80 machine code; the BASIC program’s keyword tokens are data it reads, not Z80 instructions. The processor is executing machine code in both cases, but it is doing different work.

“Native” here names the target processor. In the browser, the emulator carries out these same Z80 instructions on behalf of the Spectrum. They do not become native instructions for your host computer merely because it runs the emulator.

From Source to Silicon compares these routes in more detail, without requiring knowledge of a particular processor.

Optional: build a tape on your computer

The browser experiment is ready to use without installing anything. If you want to work locally, install Asm198x using its installation instructions. Save the complete listing as first-program.asm in a plain-text file. In a terminal opened in that directory, run:

Shell
asm198x --dialect pasmo --cpu z80 --tapbas first-program.asm -o first-program.tap

--dialect pasmo chooses the source notation; --cpu z80 chooses the original Z80. --tapbas packages the program with a small BASIC tape loader. -o names the output file. The program bytes are the same as in the browser; this command packages them for tape loading.

Pasmo is an alternative assembler for this same source:

Shell
pasmo --tapbas first-program.asm first-program.tap

Use one of these commands. Both produce the same six program bytes; tape headers need not be identical.

To run the tape, we recommend Emu198x, the 198x family’s own emulator. Download the emu198x-spectrum archive for your computer from the latest release and extract it. It needs a lawfully obtained 48K Spectrum ROM; the ROM guide explains how to obtain one. From the extracted folder, this command starts a 48K Spectrum, enters LOAD "" and plays the tape:

Shell
./emu198x-spectrum --rom "path/to/48.rom" --machine spectrum_48k --tape first-program.tap --autoload-tape --scale 3

On Windows, run .\emu198x-spectrum.exe with the same options. Replace the paths with your ROM and tape locations. In another 48K Spectrum emulator, mount the tape, enter LOAD "" and start tape playback.

The ROM provides the initial environment, including a working stack—the memory used to remember return addresses—and the ROM’s interrupt service, which handles periodic hardware requests. Our program leaves that setup in place. It neither calls a drawing routine nor returns to BASIC. The browser initialises that environment for you; the tape-loading route above does the same when running locally.

If assembly fails, read its diagnostic and check the named line, especially commas and label spelling. If assembly succeeds but the border stays white, check the entry label and, when running locally, that the new tape finished loading. Save your edited source too: the .asm file is the readable program you will change next time.

Sources

Zilog, Z80 CPU User Manual, UM008011-0816, “CPU Register” and instruction entries “LD r, n”, “OUT (n), A” and “JR e”. Sinclair Research, ZX Spectrum BASIC Programming, chapter 23, for port output and border bits. Tool commands use the Asm198x and Pasmo documentation linked above.

Julián Albo, Pasmo documentation, “Directives”, entries “DB” and “DEFB”, for emitting bytes. Sinclair Research, ZX Spectrum BASIC Programming, chapter 14, for BASIC keyword tokens, and chapter 24, for the ROM instructions and stored BASIC program.

Run it here

This unit's program, built for you.

Program
first-program.tap
Machine
ZX Spectrum 48K (PAL)

Download first-program.tap