Skip to the content
PCBKitNew board
2 layers154.9 × 100.3 mmZ80-llm, the printed circuit board in 3D

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

2

README

README.md

What?

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
QtyValueFootprintReferences
910KResistor_SMD:R_1206_3216Metric_Pad1.30x1.75mm_HandSolderR1, R2, R3, R4, R5, R6, R7, R12, R13
4100nFCapacitor_SMD:C_0805_2012Metric_Pad1.18x1.45mm_HandSolderC3, C4, C5, C6
41KResistor_SMD:R_1206_3216Metric_Pad1.30x1.75mm_HandSolderR8, R9, R10, R11
3LEDLED_THT:LED_D3.0mmD2, D3, D4
210µFCapacitor_THT:CP_Radial_D4.0mm_P2.00mmC1, C2
2Conn_01x03Connector_PinHeader_2.54mm:PinHeader_1x03_P2.54mm_VerticalJ3, J6
2Conn_01x06Connector_PinHeader_2.54mm:PinHeader_1x06_P2.54mm_VerticalJ4, J5
2P8251Package_DIP:DIP-28_W15.24mmU3, U4
2Z84C20Package_DIP:DIP-40_W15.24mmU2, U7
174HC74Package_DIP:DIP-14_W7.62mmU9
174LS08Package_SO:SO-14_3.9x8.65mm_P1.27mmU8
1Conn_02x20_Odd_EvenConnector_PinHeader_2.54mm:PinHeader_2x20_P2.54mm_VerticalJ1
1GAL22V10Package_DIP:DIP-24_W7.62mm_SocketU10
1HM628512Package_DIP:DIP-32_W15.24mmU5
1LEDLED_THT:LED_D5.0mmD1
1SST39SF010Package_DIP:DIP-32_W15.24mmU6
1SW_PushButton_Switch_THT:SW_PUSH_6mmSW1
1Screw_Terminal_01x02TerminalBlock:TerminalBlock_bornier-2_P5.08mmJ2
1Z80CPUPackage_DIP:DIP-40_W15.24mmU1

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 mmZ180-comp, the printed circuit board in 3D

    Z180-comp

    89Mods/misc-computers

    Misc microcomputer projects

    64 parts3MIT
  • 2 layers154.9 × 139.7 mm68kBoard, the printed circuit board in 3D

    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 mmMicrosdmapper, the printed circuit board in 3D

    Microsdmapper

    cristianoag/microsdmapperv1

    microSDMapper Wireless for MSX Computers

    32 parts6
  • 2 layers99.4 × 99.6 mmM5 MemMapperSD, the printed circuit board in 3D

    M5 MemMapperSD

    DNMIAN/SORD_M5_MMSD

    SORD m5でROM/RAMメモリマッパー、SDの読み書きを実装します

    36 parts3MIT
  • 4 layers150 × 150 mmHomebrew 486, the printed circuit board in 3D

    Homebrew 486

    mattheeewahn/MDA80486SBC_Final

    크기가 너무 커서 압축파일로 올립니다. PCB로 가면 아웃풋이 있고 아웃풋에 가면 homebrew486Bcu압축파일이 있는데 그대로 캐드나 온라인 거버 뷰어에 올리시면 볼수 있습니다

    130 parts1GPL-3.0
  • 2 layers254 × 134.6 mmFPGA Z80 (no traces), the printed circuit board in 3D

    FPGA Z80 (no traces)

    douggilliland/Retro-Computers

    Retro-Computer Designs (Z80, 65C816, etc)

    155 parts87
  • 2 layers106.7 × 139.7 mmTransistorfet computie 68k SMT, the printed circuit board in 3D

    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 mmOpenThereminV45, the printed circuit board in 3D

    OpenThereminV45

    GaudiLabs/OpenThereminV4

    Open Source Theremin Instrument

    107 parts249GPL-3.0