

Z80-llm
Running a GPT-2 sized LLM on a Z80
- Size
- 154.9 × 100.3 mm
- Copper layers
- 2
- Thickness
- 1.6 mm
- Parts
- 40
- Nets
- 131
- Pads
- 402
- Tracks, vias
- 1,564, 133
- Pours
- 2
- Routed
- Yes
- KiCad
- KiCad 8.0 (file 20240108)
- Created
- 27 Oct 2024
- Updated
- 6 Nov 2025
Overview
Z80-llm is an open-source printed circuit board by 89Mods, from the 89Mods/z80-llm repository on GitHub, published under the MIT licence. It is a 2-layer board of 154.9 × 100.3 mm with 40 parts on 131 nets, routed with 1,564 tracks and 133 vias.
It is built around the 74HC74D and HM628512LFP. Its parts include 13 resistors, 10 ICs, 6 capacitors, 6 connectors and 4 diodes. It belongs with the retro computing and games boards and the FPGA and CPLD boards of this library.
Main components
2README
README.mdWhat?
Z80-LLM is a bunch of code meant to enable a Z80 CPU to run a GPT-2-Medium sized LLM, but larger should theoretically be possible. This will hopefully finally bring modern AI into the retro-computing era. The code is written in simple C and should be easily portable to platforms not just based on the Z80, but also other retro CPUs with a C compiler toolchain.
Why?
How?
Hardware
The demo hardware consist only of a Z80, 32KiB of ROM and some SRAM. 32KiB is not enough, so the final 16KiB of the address range is paged 16-ways to get a bit more. Though technically less memory usage is possible at the cost of speed. Obviously, running a LLM requires still more memory, so two PIO chips drive a PATA interface to a disk drive, where data is paged in and out of raw disk blocks to get even more space for matrices and vectors. Part of the disk is formated, though, to hold the network parameters.
Two UARTs are provided: one for standard output and a second to print a verbose log.
The schematics and PCB layout files can be found in the folder Hardware.
LLM
The code to pre-train the LLM was written using libtorch in C++, using custom code for implementation of the attention layers, to make translating it into standalone C easier. This code was then supplied with a dataset of book texts and trained on a cloud compute instance containing a RTX4090 that was fed a large budget of 27.16$. The resulting LLM is good enough at generating basic, coherent text for the purposes of demonstration.
This code can be found in the folder libtorch_llm.
Standalone C program
I started out by writing a standalone, single file C program on my desktop PC that can run the LLM, to have a working reference point when writing the code for the Z80. Still, this program already contains key aspects of what is required on the Z80, such as parsing parameters from files and emulating matrices being stored in a block device. The resulting code only depends on the standard library and compiles into a tiny executable on Linux.
This code can be found in the folder c_pgm.
Z80 Firmware
z88dk was used to create the program to run the LLM on a Z80. It works as described above by using space on a connected disk drive as memory for matrices and the paged SRAM as cache for that memory. To load the parameters, a filesystem partition is also present on the disk. My choice of filesystem is, of course, ext4, to further modernize the software design. Only basic filesystem read access is supported, but code to handle checksum checking and symlink parsing is present and can be enabled, if required. This allows easy copying and modification of the network parameters on the disk from any common Linux distro!
Standard IEEE 32-bit floats are used for computation. I did experiment with 16-bit floats and fixed-point numbers, but they produced completely broken results due to precission errors. So floats are used, for maximum compatibility! Though, one optimization I did make use of is LUTs. Notably, the GeLU nonlinearity is implemented through a LUT in a file, which I found to be faster.
A byte-pair-encoder almost identical to the one used in GPT-2 is used for tokenization. I will admit, the code I wrote for it is not the best, but it successfully runs on the Z80, so I am happy with it. It also uses data stored on the filesystem to do the encoding.
Note: the resulting code does not fit into the 32KiB ROM. To work around this, the CPU copies some blocks from the disk to SRAM at bootup, allowing code to overflow into RAM. Any code changes require not only re-programming of the ROM, but also an update of the disk image. I apologize for this inconvenience.
This code can be found in the folder Z80.
Speed
At 8MHz and using a SSD as the disk drive for maximum throughput, I have approximated, using simulation, a time of 1,292,518 minutes per token, or roughly 2.4 years. Upgrading to a 20MHz Z80 should cut that down to 1 year, and I am investigating speeds using a AM9511 to accelerate the floating-point computations.
Usage
The intended method for getting this beast running is to put together a disk image on your PC, and then dd-ing it to a real drive. Open a terminal at the root of this repository.
First, create a blank file that is less than or equal in capacity to your target physical drive. I am using a SSD with slightly less than 16GiB capacity, so I will use a 3G large image: dd if=/dev/zero of=./disk.img bs=1M count=3072
Next, it needs a filesystem partition. It can’t span the whole disk, as the program requires a bit of free space after the filesystem partition, which it will use as memory. Use fdisk for this: fdisk ./disk.img
Create a MBR partition table (o) and add a new partition (n), which is to be primary (p) and number 1 (1). First sector has to be 2048, as the first blocks of the disk past the MBR will store Z80 bytecode that is overflowing from ROM into RAM. For the size of the partition, I go with 2G (+2G) so mkfs doesn’t make a fuzz on the next step.
w will tell fdisk to write changes to the disk image and exit. To continue, the disk image needs to be set up as a loop device, so it can be worked on directly: sudo losetup -fP disk.img. For the next steps, make sure you are targeting the correct device, or you may corrupt a part of your OS! lsblk shows one new device, loop0, after the previous command, so that has to be it.
Simply doing sudo mkfs.ext4 /dev/loop0p1 will create the ext4 file system on the partition that was just created. Note: it is possible your distro’s mkfs config enables filesystem features not supported by my code. If this happens, you can comment out the FS feature check in Z80/ext4.c. In most cases, it will still work.
At this point, you should be able to minimize the terminal window and continue in your DE’s file browser, as the freshly formatted partition should show up there, like a removable drive. Open it. You can change the permissions to make these next steps easier if you want, it won’t affect things on the Z80.
For now, I will only describe how to install the weights from my pre-trained model. Download the files here and extract into the root of the partition.
Next is the data files for the tokenizer. Navigate to libtorch_llm/data/ directory and simply run the prepare_vocab.sh bash script. I decided to use GPT-2’s actual tokenizer configs, and this script will download them and apply any required modifications. You can then copy the entire data folder to the root of the ext4 partition.
Note: you will need nodejs installed to run one of the helper scripts. Yes, I am aware of how absolutely insane the language spread of this repository is.
Once done, the root of the partition should have three entries LUTs, model and data. You can now un-mount the partition. To remove the loop device, I like to use this handy command, which destroys all loop devices associated with a specific file: losetup -j ./disk.img | grep -o "/dev/loop[0-9][0-9]*" | xargs -L1 sudo losetup -d.
Now, finally, comes the step of building the Z80 code. Start by installing z88dk in whichever way you prefer. Then, navigate a terminal to the Z80 directory in this repo and simply do make. The makefile should do everything for you, including modifying the disk image with the overflowing binary (assuming you created it in the right place).
Lastly, get out whatever ROM programmer you use and burn EPROM.bin onto a memory chip of your choice. In parallel, start copying the disk image to a real drive, preferably through dd.
Congratulations! You are now ready to wait 2.5 years for your Z80 to say a single word to you!
Parts
40| Qty | Value | Footprint | References |
|---|---|---|---|
| 9 | 10K | Resistor_SMD:R_1206_3216Metric_Pad1.30x1.75mm_HandSolder | R1, R2, R3, R4, R5, R6, R7, R12, R13 |
| 4 | 100nF | Capacitor_SMD:C_0805_2012Metric_Pad1.18x1.45mm_HandSolder | C3, C4, C5, C6 |
| 4 | 1K | Resistor_SMD:R_1206_3216Metric_Pad1.30x1.75mm_HandSolder | R8, R9, R10, R11 |
| 3 | LED | LED_THT:LED_D3.0mm | D2, D3, D4 |
| 2 | 10µF | Capacitor_THT:CP_Radial_D4.0mm_P2.00mm | C1, C2 |
| 2 | Conn_01x03 | Connector_PinHeader_2.54mm:PinHeader_1x03_P2.54mm_Vertical | J3, J6 |
| 2 | Conn_01x06 | Connector_PinHeader_2.54mm:PinHeader_1x06_P2.54mm_Vertical | J4, J5 |
| 2 | P8251 | Package_DIP:DIP-28_W15.24mm | U3, U4 |
| 2 | Z84C20 | Package_DIP:DIP-40_W15.24mm | U2, U7 |
| 1 | 74HC74 | Package_DIP:DIP-14_W7.62mm | U9 |
| 1 | 74LS08 | Package_SO:SO-14_3.9x8.65mm_P1.27mm | U8 |
| 1 | Conn_02x20_Odd_Even | Connector_PinHeader_2.54mm:PinHeader_2x20_P2.54mm_Vertical | J1 |
| 1 | GAL22V10 | Package_DIP:DIP-24_W7.62mm_Socket | U10 |
| 1 | HM628512 | Package_DIP:DIP-32_W15.24mm | U5 |
| 1 | LED | LED_THT:LED_D5.0mm | D1 |
| 1 | SST39SF010 | Package_DIP:DIP-32_W15.24mm | U6 |
| 1 | SW_Push | Button_Switch_THT:SW_PUSH_6mm | SW1 |
| 1 | Screw_Terminal_01x02 | TerminalBlock:TerminalBlock_bornier-2_P5.08mm | J2 |
| 1 | Z80CPU | Package_DIP:DIP-40_W15.24mm | U1 |
Questions
How big is the Z80-llm PCB?
It measures 154.9 × 100.3 mm and has 2 copper layers, 1.6 mm thick.
What parts does the Z80-llm use?
40 parts, built around the 74HC74D and HM628512LFP. The full list of its parts is on this page, with links to each part's symbol and footprint where PCBKit's library has them.
Where can I download the Z80-llm KiCad files?
From the 89Mods/z80-llm repository on GitHub, at the commit this page was made from: the board is Hardware/KiCad/Z80_llm.kicad_pcb and its schematic Hardware/KiCad/Z80_llm.kicad_sch. PCBKit opens the board in the browser, with nothing to install.
Can I use the Z80-llm design in my own project?
Its repository publishes it under the MIT licence: you may reuse it on that licence's terms (keep the author's notice, and for a copyleft licence share your changes alike).
Similar boards
- 4 layers99.3 × 88.6 mm

- 2 layers154.9 × 139.7 mm

68kBoard
transistorfet/computie
A collection of 68k computer projects, including a unix-like operating system written in C that runs on them
78 parts69GPL-3.0 - 4 layers101 × 64.6 mm

- 2 layers99.4 × 99.6 mm

- 4 layers150 × 150 mm

Homebrew 486
mattheeewahn/MDA80486SBC_Final
크기가 너무 커서 압축파일로 올립니다. PCB로 가면 아웃풋이 있고 아웃풋에 가면 homebrew486Bcu압축파일이 있는데 그대로 캐드나 온라인 거버 뷰어에 올리시면 볼수 있습니다
130 parts1GPL-3.0 - 2 layers254 × 134.6 mm

FPGA Z80 (no traces)
douggilliland/Retro-Computers
Retro-Computer Designs (Z80, 65C816, etc)
155 parts87 - 2 layers106.7 × 139.7 mm

Transistorfet computie 68k SMT
punkfab/pcb-corpus
235 real KiCad boards from open-source hardware repositories, prepared as a placement and routing benchmark
68 parts1 - 2 layers50.5 × 128.5 mm
