Skip to content

ND120_TOP

ND120 CPU, MEMORY MANAGEMENT and MEMORY.

Source: Verilog/ND120_TOP.v ยท Author: Ronny Hansen

Where it sits (Simulation): ND120_TOP

Used in: nd120_cmod_top (Cmod)

Contains: BUFG (vendor) x2 (only on Basys3, Cmod), MMCME2_BASE (vendor) (only on Basys3, Cmod), ND120_CORE, nd_storage_devices (only on Simulation), nds_mem_model (only on Simulation), sd_card_model (only on Simulation), SevenSegDebug (only on Basys3, Cmod)

Module hierarchy - All modules

ND120_TOP symbol

Schematic

Drawn from the Verilog: the yosys netlist of the Simulation (Verilator) build, where it is the top. Sub-modules are boxes (click the picture to open it full size; there every sub-module box links to its page, and every wire shows its Verilog name).

ND120_TOP schematic

Ports

Direction Width Name Description
input 1 sysclk System Clock
input 1 btn1 Button 1 - connected to sys_rst_n
input 1 btn2 Button 2
input 1 btn3 SW5 - (unused since 2026-07-06; CPU now fixed at clk_cpu ~16.67MHz)
input 1 uartRx UART Receive pin
output 1 uartTx UART Transmit pin
output [5:0] led 6-bit LED output (simulation)
output [15:0] led 16 LEDs on Basys3 (LD0-LD15)
output [6:0] seg 7-segment segments (a-g, active LOW)
output [3:0] an 7-segment digit anodes (active LOW)
input 1 DEBUGFLAG DEBUG FLAG
output [12:0] CSA_12_0 Microcode Address (for debugging)
output [15:0] XMIC_DBG_15_0 DEBUG: microsequencer address-advance probe (matches Tang capture: {SC6,s_mclk_n,MCLK_EN,regIW[12:0]})
output [15:0] DEBUG_FIDBO_15_0
output [4:0] DEBUG_CC_TERM_OUT
output [11:0] DEBUG_CYC_OUT
input 1 BREQ_n (active low) Bus Request
input 1 BINT10_n (active low) Bus Interrupt 10
input 1 BINT11_n (active low) Bus Interrupt 11
input 1 BINT12_n (active low) Bus Interrupt 12
input 1 BINT13_n (active low) Bus Interrupt 13
input 1 BINT15_n (active low) Bus Interrupt 15
input 1 POWSENSE_n (active low) Power Sense
input [23:0] BD_23_0_n_IN
output [23:0] BD_23_0_n_OUT
input 1 SEMRQ_n_IN
output 1 SEMRQ_n_OUT
input 1 BINPUT_n_IN
output 1 BINPUT_n_OUT
input 1 BDAP_n_IN
output 1 BDAP_n_OUT
input 1 BDRY_n_IN
output 1 BDRY_n_OUT
input 1 BAPR_n_IN
output 1 BAPR_n_OUT
output 1 BREF_n (active low)
output 1 BERROR_n (active low)
output 1 BINACK_n (active low)
output 1 BIOXE_n (active low)
output 1 BMEM_n (active low)
output 1 OUTGRANT_n (active low)
output 1 OUTIDENT_n (active low)
output 1 MCL
output 1 TAPE_BYTE_REQ pulse: fetch next tape byte
input 1 TAPE_BYTE_VALID pulse: TAPE_BYTE_DATA is the byte
input [7:0] TAPE_BYTE_DATA
output 1 TAPE_REWIND pulse: rewind the tape source
input 1 DMA_REQ pulse: one word transfer
input 1 DMA_WR 0 = memory read, 1 = memory write
input [23:0] DMA_ADDR physical memory address
input [15:0] DMA_WDATA
output [15:0] DMA_RDATA
output 1 DMA_ACK
output 1 DMA_ERR
output 1 DMA_BUSY
output 1 FDISK_REQ
output 1 FDISK_WR
output [15:0] FDISK_LSECT
output [1:0] FDISK_FORMAT
output [1:0] FDISK_DRIVE
output [10:0] FDISK_WORDCOUNT
input 1 FDISK_DONE
input 1 FDISK_ERR
input [3:0] FDISK_ERR_CODE
input [3:0] FDISK_MEDIA_FMT
input [9:0] FDBUF_ADDR
input [15:0] FDBUF_WDATA
input 1 FDBUF_WE
output [15:0] FDBUF_RDATA
output 1 SDISK_START
output 1 SDISK_REQ
output 1 SDISK_WR
output [15:0] SDISK_BLKADDR1
output [15:0] SDISK_BLKADDR2
output [2:0] SDISK_UNIT
output [10:0] SDISK_WORDCOUNT
input 1 SDISK_DONE
input 1 SDISK_ERR
input [3:0] SDISK_ERR_CODE
input [9:0] SDBUF_ADDR
input [15:0] SDBUF_WDATA
input 1 SDBUF_WE
output [15:0] SDBUF_RDATA
output 1 WDISK_START
output 1 WDISK_REQ
output 1 WDISK_WR
output [15:0] WDISK_BLKADDR1
output [15:0] WDISK_BLKADDR2
output [2:0] WDISK_UNIT
output [10:0] WDISK_WORDCOUNT
input 1 WDISK_DONE
input 1 WDISK_ERR
input [3:0] WDISK_ERR_CODE
input [9:0] WDBUF_ADDR
input [15:0] WDBUF_WDATA
input 1 WDBUF_WE
output [15:0] WDBUF_RDATA

Verilog source

Verilog/ND120_TOP.v on GitHub.

Show the Verilog of ND120_TOP (1345 lines)
// WaveDrom  documentation: https://wavedrom.com/tutorial.html
// WaveDrom Editor: https://wavedrom.com/editor.html
// TerosHDL:  see https://github.com/TerosTechnology/vscode-terosHDL
// Verible: https://github.com/chipsalliance/verible
// Verible plugin for VS Code: https://marketplace.visualstudio.com/items?itemName=CHIPSAlliance.verible
// Verible Lint: https://chipsalliance.github.io/verible/lint.html
// YoSys: https://github.com/YosysHQ/oss-cad-suite-build

/**************************************************************************
** ND120 CPU, MEMORY MANAGEMENT and MEMORY                               **
**                                                                       **
** TOP LEVEL FOR FPGA IMPLEMENTATION                                     **
**                                                                       **
** Last reviewed: 22-MAR-2025                                            **
** Ronny Hansen                                                          **
***************************************************************************/



// Define USE_TRANSPARENT_LATCHES for latch behavior in PALs.
// Active when: VERILATOR_SIM is set (sim build) AND FPGA_FF_MODE is NOT set.
// This allows sim builds to test FF mode with: make compile USE_LATCHES=0
`ifdef VERILATOR_SIM
  `ifndef FPGA_FF_MODE
    `define USE_TRANSPARENT_LATCHES
  `endif
`endif

//! @title ND120 CPU, MEMORY MANAGEMENT and MEMORY.
//! @author Ronny Hansen
//! TOP LEVEL FOR FPGA IMPLEMENTATION OF ND-3202D CPU BOARD

module ND120_TOP
(
    // Core FPGA pins (always present)
    input wire sysclk,    //! System Clock
    input wire btn1,      //! Button 1 - connected to sys_rst_n
    input wire btn2,      //! Button 2
`ifndef VERILATOR_SIM
    input wire btn3,      //! SW5 - (unused since 2026-07-06; CPU now fixed at clk_cpu ~16.67MHz)
`endif
    input wire uartRx,    //! UART Receive pin
    output wire uartTx,   //! UART Transmit pin
`ifdef VERILATOR_SIM
    output wire [5:0] led //! 6-bit LED output (simulation)
`else
    output wire [15:0] led, //! 16 LEDs on Basys3 (LD0-LD15)
    // 7-segment display (active LOW, Basys3)
    output wire [6:0] seg,  //! 7-segment segments (a-g, active LOW)
    output wire [3:0] an    //! 7-segment digit anodes (active LOW)
`endif

