Sharp MZ MiSTer 開発者ガイド
Part 1 — Introduction
Who is this guide for?
| Background | What you will gain |
|---|---|
| FPGA / HDL developer | Deep understanding of the VHDL architecture, clock domains, memory map, and how to add hardware modules |
| C / C++ developer | How the MiSTer main binary communicates with the FPGA over SPI, the tape queue system, ROM management, and UI state machine |
| MiSTer core developer | Integration points with the MiSTer framework (hps_io, sys_top, IOCTL protocol) |
| Student / hobbyist | End-to-end view of a non-trivial FPGA emulation project with real-world build and CI/CD pipelines |
Repositories
| Repository | Content | URL |
|---|---|---|
| SharpMZ_MiSTer | FPGA design (VHDL / SystemVerilog) + Quartus project | git.eaw.app/eaw/SharpMZ_MiSTer |
| Main_MiSTer | MiSTer main binary (C/C++) including Sharp MZ support | git.eaw.app/eaw/Main_MiSTer |
| MiSTer-devel | Upstream MiSTer framework (GitHub) | github.com/MiSTer-devel |
Emulated machines
| Model | Series | Status | Key differences |
|---|---|---|---|
| MZ-80K | Personal | Complete | Original 1978 model, 2MHz Z80, monochrome 40×25 |
| MZ-80C | Personal | Complete | Cost-reduced MZ-80K |
| MZ-1200 | Personal | Complete | Compact desktop MZ-80A predecessor |
| MZ-80A | Personal | Complete | Enhanced MZ-80K, most popular model |
| MZ-700 | Personal | Complete | Colour display, different keyboard matrix |
| MZ-800 | Personal | Partial | Extended MZ-700, graphics modes |
| MZ-80B | Business | Complete | Business series, 80-column display, GRAM, Z80 PIO |
| MZ-2000 | Business | Partial | Enhanced MZ-80B, FDD support, APSS tape |
Part 2 — Hardware Platform
MiSTer FPGA platform
The MiSTer project uses the Terasic DE10-Nano board built around the Intel (Altera) Cyclone V SoC.
| Parameter | Value |
|---|---|
| FPGA device | Cyclone V SE 5CSEBA6U23I7 |
| Package | UFBGA-672 |
| Logic elements | 41,910 |
| Embedded memory | 5,662 Kbits |
| DSP blocks | 112 (18×18 multipliers) |
| PLLs | 6 |
| HPS | Dual-core ARM Cortex-A9 @ 800MHz |
| DDR3 | 1GB (HPS side) |
| SDRAM | 32MB (active memory expansion, active I/O board dependent) |
Design constraints
The emulator targets the specific FPGA above. The Quartus project file (sharpmz.qsf) locks the device:
set_global_assignment -name FAMILY "Cyclone V"
set_global_assignment -name DEVICE 5CSEBA6U23I7
set_global_assignment -name TOP_LEVEL_ENTITY sys_top
The sys_top entity is the MiSTer framework wrapper (sys/sys_top.v), which instantiates our core-specific emu module defined in sharpmz.sv.
Part 3 — FPGA Architecture Overview
Module hierarchy
MiSTer Framework (sys/)
└── sys_top.v ← Top-level entity (framework)
└── sharpmz.sv ← Core-specific 'emu' module (SystemVerilog)
└── bridge.vhd ← HPS ↔ FPGA bridge
└── sharpmz.vhd ← Main emulation container (VHDL)
├── clkgen.vhd Clock generation & PLL
├── T80se Z80 CPU core
├── mctrl.vhd Machine control & config registers
├── video.vhd Video display engine
├── cmt.vhd Cassette tape interface
├── keymatrix.vhd PS/2 → MZ keyboard conversion
├── dpram (SYSROM) 128KB monitor ROM (dual-port)
├── dpram (SYSRAM) 64KB system RAM (dual-port)
├── mz80c.vhd Personal series controller
│ ├── i8255.vhd Programmable Peripheral Interface
│ └── i8254.vhd Programmable Interval Timer
└── mz80b.vhd Business series controller
├── i8255.vhd PPI (alternate config)
├── i8254.vhd PIT (cascade mode)
└── z8420.vhd Z80 PIO
Data flow
The FPGA design has two independent data paths:
-
Emulation path — the Z80 CPU executes Sharp MZ software, reading/writing ROM, RAM, video, peripherals, and cassette via address-decoded chip selects. This runs in the FPGA clock domain.
-
HPS path — the ARM processor on the SoC communicates with the FPGA via SPI (through
hps_io), sending configuration registers, ROM images, tape data, and keyboard maps. This runs in the IOCTL clock domain.
Both paths access dual-port RAMs simultaneously without contention — Port A for the Z80, Port B for the HPS.
Source file listing (files.qip)
| File | Language | Purpose |
|---|---|---|
sharpmz.sv |
SystemVerilog | MiSTer emu module — bridges framework to VHDL core |
rtl/sharpmz.vhd |
VHDL | Main emulation container, instantiates all sub-modules |
rtl/bridge.vhd |
VHDL | HPS ↔ FPGA IOCTL bridge and address decoder |
rtl/clkgen.vhd |
VHDL | Clock generator, PLL configuration, clock enables |
rtl/video.vhd |
VHDL | Video controller: timing, VRAM, ARAM, GRAM, CG ROM/RAM |
rtl/cmt.vhd |
VHDL | Cassette tape: PWM encode/decode, dual-port data cache |
rtl/mctrl.vhd |
VHDL | Machine control: config registers, reset management |
rtl/keymatrix.vhd |
VHDL | PS/2 scan code → Sharp MZ keyboard matrix conversion |
rtl/mz80c/mz80c.vhd |
VHDL | MZ-80K/C/1200/80A/700/800 machine controller |
rtl/mz80b/mz80b.vhd |
VHDL | MZ-80B/2000 machine controller |
rtl/i8254/i8254.vhd |
VHDL | Intel 8254 Programmable Interval Timer |
rtl/i8255/i8255.vhd |
VHDL | Intel 8255 Programmable Peripheral Interface |
rtl/z8420/z8420.vhd |
VHDL | Zilog Z80 PIO (Parallel I/O) |
rtl/T80/*.vhd |
VHDL | T80 Z80 CPU core (~14.5K lines) |
rtl/pll/*.vhd |
VHDL | Quartus PLL megafunction wrappers |
rtl/dpram.vhd |
VHDL | Generic dual-port RAM template |
Part 4 — Clock Architecture
The clkgen module generates all clocks from a single 50MHz input using PLL multiplication and divider chains. A 9-bit clock bus (CLKBUS) distributes signals to all modules:
| Index | Name | Function |
|---|---|---|
| 0 | CKMASTER | Master reference clock (50MHz or PLL output) |
| 1 | CKSOUND | Audio/sound generation clock |
| 2 | CKRTC | Real-time clock (~1Hz derived) |
| 3 | CKENVIDEO | Video clock enable (pulsed) |
| 4 | CKVIDEO | Video pixel clock (continuous) |
| 5 | CKIOP | I/O processor clock |
| 6 | CKENCPU | CPU clock enable (pulsed at selected frequency) |
| 7 | CKENLEDS | LED update clock enable |
| 8 | CKENPERIPH | Peripheral clock enable |
Clock enable technique
Rather than generating separate clock domains (which causes timing issues), the design uses a single master clock with clock enable pulses. The Z80 CPU, for example, runs at CKMASTER speed but only advances on the rising edge of CKENCPU:
-- CPU executes only when CKENCPU pulses
T80se_inst : T80se
port map (
CLK_n => CLKBUS(CKMASTER),
CLKEN => CLKBUS(CKENCPU),
...
);
This keeps the entire design in a single clock domain while supporting variable CPU speeds.
Configurable frequencies
The 71-bit CONFIG bus from mctrl controls frequency selection:
| CONFIG bits | Parameter | Options |
|---|---|---|
| 61:58 | CPUSPEED | 2MHz, 4MHz, 8MHz, 16MHz, 32MHz |
| 64:62 | VIDSPEED | Multiple pixel clock rates |
| 66:65 | PERSPEED | Peripheral clock selection |
| 68:67 | RTCSPEED | RTC clock selection |
| 70:69 | SNDSPEED | Sound clock selection |
Debug clocks
For development, clkgen can generate extremely slow clocks (100KHz down to 0.1Hz) selectable via the DEBUG2 register. This allows single-stepping the CPU at human-observable speeds.
Part 5 — Memory Architecture
Dual-port RAM strategy
Every shared memory in the design is implemented as dual-port RAM (DPRAM):
- Port A: Z80 CPU access at
CLKBUS(CKMASTER)withCLKBUS(CKENCPU)enable - Port B: HPS access at
IOCTL_CLK(independent, asynchronous)
This eliminates clock domain crossing issues — the FPGA fabric handles the dual-port arbitration internally.
Memory map
| Memory | Size | Address | Port A (CPU) | Port B (HPS) | Init file |
|---|---|---|---|---|---|
| SYSROM | 128KB | 17-bit | Read-only | Read/Write (programming) | combined_mrom.mif |
| SYSRAM | 64KB | 16-bit | Read/Write | Read/Write | combined_mainmemory.mif |
| VRAM | 4KB | Text display buffer | Read/Write | Read/Write | — |
| ARAM | 4KB | Colour attribute RAM | Read/Write | Read/Write | — |
| GRAM | 16KB | Graphics framebuffer | Read/Write | Read/Write | — |
| CGROM | 32KB | Character generator ROM | Read-only | Read/Write | — |
| CGRAM | 32KB | Programmable char RAM | Read/Write | Read/Write | — |
| CMT HDR | 128B | Tape file header | — | Read/Write | — |
| CMT DATA | 64KB | Tape data cache | Read (playback) | Read/Write | — |
| KEYMAP | 2KB | Keyboard mapping table | Read-only | Read/Write | — |
ROM banking
The SYSROM stores monitor ROMs for all machine models in a single 128KB block. Bank selection is via MROM_BANK[5:0] fed into the upper address bits:
| Machine | Banks | ROM address range |
|---|---|---|
| MZ-80K | 0, 1 | 0x00000 – 0x00FFF |
| MZ-80C | 2, 3 | 0x02000 – 0x02FFF |
| MZ-1200 | 4, 5 | 0x04000 – 0x04FFF |
| MZ-80A | 6, 7 | 0x06000 – 0x06FFF |
| MZ-700 | 8, 9 | 0x08000 – 0x08FFF |
| MZ-80B | 10, 11 | 0x0A000 – 0x0AFFF |
Switching machine models at runtime simply changes the bank select — no reloading required.
Z80 address decoding
The 64KB Z80 address space is decoded differently per machine. The mz80c and mz80b modules generate chip selects:
MZ-80A (typical Personal series):
| Address range | Component | Chip select |
|---|---|---|
| 0x0000 – 0x0FFF | Monitor ROM | CS_ROM_n |
| 0x1000 – 0xCFFF | User RAM (48KB) | CS_RAM_n |
| 0xD000 – 0xD7FF | VRAM (2KB) | CS_VRAM_n |
| 0xD800 – 0xDFFF | Colour RAM | CS_ARAM_n |
| 0xE000 – 0xE007 | i8255 PPI | CS_IO_PPI_n |
| 0xE008 – 0xE00B | i8254 PIT | CS_IO_PIT_n |
| 0xE800 – 0xEFFF | User ROM (optional) | CS_USERROM_n |
| 0xF000 – 0xFFFF | FDC ROM (optional) | CS_FDCROM_n |
CPU data bus multiplexing
The Z80 data input is multiplexed from all memory and peripheral sources:
T80_DI <= SYSROM_DO when CS_ROM_n = '0' else
SYSRAM_DO when CS_RAM_n = '0' else
VIDEO_DO when CS_VRAM_n = '0' else
PPI_DO when CS_IO_PPI_n = '0' else
PIT_DO when CS_IO_PIT_n = '0' else
(others => '1'); -- Open-drain pullup simulation
The default '1' simulates the open-drain bus behaviour of real hardware, where unselected devices float high.
Part 6 — Video Subsystem
The video.vhd module (1,980 lines) implements the complete display system.
Display modes
| Mode | Resolution | Colours | Machines |
|---|---|---|---|
| Monochrome 40×25 | 320×200 | Green/White | MZ-80K/C/1200/80A |
| Colour 40×25 | 320×200 | 8 colours | MZ-700/800 |
| Colour 80×25 | 640×200 | 8 colours | MZ-80B/2000 |
| Graphics 320×200 | 320×200 | 8 colours | MZ-700/800 (GRAM) |
| Graphics 640×200 | 640×200 | 8 colours | MZ-80B/2000 (GRAM) |
Architecture
- Character display: Text characters fetched from VRAM, looked up in CGROM/CGRAM, rendered as 8×8 pixel glyphs
- Attribute overlay: Colour attributes from ARAM applied per character cell (foreground/background colours)
- Graphics overlay: GRAM pixels composited with character display using configurable blend modes (XOR, OR, AND, replace)
- VGA upscaling: LUT-based scaling from native resolution to 640×400 or 640×480 VGA output
- Double-buffering: VRAM content copied to display buffer during blanking to eliminate snow/tearing
Timing specifications
| Parameter | MZ-80A (Mono) | MZ-700 (Colour) |
|---|---|---|
| H period | 64µs | 64.056µs |
| H display | 320px | 320px |
| H frequency | 15.625KHz | 15.611KHz |
| V period | 16.64ms | 16.654ms |
| V display | 200 lines | 200 lines |
| V frequency | 60.10Hz | 50.04Hz |
Key signals
| Signal | Direction | Purpose |
|---|---|---|
HBLANK, VBLANK |
Output | Blanking intervals |
HSYNC_n, VSYNC_n |
Output | Sync pulses |
ROUT[7:0], GOUT[7:0], BOUT[7:0] |
Output | 8-bit RGB |
CS_VRAM_n |
Input | CPU accessing VRAM |
T80_WAIT_n |
Output | Stall CPU during VRAM contention |
Adding a new display mode
To add a new display mode:
- Add a new CONFIG bit in
mctrl.vhdfor mode selection - Create a new timing generator process in
video.vhdwith the required H/V timing - Add the character/graphics rendering pipeline for the new mode
- Update the MUX that selects the active display pipeline based on CONFIG
- Add the mode to the C++ menu system (
sharpmz_set_display_type()) - Update the REGISTER_DISPLAY bitfield in
sharpmz.h
Part 7 — Cassette Tape Module
The cmt.vhd module emulates the cassette tape interface using PWM encoding/decoding and a dual-port RAM data cache.
CMT bus signals
Output signals (FPGA → HPS, 14 bits):
| Bit | Name | Function |
|---|---|---|
| 0 | PLAY_READY | Cache loaded, ready for playback |
| 1 | PLAYING | Playback in progress |
| 2 | RECORD_READY | Record buffer full (data available for save) |
| 3 | RECORDING | Recording in progress |
| 4 | ACTIVE | Transfer active |
| 5 | SENSE | Tape state sense output to Z80 |
| 6 | WRITEBIT | Bit value being sent to Z80 (playback) |
| 7 | TAPEREADY | Cassette loaded |
| 8 | WRITEREADY | Write enabled |
| 9-13 | APSS_* | Automatic Program Search System controls |
Input signals (Z80 → CMT, 8 bits):
| Bit | Name | Function |
|---|---|---|
| 0 | READBIT | Bit value from Z80 (recording) |
| 1 | REEL_MOTOR | Motor control |
| 2 | STOP | Stop motor |
| 3 | PLAY | Play command |
| 4 | SEEK | Fast-forward/rewind |
| 5 | DIRECTION | Seek direction |
| 6 | EJECT | Eject cassette |
| 7 | WRITEENABLE | Enable recording |
Tape data flow
HPS (ARM) FPGA (CMT module) Z80 CPU
Load .MZF file ──────────────────→ CMT DATA RAM (64KB) ─── PWM ───→ READBIT → PPI
│ CMT HDR RAM (128B) (via i8255)
│ sharpmz_load_tape_to_ram()
│
│ CMT DATA RAM ←──── PWM ←──────── WRITEBIT
Save .MZF file ←─────────────────── CMT HDR RAM (recording)
sharpmz_save_tape_from_cmt()
Speed options
| Setting | Speed | Use case |
|---|---|---|
| 0 | 1× (original) | Authentic experience |
| 1 | 2× | Slightly faster loading |
| 2 | 4× | Practical loading |
| 3 | 8× | Quick testing |
| 4 | 16× | Development |
| 5 | 32× | Near-instant load |
Part 8 — Machine-Specific Controllers
The design splits into two controller branches based on the machine family:
mz80c.vhd — Personal series (MZ-80K/C/1200/80A/700/800)
Peripherals instantiated:
- i8255 PPI: Keyboard scanning (Port A), keyboard data (Port B), cassette/sound control (Port C)
- i8254 PIT: Counter 0 for sound tone generation, Counter 1 cascaded, Counter 2 for RTC
Key I/O addresses: | Address | Read | Write | |—|—|—| | 0xE000 | PPI Port A | PPI Port A | | 0xE001 | PPI Port B (keyboard data) | PPI Port B | | 0xE002 | PPI Port C (status) | PPI Port C (control) | | 0xE003 | — | PPI control word | | 0xE004-E007 | PIT Counter 0-2 + control | PIT Counter 0-2 + control |
mz80b.vhd — Business series (MZ-80B/2000)
Additional peripherals:
- Z8420 PIO: Replaces some PPI functions, adds interrupt-driven I/O
- Graphics controller: 640×200 framebuffer with palette registers
- Bootstrap ROM: Boot IPL sequence with configurable reset
Additional I/O addresses: | Address | Function | |—|—| | 0xF4-F7 | Z80 PIO Port A/B data and control | | 0xF8-FD | Graphics framebuffer control registers | | 0xFE-FF | Memory banking control |
Adding a new machine model
- Decide which controller branch to extend (mz80c or mz80b)
- Add the new model to
mctrl.vhdCONFIG machine selection (bits 7:0) - Create a new ROM bank pair in the SYSROM address map
- Define the Z80 memory map and I/O address decoding in the controller
- Add machine-specific peripheral instances if needed
- Update
keymatrix.vhdif the keyboard matrix differs - Update the C++ code:
sharpmz_get_machine_group(), model names array, ROM filenames - Update CONFIG register documentation
Part 9 — HPS-FPGA Bridge Protocol
Communication architecture
ARM Cortex-A9 (Linux)
│
│ SPI bus (directly mapped via /dev/spidev)
│
├──→ hps_io.v (MiSTer framework)
│ │
│ ├──→ IOCTL bus (address, data, rd/wr, download/upload)
│ │
│ └──→ sharpmz.sv → bridge.vhd → sharpmz.vhd → sub-modules
│
└──→ Config registers (separate SPI command channel)
IOCTL bus signals
| Signal | Width | Direction | Purpose |
|---|---|---|---|
IOCTL_CLK |
1 | — | Independent clock for HPS transfers |
IOCTL_ADDR |
25 | HPS→FPGA | Memory address (bank + offset) |
IOCTL_DOUT |
32 | HPS→FPGA | Data from HPS |
IOCTL_DIN |
32 | FPGA→HPS | Data to HPS |
IOCTL_WR |
1 | HPS→FPGA | Write enable pulse |
IOCTL_RD |
1 | HPS→FPGA | Read enable pulse |
IOCTL_DOWNLOAD |
1 | HPS→FPGA | File download in progress |
IOCTL_UPLOAD |
1 | HPS→FPGA | File upload in progress |
SPI command protocol
The C++ code uses these SPI commands to communicate with the FPGA:
| Command | Code | Direction | Purpose |
|---|---|---|---|
SHARPMZ_FILE_TX |
0x53 | HPS→FPGA | File transfer (SOF/EOF markers) |
SHARPMZ_FILE_ADDR_TX |
0x58 | HPS→FPGA | Set upload destination address |
SHARPMZ_FILE_ADDR_RX |
0x59 | FPGA→HPS | Set download source address |
SHARPMZ_CONFIG_RX |
0x5A | FPGA→HPS | Read configuration register |
SHARPMZ_CONFIG_TX |
0x5B | HPS→FPGA | Write configuration register |
Memory bank addressing
The 25-bit IOCTL address is divided into bank (bits 23:16) and offset (bits 15:0):
| Bank | Code | Size | Content |
|---|---|---|---|
| SYSROM | 0x00 | 128KB | Monitor ROM images (all machines) |
| SYSRAM | 0x10 | 64KB | Z80 system memory |
| KEYMAP | 0x20 | 2KB | Keyboard mapping table |
| VRAM | 0x30 | 16KB | Video RAM + attributes |
| CMT_HDR | 0x40 | 128B | Cassette tape file header |
| CMT_DATA | 0x41 | 64KB | Cassette tape data buffer |
| CGROM | 0x50 | 32KB | Character generator ROM |
| CGRAM | 0x60 | 32KB | Programmable character RAM |
File upload sequence (C++ → FPGA)
// 1. Set destination address
EnableFpga();
spi8(SHARPMZ_FILE_ADDR_TX); // 0x58
spi8(0x00); // Padding
spi8(bank); // Memory bank (e.g., 0x41 for CMT_DATA)
spi8((offset >> 8) & 0xFF); // Address MSB
spi8(offset & 0xFF); // Address LSB
DisableFpga();
// 2. Stream data in 512-byte chunks
while (data_remaining) {
EnableFpga();
spi_write(buffer, 512); // DMA-like bulk transfer
DisableFpga();
}
// 3. Signal end of transfer
EnableFpga();
spi8(SHARPMZ_FILE_TX); // 0x53
spi8(SHARPMZ_EOF); // 0x00
DisableFpga();
Configuration register protocol
Write register:
EnableFpga();
spi8(SHARPMZ_CONFIG_TX); // 0x5B
spi8(register_address); // 0x00-0x0F
spi8(value);
DisableFpga();
Read register:
EnableFpga();
spi8(SHARPMZ_CONFIG_RX); // 0x5A
spi8(register_address); // 0x00-0x0F
spi8(0x00); // Dummy cycle
value = spi_b(0x00); // Read value
DisableFpga();
Part 10 — Configuration Register Map
The emulator uses 16 8-bit configuration registers accessible via the SPI protocol. These are defined in mctrl.vhd (FPGA side) and sharpmz.h (C++ side).
| Register | Address | Name | Description |
|---|---|---|---|
| 0 | 0x00 | MODEL | Machine model selection (bits 2:0) |
| 1 | 0x01 | DISPLAY | Display type, VRAM/GRAM enable, PCG mode |
| 2 | 0x02 | DISPLAY2 | VGA mode, GRAM base address |
| 3 | 0x03 | — | Reserved |
| 4 | 0x04 | CPU | CPU speed (bits 2:0), boot reset (bit 7) |
| 5 | 0x05 | AUDIO | Audio source: 0=sound generator, 1=tape |
| 6 | 0x06 | CMT | Fast tape (2:0), tape buttons (4:3), ASCII mapping (6:5) |
| 7 | 0x07 | CMT2 | APSS controls (MZ-80B/2000 only) |
| 8 | 0x08 | USERROM | Per-machine user ROM enable (1 bit per machine) |
| 9 | 0x09 | FDCROM | Per-machine FDC ROM enable (1 bit per machine) |
| 10-13 | — | — | Reserved for future use |
| 14 | 0x0E | DEBUG | Debug LED bank/subbank, debug enable |
| 15 | 0x0F | DEBUG2 | LED sample frequency, CPU frequency override |
REGISTER_DISPLAY (0x01) bit layout
| Bit | Field | Values |
|---|---|---|
| 2:0 | Display Type | 0=Mono 40×25, 2=Colour 40×25, 3=Colour 80×25 |
| 4 | VRAM Disable | 0=enabled, 1=disabled |
| 5 | GRAM Disable | 0=enabled, 1=disabled |
| 6 | VRAM Wait | 0=no wait states, 1=insert wait states |
| 7 | PCG Mode | 0=ROM (fixed chars), 1=RAM (programmable chars) |
REGISTER_CMT (0x06) bit layout
| Bit | Field | Values |
|---|---|---|
| 2:0 | Fast Tape | 0=1×, 1=2×, 2=4×, 3=8×, 4=16×, 5=32× |
| 4:3 | Tape Buttons | 0=Off, 1=Play, 2=Record, 3=Auto |
| 6:5 | ASCII Mapping | 0=Off, 1=MZ→ASCII, 2=ASCII→MZ, 3=Both |
Part 11 — MiSTer Main Program — Sharp MZ Additions
The MiSTer main binary (MiSTer) runs on the ARM Cortex-A9 HPS. The Sharp MZ support code is in support/sharpmz/:
| File | Lines | Purpose |
|---|---|---|
sharpmz.h |
465 | Data structures, protocol definitions, register map, constants |
sharpmz.cpp |
3,122 | Init, polling, tape management, ROM handling, UI state machine |
Integration with MiSTer
The Sharp MZ module integrates via three entry points called by the MiSTer framework:
| Entry point | Function | When called |
|---|---|---|
sharpmz_init() |
Initialise config, load ROMs, set registers | Core startup |
sharpmz_poll() |
Tape queue management, auto-advance | Main loop (throttled to ~1s intervals) |
sharpmz_ui() |
Menu state machine, user interaction | On OSD key event |
The core type is identified by CORE_TYPE_SHARPMZ = 0xa7.
Initialisation sequence
sharpmz_init()
├── usleep(50000) Wait for FPGA registers
├── sharpmz_reload_config(0) Load SHARPMZ.CFG from SD
│ └── sharpmz_reset_config() if fail Use defaults (MZ-80A, Mono, 2MHz)
├── set_status(system_ctrl) Write system control register
├── for reg in 0..15:
│ sharpmz_set_config_register(reg) Write all config registers to FPGA
├── for machine in 0..7:
│ for rom in MROM..FDCROM:
│ if rom.enabled:
│ sharpmz_set_rom(machine, rom) Upload ROM file via SPI
└── sharpmz_clear_filelist() Init empty tape queue
Part 12 — Tape Queue System
The tape queue manages multiple MZF tape files for sequential loading — essential for multi-part programs or automated testing.
Data structure
typedef struct {
char *queue[MAX_TAPE_QUEUE]; // Array of dynamically allocated filenames (max 5)
char fileName[MAX_FILENAME_SIZE]; // Current/working filename buffer
int tapePos; // Current position for APSS seeking
int elements; // Number of queued items
} tape_queue_t;
Operations
| Operation | Function | Behaviour |
|---|---|---|
| Push | sharpmz_push_filename() |
Add file to end of queue (FIFO). Rejects if full (5 max). |
| Pop | sharpmz_pop_filename() |
Remove oldest entry, shift remaining down. Returns filename. |
| APSS search | sharpmz_apss_search() |
Advance/retreat position for MZ-80B/2000 auto-search. |
| Clear | sharpmz_clear_filelist() |
Free all entries, reset counters. |
| Get next | sharpmz_get_next_filename() |
Iterator for display (cycles with static position). |
Polling loop
The sharpmz_poll() function is called at ~1-second intervals and handles automatic tape advancement:
For MZ-80K/C/1200/80A/700/800 (simple FIFO):
- Read CMT register → check SENSE and PLAY_READY flags
- If SENSE active AND PLAY_READY empty (tape consumed):
- Pop next file from queue
- Call
sharpmz_load_tape_to_ram(filename, 1)to load into CMT buffer
- If RECORD_READY flag set:
- Call
sharpmz_save_tape_from_cmt()to save recording to SD
- Call
For MZ-80B/2000 (APSS mode):
- Read CMT2 register → check EJECT, APSS, PLAY flags
- If EJECT → clear entire queue
- If APSS → call
sharpmz_apss_search(direction)to rotate position - If PLAY active AND no play buffer → load from current position
Tape file format (MZF)
| Offset | Size | Field | Description |
|---|---|---|---|
| 0x00 | 1 | dataType | 0x01=Machine Code, 0x02=MZ80 BASIC, 0x03=Data, 0x05=MZ700 BASIC |
| 0x01 | 17 | fileName | File name (0x0D terminated) |
| 0x12 | 2 | fileSize | Data partition size (little-endian) |
| 0x14 | 2 | loadAddress | Memory load address |
| 0x16 | 2 | execAddress | Execution start address |
| 0x18 | 104 | comment | Free text / bootstrap code |
| 0x80 | fileSize | data | Program/data payload |
Part 13 — ROM Management
ROM types
| Index | Name | Purpose | Typical size |
|---|---|---|---|
| 0 | MROM | Main monitor ROM | 4KB |
| 1 | MROM_80C | 80-column monitor variant | 4KB |
| 2 | CGROM | Character generator ROM | 2-4KB |
| 3 | KEYMAP | Keyboard mapping table | 256B |
| 4 | USERROM | User-programmable ROM (E800-EFFF) | 2KB |
| 5 | FDCROM | Floppy disk controller ROM (F000-FFFF) | 4KB |
Per-machine ROM addresses
Each machine model has unique load addresses in the SYSROM/CGROM/KEYMAP memory banks:
| Machine | MROM addr | CGROM addr | KEYMAP addr |
|---|---|---|---|
| MZ-80K | 0x000000 | 0x500000 | 0x200000 |
| MZ-80C | 0x003800 | 0x501000 | 0x200100 |
| MZ-1200 | 0x005000 | 0x502000 | 0x200200 |
| MZ-80A | 0x00A800 | 0x503000 | 0x200300 |
| MZ-700 | 0x00E000 | 0x504000 | 0x200400 |
| MZ-800 | 0x012800 | 0x505000 | 0x200500 |
| MZ-80B | 0x014000 | 0x506000 | 0x200600 |
| MZ-2000 | 0x016800 | 0x507000 | 0x200700 |
ROM upload workflow
- User enables a ROM via the menu →
sharpmz_set_*_rom_enabled(machine, 1) sharpmz_set_rom(machine, romType)is called- ROM filename resolved from
config.romData[machine][romType].romFileName sharpmz_send_file()opens the file and streams it via SPI to the FPGA at the correct memory bank address- The dual-port SYSROM/CGROM is written via IOCTL Port B
- The Z80 CPU reads from Port A, seeing the new ROM contents immediately
Part 14 — Menu UI State Machine
The OSD menu is driven by a state machine in sharpmz_ui(). Each state pair (state1/state2) renders the menu and handles input respectively.
State diagram
┌──────────────────────┐
│ MAIN MENU │
│ ├─ Tape Storage │──→ TAPE_STORAGE
│ ├─ Machine │──→ MACHINE
│ ├─ Display │──→ DISPLAY
│ ├─ Debug │──→ DEBUG
│ └─ System │──→ (MiSTer system menu)
└──────────────────────┘
│
┌─────────────────────┼──────────────────────┐
▼ ▼ ▼
TAPE_STORAGE MACHINE DISPLAY
├─ Load to RAM ├─ Model select ├─ Display type
├─ Queue tape ├─ CPU speed ├─ VGA mode
├─ Clear queue ├─ Audio source ├─ VRAM/GRAM
├─ Tape buttons └─ ROM Management └─ PCG mode
├─ Fast tape └─ ROMS
└─ ASCII mapping ├─ Machine select
├─ User ROM toggle
├─ FDC ROM toggle
└─ ROM file browse
Adding a new menu item
- Define new state constants in the state enum (e.g.,
MENU_SHARPMZ_NEWFEATURE1 = 0xC0) - Add the menu entry text in the parent state’s State1 handler using
OsdWrite() - Add the transition in the parent state’s State2 handler
- Implement the new State1 (render) and State2 (input) handlers
- Update the FPGA registers if the feature requires hardware changes
Part 15 — Building from Source
FPGA bitstream
Requirements:
- Intel Quartus Prime 17.0.2 Lite Edition (or compatible 17.1.x)
- Target: Cyclone V 5CSEBA6U23I7
Manual build:
cd /path/to/SharpMZ_MiSTer
quartus_sh --flow compile sharpmz
Output files:
output_files/sharpmz.sof— JTAG programming fileoutput_files/sharpmz.rbf— Raw Binary File (for MiSTer SD card)
Jenkins CI/CD:
The project has a Jenkins pipeline (SharpMZ-MiSTer-Build) that:
- Checks out both
SharpMZ_MiSTer(FPGA) andMain_MiSTer(HPS binary) - Compiles the FPGA bitstream using Quartus 17.1.1 in Docker
- Cross-compiles the MiSTer binary using ARM GCC 10.2 in Docker
- Packages and creates a Gitea release with FPGA (.rbf) and binary artifacts
- Triggered automatically by Gitea push webhooks
MiSTer main binary
Requirements:
- ARM GCC cross-compiler:
arm-none-linux-gnueabihf-gcc10.2 (Linaro) - Pre-built ARM shared libraries (included in repo under
lib/)
Manual build:
cd /path/to/Main_MiSTer
make -j$(nproc)
Output: bin/MiSTer — stripped ARM Linux binary
Cross-compilation Docker:
docker run --rm -v "$(pwd):/build" -w /build mister-arm-gcc:latest make -j$(nproc)
Deploying to MiSTer hardware
- Copy
sharpmz.rbf→ MiSTer SD card asSharpMZ.rbf(orSharpMZ_YYYYMMDD.rbfin_Computer/) - Copy
bin/MiSTer→ MiSTer SD card as/media/fat/MiSTer - Copy ROM files →
/media/fat/SharpMZ/directory on SD card - Reboot MiSTer or select core from OSD menu
Part 16 — Extending the Emulator
Adding a new peripheral
Example: Adding an SN76489 sound chip (MZ-700/800)
1. Create the VHDL entity (rtl/sn76489/sn76489.vhd):
entity sn76489 is
port (
CLK : in std_logic;
CLKEN : in std_logic;
RESET_n : in std_logic;
WR_n : in std_logic;
DI : in std_logic_vector(7 downto 0);
AUDIO_OUT : out std_logic_vector(7 downto 0)
);
end entity;
2. Add to files.qip:
set_global_assignment -name VHDL_FILE rtl/sn76489/sn76489.vhd
3. Instantiate in mz80c.vhd (inside the MZ-700/800 section):
SN76489_INST : sn76489
port map (
CLK => CLKBUS(CKMASTER),
CLKEN => CLKBUS(CKSOUND),
RESET_n => T80_RST_n,
WR_n => T80_WR_n,
DI => T80_DO,
AUDIO_OUT => SN76489_AUDIO
);
4. Add address decoding:
CS_SN76489_n <= '0' when T80_IORQ_n = '0' and T80_A16(7 downto 0) = X"06" else '1';
5. Mix audio output with existing beeper output.
Adding floppy disk support (MB8876)
The MZ-80B/2000 modules already have stub references for the MB8876 floppy disk controller. To complete FDD support:
- VHDL: Implement
rtl/mz80b/mb8876.vhd— the Western Digital WD1793-compatible FDD controller - Memory: Add a new memory bank for disk image data (may require SDRAM for capacity)
- C++ menu: Add disk image browsing and loading (similar to tape queue but for .D88/.DSK files)
- Protocol: Add new SPI command or reuse IOCTL with a new memory bank address for disk data
- Config: Add REGISTER_FDC or extend REGISTER_FDCROM for disk configuration
Adding SDRAM support
For features requiring more than the on-chip 5.6Mbits (e.g., large disk images):
- Framework: The MiSTer
sys/directory includes SDRAM controller modules - Bridge: Route SDRAM requests through
sharpmz.svusing the framework’s SDRAM interface - Arbitration: Implement a priority arbiter between the emulation (reads) and HPS (writes)
Part 17 — Development Workflow
Recommended setup
- Quartus Prime 17.1.1 (or Lite Edition) for FPGA synthesis
- ModelSim / Questa for VHDL/Verilog simulation (included with Quartus)
- ARM GCC 10.2 cross-compiler for MiSTer binary
- MiSTer hardware (DE10-Nano + I/O board) for testing
- SSH/FTP access to MiSTer for quick deployment
Edit-compile-test cycle
FPGA changes:
# 1. Edit VHDL/SystemVerilog
# 2. Compile (15-30 minutes for full compile)
quartus_sh --flow compile sharpmz
# 3. Deploy .rbf to MiSTer
scp output_files/sharpmz.rbf root@<mister-ip>:/media/fat/SharpMZ.rbf
# 4. Reboot or re-select core
ssh root@<mister-ip> 'reboot'
C++ changes:
# 1. Edit support/sharpmz/sharpmz.cpp or .h
# 2. Cross-compile
make -j$(nproc)
# 3. Deploy
ssh root@<mister-ip> 'killall MiSTer'
scp bin/MiSTer root@<mister-ip>:/media/fat/MiSTer
ssh root@<mister-ip> 'sync; /media/fat/MiSTer &'
Debug techniques
| Technique | For | How |
|---|---|---|
| Debug registers | FPGA signal inspection | Set REGISTER_DEBUG, observe LED bank outputs |
| Slow clocks | Timing analysis | Set REGISTER_DEBUG2 CPU frequency to 0.1-100Hz |
| Memory dump | RAM/ROM verification | Use sharpmz_read_ram() to dump FPGA memory to SD |
| SignalTap | Deep FPGA debug | Add SignalTap instances in Quartus (uses JTAG) |
| printf debug | C++ side | Log to /tmp/MiSTer.log or serial console |
Coding conventions
- VHDL: Entity names match filenames. All signals
UPPER_CASE. Clock enables suffixed_ENorCK. - C++: Functions prefixed
sharpmz_. Config access via getter/setter pairs. SPI transactions bracketed byEnableFpga()/DisableFpga(). - Register changes: Always update both
mctrl.vhd(FPGA) andsharpmz.h(C++) simultaneously.
Part 18 — CI/CD Pipeline
Jenkins build overview
The SharpMZ-MiSTer-Build Jenkins pipeline automates the complete build:
| Stage | Tool | Output |
|---|---|---|
| Checkout FPGA | Git | SharpMZ_MiSTer source |
| Checkout Main | Git | Main_MiSTer source |
| Version | Gitea API | Auto-incremented version number |
| FPGA Build | Quartus 17.1.1 (Docker) | SharpMZ.sof, SharpMZ.rbf |
| Main Build | ARM GCC 10.2 (Docker) | MiSTer binary |
| Package | tar/gz | SharpMZ-FPGA, SharpMZ-Main, SharpMZ-Complete |
| Release | Gitea API | Tagged release with assets |
Triggering builds
- Automatic: Gitea push webhook on
masterbranch of either repository - Manual: Jenkins “Build Now” button at
https://jenkins.eaw.app/job/SharpMZ-MiSTer-Build/
Docker build environments
| Image | Version | Purpose |
|---|---|---|
quartus-ii-17.1.1 |
Intel Quartus Prime 17.1.1 | FPGA synthesis for Cyclone V |
mister-arm-gcc |
ARM GCC 10.2 (Linaro) | MiSTer binary cross-compilation |
Part 19 — Resources
Project links
| Resource | URL |
|---|---|
| SharpMZ_MiSTer source | git.eaw.app/eaw/SharpMZ_MiSTer |
| Main_MiSTer source | git.eaw.app/eaw/Main_MiSTer |
| MiSTer project | github.com/MiSTer-devel |
| MiSTer wiki | github.com/MiSTer-devel/Main_MiSTer/wiki |
| Jenkins CI | jenkins.eaw.app/job/SharpMZ-MiSTer-Build |
| Sharp MZ Emulator (v1.0) | eaw.app/sharpmz-emulator-de10-nano |
| Sharp MZ Emulator (v2.0 tranZPUter) | eaw.app/sharpmz-emulator-tzpu-700 |
Reference documentation
| Document | Content |
|---|---|
| T80 CPU core | Z80 instruction set implementation, bus timing |
| Intel i8255 datasheet | Programmable Peripheral Interface specification |
| Intel i8254 datasheet | Programmable Interval Timer specification |
| Zilog Z8420 datasheet | Z80 PIO specification |
| MiSTer core template | Framework integration guide at github.com/MiSTer-devel |
| Quartus Prime handbook | FPGA synthesis, timing analysis, SignalTap |
Sharp MZ technical references
| Machine | Key specs |
|---|---|
| MZ-80K/C/1200 | Z80 @ 2MHz, 48KB RAM, 4KB ROM, 40×25 mono, cassette 1200baud |
| MZ-80A | Z80 @ 2MHz, 48KB RAM, 4KB ROM, 40×25 mono, improved keyboard |
| MZ-700 | Z80 @ 3.58MHz, 64KB RAM, colour display, PCG, built-in plotter |
| MZ-800 | Z80 @ 3.58MHz, 64KB RAM, QD drive, enhanced graphics |
| MZ-80B | Z80 @ 4MHz, 64KB RAM, 80-column display, GRAM, IPL boot |
| MZ-2000 | Z80 @ 4MHz, 64KB RAM, FDD, APSS cassette, enhanced graphics |