Sharp MZ MiSTer 開発者ガイド

Part 1 — Introduction

This guide is aimed at developers who want to understand, modify, or extend the Sharp MZ Series FPGA Emulator running on the MiSTer platform. It covers the complete architecture — from the VHDL/SystemVerilog design synthesised into the Cyclone V FPGA, through the HPS-FPGA bridge protocol, to the C++ support code running on the ARM Cortex-A9 Hard Processor System (HPS). By the end, you should be able to add a new machine model, extend an existing peripheral, or create entirely new features.

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:

  1. 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.

  2. 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) with CLKBUS(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

  1. Character display: Text characters fetched from VRAM, looked up in CGROM/CGRAM, rendered as 8×8 pixel glyphs
  2. Attribute overlay: Colour attributes from ARAM applied per character cell (foreground/background colours)
  3. Graphics overlay: GRAM pixels composited with character display using configurable blend modes (XOR, OR, AND, replace)
  4. VGA upscaling: LUT-based scaling from native resolution to 640×400 or 640×480 VGA output
  5. 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:

  1. Add a new CONFIG bit in mctrl.vhd for mode selection
  2. Create a new timing generator process in video.vhd with the required H/V timing
  3. Add the character/graphics rendering pipeline for the new mode
  4. Update the MUX that selects the active display pipeline based on CONFIG
  5. Add the mode to the C++ menu system (sharpmz_set_display_type())
  6. 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 Slightly faster loading
2 Practical loading
3 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

  1. Decide which controller branch to extend (mz80c or mz80b)
  2. Add the new model to mctrl.vhd CONFIG machine selection (bits 7:0)
  3. Create a new ROM bank pair in the SYSROM address map
  4. Define the Z80 memory map and I/O address decoding in the controller
  5. Add machine-specific peripheral instances if needed
  6. Update keymatrix.vhd if the keyboard matrix differs
  7. Update the C++ code: sharpmz_get_machine_group(), model names array, ROM filenames
  8. 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):

  1. Read CMT register → check SENSE and PLAY_READY flags
  2. 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
  3. If RECORD_READY flag set:
    • Call sharpmz_save_tape_from_cmt() to save recording to SD

For MZ-80B/2000 (APSS mode):

  1. Read CMT2 register → check EJECT, APSS, PLAY flags
  2. If EJECT → clear entire queue
  3. If APSS → call sharpmz_apss_search(direction) to rotate position
  4. 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

  1. User enables a ROM via the menu → sharpmz_set_*_rom_enabled(machine, 1)
  2. sharpmz_set_rom(machine, romType) is called
  3. ROM filename resolved from config.romData[machine][romType].romFileName
  4. sharpmz_send_file() opens the file and streams it via SPI to the FPGA at the correct memory bank address
  5. The dual-port SYSROM/CGROM is written via IOCTL Port B
  6. 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

  1. Define new state constants in the state enum (e.g., MENU_SHARPMZ_NEWFEATURE1 = 0xC0)
  2. Add the menu entry text in the parent state’s State1 handler using OsdWrite()
  3. Add the transition in the parent state’s State2 handler
  4. Implement the new State1 (render) and State2 (input) handlers
  5. 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 file
  • output_files/sharpmz.rbf — Raw Binary File (for MiSTer SD card)

Jenkins CI/CD:

The project has a Jenkins pipeline (SharpMZ-MiSTer-Build) that:

  1. Checks out both SharpMZ_MiSTer (FPGA) and Main_MiSTer (HPS binary)
  2. Compiles the FPGA bitstream using Quartus 17.1.1 in Docker
  3. Cross-compiles the MiSTer binary using ARM GCC 10.2 in Docker
  4. Packages and creates a Gitea release with FPGA (.rbf) and binary artifacts
  5. Triggered automatically by Gitea push webhooks

MiSTer main binary

Requirements:

  • ARM GCC cross-compiler: arm-none-linux-gnueabihf-gcc 10.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

  1. Copy sharpmz.rbf → MiSTer SD card as SharpMZ.rbf (or SharpMZ_YYYYMMDD.rbf in _Computer/)
  2. Copy bin/MiSTer → MiSTer SD card as /media/fat/MiSTer
  3. Copy ROM files → /media/fat/SharpMZ/ directory on SD card
  4. 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:

  1. VHDL: Implement rtl/mz80b/mb8876.vhd — the Western Digital WD1793-compatible FDD controller
  2. Memory: Add a new memory bank for disk image data (may require SDRAM for capacity)
  3. C++ menu: Add disk image browsing and loading (similar to tape queue but for .D88/.DSK files)
  4. Protocol: Add new SPI command or reuse IOCTL with a new memory bank address for disk data
  5. 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):

  1. Framework: The MiSTer sys/ directory includes SDRAM controller modules
  2. Bridge: Route SDRAM requests through sharpmz.sv using the framework’s SDRAM interface
  3. Arbitration: Implement a priority arbiter between the emulation (reads) and HPS (writes)

Part 17 — Development Workflow

  1. Quartus Prime 17.1.1 (or Lite Edition) for FPGA synthesis
  2. ModelSim / Questa for VHDL/Verilog simulation (included with Quartus)
  3. ARM GCC 10.2 cross-compiler for MiSTer binary
  4. MiSTer hardware (DE10-Nano + I/O board) for testing
  5. 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 _EN or CK.
  • C++: Functions prefixed sharpmz_. Config access via getter/setter pairs. SPI transactions bracketed by EnableFpga()/DisableFpga().
  • Register changes: Always update both mctrl.vhd (FPGA) and sharpmz.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 master branch 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

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