

Control unit central timing
Design for a DIY computer
- Size
- 100 × 100 mm
- Copper layers
- 2
- Thickness
- 1.6 mm
- Parts
- 25
- Nets
- 91
- Pads
- 285
- Tracks, vias
- 939, 93
- Pours
- 2
- Routed
- Yes
- KiCad
- KiCad 8.0 (file 20240108)
- Created
- 11 Mar 2024
- Updated
- 26 Sep 2026
Overview
Control unit central timing is an open-source printed circuit board by emperor-hadrian, from the emperor-hadrian/stack_machine repository on GitHub. It is a 2-layer board of 100 × 100 mm with 25 parts on 91 nets, routed with 939 tracks and 93 vias.
Its parts include 23 ICs and 2 resistors.
README
README.mdStack Machine Version 2

Photo of Version 1
Changes from previous version
- The timing will be completely redone: A single normal clock signal, not the stupid "AB Alternating" I made up for the original.
- The PC will be incremented and a new instruction fetched during any non-flow-control instructions except when the GPRAM is used and the PC is in the same RAM chip (the RAM chips have 15 address lines each) as whatever is being accessed. The bus timing will also be very flexible and will be faster when possible but still slow when the memory is being accessed twice for example.
- Bus read/write addresses are both now 5 bits. The WRITE/MOVE instruction formats will remain the same and the extra bit (MSB to be exact) will be set based on duplicate opcodes for WRITE/MOVE that are otherwise interpreted the same. 1 extra WRITE opcode and 3 extra MOVE opcodes.
- The opcode decoder is actually 3 bits. There will be 4 bits used for extra MOVE/WRITE instructions (see above).
- Interrupts: There will be seperate interrupt GOTO latches that go to a dedicated interrupt handler function. That function should save the state of everything else (including the regular GOTO latches, which is why interrupt GOTO latches are seperate) onto the stack then do whatever depending on the interrupt code.
- GOTO latches can be written to the bus (see above). The goto decider doesn't need to be because it's simple to get its value from an IF-ELSE.
- The control unit will be able to fetch instructions from the GPRAM: Program addresses starting at 2^15 (32,768, halfway through the old flash memory space) will load 2 consecutive bytes (an instruction is 2 bytes) from the GPRAM. The GPRAM address of the first byte (lower byte of the instruction) will be determined by:
(prog_addr - 32,768) * 2. Since that will only happen at or above 32,768 then the MSB will not be used in the math and will be used to determine whether to read from flash or GPRAM. - Flash program space is selectable: The flash chips have 17 address lines, while 15 are used (see above about half the program address space being mapped to the GPRAM). This means that the top 2 lines can be connected to jumpers to select between 4 different programs.
- Flash program can be written to from the bus: The programming interface takes 8 data inputs + power and GND. The old interface to plug an Aduino in to program it will still be there of course but it can be selected by a jumper to be either the external pins (to Arduino) or from the bus.
- The stack is 32k instead of 64k so only one memory chip is used, although 32k is still incredibly excessive.
GPRAM Parallelization
The RAM chips have 15 address lines each so there will be two of them (Lower and Upper Domains). This means that they can be used seperately at the same time, for example fetching the new instruction (If the PC is in the RAM) and also doing GPRAM read/write, as long as these operations are using seperate domains. Memory users will use either (or both) of the lower or upper RAM domains:
- Program: PC and data read into instruction registers A & B
- Heap R/W: Exactly the same as the original
Program instructions
Each instruction will be 16 bits and interpreted by the control unit. The first 4 bits (0 - 3) of the instruction will address the below list of operations. The rest of the instruction (bits 4 - 15) may be ignored or used for different things depending on the specific operation.
Here's the current list of the operation codes (opcodes):
MOVE- Bus usage - The rest of the instruction will be interpreted as follows: bits 8 - 11 address the device which will set the state of the bus (TX) and bits 12 - 15 will address the device to read from it (RX). Bits 4 - 7 are sent to the ALU as it's opcode incase the data is comming from it.WRITE- Writes instruction bits 4 - 11 to the bus. Bits 12 - 15 address the device to read from it.GOTO- Saves the 2 execution pointer GOTO latches (each of them are 1 byte) to the program counterGOTO-IF- Reads the LSB of the value in the goto decider latch and does a GOTO only if it is 1, otherwise does nothingHALT- Stops the clock, usefull for debuggingCALL- Effectively the same asGOTObut also pushes the return address (current value of program counter) onto the call stack. Note: The return address can be copied as-is and does not need to be incremented because it will be incremented normally after eachRETURNinstruction when it is used. If bit 4 is 1 this means that this call is calling the interrupt handler using the Interrupt GOTO latches.RETURN- The program counter will be set to the return address popped off the top of the call stack. If bit 4 is 1 this means that this return instruction is returning from the interrupt handler function and will allow interrupts again.CONFIG-INT- Configure interrupt. Bit 4 enables or disables interrupts. Interrupts are disabled on startup.
The following are treated as either MOVE or WRITE by the sequencer and set the bus TX and RX MSBs. The original MOVE and WRITE have all MSBs = 0.
MOVE- MSBs TX=1, RX=0MOVE- MSBs TX=0, RX=1MOVE- MSBs TX=1, RX=1WRITE- MSBs RX=1
Bus
Up to 15 devices can read the bus and 16 write to it. Devices that can read the bus (RX):
NONE- Nothing reads this so that bytes can be popped from the stack without them going anywhere.STACK-PUSH- Stack controller (Push)ALU-A- ALU latch AALU-B- ALU latch BGOTO-A- Control unit - Program counter GOTO latch A (first byte)GOTO-B- Control unit - Program counter GOTO latch B (second byte)GOTO-DECIDER- Control unit - GOTO decider latch (For GOTO-IF)GPRAM- GPRAM - WriteGPRAM-INC-ADDR- GPRAM - Write (++ address)GPRAM-ADDR-A- GPRAM - Address bits 0 - 7GPRAM-ADDR-B- GPRAM - Address bits 8 - 15GPIO-WRITE-A- Writes to GPIO output pins 0 - 7OFFSET-WRITE- Replaces value in stack atToS - offsetSET-STACK-OFFSET- Sets the stack offset byteALU-C-IN- ALU latch for carry and borrow, only LSB is used. Make sure this is always zero when not being used otherwise additions will be wrong.GPIO-WRITE-B- Writes to GPIO output pins 8 - 15INT-GOTO-A- Interrupt GOTO AINT-GOTO-B- Interrupt GOTO BVECTORS-A- Vector graphics A inputVECTORS-B- Vector graphics B inputVECTORS-C- Vector graphics C inputVECTORS-D- Vector graphics D inputINT-AND-MAIN-TIMER-ADDRESS- Bits 0 - 1 address 1 of the 4 interrupt timers. Bit 2 will reset the addressed counter to 0.INT-TIMER-CONFIG-MAX- Sets the start value of one of the interrupt timersINT-TIMER-CONFIG-TIMEBASE-AND-ENABLE- Configures one of the interrupt timers, seetimer_board.mdFLASH- Flash programming interface, same as the arduino connectionRX-26- Expansion RX 25
...
RX-31- Expansion RX 31
Devices that can write to the bus (TX):
STACK-POP- Stack controller (pop)OFFSET-READ- Reads value from stack atToS - offset, does not affect the ToSALU- ALU output- Control unit instruction bits 4 - 11, used for the
WRITEinstruction GPRAM- GPRAM - ReadGPRAM-INC-ADDR- GPRAM - Read (++ address)GPRAM-ADDR-A- GPRAM - Address bits 0 - 7GPRAM-ADDR-B- GPRAM - Address bits 8 - 15GPIO-READ-A- Reads GPIO input pins 0 - 7MAIN-TIMER- 8 bits of the main timer, addressed byINT-AND-MAIN-TIMER-ADDRESSINT-TIMER- Current value of 1 of the 4 interrupt timers, addressed byINT-AND-MAIN-TIMER-ADDRESSGPIO-READ-B- Reads GPIO input pins 8 - 15INT-CODE- Most recent interrupt code, will be cleared upon readINT-COUNT- Number of interrupts in queue, will overflow past 255GET-STACK-OFFSET- Retrieves stack offset, needed to save state during interruptGET-GOTO-A- Read GOTO Latch AGET-GOTO-B- Read GOTO Latch BTX-17- Expansion TX 17
...
TX-31- Expansion TX 31
The Stack
The stack will simply be a piece of memory seperate from the GPRAM and managed by hardware, its has 32k x 8-bit words.
There will be two ways to access the stack:
- Push and Pop
- Using the current ToS pointer - stack offset (
OFFSET-WRITEandOFFSET-READ) (does not change ToS). The stack offset will be a 1-byte number set from the bus (SET-STACK-OFFSET) and will not affect pushes and pops.
ALU
The ALU uses 2 8-bit latches for input and has 1 output. The specific operation it does is controlled by 4 bits (2^4 = 16 operations). All operations that result in a boolean output all 0s except for the result which is the LSB.
ADD- AddADD-C- Addition carry output (just the LSB will be set), this can be written directly intoALU-C-INNOT- Bitwise NOT (latch A)OR- Bitwise ORAND- Bitwise ANDXNOR- Bitwise XNOR, whether each pair of bits are equalSHIFT- Bitshifts A left by the first 3 bits of B, bits that go off the end DO wrap around.EQ- Whether bytes are equalA- Contents of A latchB- Contents of B latchEXT-10- Extension (not implemented)EXT-11- Extension (not implemented)EXT-12- Extension (not implemented)EXT-13- Extension (not implemented)EXT-14- Extension (not implemented)EXT-15- Extension (not implemented)
Flow Control
There are four instructions for flow control: GOTO, GOTO-IF, CALL, and RETURN.
GOTO and GOTO-IF
A GOTO instruction is used to jump to anywhere in the program. it uses the two 8-bit latches (GOTO-A and GOTO-B) to set the 16-bit program counter. Due to how the hardware is set up, the PC will be incremented after each goto instruction. This means that the previous address to where the execution should jump to must be used.
GOTO-IF is a conditional goto which will do what is described above only if the bit set by GOTO-DECIDER from the LSB of the bus is 1, otherwise it does nothing. This is the only instruction that allows the computer to "branch" or "make decisions".
There are macros, @goto(anchor_name) and @goto_if(anchor_name) that make GOTOs a lot easier.
Functions and the call stack
Functions in the machine code are defined by CALL and RETURN instructions. To call a function, make sure the correct pointer to the beginning of the function is stored in the control unit goto A and B latches. Then use the CALL instruction which is basically a GOTO but will first put the current program counter value onto the call stack as the return address. The return instruction pops a return address off of the call stack and continues execution.
There is a macro in the assembly language, @call(function_name) that makes calling a lot easier.
The call stack is a seperate stack memory containing 256 (1 byte address size) 16-bit words. Each word will be a return address - where to set the program counter during a RETURN instruction.
I/O
There are 16 input and 16 seperate output pins accessible with the GPIO-READ-A, -B and GPIO-WRITE-A, -B bus sources/destinations.
Writing to the display
The display has 32 x 32 LEDs, any given row of 8 pixels are written all at once with one byte. These rows of 8 are addressed by a 7-bit number starting at the top-left and going across for 4 per row (32 / 8 = 4) for each of the 32 rows of the display. The display currently is attached to the gpio-write a and b outputs with gpio-write-a being the address (MSB is ignored) and gpio-write-b being the data. The signal to write the address/data to the display's memory is sent by the hardware when gpio-write-b is written to, meaning the address should be written first and then the data.
General purpose static-RAM (GPRAM)
This piece of memory does not have any hardware protection like the stack and can be writen to and read from at any location. It will have a 16-bit address by 8-bit word size (65,536 bytes) just like the stack.
Its address can be optionally incremented upon reads/writes and can be directly set from the bus (GPRAM-ADDR-A and GPRAM-ADDR-B). For the instructions where the address is incremented, that will happen after the read/write.
Design software
First, everything was built and tested in a digital circuit simulator Rusty Logic which allowed my to check that all the timing diagrams would be followed.
Once everything was working in Rusty Logic, KiCad was used to layout all of the actual PCBs.
Parts
25| Qty | Value | Footprint | References |
|---|---|---|---|
| 11 | ~ | Package_DIP:DIP-14_W7.62mm | U2, U4, U5, U9, U10, U11, U12, U13, U14, U15, U16 |
| 8 | ~ | custom_footprint_library:jumper | U6, U17, U18, U19, U20, U21, U22, U23 |
| 2 | 10k | custom_footprint_library:0.4_pitch_resistor | R1, R2 |
| 2 | ~ | Package_DIP:DIP-20_W7.62mm | U7, U8 |
| 1 | ~ | Package_DIP:DIP-16_W7.62mm | U3 |
| 1 | ~ | Connector_PinHeader_2.54mm:PinHeader_1x23_P2.54mm_Horizontal | U1 |
Questions
How big is the Control unit central timing PCB?
It measures 100 × 100 mm and has 2 copper layers, 1.6 mm thick.
What parts does the Control unit central timing use?
25 parts. 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 Control unit central timing KiCad files?
From the emperor-hadrian/stack_machine repository on GitHub, at the commit this page was made from: the board is kicad/boards_version_1/control_unit_central_timing/control_unit_central_timing.kicad_pcb. PCBKit opens the board in the browser, with nothing to install.
Can I use the Control unit central timing design in my own project?
Its repository states no licence, so its author keeps all rights: ask emperor-hadrian before reusing the design.
More from emperor-hadrian/stack_machine
12- 2 layers428 × 293 mm