`ifdef VERILATOR_SIM
    // Simulation-only ports: full bus interface for device emulation
    ,
    input wire DEBUGFLAG,  // DEBUG FLAG
    output wire [12:0] CSA_12_0,  //! Microcode Address (for debugging)
    output wire [15:0] XMIC_DBG_15_0, //! DEBUG: microsequencer address-advance probe (matches Tang capture: {SC6,s_mclk_n,MCLK_EN,regIW[12:0]})
    //! DEBUG: FIDBO, the internal data bus. Brought out as a PORT (01-SEP-2026)
    //! because the wire alone is driven and never read, so Verilator optimises
    //! it away and the C harness cannot reach it. Needed to read the PIE value
    //! at microcode 001011 - the value the 001013 branch is decided on, and
    //! where the MiSTer board first diverges from this boot.
    output wire [15:0] DEBUG_FIDBO_15_0,
    //! DEBUG: {TERM_n, CC3_n, CC2_n, CC1_n, CC0_n} - the condition and
    //! terminate lines the microsequencer branches on. Brought out for the
    //! same reason as FIDBO above: the MiSTer board reaches NOTI2 and fails to
    //! leave it while the datapath values agree, which points at the CONDITION
    //! rather than the data.
    output wire [4:0]  DEBUG_CC_TERM_OUT,
    //! DEBUG: cycle-controller terminate-plane inputs (CYC_36.XCYC_DBG_7_0).
    output wire [11:0] DEBUG_CYC_OUT,

    // C-PLUG bus signals
    input wire BREQ_n,      // Bus Request
    input wire BINT10_n,    // Bus Interrupt 10
    input wire BINT11_n,    // Bus Interrupt 11
    input wire BINT12_n,    // Bus Interrupt 12
    input wire BINT13_n,    // Bus Interrupt 13
    input wire BINT15_n,    // Bus Interrupt 15
    input wire POWSENSE_n,  // Power Sense

    // BUS data in/out
    input wire [23:0] BD_23_0_n_IN,
    output wire [23:0] BD_23_0_n_OUT,

    // Bidirectional bus signals
    input  SEMRQ_n_IN,
    output SEMRQ_n_OUT,
    input  BINPUT_n_IN,
    output BINPUT_n_OUT,
    input  BDAP_n_IN,
    output BDAP_n_OUT,
    input  BDRY_n_IN,
    output BDRY_n_OUT,
    input  BAPR_n_IN,
    output BAPR_n_OUT,

    // Bus control outputs
    output wire BREF_n,
    output wire BERROR_n,
    output wire BINACK_n,
    output wire BIOXE_n,
    output wire BMEM_n,
    output wire OUTGRANT_n,
    output wire OUTIDENT_n,
    output wire MCL

`ifdef ND120_VERILOG_DEVICES
    // Byte source for the Verilog papertape device (ND-BUS-DEVICES/TAPE-400):
    // the sim harness serves a BPUN file; hardware serves an SD-FAT stream.
    ,
    output wire       TAPE_BYTE_REQ,   // pulse: fetch next tape byte
    input  wire       TAPE_BYTE_VALID, // pulse: TAPE_BYTE_DATA is the byte
    input  wire [7:0] TAPE_BYTE_DATA,
    output wire       TAPE_REWIND,    // pulse: rewind the tape source

    // DMA test client (full-RTL DMA gate: ND_DMA_MASTER against the
    // real bus arbiter and RAM; the sim harness drives these)
    input  wire        DMA_REQ,        // pulse: one word transfer
    input  wire        DMA_WR,         // 0 = memory read, 1 = memory write
    input  wire [23:0] DMA_ADDR,       // physical memory address
    input  wire [15:0] DMA_WDATA,
    output wire [15:0] DMA_RDATA,
    output wire        DMA_ACK,
    output wire        DMA_ERR,
    output wire        DMA_BUSY,

    // Floppy disk-image backend for the DMA-flavor controller
    // (sim harness serves FLOPPY.IMG; hardware later serves the
    // SDRAM-cached image). Position = logical sector * sector size.
    output wire        FDISK_REQ,
    output wire        FDISK_WR,
    output wire [15:0] FDISK_LSECT,
    output wire [1:0]  FDISK_FORMAT,
    output wire [1:0]  FDISK_DRIVE,
    output wire [10:0] FDISK_WORDCOUNT,
    input  wire        FDISK_DONE,
    input  wire        FDISK_ERR,
    input  wire [ 3:0] FDISK_ERR_CODE,
    // media format from the image size (deviceFloppyDMA.c READ FORMAT):
    // {doubleDensity, doubleSided, bytesPerSector[1:0]}; 4'b0000 = 8-inch
    // 315392-byte image, 4'b1111 = 5.25" 1.2MB image (drive default)
    input  wire [3:0]  FDISK_MEDIA_FMT,
    input  wire [9:0]  FDBUF_ADDR,
    input  wire [15:0] FDBUF_WDATA,
    input  wire        FDBUF_WE,
    output wire [15:0] FDBUF_RDATA,

    // SMD disk backend (sim harness serves the disk-0 image; the
    // backend owns the block-address-to-image mapping)
    output wire        SDISK_START,
    output wire        SDISK_REQ,
    output wire        SDISK_WR,
    output wire [15:0] SDISK_BLKADDR1,
    output wire [15:0] SDISK_BLKADDR2,
    output wire [2:0]  SDISK_UNIT,
    output wire [10:0] SDISK_WORDCOUNT,
    input  wire        SDISK_DONE,
    input  wire        SDISK_ERR,
    input  wire [ 3:0] SDISK_ERR_CODE,
    input  wire [9:0]  SDBUF_ADDR,
    input  wire [15:0] SDBUF_WDATA,
    input  wire        SDBUF_WE,
    output wire [15:0] SDBUF_RDATA,

    // Winchester disk backend (ST506/8 inch at 500). Same shape as the SMD
    // seam above; the card reuses nd_storage_disc_adapter with Winchester
    // geometry. Images are WDn.IMG, never SMDn.IMG.
    output wire        WDISK_START,
    output wire        WDISK_REQ,
    output wire        WDISK_WR,
    output wire [15:0] WDISK_BLKADDR1,
    output wire [15:0] WDISK_BLKADDR2,
    output wire [2:0]  WDISK_UNIT,
    output wire [10:0] WDISK_WORDCOUNT,
    input  wire        WDISK_DONE,
    input  wire        WDISK_ERR,
    input  wire [ 3:0] WDISK_ERR_CODE,
    input  wire [9:0]  WDBUF_ADDR,
    input  wire [15:0] WDBUF_WDATA,
    input  wire        WDBUF_WE,
    output wire [15:0] WDBUF_RDATA
`endif
`endif
);


  /**********************************************
  *    ND-100 BUS Wires                         *
  ***********************************************/

`ifndef VERILATOR_SIM
  // FPGA mode: bus signals tied to safe defaults (no external bus)
  wire DEBUGFLAG = 1'b0;
  wire [12:0] CSA_12_0;

  wire BREQ_n = 1'b1;
  wire BINT10_n = 1'b1;
  wire BINT11_n = 1'b1;
  wire BINT12_n = 1'b1;
  wire BINT13_n = 1'b1;
  wire BINT15_n = 1'b1;
  wire POWSENSE_n = 1'b1;

  wire [23:0] BD_23_0_n_IN = 24'hFFFFFF;  // Pulled high (inactive)
  wire [23:0] BD_23_0_n_OUT;

  wire SEMRQ_n_IN = 1'b1;
  wire SEMRQ_n_OUT;
  wire BINPUT_n_IN = 1'b1;
  wire BINPUT_n_OUT;
  wire BDAP_n_IN = 1'b1;
  wire BDAP_n_OUT;
  wire BDRY_n_IN = 1'b1;
  wire BDRY_n_OUT;
  wire BAPR_n_IN = 1'b1;
  wire BAPR_n_OUT;

  wire BREF_n;
  wire BERROR_n;
  wire BINACK_n;
  wire BIOXE_n;
  wire BMEM_n;
  wire OUTGRANT_n;
  wire OUTIDENT_n;
  wire MCL;
`endif

  // NOTE: installation_number, the s_high/s_low helpers, oc_select,
  // SEL_TESTMUX and the baud-rate switch moved INTO ND120_CORE.v -- they are
  // CPU-board constants, not board-level I/O.
  //
  // installation_number is no longer a constant at all: ND120_CORE now holds a
  // real 16-byte BACK-WIRING PROM (BACKWIRING_PROM, addressed by PIL 3:0) that
  // SINTRAN reads with VERSN / IDBS,INR=35. Its contents are set at BUILD time
  // with -DND120_SYSNO / -DND120_HWINFO2 / -DND120_NLEGU, e.g.
  //   make -C runSim compile EXTRA_VDEFINES="-DND120_SYSNO=16'd42"
  // Defaults and the "not present" sentinels:
  // Shared/support/nd120_backwiring_defaults.vh;
  // full mechanism: docs/backwiring-prom-installation-number.md.

  wire sys_rst_n;
`ifndef VERILATOR_SIM
  wire mmcm_locked;
`endif

  // input signals
  wire clk1;  //! Clock Signal 1
  //wire clk2;  //! Clock Signal 2

  // output wire from CPU

  wire [6:0] s_cpu_led;  // 7 bit LED signals (ND3202D outputs 7 bits)
  //   0=CPU RED
  //   1=CPU GREEN
  //   2=LED4_RED_PARITY_ERROR
  //   3=LED_CPU_GRANT_INDICATOR
  //   4=LED_BUS_GRANT_INDICATOR
  //   5=LED1 from MMU
  //   6=LED5_RED_DISABLE_PARITY

  // Debug signals -- mark_debug lets Vivado ILA probe these in Hardware Manager.
  // After synthesis, in Vivado: Open Synthesized Design -> Set Up Debug -> add these nets.
  // Then re-run Implementation and Generate Bitstream.
  (* mark_debug = "true" *) wire s_run;
  (* mark_debug = "true", DONT_TOUCH = "true" *) wire [12:0] s_debug_csa;
  (* mark_debug = "true" *) wire s_debug_uartTx;
  (* mark_debug = "true" *) wire s_debug_uartRx;
  (* mark_debug = "true" *) wire [6:0] s_debug_cpu_led;

  assign s_debug_csa = CSA_12_0;
  assign s_debug_uartTx = uartTx;
  assign s_debug_uartRx = uartRx;
  assign s_debug_cpu_led = s_cpu_led;

  // MAC (Memory Access Controller) address debug wires
  (* mark_debug = "true" *) wire [13:0] s_debug_la_23_10;  // LA 23:10
  (* mark_debug = "true" *) wire [9:0]  s_debug_ca_9_0;    // CA 9:0

  // Cycle state machine debug
  (* mark_debug = "true" *) wire [4:0] s_debug_cc_term;    // {TERM_n, CC3_n, CC2_n, CC1_n, CC0_n}
  (* mark_debug = "true" *) wire       s_debug_mclk;       // Memory clock
  (* mark_debug = "true" *) wire       s_debug_lcs_n;      // LCS_n: 0=loading, 1=loaded
  (* mark_debug = "true" *) wire       s_debug_fetch;
  wire s_debug_map_n /* verilator public_flat_rd */;  // one falling edge per macro instruction - MIPS validation probe
  wire s_debug_cfetch_dbg /* verilator public_flat_rd */;  // CGA CFETCH - the MIPS event, validate vs TRACE_VERIFY
  (* mark_debug = "true" *) wire       s_debug_mr_n;       // Master Reset
  (* mark_debug = "true" *) wire       s_debug_clear_n;    // Clear
  (* mark_debug = "true" *) wire       s_debug_refrq_n;    // Refresh Request
  (* mark_debug = "true" *) wire       s_debug_intrq_n;    // Interrupt Request
  (* mark_debug = "true" *) wire       s_debug_powfail_n;  // Power Fail
  (* mark_debug = "true", DONT_TOUCH = "true" *) wire [15:0] s_debug_fidbo;  // FIDBO internal data bus
  assign DEBUG_FIDBO_15_0 = s_debug_fidbo;
  assign DEBUG_CC_TERM_OUT = s_debug_cc_term;
  wire [11:0] s_debug_cyc;
  assign DEBUG_CYC_OUT = s_debug_cyc;

  // ALU debug probes: mark_debug applied directly in submodule source files:
  //   CGA_ALU.v:  s_q_15_0 (Q reg), s_f_15_0 (F result)
  //   CGA.v:      s_zf (zero flag), s_cry (carry), s_cond (condition)

  // NOTE: s_test_4_0 / s_dp_5_1_n / s_tp1_intrq_n / s_csbits moved INTO
  // ND120_CORE.v (they are CPU-board debug nets, not board I/O). Verilator
  // hierarchical readers now reach s_csbits at
  // ND120_TOP__DOT__CORE__DOT__s_csbits.

  reg [32:0] clockTicks;

