Z80 Assembly 20: Writing a Simple Macro (Code Generation)

What is a Macro? A macro is a sequence of instructions or data definitions that you define once and can then reuse multiple times throughout your code by simply typing the macro’s name. When the assembler encounters the macro name, it substitutes the entire predefined block of code in its place. Macro vs. Subroutine: Subroutine (CALL): Saves memory by running one copy of the code, but is slower because it incurs the overhead of the CALL and RET instructions (17 cycles). Macro: Faster because the code is pasted directly (inline) every time it’s used, but increases the size of the assembled program. Defining a Simple Macro The syntax for defining a macro varies slightly between assemblers (e.g., TASM, Z80Asm, sjasmplus), but they typically use MACRO and ENDM or DEFM. ...

September 27, 2025

Z80 Assembly 19: Relocatable Code and Position-Independent Code (PIC)

The Problem with Absolute Addressing Normally, Z80 instructions like JP 8000H or LD HL, DATA_ADDR use absolute addressing. If you compile a program to run at address 8000H but later load it at 9000H, every one of those absolute addresses will be wrong, and the program will crash. Relocatable Code and Position-Independent Code (PIC) solve this problem. Relocatable Code (The Assembler’s Job) Relocatable code is written with absolute addresses, but the assembler generates a file that includes a relocation table. ...

September 27, 2025

Z80 Assembly 18: Optimizing for T-States (Clock Cycles)

The T-State: Your Ultimate Performance Metric In Z80 programming, the speed of your code isn’t measured in lines, but in T-states (or clock cycles). Every instruction takes a precise, fixed number of T-states to execute. To write fast code, you must choose instructions that minimize this count. Why T-States Matter: If your Z80 runs at 3.5 MHz (as in the ZX Spectrum), 3,500,000 T-states happen every second. A faster instruction in a critical loop can save dozens of T-states every frame, leading to smoother graphics or faster game logic. ...

September 27, 2025

Z80 Assembly 17: Bitwise State Machines (Managing Flags with BIT/SET/RES)

State Machines: Why Bitwise is Best A State Machine manages the status of a system (e.g., a game character is ‘Jumping’, ‘Firing’, or ‘Hit’). In high-level languages, you might use three separate Boolean variables for these. In Z80 assembly, that’s inefficient. By using a single register (like C) as a status register, you can manage up to eight independent Boolean flags, dedicating one bit to each status. This is the fastest method for context management. ...

September 27, 2025

Z80 Assembly 16: Efficient 16-bit Multiplication Algorithm

The Challenge: Multiplying 16-bit Numbers The Z80 performs 8-bit addition natively (ADD), but has no instruction for 16-bit multiplication (RR×RR). When you multiply two 16-bit numbers (like HL and DE), the result is a 32-bit number. We must build this algorithm using basic operations. The Algorithm: We use the same technique taught in grade school: break the numbers into parts, multiply each part, and sum the results. However, in assembly, it’s faster to use repeated shifting and conditional addition. ...

September 27, 2025

Z80 Assembly 15: Timers, Delays, and the Refresh Register (R)

Creating Software Delay Loops Since the Z80 runs at a fixed clock speed, you can create a precise time delay by executing a loop a fixed number of times, knowing the exact number of clock cycles each instruction takes. The Delay Routine: A delay routine typically uses nested loops and relies on the DJNZ instruction, as it’s efficient for looping. Example: Basic 16-bit Delay Loop This example uses the BC register pair for a longer delay (B for the outer loop, C for the inner loop). ...

September 27, 2025

Z80 Assembly 14: Directives, Labels, and Debugging Techniques

Assembler Directives: Structuring Your Code Assembler directives are commands that are read and acted upon by the assembler program, not the Z80 CPU. They organize code, reserve memory, and define data. Directive Action Purpose ORG NNNNH Sets the origin (starting address) where the following code should be placed in memory. Essential for defining where your program loads. EQU symbol, value Equates a symbolic name to a numerical value. Defines constants like port addresses or screen dimensions (SCREEN_WIDTH EQU 32). DB val1, val2, ... Define Byte: Reserves memory and places 8-bit data bytes (numbers or ASCII characters). Used to define text strings or data tables. DW val1, val2, ... Define Word: Reserves memory and places 16-bit words (often addresses). Used for jump tables or storing address pointers. DEFS N Define Space: Reserves N bytes of uninitialized memory. Used for buffers, variables, or the stack area. Example: Data Definition ...

September 27, 2025

Z80 Assembly 13: Instruction Set Encoding (Reading Machine Code)

Assembly vs. Machine Code Assembly language uses mnemonics (like LD or ADD) that are easy for humans to read. Machine code is the raw binary sequence of bytes (the opcodes) that the CPU directly executes. Understanding the encoding helps with optimization and debugging. Opcode Structure: The Z80’s Chessboard Most Z80 instructions fit into a flexible, 8-bit format that can be visualized as a grid. An 8-bit opcode is often broken into three 2- or 3-bit fields: ...

September 27, 2025

Z80 Assembly 12: Block I/O (INIR, OTDR) for Hardware Speed

Block I/O: The Fastest Way to Talk to Hardware The Z80 features a unique set of block I/O instructions designed to quickly move data between a memory block and a single, fixed I/O port. These are crucial for fast operations like dumping screen memory to a display controller or quickly reading keyboard buffers. The Register Setup: Block I/O relies on the following registers for definition: Register Pair Purpose in Block I/O HL Memory Address (Source or Destination) B Counter (Number of transfers to perform) C I/O Port Address (The fixed hardware port) Input Block Commands (Reading from Port) The INIR (Input, Increment, Repeat) and INDR (Input, Decrement, Repeat) commands read data from the port specified by C and store it into memory pointed to by HL. ...

September 27, 2025

Z80 Assembly 11: Self-Modifying Code and Look-Up Tables

Look-Up Tables (LUTs): Trading Memory for Speed A Look-Up Table (LUT) is a pre-calculated block of data stored in memory. Instead of performing a time-consuming calculation (like a sine function or a multiplication), the CPU simply uses the input value as an index to quickly read the pre-computed result. How to Use a LUT: Use the input value (N) to calculate the offset within the table. Add this offset to the table’s starting address. Read the byte (result) at that final calculated address. Example: 10-Value Square Root Table ...

September 27, 2025