- 2 layers296 × 139 mm

- 2 layers126.5 × 84 mm

- 2 layers201 × 169 mm

- 2 layers208 × 159 mm

- 2 layers268 × 99.1 mm

- 2 layers165 × 118 mm

- 2 layers190 × 125 mm

- 2 layers220 × 172 mm

- 2 layers157.5 × 127.5 mm

- 2 layers100 × 100 mm

- 2 layers140.5 × 100 mm

Similar boards
- 2 layers159.5 × 30 mm

Type f
google/mozc-devices
Circuit diagrams and firmware source code for Gboard DIY keyboards
25 parts3kApache-2.0 - 2 layers113.5 × 95.1 mm

Sweep high
davidphilipbarr/Sweep
Sweep - a small promicro based keyboard inspired by the Ferris.
25 parts2.1k - 2 layers75 × 30 mm

- 2 layers58.4 × 114.3 mm

PicoPedal
hackclub/OnBoard
💡 Join 1,000 teenagers and make your first PCB with a $100 grant!
25 parts915 - 2 layers54 × 15 mm

Xyp gpib
xyphro/UsbGpib
Versatile, cheap and portable USB to GPIB converter (USBTMC class based)
25 parts690MIT - 2 layers50 × 76 mm

- 2 layers237.5 × 102.9 mm

Storage
jes/scamp-cpu
A homebrew 16-bit CPU with a homebrew Unix-like-ish operating system.
25 parts301Unlicense - 2 layers43.3 × 21 mm
