Please, check our SMD/THT services - smd.lotharek.pl; from idea to ready devices
Please, check our SMD/THT services - smd.lotharek.pl; from idea to ready devices
Amstrad CPC6128 4MB / FPGA nmos CPU upgrade
The T80 family (VHDL files) is an open-source Z80 CPU soft-core. Multiple variants are included — T80a, T80as, T80s, T80se, T80pa, T8080se, GBse — supporting Z80, 8080, and Game Boy CPU modes. The top module selects between T80a (standard Z80) and T80as (with separate data in/out buses) via a compile-time define.
CPC_512 implements the Amstrad CPC 6128 memory management unit — it replicates the gate array's memory banking logic, decoding I/O port writes (the classic 0x7Fxx CPC banking register) to generate extended address bits SA[18:14] for up to 512KB of RAM, matching the CPC 6128's memory expansion scheme.
QSPI (Quad SPI) PSRAM controller that interfaces the Z80's memory bus to an external quad-SPI PSRAM chip, providing the actual RAM storage. The address is assembled from the CPC bank bits (SAA) plus the CPU address.
CLK_DELAY generates a delayed CPU clock — it uses an 80MHz PLL clock to create a phase-shifted version of the input Z80 clock (4 cycles of delay), which is used to time the CPU core relative to the memory interface.
TOP ties everything together: PLL → clock delay → Z80 core → CPC memory controller → QSPI RAM, with the standard Z80 bus signals (MREQ, IORQ, RD, WR, RFSH, HALT, BUSAK) exposed as top-level I/Os.
In summary: This is an FPGA-based 512KB RAM expansion for the Amstrad CPC 6128 (or compatible). The FPGA sits on the CPC's Z80 bus, intercepts memory requests, implements the CPC's memory banking/paging logic, and routes read/write accesses to an external QSPI PSRAM chip — all running at 80MHz internally to meet Z80 bus timing.
The FPGA sits between a real Amstrad CPC's Z80 bus and an external PSRAM chip. The CPC's CPU generates bus cycles normally; the FPGA intercepts them, decodes the banking registers, translates the 16-bit Z80 address into a 23-bit physical address, and then performs QSPI transactions to read or write the PSRAM — all fast enough that the Z80 never has to wait.
The CPC's Z80 runs at roughly 4MHz. The FPGA needs to run much faster internally to handle QSPI transactions (which require many serial clock cycles per byte) within a single Z80 bus cycle.
The Gowin rPLL multiplies the incoming 4MHz clock up to 80MHz — giving the FPGA 20 internal clock cycles for every Z80 cycle to do its work.
The CLK_DELAY module then takes that 80MHz clock and the original Z80 clock, and implements a simple shift-register delay line (4 cycles deep by default). It samples CLK_n on every 80MHz rising edge and shifts it through a 4-stage register, outputting a version of the Z80 clock that is delayed by 4×12.5ns = 50ns. This delayed clock (new_clk) is fed to the T80 CPU core. The purpose is to ensure that address and data bus signals from the real CPC have settled before the FPGA's internal CPU core latches them, preventing metastability and setup-time violations.
The project instantiates the T80a open-source Z80 core (selectable via `define between T80a and T80as). The T80 core here is used to re-drive and re-synchronize the Z80 bus signals into the FPGA's clock domain, acting as a bus monitor/follower. It receives all the bus signals (WAIT_n, INT_n, NMI_n, BUSRQ_n, D) and generates the bus control outputs (M1_n, MREQ_n, IORQ_n, RD_n, WR_n, RFSH_n, HALT_n, BUSAK_n, A[15:0]).
This is the core logic. The original Amstrad CPC 6128 had 128KB RAM and a gate array chip that handled bank switching via a software-controlled register. This module reimplements that gate array's memory management, extended to 512KB+.
How bank switching works on a real CPC: Software writes to I/O port &7Fxx (address bit 15=0, IORQ active, WR active, D[7:6]=11) to select which RAM configuration is active. The gate array responds by routing different 16KB blocks of physical RAM into the Z80's four 16KB pages.
In this FPGA module: When the Z80 does such a port write, DFF_CLK fires, and the flip-flops latch bits D[5:0] and A[10:8]. These stored bits (SA[16:14] and SA_ext[2:0]) define the current bank configuration.
From these, the combinational logic generates:
MREQ_AMSTRAD is the gated memory request signal that the FPGA drives back onto the bus — it suppresses the CPC's own internal RAM when the FPGA is handling the access, and allows normal bus activity during refresh cycles.
RAMDIS (IO[9]) is asserted when an extended bank (SAA_EXT != 0) is being accessed, telling the CPC's internal logic to stand down and let the FPGA handle it.
Once the CPC6128 module has decided that a memory access should go to the expansion RAM and produced the physical address ({SAA[18:14], SAA_EXT[2:0], A[13:0]} — up to 23 bits = 8MB address space), the qspiram module performs the actual memory transaction.
It's a state machine with four states:
INIT: On power-up, it sends the command byte 0x35 (Enter QSPI mode) to the PSRAM chip on the single SI line, switching it from standard SPI to quad-SPI mode for all future transactions.
IDLE: It watches cpu_rd_n and cpu_wr_n. When one goes low (active), it assembles a 40-bit shift register payload and kicks off a transaction:
0xEB (Fast Quad Read) + 23-bit address + 8 dummy/NOP bits0x38 (Quad Write) + 23-bit address + 8-bit dataSEND: The 40-bit shift register is clocked out 4 bits at a time (one nibble per 80MHz clock) on the 4 QSPI data lines. sregcnt counts down the number of nibbles to send.
RECV: For reads, after the command and address are sent, the state machine switches the QSPI lines to input, receives 8 nibbles of data (2 nibbles = 1 byte), and assembles the byte into cpu_rdata. This is then driven back onto the Z80 data bus D while cpu_rd_n is low.
The entire read transaction takes: 1 (CS assert) + 2 (cmd) + 6 (addr) + 2 (dummy) + 2 (data) = ~13 nibble-clocks at 80MHz = ~162ns. The Z80 at 4MHz has about 250ns per half-cycle, so the timing is tight but workable.