`ifdef VERILATOR_SIM
  // Simulation: testbench controls btn1 directly (0 for 100 cycles, then 1)
  assign sys_rst_n = btn1;
`else
  // FPGA: Power-on reset holds sys_rst_n LOW for 256 cycles after configuration
  // or after btn1 (SW0) goes low. Ensures a clean reset-release transition
  // every time the switch is toggled, re-triggering the full CPU boot sequence.
  //
  // Clocked on clk_cpu (the CPU domain), NOT sysclk: sys_rst_n fans out to
  // hundreds of CPU-register reset/enable pins. On sysclk that made 462
  // sys_clk->clk_cpu crossings that could not meet the tight related-clock
  // window. On clk_cpu they are intra-domain (60 ns) and meet easily. clk_cpu
  // only runs once the MMCM locks, so before lock the counter is frozen at 0 and
  // sys_rst_n = por_done & mmcm_locked holds reset asserted anyway. 256 clk_cpu
  // cycles ~= 15 us reset pulse.
  reg [7:0] por_count = 8'd0;
  reg       por_done  = 1'b0;
  always @(posedge clk_cpu) begin
    if (!btn1) begin
      // SW0 down: reset the POR counter
      por_count <= 8'd0;
      por_done  <= 1'b0;
    end else if (!por_done) begin
      if (por_count == 8'hFF)
        por_done <= 1'b1;
      else
        por_count <= por_count + 1'b1;
    end
  end
  assign sys_rst_n = por_done & mmcm_locked;
`endif

`ifdef VERILATOR_SIM
  assign clk1 = sysclk;  // Simulation: always full speed
`else
  // FPGA: the whole ND-120 CPU + bus domain runs on ONE clock, clk_cpu, at
  // ~16.67 MHz (100 MHz / 6 = 60 ns period). Rationale (2026-07-06 timing study):
  // the microengine's deepest path -- reading the microcode word out of the WCS
  // control-store BRAM and propagating it through the decode/next-address/FSM
  // logic -- is ~49 ns (76 logic levels). It CANNOT close at 100 MHz (10 ns) or
  // even 39 MHz (25.6 ns); those paths legitimately span a whole microcycle on
  // the real machine. 60 ns closes it with margin and no risky multicycle
  // constraints. clk_cpu also feeds CLOCK_1/CLOCK_2 (the OSC/bus inputs) so the
  // cycle controller and bus arbiter share the same domain -- no internal CDC.
  // sysclk (100 MHz pin) still clocks the POR, 7-seg, heartbeat and ILA only.
  //
  // NOTE: the original SW5 runtime 100/12.5 MHz mux is removed -- 100 MHz never
  // closed timing, so a runtime-selectable fast mode was dead. Push clk_cpu
  // faster (or add validated multicycle paths for a 39 MHz bus) as a follow-up.

  wire clk_cpu_pre, clkfb_out, clkfb_in;
  wire clk_cpu;

`ifdef TARGET_CMOD_A7
  // Cmod A7: 12 MHz crystal. VCO = 12 x 63 = 756 MHz (in the 600-1200 MHz
  // MMCM range); clk_cpu = 756 / 28 = 27.000 MHz EXACTLY - the same CPU
  // speed as the Tang Nano 20K full-speed build, so BOARD_CLK_FREQ=27000000
  // and every derived count matches. Fallback if 27 MHz does not close
  // timing: pass -verilog_define ND120_CMOD_MMCM_DIV=56.0 for 13.5 MHz
  // (or 42.0 for 18 MHz) - same VCO, one divider change.
  `ifndef ND120_CMOD_MMCM_DIV
    `define ND120_CMOD_MMCM_DIV 28.0
  `endif
  MMCME2_BASE #(
    .BANDWIDTH        ("OPTIMIZED"),
    .CLKFBOUT_MULT_F  (63.0),    // VCO = 12 * 63 = 756 MHz
    .CLKIN1_PERIOD    (83.333),  // 12 MHz input
    .CLKOUT0_DIVIDE_F (`ND120_CMOD_MMCM_DIV),  // 756 / 28 = 27 MHz (CPU/bus clock)
    .DIVCLK_DIVIDE    (1),
    .STARTUP_WAIT     ("FALSE")
  ) mmcm_cpu_clk (
`elsif TARGET_NEXYS4DDR
  // Nexys 4 DDR / Nexys A7-100T: 100 MHz oscillator, same VCO as the Basys3
  // (1000 MHz), but the divider is a build flag so the bigger -100T part can
  // be pushed past the Basys3-proven 16.667 MHz. ND120_N4DDR_MMCM_DIV and
  // BOARD_CLK_FREQ are set together by fpga/nexys4ddr/build.tcl's clk=<MHz>
  // argument; changing one without the other breaks every derived count
  // (UART baud, RTC tick, watchdogs).
  //   60.0 = 16.667 MHz (default, Basys3-proven)   30.0 = 33.333 MHz
  //   50.0 = 20 MHz      40.0 = 25 MHz             20.0 = 50 MHz
  //   37.0 = 27.027 MHz (Tang full speed)          10.0 = 100 MHz
  `ifndef ND120_N4DDR_MMCM_DIV
    `define ND120_N4DDR_MMCM_DIV 60.0
  `endif
  MMCME2_BASE #(
    .BANDWIDTH        ("OPTIMIZED"),
    .CLKFBOUT_MULT_F  (10.0),   // VCO = 100 * 10 = 1000 MHz
    .CLKIN1_PERIOD    (10.0),   // 100 MHz input
    .CLKOUT0_DIVIDE_F (`ND120_N4DDR_MMCM_DIV),  // CPU/bus clock
    .DIVCLK_DIVIDE    (1),
    .STARTUP_WAIT     ("FALSE")
  ) mmcm_cpu_clk (
`else
  MMCME2_BASE #(
    .BANDWIDTH        ("OPTIMIZED"),
    .CLKFBOUT_MULT_F  (10.0),   // VCO = 100 * 10 = 1000 MHz
    .CLKIN1_PERIOD    (10.0),   // 100 MHz input
    .CLKOUT0_DIVIDE_F (60.0),   // CLKOUT0 = 1000 / 60 = 16.667 MHz (CPU/bus clock)
    .DIVCLK_DIVIDE    (1),
    .STARTUP_WAIT     ("FALSE")
  ) mmcm_cpu_clk (
`endif
    .CLKIN1   (sysclk),
    .CLKFBIN  (clkfb_in),
    .CLKFBOUT (clkfb_out),
    .CLKOUT0  (clk_cpu_pre),
    .LOCKED   (mmcm_locked),
    .PWRDWN   (1'b0),
    .RST      (1'b0)
  );
  BUFG bufg_fb  (.I(clkfb_out),   .O(clkfb_in));
  BUFG bufg_cpu (.I(clk_cpu_pre), .O(clk_cpu));

  assign clk1 = clk_cpu;  // CLOCK_1/CLOCK_2 (OSC/bus) run at CPU speed
`endif
  //assign clk2 = sysclk;  // XTAL2 = 35 MHZ (for slow operations?)

`ifdef VERILATOR_SIM
  // Simulation: original 6-bit LED mapping (active low, matching Run120.cpp)
  assign led[1:0] = ~s_cpu_led[1:0];  // 0=RED, 1=GREEN
  assign led[2] = !s_run;
  assign led[3] = !s_cpu_led[3];      // CPU GRANT INDICATOR
  assign led[4] = !s_cpu_led[4];      // BUS GRANT INDICATOR
  assign led[5] = !s_cpu_led[5];      // LED1 from MMU
`else
  // FPGA: 16-bit LED mapping for Basys3 (active HIGH)
  //
  // LD0 = led[0] = CPU RED LED        (ON = error/halt)
  // LD1 = led[1] = CPU GREEN LED      (ON = running)
  // LD2 = led[2] = RUN indicator      (ON = CPU NOT running/OPCOM, OFF = running)
  // LD3 = led[3] = sys_rst_n state    (ON = reset released, OFF = in reset)
  // LD4 = led[4] = UART TX activity   (blinks when transmitting)
  // LD5 = led[5] = Heartbeat          (blinks ~1.5Hz if clock running)
  //                To restore LED1 from MMU: assign led[5] = !s_cpu_led[5];

  // RIGHT SIDE: CPU status (LD0-LD5)
  // s_cpu_led[0] = s_emcl_n  (negative-logic from CHIP_28A_IOC in IO_REG_41)
  // s_cpu_led[1] = s_led3_green_n (negative-logic from CHIP_28A_IOC)
  // Both are active-low chip outputs; invert for active-high Basys3 LEDs.
  // (The Verilator branch above already does this with ~s_cpu_led[1:0].)
  assign led[0]  = ~s_cpu_led[0];      // LD0:  CPU RED   (ON when s_emcl_n=0       = master clear active)
  assign led[1]  = ~s_cpu_led[1];      // LD1:  CPU GREEN (ON when s_led3_green_n=0 = init complete)
  assign led[2]  = ~s_run;             // LD2:  ON when CPU running
  assign led[3]  = sys_rst_n;          // LD3:  ON when reset released
  assign led[4]  = ~uartTx;            // LD4:  UART TX activity
  assign led[5]  = clockTicks[26];     // LD5:  Heartbeat ~1.5Hz
  assign led[6]  = s_debug_mclk;       // LD6:  Memory clock
  assign led[7]  = ~s_debug_lcs_n;     // LD7:  LCS (ON=microcode loaded)
  assign led[8]  = s_debug_mr_n;       // LD8:  MR_n (ON=not in reset)
  assign led[9]  = 1'b0;               // LD9:  (spare)
  assign led[10] = 1'b0;               // LD10: (spare)

  // LEFT SIDE: Cycle state machine (LD11-LD15)
  assign led[11] = ~s_debug_cc_term[0]; // LD11: CC0  (inverted: ON=active)
  assign led[12] = ~s_debug_cc_term[1]; // LD12: CC1
  assign led[13] = ~s_debug_cc_term[2]; // LD13: CC2
  assign led[14] = ~s_debug_cc_term[3]; // LD14: CC3
  assign led[15] = ~s_debug_cc_term[4]; // LD15: TERM (leftmost)
`endif

  // Free-running clock counter (no reset needed)
  always @(posedge sysclk)
  begin
    clockTicks <= clockTicks + 1;
  end

`ifndef VERILATOR_SIM
  // 7-segment display: shows CPU addresses in hex (FPGA only).
  // SW1 (btn2) selects what to display:
  //   SW1 OFF (DOWN): MIC address = CSA_12_0 (microcode address, 13 bits)
  //   SW1 ON  (UP):   MAC address = LA_23_10 (memory logical address, 14 bits)
  // If display shows changing values, the CPU is executing.
  // If stuck at 0000, the CPU is not running.
  wire [15:0] seg_display_value = btn2
      ? {2'b0, s_debug_la_23_10}
      : {3'b0, CSA_12_0};

  SevenSegDebug SEVEN_SEG (
      .clk(sysclk),
      .value(seg_display_value),
      .seg(seg),
      .an(an)
  );
`endif

  /**********************************************
  *  The board-independent ND-120 core          *
  *  (ND3202D CPU board + the ND-BUS device     *
  *  chain). Everything board-specific -- POR,  *
  *  MMCM, LED map, 7-seg, heartbeat, and the   *
  *  FPGA bus tie-offs above -- stays here.     *
  *                                             *
  *  The device chain is populated exactly as   *
  *  the old `ifdef ND120_VERILOG_DEVICES did:  *
  *  present for the sim harness, absent        *
  *  otherwise.                                 *
  ***********************************************/

`ifdef ND120_VERILOG_DEVICES
  localparam CORE_INCLUDE_TAPE   = 1;
  localparam CORE_INCLUDE_FLOPPY = 1;
  localparam CORE_INCLUDE_SMD    = 1;
  // OPT-IN: IOX 500-507 is the CDC cartridge disc's block, so the Winchester
  // must not appear unless asked for. Build with -DND120_INCLUDE_WD.
`ifdef ND120_INCLUDE_WD
  localparam CORE_INCLUDE_WD     = 1;
`else
  localparam CORE_INCLUDE_WD     = 0;
`endif

  /*--------------------------------------------------------------------
  *  Tape byte source: SD-FAT stack (ND120_SD_STORAGE) or the C harness.
  *
  *  SD_STORAGE=1 (the runSim default) feeds ND_TAPE_400 from the REAL
  *  Verilog SD-FAT stack reading a simulated SD card - the same RTL that
  *  runs on Tang - instead of the C file server in simDevices/NDBus.cpp.
  *  The card holds BOOT.BPUN; build it with SD-FAT/sim/make_boot_card.sh.
  *
  *  SIM-ONLY. sd_card_model / nds_mem_model are testbench models and must
  *  never reach an FPGA build, hence the VERILATOR_SIM guard: on hardware
  *  the byte source is nd_storage_devices on the BOARD, wired to a real
  *  card and to the SDRAM device port (see docs/nd-storage-design.md).
  *
  *  The TAPE_BYTE_* ports stay in place either way so the C harness still
  *  compiles; under SD_STORAGE its VALID/DATA inputs are simply ignored
  *  (NDBus.cpp also stops serving tape - same define).
  *-------------------------------------------------------------------*/
  wire       s_tape_byte_valid;
  wire [7:0] s_tape_byte_data;

  /*-------------------------------------------------------------------*
   *  Winchester backend seam - WHO ANSWERS WDISK_*                      *
   *                                                                     *
   *  The core's WDISK_ and WDBUF_ forward signals always reach the     *
   *  top-level ports (the C model in simDevices/NDBus.cpp reads them).  *
   *  Only the RETURN path is switched, because only one backend may     *
   *  drive it:                                                          *
   *                                                                     *
   *    default          the C file server, process_verilog_wd(), which  *
   *                     opens the host file named by env ND120_WD_IMG   *
   *                     and answers a block read with an fread. The     *
   *                     SD-FAT stack is not in the path at all.         *
   *                                                                     *
   *    ND120_SD_WD      nd_storage_devices client 6 - the REAL RTL:     *
   *                     sd_card_ctrl -> sd_file_reader (FAT mount +     *
   *                     root scan) -> nd_storage_engine -> the Phase-4  *
   *                     block CACHE -> nd_storage_disc_adapter. WD0.IMG *
   *                     is a file on a simulated FAT card, exactly as   *
   *                     it is on the Tang's real card.                  *
   *                                                                     *
   *  ND120_SD_WD requires ND120_SD_STORAGE (the card/SDRAM models) and  *
   *  ND120_INCLUDE_WD (the Winchester card itself); both are checked    *
   *  below. Its whole purpose is to put the SD/FAT/cache path under a   *
   *  full SINTRAN boot, which no testbench does: the storage benches    *
   *  read a 16 KB stand-in WD0.IMG, and a real image is ~75 MB.         *
   *-------------------------------------------------------------------*/
  wire        s_wdisk_done;
  wire        s_wdisk_err;
  wire [ 3:0] s_wdisk_err_code;
  wire [ 9:0] s_wdbuf_addr;
  wire [15:0] s_wdbuf_wdata;
  wire        s_wdbuf_we;

`ifdef ND120_SD_WD
`ifndef ND120_SD_STORAGE
  initial begin
    $display("FATAL: ND120_SD_WD needs ND120_SD_STORAGE (the SD card model)");
    $finish;
  end
`endif
`ifndef ND120_INCLUDE_WD
  initial begin
    $display("FATAL: ND120_SD_WD needs ND120_INCLUDE_WD (the Winchester card)");
    $finish;
  end
`endif
`else
  // C file server answers; nd_storage is either absent or tape-only.
  assign s_wdisk_done     = WDISK_DONE;
  assign s_wdisk_err      = WDISK_ERR;
  assign s_wdisk_err_code = WDISK_ERR_CODE;
  assign s_wdbuf_addr     = WDBUF_ADDR;
  assign s_wdbuf_wdata    = WDBUF_WDATA;
  assign s_wdbuf_we       = WDBUF_WE;
`endif

`ifdef ND120_SD_STORAGE
`ifndef VERILATOR_SIM
  // ND120_SD_STORAGE pulls in simulation-only card/memory models.
  initial begin
    $display("FATAL: ND120_SD_STORAGE is a Verilator-sim-only path");
    $finish;
  end
`endif
  // clk_stor: the SD/SDRAM domain. In sim it shares the CPU clock (clk1);
  // nd_storage's CDC (nds_sync) handles the equal-clock case fine. Skewing
  // it to stress the CDC the way SD-FAT/sim does (27.03 vs 23.04 MHz) is a
  // follow-up, not a correctness requirement here.
  wire s_stor_clk   = clk1;
  wire s_stor_rst_n = sys_rst_n;

  wire s_sd_clk_o, s_sd_cmd_o, s_sd_cmd_oe, s_sd_dat0_o, s_sd_dat0_oe;
  wire s_card_cmd_o, s_card_cmd_oe, s_card_dat0_o, s_card_dat0_oe;

  // SD lines resolved by MUX - no tristates (the card model is z-free):
  // host output-enable wins, then the card, then the bus pullup (1).
  wire s_sd_cmd  = s_sd_cmd_oe  ? s_sd_cmd_o  : (s_card_cmd_oe  ? s_card_cmd_o  : 1'b1);
  wire s_sd_dat0 = s_sd_dat0_oe ? s_sd_dat0_o : (s_card_dat0_oe ? s_card_dat0_o : 1'b1);

  wire        s_stor_mem_start, s_stor_mem_we, s_stor_mem_busy, s_stor_mem_done;
  wire [19:0] s_stor_mem_addr;
  wire [31:0] s_stor_mem_wdata, s_stor_mem_rdata;
  /* verilator lint_off UNUSEDSIGNAL */
  wire [1:0]  s_sd_status;
  /* verilator lint_on UNUSEDSIGNAL */

  nd_storage_devices #(
      .SIMULATE(1),  // short SD init in sim
`ifdef ND120_SD_WD
      .INCLUDE_WD(1)  // client 6 = WD0.IMG, CACHED (CACHE_MASK bit 6)
`else
      .INCLUDE_WD(0)
`endif
  ) TAPE_SDFAT_SOURCE (
      .clk_stor  (s_stor_clk),
      .rst_stor_n(s_stor_rst_n),
      .clk_cpu   (clk1),
      .rst_cpu_n (sys_rst_n),

      // byte source port -> ND_TAPE_400 inside the core
      .byte_req     (TAPE_BYTE_REQ),
      .byte_valid   (s_tape_byte_valid),
      .byte_data    (s_tape_byte_data),
      .source_rewind(TAPE_REWIND),

      .sd_clk_o  (s_sd_clk_o),
      .sd_cmd_i  (s_sd_cmd),
      .sd_cmd_o  (s_sd_cmd_o),
      .sd_cmd_oe (s_sd_cmd_oe),
      .sd_dat0_i (s_sd_dat0),
      .sd_dat0_o (s_sd_dat0_o),
      .sd_dat0_oe(s_sd_dat0_oe),

      .mem_start(s_stor_mem_start),
      .mem_we   (s_stor_mem_we),
      .mem_addr (s_stor_mem_addr),
      .mem_wdata(s_stor_mem_wdata),
      .mem_rdata(s_stor_mem_rdata),
      .mem_busy (s_stor_mem_busy),
      .mem_done (s_stor_mem_done),

`ifdef ND120_SD_WD
      // Winchester client 6. Forward signals are read straight off the
      // top-level ports the core already drives; the return path lands on
      // the s_wdisk_*/s_wdbuf_* seam instead of on the C model's.
      .WDISK_START    (WDISK_START),
      .WDISK_REQ      (WDISK_REQ),
      .WDISK_WR       (WDISK_WR),
      .WDISK_BLKADDR1 (WDISK_BLKADDR1),
      .WDISK_BLKADDR2 (WDISK_BLKADDR2),
      .WDISK_UNIT     (WDISK_UNIT),
      .WDISK_WORDCOUNT(WDISK_WORDCOUNT),
      .WDISK_DONE     (s_wdisk_done),
      .WDISK_ERR      (s_wdisk_err),
      .WDISK_ERR_CODE (s_wdisk_err_code),
      .WDBUF_ADDR     (s_wdbuf_addr),
      .WDBUF_WDATA    (s_wdbuf_wdata),
      .WDBUF_WE       (s_wdbuf_we),
      .WDBUF_RDATA    (WDBUF_RDATA),

`endif
      .sd_status(s_sd_status)
  );

  // The simulated card. IMAGE is relative to the sim CWD (runSim/).
  sd_card_model #(
      .IMAGE    (`ND120_SD_CARD_IMG),
      // sd_card_model slurps the WHOLE image into a byte array at time 0, so
      // this is both the card's capacity and its cost in host RAM. 8 MB is
      // ample for a BOOT.BPUN card; a card carrying a real ~75 MB WD0.IMG is
      // not, and every sector past the end reads back as 0xFF - a mount that
      // succeeds followed by garbage. Override with ND120_SD_CARD_BYTES.
`ifdef ND120_SD_CARD_BYTES
      .MAX_BYTES(`ND120_SD_CARD_BYTES)
`else
      .MAX_BYTES(8 * 1024 * 1024)
`endif
  ) SD_CARD (
      .sd_clk   (s_sd_clk_o),
      .sd_cmd_i (s_sd_cmd),  .sd_cmd_o (s_card_cmd_o),  .sd_cmd_oe (s_card_cmd_oe),
      .sd_dat0_i(s_sd_dat0), .sd_dat0_o(s_card_dat0_o), .sd_dat0_oe(s_card_dat0_oe),
      .sd_dat1_i(1'b1), .sd_dat1_o(), .sd_dat1_oe(),
      .sd_dat2_i(1'b1), .sd_dat2_o(), .sd_dat2_oe(),
      .sd_dat3_i(1'b1), .sd_dat3_o(), .sd_dat3_oe()
  );

  // Stands in for the SDRAM device region that nd_storage caches into.
  // runSim has no SDRAM (MEM_RAM_49_SIM is the main RAM); nd_storage's
  // mem_* is a generic 32-bit word port, so the behavioral model is a
  // faithful backend - same contract, randomized 4..40-cycle latency.
  nds_mem_model MEM_STOR (
      .clk  (s_stor_clk),
      .rst_n(s_stor_rst_n),
      .start(s_stor_mem_start),
      .we   (s_stor_mem_we),
      .addr (s_stor_mem_addr),
      .wdata(s_stor_mem_wdata),
      .rdata(s_stor_mem_rdata),
      .busy (s_stor_mem_busy),
      .done (s_stor_mem_done)
  );

`else
  // C harness serves the tape bytes through the top-level ports.
  assign s_tape_byte_valid = TAPE_BYTE_VALID;
  assign s_tape_byte_data  = TAPE_BYTE_DATA;
`endif

`else
  localparam CORE_INCLUDE_TAPE   = 0;
  localparam CORE_INCLUDE_FLOPPY = 0;
  localparam CORE_INCLUDE_SMD    = 0;
  localparam CORE_INCLUDE_WD     = 0;

  // No device chain: the storage seam is unused. Tie the core's source
  // inputs inactive and leave its source outputs unread.
  wire        TAPE_BYTE_REQ;
  wire        TAPE_BYTE_VALID = 1'b0;
  wire [7:0]  TAPE_BYTE_DATA  = 8'd0;
  wire        TAPE_REWIND;
  wire        s_tape_byte_valid = TAPE_BYTE_VALID;
  wire [7:0]  s_tape_byte_data  = TAPE_BYTE_DATA;

  wire        DMA_REQ   = 1'b0;
  wire        DMA_WR    = 1'b0;
  wire [23:0] DMA_ADDR  = 24'd0;
  wire [15:0] DMA_WDATA = 16'd0;
  wire [15:0] DMA_RDATA;
  wire        DMA_ACK;
  wire        DMA_ERR;
  wire        DMA_BUSY;

  wire        FDISK_REQ;
  wire        FDISK_WR;
  wire [15:0] FDISK_LSECT;
  wire [1:0]  FDISK_FORMAT;
  wire [1:0]  FDISK_DRIVE;
  wire [10:0] FDISK_WORDCOUNT;
  wire        FDISK_DONE       = 1'b0;
  wire        FDISK_ERR        = 1'b0;
  wire [ 3:0] FDISK_ERR_CODE   = 4'd0;
  wire [3:0]  FDISK_MEDIA_FMT  = 4'd0;
  wire [9:0]  FDBUF_ADDR       = 10'd0;
  wire [15:0] FDBUF_WDATA      = 16'd0;
  wire        FDBUF_WE         = 1'b0;
  wire [15:0] FDBUF_RDATA;

  wire        SDISK_START;
  wire        SDISK_REQ;
  wire        SDISK_WR;
  wire [15:0] SDISK_BLKADDR1;
  wire [15:0] SDISK_BLKADDR2;
  wire [2:0]  SDISK_UNIT;
  wire [10:0] SDISK_WORDCOUNT;
  wire        SDISK_DONE  = 1'b0;
  wire        SDISK_ERR   = 1'b0;
  wire [ 3:0] SDISK_ERR_CODE = 4'd0;
  wire [9:0]  SDBUF_ADDR  = 10'd0;
  wire [15:0] SDBUF_WDATA = 16'd0;
  wire        SDBUF_WE    = 1'b0;
  wire [15:0] SDBUF_RDATA;

  wire        WDISK_START;
  wire        WDISK_REQ;
  wire        WDISK_WR;
  wire [15:0] WDISK_BLKADDR1;
  wire [15:0] WDISK_BLKADDR2;
  wire [2:0]  WDISK_UNIT;
  wire [10:0] WDISK_WORDCOUNT;
  wire        WDISK_DONE  = 1'b0;
  wire        WDISK_ERR   = 1'b0;
  wire [ 3:0] WDISK_ERR_CODE = 4'd0;
  wire [9:0]  WDBUF_ADDR  = 10'd0;
  wire [15:0] WDBUF_WDATA = 16'd0;
  wire        WDBUF_WE    = 1'b0;
  wire [15:0] WDBUF_RDATA;

  // Same Winchester return seam as the device branch, tied inactive: no
  // device chain means nothing ever asks, so nothing ever answers.
  wire        s_wdisk_done     = 1'b0;
  wire        s_wdisk_err      = 1'b0;
  wire [ 3:0] s_wdisk_err_code = 4'd0;
  wire [ 9:0] s_wdbuf_addr     = 10'd0;
  wire [15:0] s_wdbuf_wdata    = 16'd0;
  wire        s_wdbuf_we       = 1'b0;
`endif

`ifdef MAIN_RAM_DDR2
  /*************************************************************************
   * Sim-only DDR2 backend plumbing (25-AUG-2026, freeze-injection study):
   * the REAL MEM_RAM_49_DDR2 runs inside the core; this block provides the
   * ui_clk domain and a behavioral nd_ddr2_port (random 10..70-cycle
   * latency, 2M x 16 backing store) so full-system Verilator runs exercise
   * the REAL cache-miss freeze against the REAL PALs and microcode.
   *************************************************************************/
  // no-timing Verilator: run the "ui" domain on the same clock; the toggle
  // CDC degrades to a synchronous pipeline and the model's 10..70-cycle
  // latency becomes 10..70 SYSCLK cycles = LONGER stalls = more stress.
  wire sim_ui_clk = sysclk;

  wire         mm_req_valid, mm_req_we;
  wire [26:0]  mm_req_addr;
  wire [127:0] mm_req_wdata;
  wire [15:0]  mm_req_wmask;
  reg          mm_req_ready = 0;
  reg          mm_rsp_valid = 0;
  reg  [127:0] mm_rsp_rdata = 0;
  wire [7:0]   mm_dbg_bridge;

  reg [15:0] sim_ddr_mem[0:2097151];
  integer sd_lat = 0, sd_state = 0, sd_cnt = 0, sd_i;
  reg [26:0] sd_addr; reg sd_we; reg [127:0] sd_wdata; reg [15:0] sd_wmask;
  reg [20:0] sd_unit;
  reg [31:0] sd_lfsr = 32'hACE1ACE1;
  always @(posedge sim_ui_clk) begin
    mm_rsp_valid <= 0;
    if (!sys_rst_n) begin sd_state <= 0; mm_req_ready <= 0; end
    else begin
      mm_req_ready <= (sd_state == 0);
      if (sd_state == 0 && mm_req_valid && mm_req_ready) begin
        sd_addr <= mm_req_addr; sd_we <= mm_req_we;
        sd_wdata <= mm_req_wdata; sd_wmask <= mm_req_wmask;
        sd_lfsr <= {sd_lfsr[30:0], sd_lfsr[31]^sd_lfsr[21]^sd_lfsr[1]^sd_lfsr[0]};
        sd_lat <= 10 + (sd_lfsr[5:0] % 61);
        sd_cnt <= 0; sd_state <= 1; mm_req_ready <= 0;
      end else if (sd_state == 1) begin
        sd_cnt <= sd_cnt + 1;
        if (sd_cnt == sd_lat) begin
          sd_unit = sd_addr[20:0] & 21'h1FFFF8;
          if (sd_we) begin
            for (sd_i = 0; sd_i < 8; sd_i = sd_i + 1) begin
              if (!sd_wmask[2*sd_i])   sim_ddr_mem[sd_unit+sd_i][7:0]  <= sd_wdata[16*sd_i+:8];
              if (!sd_wmask[2*sd_i+1]) sim_ddr_mem[sd_unit+sd_i][15:8] <= sd_wdata[16*sd_i+8+:8];
            end
          end else begin
            for (sd_i = 0; sd_i < 8; sd_i = sd_i + 1)
              mm_rsp_rdata[16*sd_i+:16] <= sim_ddr_mem[sd_unit+sd_i];
          end
          mm_rsp_valid <= 1; sd_state <= 0;
        end
      end
    end
  end
`endif

  ND120_CORE #(
      .INCLUDE_TAPE  (CORE_INCLUDE_TAPE),
      .INCLUDE_FLOPPY(CORE_INCLUDE_FLOPPY),
      .INCLUDE_SMD   (CORE_INCLUDE_SMD),
      .INCLUDE_WD    (CORE_INCLUDE_WD)
  ) CORE (
      .CACHE_SW(1'b1),   // console SW1: cache on, as it always was in sim
      .BAUD_9600(1'b0),  // no baud switch in sim: console at the build default speed
      // (a) clock / reset. clk1 is the CPU+bus+device domain in BOTH
      // branches: sim assigns clk1 = sysclk, FPGA assigns clk1 = clk_cpu.
      .clk_cpu(clk1),
      .sys_rst_n(sys_rst_n),

      // (e) C-PLUG bus: driven by the C harness in sim, tied off above on FPGA
      .BREQ_n(BREQ_n),
      .BINT10_n(BINT10_n),
      .BINT11_n(BINT11_n),
      .BINT12_n(BINT12_n),
      .BINT13_n(BINT13_n),
      .BINT15_n(BINT15_n),
      .POWSENSE_n(POWSENSE_n),

      .BD_23_0_n_IN(BD_23_0_n_IN),
      .BD_23_0_n_OUT(BD_23_0_n_OUT),

      .SEMRQ_n_IN(SEMRQ_n_IN),
      .SEMRQ_n_OUT(SEMRQ_n_OUT),
      .BINPUT_n_IN(BINPUT_n_IN),
      .BINPUT_n_OUT(BINPUT_n_OUT),
      .BDAP_n_IN(BDAP_n_IN),
      .BDAP_n_OUT(BDAP_n_OUT),
      .BDRY_n_IN(BDRY_n_IN),
      .BDRY_n_OUT(BDRY_n_OUT),
      .BAPR_n_IN(BAPR_n_IN),
      .BAPR_n_OUT(BAPR_n_OUT),

      .BREF_n(BREF_n),
      .BERROR_n(BERROR_n),
      .BINACK_n(BINACK_n),
      .BIOXE_n(BIOXE_n),
      .BMEM_n(BMEM_n),
      .OUTGRANT_n(OUTGRANT_n),
      .OUTIDENT_n(OUTIDENT_n),
      .MCL(MCL),

      // (d) UART
      .RXD(uartRx),
      .TXD(uartTx),

      // (c) storage backend: forwarded 1:1 to the ND120_TOP ports, which the
      // C harness serves (BPUN file / FLOPPY.IMG / disk-0 image)
      .TAPE_BYTE_REQ(TAPE_BYTE_REQ),
      .TAPE_BYTE_VALID(s_tape_byte_valid),  // SD-FAT stack or C harness
      .TAPE_BYTE_DATA(s_tape_byte_data),
      .TAPE_REWIND(TAPE_REWIND),

      .DMA_REQ(DMA_REQ),
      .DMA_WR(DMA_WR),
      .DMA_ADDR(DMA_ADDR),
      .DMA_WDATA(DMA_WDATA),
      .DMA_RDATA(DMA_RDATA),
      .DMA_ACK(DMA_ACK),
      .DMA_ERR(DMA_ERR),
      .DMA_BUSY(DMA_BUSY),

      .FDISK_REQ(FDISK_REQ),
      .FDISK_WR(FDISK_WR),
      .FDISK_LSECT(FDISK_LSECT),
      .FDISK_FORMAT(FDISK_FORMAT),
      .FDISK_DRIVE(FDISK_DRIVE),
      .FDISK_WORDCOUNT(FDISK_WORDCOUNT),
      .FDISK_DONE(FDISK_DONE),
      .FDISK_ERR(FDISK_ERR),
      .FDISK_ERR_CODE(FDISK_ERR_CODE),
      .FDISK_MEDIA_FMT(FDISK_MEDIA_FMT),
      .FDBUF_ADDR(FDBUF_ADDR),
      .FDBUF_WDATA(FDBUF_WDATA),
      .FDBUF_WE(FDBUF_WE),
      .FDBUF_RDATA(FDBUF_RDATA),

      .SDISK_START(SDISK_START),
      .SDISK_REQ(SDISK_REQ),
      .SDISK_WR(SDISK_WR),
      .SDISK_BLKADDR1(SDISK_BLKADDR1),
      .SDISK_BLKADDR2(SDISK_BLKADDR2),
      .SDISK_UNIT(SDISK_UNIT),
      .SDISK_WORDCOUNT(SDISK_WORDCOUNT),
      .SDISK_DONE(SDISK_DONE),
      .SDISK_ERR(SDISK_ERR),
      .SDISK_ERR_CODE(SDISK_ERR_CODE),
      .SDBUF_ADDR(SDBUF_ADDR),
      .SDBUF_WDATA(SDBUF_WDATA),
      .SDBUF_WE(SDBUF_WE),
      .SDBUF_RDATA(SDBUF_RDATA),
      .WDISK_START(WDISK_START),
      .WDISK_REQ(WDISK_REQ),
      .WDISK_WR(WDISK_WR),
      .WDISK_BLKADDR1(WDISK_BLKADDR1),
      .WDISK_BLKADDR2(WDISK_BLKADDR2),
      .WDISK_UNIT(WDISK_UNIT),
      .WDISK_WORDCOUNT(WDISK_WORDCOUNT),
      // Return path comes from the s_wdisk_*/s_wdbuf_* seam, not straight
      // off the ports: under ND120_SD_WD the in-RTL SD-FAT stack answers
      // instead of the C file server. See the seam comment above.
      .WDISK_DONE(s_wdisk_done),
      .WDISK_ERR(s_wdisk_err),
      .WDISK_ERR_CODE(s_wdisk_err_code),
      .WDBUF_ADDR(s_wdbuf_addr),
      .WDBUF_WDATA(s_wdbuf_wdata),
      .WDBUF_WE(s_wdbuf_we),
      .WDBUF_RDATA(WDBUF_RDATA),

      // (f) debug / status -> the board's LED map, 7-seg and ILA wires
      .LED(s_cpu_led[6:0]),
      .RUN_n(s_run),
      .CSA_12_0(CSA_12_0),
      .PIL(),                 // debug-capture passthrough; unused in this top
      .DEBUG_IREQ_15_0_N(),   // debug-capture passthrough; unused in this top
      .XMIC_DBG_15_0(XMIC_DBG_15_0), // microsequencer address-advance probe (sim golden log)
      // Register-file B port {LBA_3_0, B_15_0}. Used by the MiSTer bring-up to
      // read the STERR error number out of R2; this top has no use for it, but
      // the pin is listed so the instantiation stays complete.
      .XWRFB_DBG_19_0(),
      .XCYC_DBG_7_0(s_debug_cyc),
      .LA_23_10(s_debug_la_23_10),
      .CA_9_0(s_debug_ca_9_0),
      .DEBUG_CC_TERM(s_debug_cc_term),
      .DEBUG_MCLK(s_debug_mclk),
      .DEBUG_LCS_n(s_debug_lcs_n),
      .DEBUG_FETCH(s_debug_fetch),
      .DEBUG_MAP_n(s_debug_map_n),
      .DEBUG_CFETCH(s_debug_cfetch_dbg),
      .DEBUG_MR_n(s_debug_mr_n),
      .DEBUG_CLEAR_n(s_debug_clear_n),
      .DEBUG_REFRQ_n(s_debug_refrq_n),
      .DEBUG_INTRQ_n(s_debug_intrq_n),
      .DEBUG_POWFAIL_n(s_debug_powfail_n),
      .DEBUG_FIDBO_15_0(s_debug_fidbo),
      // ND120_CORE brings these four out unconditionally (they are debug taps
      // that must exist for every memory backend). This top does not use them;
      // naming them empty says "deliberately unconnected" so PINMISSING stays
      // a real gate for a genuinely forgotten pin.
      .DBG_PTW_LVL(),         // PT write-strobe level probe; unused in this top
      .DBG_PANEL(),           // panel debug byte; unused in this top
      .PANEL_ACTLV(),         // panel ACTIVE LEVEL word; unused in this top
      .DBG_CACHE()            // cache debug byte; unused in this top
`ifdef MAIN_RAM_DDR2
      ,
      .ui_clk      (sim_ui_clk),
      .ui_rst      (~sys_rst_n),
      .mm_req_valid(mm_req_valid),
      .mm_req_we   (mm_req_we),
      .mm_req_addr (mm_req_addr),
      .mm_req_wdata(mm_req_wdata),
      .mm_req_wmask(mm_req_wmask),
      .mm_req_ready(mm_req_ready),
      .mm_rsp_valid(mm_rsp_valid),
      .mm_rsp_rdata(mm_rsp_rdata),
      .DBG_DDR2_BRIDGE(mm_dbg_bridge)
`endif
  );

`ifdef ND120_INSTR_TRACE
  // ------------------------------------------------------------------
  // MACRO-INSTRUCTION TRACE (inert unless -DND120_INSTR_TRACE). 18-AUG-2026.
  //
  // Emits one line per retired macro instruction in the SAME COLUMN FORMAT as
  // the nd100x oracle trace, so the two can be diffed to find the exact
  // instruction where this machine stops behaving like a machine that boots:
  //
  //     PIL PC OP A D T X B L STS
  //
  // PC is the address of the instruction itself (P-1: P has already been
  // advanced past the fetched word by the time the boundary is detected), and
  // OP is the instruction word, which lives in the ALU's GPR register.
  //
  // BOUNDARY DETECTION is not "P changed" - that is wrong in both directions.
  // Two architectural signatures are needed, and both come from the existing
  // emitter in runSim (read, not modified - it is proven by the
  // instruction-verify campaign):
  //
  //   (a) an instruction FETCH commits P and GPR together in ONE microcycle.
  //       Testing P alone is not enough: shift operations change GPR alone,
  //       and jumps or level restores change P alone.
  //   (b) dispatches THROUGH microcode address 0 (a level switch, or an EXR'd
  //       instruction) enter the new instruction without (a) ever happening.
  //
  // GPR must also hold a real opcode: skip-bumps and panel-service entries
  // satisfy the rules with GPR momentarily 0, and no golden trace contains a
  // genuine 000000 opcode.
  //
  // Sampled on the rising edge of the CPU CLK (the UART's copy of it), which
  // is the edge the register file commits on - NOT sysclk, which would sample
  // mid-microcycle and catch registers in transit.
  //
  // ND120_INSTR_TRACE_MAX caps the output; a whole boot is ~20 million
  // instructions, so an uncapped run would produce gigabytes.
  // ------------------------------------------------------------------
`ifndef ND120_INSTR_TRACE_MAX
  `define ND120_INSTR_TRACE_MAX 30000000
`endif

  wire        w_itr_clk = CORE.CPU_BOARD.IO.UART.s_clk;
  wire [15:0] w_itr_a   = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.WRF.RBLOCK.s_reg5_a_15_0;
  wire [15:0] w_itr_d   = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.WRF.RBLOCK.s_reg1_d_15_0;
  wire [15:0] w_itr_t   = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.WRF.RBLOCK.s_reg6_t_15_0;
  wire [15:0] w_itr_x   = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.WRF.RBLOCK.s_reg7_x_15_0;
  wire [15:0] w_itr_b   = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.WRF.RBLOCK.s_reg3_b_15_0;
  wire [15:0] w_itr_l   = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.WRF.RBLOCK.s_reg4_l_15_0;
  wire [15:0] w_itr_p   = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.WRF.RBLOCK.s_reg2_p_15_0;
  wire [15:0] w_itr_sts = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.WRF.RBLOCK.s_reg8_sts_15_0;
  wire [15:0] w_itr_gpr = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.ALU.s_grp_15_0;
  wire [ 3:0] w_itr_pil = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.sx_pil_3_0_out;

  reg         r_itr_clk_d  = 1'b0;
  reg         r_itr_have   = 1'b0;
  reg  [15:0] r_itr_p_d    = 16'd0;
  reg  [15:0] r_itr_gpr_d  = 16'd0;
  reg  [12:0] r_itr_csa_d  = 13'h1FFF;
  reg  [31:0] r_itr_n      = 32'd0;

  always @(posedge sysclk) begin
    r_itr_clk_d <= w_itr_clk;
    if (w_itr_clk && !r_itr_clk_d) begin           // CPU CLK rising edge
      if (r_itr_have && (w_itr_gpr != 16'd0) &&
          (((w_itr_p != r_itr_p_d) && (w_itr_gpr != r_itr_gpr_d)) ||
           ((r_itr_csa_d == 13'd0) && (CSA_12_0 != 13'd0)))) begin
        if (r_itr_n < `ND120_INSTR_TRACE_MAX) begin
          $display("[itr] %0d %06o %06o %06o %06o %06o %06o %06o %06o %06o",
                   w_itr_pil, (w_itr_p - 16'd1) & 16'hFFFF, w_itr_gpr,
                   w_itr_a, w_itr_d, w_itr_t, w_itr_x, w_itr_b, w_itr_l,
                   w_itr_sts);
        end
        r_itr_n <= r_itr_n + 32'd1;
      end
      r_itr_have  <= 1'b1;
      r_itr_p_d   <= w_itr_p;
      r_itr_gpr_d <= w_itr_gpr;
      r_itr_csa_d <= CSA_12_0;
    end
  end
`endif

`ifdef ND120_JPL_RING
  // ------------------------------------------------------------------
  // JPL OFF-BY-ONE INSTRUMENT (inert unless -DND120_JPL_RING). 19-AUG-2026.
  //
  // THE FAULT BEING CHASED. `000465` holds `135014` = JPL I *14, i.e. an
  // indirect call through the pointer word at 000465+14 = 000501 (5CLOA),
  // which holds 144163. Once in 15 executions the jump lands at 144162 -
  // ONE WORD EARLY - with the link register L CORRECT at 000466.
  //
  // WHY A RING AND NOT MORE TRACING. The macro-instruction trace is complete
  // and shows NOTHING wrong before the bad landing: 000460..000465 execute
  // exactly as they do on the 14 good calls, with no retry of 000465 and no
  // level-14 handler in between. So the cause is BELOW macro level, in the
  // microcode steps of the JPL itself. This captures every CPU-clock edge
  // (microcode granularity) and dumps the history the moment P lands on the
  // wrong address - i.e. it goes BACKWARDS from the failure.
  //
  // WRITE WATCH. It also reports any write whose effective address is the
  // pointer word 000501, naming the instruction that did it. That separates
  // "someone stored a wrong value" from "the read/P-load produced value-1".
  // ------------------------------------------------------------------
`ifndef ND120_JPL_RING_DEPTH
  `define ND120_JPL_RING_DEPTH 512
`endif
  localparam integer JR_N   = `ND120_JPL_RING_DEPTH;
  localparam [15:0]  JR_BAD = 16'o144162;   // the wrong landing
  localparam [15:0]  JR_OK  = 16'o144163;   // the correct target
  localparam [15:0]  JR_PTR = 16'o000501;   // 5CLOA, the pointer word

  // NOTE the hierarchy: the instance named CGA holds the real CGA module as
  // DELILAH, which is why every probe here goes through .CGA.DELILAH.
  wire [15:0] w_jr_ea = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.s_ea_15_0;
  wire        w_jr_wr = ~CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.s_write_n;

  reg  [15:0] jr_p    [0:JR_N-1];
  reg  [15:0] jr_gpr  [0:JR_N-1];
  reg  [12:0] jr_csa  [0:JR_N-1];
  reg  [15:0] jr_ea   [0:JR_N-1];
  reg  [ 3:0] jr_pil  [0:JR_N-1];
  localparam integer JR_WIN = 400;   // cycles logged after each arm
  reg  [15:0] jr_win  = 16'd0;
  reg  [31:0] jr_wp   = 32'd0;
  reg         jr_done = 1'b0;
  reg         jr_clkd = 1'b0;
  integer     jr_i, jr_k;

  // FULL MICROCODE TRACE TO DISK, WINDOWED. The ring above only holds the last
  // 512 clock edges - enough to see the trigger, useless for real analysis.
  // This emits EVERY CPU-clock edge once the macro-instruction count passes
  // ND120_JPL_LOG_FROM, so the whole run-up to the failure lands in the log
  // file and can be picked over afterwards. Windowed because a whole boot is
  // ~16M instructions x ~30 clock edges = ~500M lines; starting at 15.8M gives
  // ~200k instructions of full detail (~6M lines) around a failure that is
  // deterministic and always lands at ~16.02M.
`ifndef ND120_JPL_LOG_FROM
  `define ND120_JPL_LOG_FROM 15800000
`endif

  always @(posedge sysclk) begin
    jr_clkd <= w_itr_clk;
    if (w_itr_clk && !jr_clkd) begin
      // WINDOW BY EVENT, NOT BY COUNT. The old guard used r_itr_n, which counts
      // only instructions the boundary rule EMITS - not executed instructions -
      // so the window opened in the wrong place and MISSED the failing JPL
      // (log held 1.11M dispatches where the window should have held ~219k).
      // Arm on the JPL's own address instead: cannot drift, and it captures
      // EVERY execution of this instruction, good and bad.
      if (w_itr_p == 16'o000465)      jr_win <= JR_WIN[15:0];
      else if (jr_win != 16'd0)       jr_win <= jr_win - 16'd1;
      if ((jr_win != 16'd0) || (w_itr_p == 16'o000465))
        $display("[jplmic] %0d %06o %06o %04o %06o %b",
                 w_itr_pil, w_itr_p, w_itr_gpr, CSA_12_0, w_jr_ea, w_jr_wr);
      jr_p  [jr_wp[8:0]] <= w_itr_p;
      jr_gpr[jr_wp[8:0]] <= w_itr_gpr;
      jr_csa[jr_wp[8:0]] <= CSA_12_0;
      jr_ea [jr_wp[8:0]] <= w_jr_ea;
      jr_pil[jr_wp[8:0]] <= w_itr_pil;
      jr_wp <= jr_wp + 32'd1;

      // Trigger: P has landed on the wrong address. Dump the ring OLDEST ->
      // NEWEST so the run-up to the failure reads top to bottom.
      if (!jr_done && (w_itr_p == JR_BAD)) begin
        jr_done <= 1'b1;
        $display("[jplring] TRIGGER P=%06o (expected %06o) at ringpos %0d",
                 JR_BAD, JR_OK, jr_wp);
        /* verilator lint_off BLKSEQ */
        for (jr_i = 0; jr_i < JR_N; jr_i = jr_i + 1) begin
          jr_k = (jr_wp + jr_i) % JR_N;
          $display("[jplring] %4d PIL=%0d P=%06o GPR=%06o CSA=%04o EA=%06o",
                   jr_i - JR_N, jr_pil[jr_k], jr_p[jr_k], jr_gpr[jr_k],
                   jr_csa[jr_k], jr_ea[jr_k]);
        end
        /* verilator lint_on BLKSEQ */
      end
    end

    // Any write to the pointer word, whenever it happens.
    if (w_jr_wr && (w_jr_ea == JR_PTR))
      $display("[jplwr] WRITE to pointer %06o : P=%06o GPR=%06o CSA=%04o PIL=%0d",
               JR_PTR, w_itr_p, w_itr_gpr, CSA_12_0, w_itr_pil);
  end
`endif

`ifdef ND120_MAC_CAPTURE
  // ------------------------------------------------------------------
  // CGA_MAC SIGNAL CAPTURE (inert unless -DND120_MAC_CAPTURE). 19-AUG-2026.
  //
  // PURPOSE. The indirect `JPL I *14` at 000465 lands at 144162 instead of
  // 144163 once in 15 executions - bit 0 of the loaded P is 0 when it should
  // be 1. The IDB is an OR-merge (CGA.v:627) and an OR can never CLEAR a bit,
  // so the wrong value did not come from bus contention: either the read data
  // was wrong or P captured the bus before bit 0 settled. CGA_MAC is where the
  // address arithmetic lives - NLCA_15_0 is the "+1" output and PCR_15_0 is P.
  //
  // WHAT IT DOES. Records EVERY input and output of the CGA_MAC instance on
  // every sysclk into a ring, and dumps the whole ring when P lands on the
  // wrong address. The dump is column-formatted so a testbench can replay the
  // exact input sequence into a standalone CGA_MAC and compare its outputs.
  // ------------------------------------------------------------------
`ifndef ND120_MAC_CAP_DEPTH
  `define ND120_MAC_CAP_DEPTH 2048
`endif
  localparam integer MC_N = `ND120_MAC_CAP_DEPTH;

  // inputs
  wire        mc_mclken = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.MCLK_EN;
  wire        mc_csmreq = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.CSMREQ;
  wire        mc_double = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.DOUBLE;
  wire        mc_ilcsn  = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.ILCSN;
  wire        mc_mclk   = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.MCLK;
  wire        mc_poni   = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.PONI;
  wire        mc_ptm    = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.PTM;
  wire        mc_wr3    = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.WR3;
  wire        mc_wr7    = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.WR7;
  wire [ 1:0] mc_cmis   = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.CMIS_1_0;
  wire [ 4:0] mc_cscomm = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.CSCOMM_4_0;
  wire [15:0] mc_rb     = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.RB_15_0;
  wire [15:0] mc_cd     = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.CD_15_0;
  wire [15:0] mc_fidbo  = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.FIDBO_15_0;
  wire [15:0] mc_pr     = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.PR_15_0;
  wire [15:0] mc_br     = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.BR_15_0;
  wire [15:0] mc_xr     = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.XR_15_0;
  // ---- THE +1 CHAIN, INTERNAL SIGNALS ----------------------------------
  // Module ports alone cannot validate
  //   MCA_9_0 -> R81_EN(LCA) -> LCA_15_0 -> APOS_INC(+1) -> NLCA_15_0
  // because LCA (the register output the incrementer adds to), ICA (its input
  // path) and every select/clock that decides what gets captured are INTERNAL.
  // Without them a mismatch cannot be pinned to the register, the select or
  // the increment. AP09 exposes LCA/ICA as ports of its own instance.
  wire [15:0] mc_lca    = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.MAC_AP09.LCA_15_0;
  wire [15:0] mc_ica    = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.MAC_AP09.ICA_15_0;
  // selects + clocking that steer the capture (all named wires in CGA_MAC)
  wire        mc_nlcasel= CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.s_nlcasel;
  wire        mc_psel   = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.s_psel;
  wire        mc_addsel = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.a_addsel;
  wire        mc_hold   = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.s_hold;
  wire        mc_cdsel  = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.s_cdsel;
  wire        mc_smclk  = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.s_mclk;

  // outputs
  wire        mc_eccr   = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.ECCR;
  wire [13:0] mc_la     = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.LA_23_10;
  wire        mc_lshad  = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.LSHADOW;
  wire [ 9:0] mc_mca    = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.MCA_9_0;
  wire [15:0] mc_nlca   = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.NLCA_15_0;
  wire [15:0] mc_pcr    = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.PCR_15_0;
  wire        mc_vex    = CORE.CPU_BOARD.CPU.PROC.CGA.DELILAH.MAC.VEX;

  reg [10:0] mc_b   [0:MC_N-1];   // {mclken,csmreq,double,ilcsn,mclk,poni,ptm,wr3,wr7,eccr,lshadow}
  reg [ 1:0] mc_a   [0:MC_N-1];   // cmis
  reg [ 4:0] mc_c   [0:MC_N-1];   // cscomm
  reg [15:0] mc_r   [0:MC_N-1];
  reg [15:0] mc_d   [0:MC_N-1];
  reg [15:0] mc_f   [0:MC_N-1];
  reg [15:0] mc_p   [0:MC_N-1];
  reg [15:0] mc_bb  [0:MC_N-1];
  reg [15:0] mc_x   [0:MC_N-1];
  reg [13:0] mc_l   [0:MC_N-1];
  reg [ 9:0] mc_m   [0:MC_N-1];
  reg [15:0] mc_n   [0:MC_N-1];
  reg [15:0] mc_q   [0:MC_N-1];
  reg        mc_v   [0:MC_N-1];
  reg [15:0] mc_lcaR[0:MC_N-1];
  reg [15:0] mc_icaR[0:MC_N-1];
  reg [ 5:0] mc_sel [0:MC_N-1];   // {nlcasel,psel,addsel,hold,cdsel,s_mclk}
  reg [31:0] mc_wp   = 32'd0;
  reg        mc_done = 1'b0;
  reg        mc_clkd = 1'b0;
  integer    mc_i, mc_k;

  always @(posedge sysclk) begin
    mc_b [mc_wp[10:0]] <= {mc_mclken,mc_csmreq,mc_double,mc_ilcsn,mc_mclk,
                           mc_poni,mc_ptm,mc_wr3,mc_wr7,mc_eccr,mc_lshad};
    mc_a [mc_wp[10:0]] <= mc_cmis;
    mc_c [mc_wp[10:0]] <= mc_cscomm;
    mc_r [mc_wp[10:0]] <= mc_rb;
    mc_d [mc_wp[10:0]] <= mc_cd;
    mc_f [mc_wp[10:0]] <= mc_fidbo;
    mc_p [mc_wp[10:0]] <= mc_pr;
    mc_bb[mc_wp[10:0]] <= mc_br;
    mc_x [mc_wp[10:0]] <= mc_xr;
    mc_l [mc_wp[10:0]] <= mc_la;
    mc_m [mc_wp[10:0]] <= mc_mca;
    mc_n [mc_wp[10:0]] <= mc_nlca;
    mc_q [mc_wp[10:0]] <= mc_pcr;
    mc_v [mc_wp[10:0]] <= mc_vex;
    mc_lcaR[mc_wp[10:0]] <= mc_lca;
    mc_icaR[mc_wp[10:0]] <= mc_ica;
    mc_sel [mc_wp[10:0]] <= {mc_nlcasel,mc_psel,mc_addsel,mc_hold,mc_cdsel,mc_smclk};
    mc_wp <= mc_wp + 32'd1;

    mc_clkd <= w_itr_clk;
    if (w_itr_clk && !mc_clkd && !mc_done && (w_itr_p == 16'o144162)) begin
      mc_done <= 1'b1;
      $display("[maccap] TRIGGER P=144162 ringpos=%0d depth=%0d", mc_wp, MC_N);
      $display("[maccap] idx MCLKEN CSMREQ DOUBLE ILCSN MCLK PONI PTM WR3 WR7 CMIS CSCOMM RB CD FIDBO PR BR XR | ECCR LA LSHADOW MCA NLCA PCR VEX || ICA LCA NLCASEL PSEL ADDSEL HOLD CDSEL SMCLK");
      /* verilator lint_off BLKSEQ */
      for (mc_i = 0; mc_i < MC_N; mc_i = mc_i + 1) begin
        mc_k = (mc_wp + mc_i) % MC_N;
        $display("[maccap] %0d %b %b %b %b %b %b %b %b %b %0o %0o %06o %06o %06o %06o %06o %06o | %b %05o %b %04o %06o %06o %b || %06o %06o %b %b %b %b %b %b",
                 mc_i - MC_N,
                 mc_b[mc_k][10], mc_b[mc_k][9], mc_b[mc_k][8], mc_b[mc_k][7],
                 mc_b[mc_k][6],  mc_b[mc_k][5], mc_b[mc_k][4], mc_b[mc_k][3],
                 mc_b[mc_k][2],
                 mc_a[mc_k], mc_c[mc_k],
                 mc_r[mc_k], mc_d[mc_k], mc_f[mc_k], mc_p[mc_k], mc_bb[mc_k], mc_x[mc_k],
                 mc_b[mc_k][1], mc_l[mc_k], mc_b[mc_k][0], mc_m[mc_k],
                 mc_n[mc_k], mc_q[mc_k], mc_v[mc_k],
                 mc_icaR[mc_k], mc_lcaR[mc_k],
                 mc_sel[mc_k][5], mc_sel[mc_k][4], mc_sel[mc_k][3],
                 mc_sel[mc_k][2], mc_sel[mc_k][1], mc_sel[mc_k][0]);
      end
      /* verilator lint_on BLKSEQ */
    end
  end
`endif

endmodule