Skip to content

ND120_TANG20K_TOP

Source: Verilog/fpga/tang-nano-20k/src/ND120_TANG20K_TOP.v

Where it sits (Tang): ND120_TANG20K_TOP

Used in: nothing - this is a top (Tang).

Contains: Gowin_rPLL_ND120 (vendor), ND120_CORE, nd_storage_devices, uart_tx

Module hierarchy - All modules

ND120_TANG20K_TOP symbol

Schematic

Drawn from the Verilog: the yosys netlist of the Tang Nano 20K 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_TANG20K_TOP schematic

Description

ND120 CPU, MEMORY MANAGEMENT and MEMORY TOP LEVEL FOR THE TANG NANO 20K (Gowin GW2AR-18) Sibling of ND120_TOP.v (the Basys3/Verilator top) - the Basys3 top is untouched by this file. Board decisions (8-JUL-2026): - one rPLL: 27 MHz CPU/bus/OSC + 54 MHz SDRAM clock pair - main memory = embedded 8 MB SDRAM (MEM_RAM_49_SDRAM, 2 banks) - microcode: SKIP_WCS_LOAD bitstream-preloaded WCS, PROM dropped - console: OPCOM UART 9600 on the BL616 USB serial (pins 69/70) - S1 = Master Clear (power-on-reset retrigger), S2 spare Build defines come from src/tang20k_defines.v (first file in the Gowin project). Last reviewed: 8-JUL-2026 Ronny Hansen

Ports

Direction Width Name Description
input 1 sys_clk 27 MHz crystal (pin 4)
input 1 s1 S1 push button (pin 88) - Master Clear / reset
input 1 s2 S2 push button (pin 87) - spare
input 1 uart_rxp UART receive (from BL616, pin 70)
output 1 uart_txp UART transmit (to BL616, pin 69)
output [5:0] led 6 board LEDs, ACTIVE LOW (pins 15-20)
output 1 sd_clk
inout 1 sd_cmd bidirectional: host commands / card responses
inout 1 sd_dat0 bidirectional: card read data
inout 1 sd_dat1 4-bit bus mode only; released (pulled high) otherwise
inout 1 sd_dat2
inout 1 sd_dat3
output 1 O_sdram_clk
output 1 O_sdram_cke
output 1 O_sdram_cs_n (active low)
output 1 O_sdram_cas_n (active low)
output 1 O_sdram_ras_n (active low)
output 1 O_sdram_wen_n (active low)
inout [31:0] IO_sdram_dq
output [10:0] O_sdram_addr
output [1:0] O_sdram_ba
output [3:0] O_sdram_dqm

Verilog source

Verilog/fpga/tang-nano-20k/src/ND120_TANG20K_TOP.v on GitHub.

Show the Verilog of ND120_TANG20K_TOP (2434 lines)
/**************************************************************************
** ND120 CPU, MEMORY MANAGEMENT and MEMORY                               **
**                                                                       **
** TOP LEVEL FOR THE TANG NANO 20K (Gowin GW2AR-18)                      **
**                                                                       **
** Sibling of ND120_TOP.v (the Basys3/Verilator top) - the Basys3 top    **
** is untouched by this file. Board decisions (8-JUL-2026):              **
**   - one rPLL: 27 MHz CPU/bus/OSC + 54 MHz SDRAM clock pair            **
**   - main memory = embedded 8 MB SDRAM (MEM_RAM_49_SDRAM, 2 banks)     **
**   - microcode: SKIP_WCS_LOAD bitstream-preloaded WCS, PROM dropped    **
**   - console: OPCOM UART 9600 on the BL616 USB serial (pins 69/70)     **
**   - S1 = Master Clear (power-on-reset retrigger), S2 spare            **
** Build defines come from src/tang20k_defines.v (first file in the      **
** Gowin project).                                                       **
**                                                                       **
** Last reviewed: 8-JUL-2026                                             **
** Ronny Hansen                                                          **
***************************************************************************/

module ND120_TANG20K_TOP (
    input wire sys_clk,   //! 27 MHz crystal (pin 4)
    input wire s1,        //! S1 push button (pin 88) - Master Clear / reset
    input wire s2,        //! S2 push button (pin 87) - spare
    input wire uart_rxp,  //! UART receive (from BL616, pin 70)
    output wire uart_txp, //! UART transmit (to BL616, pin 69)

    output wire [5:0] led,  //! 6 board LEDs, ACTIVE LOW (pins 15-20)

    // microSD slot (SD-native mode). Pin map and PULL_MODE come from the
    // silicon-proven sd-fat-test/src/nano20k_sd.cst - the slot has external
    // 10K pull-ups (R53-R57), so released lines idle high. Only CMD and DAT0
    // are used: the storage reader is 1-bit (the 4-bit writer is not built in
    // this design), so DAT1-3 are not brought out at all.
    output wire sd_clk,
    inout  wire sd_cmd,   //! bidirectional: host commands / card responses
    inout  wire sd_dat0,  //! bidirectional: card read data
    inout  wire sd_dat1,  //! 4-bit bus mode only; released (pulled high) otherwise
    inout  wire sd_dat2,
    inout  wire sd_dat3,

    // Embedded SDRAM ("magic" port names - Gowin EDA connects these to the
    // on-package SDRAM die automatically; the OSS flow pins them in a cst)
    output        O_sdram_clk,
    output        O_sdram_cke,
    output        O_sdram_cs_n,
    output        O_sdram_cas_n,
    output        O_sdram_ras_n,
    output        O_sdram_wen_n,
    inout  [31:0] IO_sdram_dq,
    output [10:0] O_sdram_addr,
    output [ 1:0] O_sdram_ba,
    output [ 3:0] O_sdram_dqm
);

  /**********************************************
  *  Clocks: 27 MHz CPU/bus + 54 MHz SDRAM pair *
  ***********************************************/
  wire clk2x;        // 54 MHz - SDRAM controller
  wire clk2x_sdram;  // 54 MHz shifted - SDRAM chip
  wire pll_lock;

`ifdef SIM
  // Simulation (iverilog testbench): no rPLL primitive. sys_clk plays the
  // 2x clock; clk_cpu is an edge-aligned divide-by-2, clk2x_sdram is the
  // 180-degree inversion - same relationships the PLL provides.
  reg clk_cpu_div = 0;
  always @(posedge sys_clk) clk_cpu_div <= ~clk_cpu_div;
  assign clk2x = sys_clk;
  assign clk2x_sdram = ~sys_clk;
  wire clk_cpu = clk_cpu_div;
  assign pll_lock = 1'b1;
`else
  wire clk_cpu;      // 27 MHz - CPU / bus / OSC domain

  Gowin_rPLL_ND120 pll (
      .clkout (clk2x),
      .clkoutp(clk2x_sdram),
      .clkoutd(clk_cpu),
      .lock   (pll_lock),
      .clkin  (sys_clk)
  );
`endif

  /**********************************************
  *  Reset: power-on + S1 (Master Clear)        *
  ***********************************************/
  // Same scheme as ND120_TOP's FPGA branch: hold sys_rst_n low for 256
  // clk_cpu cycles after PLL lock; pressing S1 (active high) restarts the
  // counter, retriggering the full CPU boot sequence.
  reg s1_r1, s1_r2;
  always @(posedge clk_cpu) begin
    s1_r1 <= s1;
    s1_r2 <= s1_r1;
  end

  // Remote reset: a UART BREAK on the console RX (line held LOW for >=200 ms,
  // ~2 character times would be enough but 200 ms rejects any glitch) acts
  // exactly like pressing S1. BREAK is out-of-band: normal typed characters
  // and ndcomm's binary deposit streams always return the line high between
  // frames, so nothing legitimate can fake it. Host side: send a break
  // (python termios.tcsendbreak / picocom C-a C-\ ) to reset the board
  // without touching it.
  localparam integer BREAK_CYCLES = (`BOARD_CLK_FREQ / 5);  // 200 ms of low
  reg [24:0] brk_cnt = 25'd0;
  reg        brk_rst = 1'b0;
  reg        rx_r1 = 1'b1, rx_r2 = 1'b1;
  always @(posedge clk_cpu) begin
    rx_r1 <= uart_rxp;
    rx_r2 <= rx_r1;
    if (rx_r2) begin
      brk_cnt <= 25'd0;
      brk_rst <= 1'b0;
    end else if (brk_cnt >= BREAK_CYCLES[24:0]) begin
      brk_rst <= 1'b1;   // held until the line returns high
    end else begin
      brk_cnt <= brk_cnt + 1'b1;
    end
  end

  reg [7:0] por_count = 8'd0;
  reg       por_done = 1'b0;
  always @(posedge clk_cpu) begin
    if (s1_r2 | brk_rst) begin  // S1 pressed or console BREAK: Master Clear
      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
  wire sys_rst_n = por_done & pll_lock;

  /**********************************************
  *  ND-100 bus: tied off (no external bus)     *
  ***********************************************/
  wire [12:0] CSA_12_0 /* synthesis syn_keep=1 */;  // GAO probe net - see GAO-HOWTO.md
  wire  [3:0] s_pil_3_0 /* synthesis syn_keep=1 */;  // PIL for the grant-capture probe (TANG_GRANT_CAPTURE)
  wire [15:0] s_ireq_15_0_n /* synthesis syn_keep=1 */;  // raw interrupt-request vector (active low) for grant-source capture
  wire [15:0] s_xmic_dbg /* synthesis syn_keep=1 */;  // microsequencer address-advance probe (Tang 06000-hang root cause)

  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 SEMRQ_n_IN = 1'b1;
  wire BINPUT_n_IN = 1'b1;
  wire BDAP_n_IN = 1'b1;
  wire BDRY_n_IN = 1'b1;
  wire BAPR_n_IN = 1'b1;

  // Installation number, the s_high/s_low helpers, SEL_TESTMUX and the baud
  // rate thumbwheel (8 = 9600 baud, BAUDV microcode page 158) are CPU-board
  // constants and now live inside ND120_CORE.v -- not duplicated per board.
  // UART_BAUD_RATE in tang20k_defines.v must still match 9600.

  /**********************************************
  *  Status / debug wires                       *
  ***********************************************/
  wire [6:0] s_cpu_led;  // ND3202D LED bundle, see ND3202D.v port comment
  wire s_run;            // RUN_n: low while the CPU is running
  wire [4:0] s_debug_cc_term;
  wire s_debug_mclk, s_debug_lcs_n, s_debug_fetch;
  wire s_debug_mr_n, s_debug_clear_n, s_debug_refrq_n;
  wire s_debug_intrq_n, s_debug_powfail_n;
  wire [15:0] s_debug_fidbo;
  wire [13:0] s_debug_la_23_10;
  wire [9:0] s_debug_ca_9_0;
  wire [4:0] s_test_4_0;
  wire [4:0] s_dp_5_1_n;
  wire s_tp1_intrq_n;
  wire [63:0] s_csbits;

  reg [26:0] clockTicks;
  always @(posedge clk_cpu) clockTicks <= clockTicks + 1'b1;

  //! WIDE tick counter for timestamping a whole boot.
  //! clockTicks above is 27 bits, which at clk_cpu wraps about every 20 s - it
  //! is a heartbeat for led[5], not a timebase. The boot reaches the ERRFATAL
  //! at roughly 143 s, so timestamping it needs a counter that does not wrap:
  //! 40 bits is ~45 hours at 6.75 MHz.
  reg [39:0] tickWide = 40'd0;
  always @(posedge clk_cpu) tickWide <= tickWide + 1'b1;

  /* verilator lint_off UNUSEDSIGNAL */
  wire unused_s2 = s2;
  /* verilator lint_on UNUSEDSIGNAL */

  /**********************************************
  *  LEDs (ACTIVE LOW): agreed bring-up set     *
  ***********************************************/
  // s_cpu_led[3] = LED_CPU_GRANT_INDICATOR (= CGNT_n, low when granted)
  // s_cpu_led[4] = LED_BUS_GRANT_INDICATOR (= BGNT_n, low when granted)
  // s_cpu_led[2] = LED4_RED_PARITY_ERROR (polarity: verify on first light)
  // WRITE-GENERATION ANALYZER BUILD (8-JUL-2026 late): bus retargeted at
  // the DGA WRITE chain (see ND3202D.v DBG_MEMW assign for the bit map).
  // [7] = wdec (F924 A160 D3 decode input), [6] = WRITE (registered out).
  wire [15:0] s_dbg_memw;
  wire [15:0] s_dbg_ptw;   // page-table write stream from CPU_MMU_24 (23-AUG)
  wire [1:0]  s_dbg_wdstage;   // [0] WD controller active, [1] its DMA busy
  wire        s_dbg_sd_busy, s_dbg_cache_pend;
  wire [15:0] s_dbg_pgw;    // SDRAM-bridge page-write watch (24-AUG zero-page campaign)
  wire [13:0] s_dbg_ppn;    // physical page number PPN[23:10] (24-AUG zero-fetch campaign)
  wire [20:0] s_pf_captured; // ND120_PF_CAPTURE: [0] frozen, [1] no-permit access pulse, [2] fault pulse at the matched page, [3] fault pulse ANY address with [13:4]=LA[19:10] [20:14]=PT[15:9] (23-AUG)
  wire dbg_dumping;
`ifdef TANG_WD_TRACE_DUMP
  wire [19:0] wd_trace_rec;
  wire        wd_trace_we;
  wire        wd_trace_done;
`endif
  reg wdec_seen, write_seen;         // sticky since arm (write-path analyzer)
  // The LED assignments live further down, AFTER the storage block declares
  // the signals they show - see "STORAGE BRING-UP LED SET".

  /**********************************************
  *  The CPU board                              *
  ***********************************************/
  /**********************************************
  *  On-chip write-path analyzer (clk2x domain) *
  ***********************************************/
  // 512 samples of the 16-bit bus at clk2x; trigger = first rising edge of
  // the write DECODE (bit [7], the F924 D3 input) after a ~2.5 s arm delay
  // (skips boot; deposit at the console fires it). 64 pre-trigger + 448
  // post, so the whole decode -> WRITE -> ECREQ -> grant -> RAS/CAS
  // sequence lands after the trigger. Then dumps "hhhh\r\n" x512 over the
  // UART at 9600 (taking the TX pin over from the CPU console).
  // If LED3 (decode seen) never lights, the decode itself never fires on
  // silicon - that is a result too.
  // Capture source / trigger / pre-post split are switchable:
  //  - default: the write-path analyzer (source = s_dbg_memw, trigger = write
  //    decode rising, 64 pre + 448 post).
  //  - TANG_GRANT_CAPTURE: the masked-level-10 grant probe. Source packs
  //    {PIL[3:0], CSA[11:0]}; trigger = PIL entering level 10 (0->10 is the
  //    silicon wedge); 448 PRE + 64 post so the whole lead-up to the switch is
  //    recorded. Reading back the CSA sequence shows whether PIL->10 goes
  //    through the normal level-switch microcode (PLINT 01133 / PLVO 01140 /
  //    LVSWP 01146-01155, as a legit level-13 switch does in sim) or bypasses
  //    it - the decisive fork for the root cause.
  // Ring depth. The free-running capture modes want every one of 512 clocks;
  // the WD IOX trace stores one entry per REGISTER ACCESS, and the sequence
  // it has to catch is about 33 accesses long. At 20 bits Gowin puts this in
  // distributed RAM (SSRAM), not block RAM, so depth is paid for in LOGIC
  // cells: 512 deep overflowed the part by 114 cells (20850 of 20736) with
  // the Winchester build in, and 128 deep STILL overflowed it by 9 (20745)
  // once the Winchester and the floppy were both in - measured 09-AUG-2026.
  //
  // 64 is still comfortably enough for the sequence that matters. The
  // captured File System Investigator trace (nd100x, ND100X_WD_DEBUG=1) is
  // 72 accesses END TO END for a whole LIST-FILE-NAMES, and the trigger - a
  // read of +0 - first fires at access 17, so a 64-deep ring holds every
  // access from power-on up to and including the first failing transfer.
  //
  // 10-AUG-2026: 64 was NOT enough for the '20500&' mass load. That sequence
  // is hundreds of accesses long, so a 64-deep ring wrapped and returned only
  // its tail - every transfer in it completed with status 060010 and a
  // correctly advanced memory address register, which says nothing about
  // where the guest decided the load had failed. 256 buys the whole window,
  // and the cells for it come from building this diagnostic with TANG_FLOPPY
  // off: the mass load never touches the floppy, and its ND_FLOPPY_DMA plus
  // its own DMA master are the largest block that can be removed without
  // changing what the Winchester path does.
`ifdef TANG_WD_TRACE_DUMP
  localparam CAP_AW = 8;
`else
  localparam CAP_AW = 9;
`endif
  localparam [CAP_AW-1:0] CAP_LAST = {CAP_AW{1'b1}};

  reg [19:0] cap_mem[0:(1<<CAP_AW)-1];   // 20 bits: rw + register + data
  reg [CAP_AW-1:0] cap_wptr;
  reg [8:0] cap_post;
  reg [28:0] arm_cnt;  // 30-JUL: widened 25->29 bits (arm ~40s, was ~2.5s) -
                       // the WCS microcode load keeps CSA STATIC longer than
                       // 2.5s, so the hang trigger fired mid-load and the
                       // dumper seized the TX pin before the console ever
                       // spoke (SRAM-load-no-boot mystery, plan Issue I).
  reg cap_armed, cap_trig, cap_done;
`ifdef TANG_PC_HISTORY
  reg [19:0] pc_prev = 20'd0;      //! previous {PIL,P}; the strobe fires on a change
  reg [ 9:0] la_prev_pc = 10'd0;   //! previous LA[19:10]; the trigger is its EDGE
  //! FALLBACK TRIGGER. The first run (22-AUG-2026) reproduced the ERRFATAL but
  //! produced NO dump at all - zero bytes at every baud - and there was no way
  //! to tell "the LA trigger never fired" from "the dumper ran and sent
  //! nothing". This counter guarantees a dump either way: if the LA match has
  //! not fired by ~298 s (the fault lands at ~150 s, so it gets first refusal),
  //! trigger anyway. A trail ending in 0644xx means the LA match won; a trail
  //! ending anywhere else means it did not, and the ring/dumper chain is
  //! nonetheless proven to work.
  //!
  //! TIMING, worked out rather than guessed: the counter only runs once
  //! cap_armed is set, which is arm_cnt reaching 0x1FFFFFFF = 536,870,911
  //! clk2x cycles = 39.8 s at 13.5 MHz. Bit 31 then sets after a further
  //! 2^31 = 2,147,483,648 cycles = 159.1 s. So the fallback fires about
  //! 199 s after reset. The measured boot reaches the ERRFATAL about 143 s
  //! after the boot command, and the dump follows cap_done by the ~10 s
  //! hold_cnt delay, so a real LA match still dumps first with room to spare.
  reg [31:0] fallback_cnt = 32'd0;
  reg [ 1:0] post_word    = 2'd0;   //! which half of the tick to emit after the trigger
  reg [39:0] tick_at_trig = 40'd0;  //! tickWide latched the cycle the trigger fired
  //! LOOP SUPPRESSOR (22-AUG-2026). Run 3 spent 465 of its 502 ring entries on
  //! ONE three-instruction loop - the ERRFATAL console print at 0o31444 /
  //! 0o31445 / 0o31446 spinning on the 9600-baud transmitter - so the trail
  //! never reached back to the page fault. These hold the last FOUR words
  //! actually written to the ring; a word that matches any of them is not
  //! written again, which collapses any loop of up to four instructions to its
  //! first iteration and leaves the 512 entries to cover real forward progress.
  //!
  //! The comparison is on the FULL 20-bit word {PIL,P}, not on P alone, so the
  //! same address executed at a different interrupt level is still recorded -
  //! a level switch must never be swallowed by the filter.
  //!
  //! Once the loop's three words occupy p_r0..p_r2 the shift register stops
  //! moving (every iteration matches), so suppression is stable for as long as
  //! the loop runs, however many millions of times it goes round.
  //!
  //! COST OF THIS FILTER, stated so the trail is not misread: the dump is a
  //! list of DISTINCT-within-4 program counters, NOT an instruction count. A
  //! tight loop appears once. Nothing in the trail says how long anything took.
  reg [19:0] p_r0 = 20'd0, p_r1 = 20'd0, p_r2 = 20'd0, p_r3 = 20'd0;
`endif
`ifdef TANG_JPL_CAPTURE
  //! MICROCODE-ADDRESS CAPTURE across the two JPL I 111 instructions.
  reg [12:0] csa_cap_prev = 13'd0;  //! last CSA written to the ring
  reg [15:0] pc_cap_prev  = 16'd0;  //! last PC written to the ring
  reg [15:0] fid_cap_prev = 16'd0;  //! last FIDBO word written to the ring
  reg [13:0] ppn_cap_prev = 14'd0;  //! last PPN written to the ring
  reg        jpl_win      = 1'b0;   //! open while the window runs
  reg        jpl_done     = 1'b0;   //! one window only
  reg [ 9:0] jpl_cnt      = 10'd0;  //! hard bound on the window
`endif
  reg wdec_d2;
  reg [3:0] pil_prev;
  // TRAPN is ACTIVE LOW and is asserted only for the dispatch microcycle, so
  // its previous value is what makes a one-record-per-trap strobe possible.
  // Sampling at the PIL->14 transition instead is TOO LATE: measured
  // 11-AUG-2026, all 255 records came back TVEC=15 TRAPN=1, the idle state.
  reg       trapn_prev;

  // Sticky "has this machine EVER executed the page-in microcode" flags.
  // CLPT = 0o5705 unmaps a victim page, ENPT = 0o5706 maps the new one in.
  // Set once and never cleared, so a single ring dump answers the question
  // even though the ring itself only covers a few microseconds.
  reg       seen_enpt, seen_clpt;
  always @(posedge clk2x or negedge sys_rst_n) begin
    if (!sys_rst_n) begin
      seen_enpt <= 1'b0;
      seen_clpt <= 1'b0;
    end else begin
      if (CSA_12_0 == 13'o5706) seen_enpt <= 1'b1;
      if (CSA_12_0 == 13'o5705) seen_clpt <= 1'b1;
    end
  end
  reg [4:0] estate_prev;
  reg [7:0] rxraw_prev;
  reg [31:0] lba_prev;
  reg [15:0] wdata_prev;
  reg [12:0] csa_prev;
  reg [21:0] csa_stable;   // clk2x cycles the microcode CSA has been unchanged
`ifdef TANG_PF_CAPTURE
  // ---------------------------------------------------------------------------
  // FIRST PAGE-FAULT FREEZE REGISTER readout (21-AUG-2026).
  //
  // ND120_PF_CAPTURE (inside CGA) froze the trap-logic inputs at the TCLK edge
  // that latched a page-fault vector. It emits that 56-bit word through
  // XMIC_DBG_15_0 as four 14-bit slices, slice index in [15:14]:
  //
  //   slice 0 -> frozen[13:0]   PT_15_9[6:0], VACC, LA[5:0]
  //   slice 1 -> frozen[27:14]  LA[13:6]
  //   slice 2 -> frozen[41:28]  TVEC, PVIOL, RESTR, ptram CS_n/OE_n,
  //                             captured(bit 2), strobes_valid(bit 3), cycle[9:0]
  //   slice 3 -> frozen[55:42]  cycle[23:10]
  //
  // Full layout and the decode are in
  //   Verilog/fpga/tang-nano-20k/PLAN-pagefault-root-cause.md (retired, git 043c460)
  //
  // NOTE: while this define is set, XMIC_DBG_15_0 carries the capture readout,
  // NOT the microsequencer address-advance probe. Do not decode it as the latter.
  //
  // Trigger: the frozen word's `captured` flag is bit 30, which lands in slice 2
  // at bit 2. Latch it when that slice goes past, so the dump fires once the
  // evidence actually exists rather than on a rotation of an empty register.
  // Slice index is THREE bits ([15:13]) and the payload is 13 bits. The
  // `captured` flag is frozen[30], and slice 2 carries frozen[38:26], so it
  // arrives at payload bit 30-26 = 4.
  //
  // Measured 21-AUG-2026: this trigger was left decoding the OLD 2-bit index
  // and bit 2 after the word was widened. `[15:14] == 2'd2` then matched
  // slices 4 AND 5, bit 2 of those payloads is arbitrary, so pf_seen set at
  // power-up, the dumper took the TX pin immediately and streamed zeros. The
  // layout has THREE consumers - this file, ND120_PF_CAPTURE.v and
  // pf_capture_run.py - and all three must move together.
  reg  pf_seen = 1'b0;
  reg [1:0] pf_s5_cnt = 2'd0;
  reg [2:0] pf_slice_prev;
  always @(posedge clk_cpu) begin
    pf_slice_prev <= s_xmic_dbg[15:13];
    // Trigger on c_next_valid (frozen[71]), NOT on `captured` (frozen[30]).
    // c_next_valid means the DISCRIMINATOR sample - PT one capturing edge
    // AFTER the fault - has actually been taken. Triggering on `captured`
    // dumped the ring before that sample existed and the readout came back
    // "NOT TAKEN", losing the one field that separates a real page fault from
    // a late-arriving page-table entry. frozen[71] is slice 5 payload bit 6.
    // Fire only after a COMPLETE post-capture rotation is in the ring.
    //
    // Measured 21-AUG-2026: triggering on the first sighting of slice 5 ended
    // the dump immediately after it, so the rotation never wrapped back to
    // slice 0 and the ring held only the STALE pre-capture slice 0 (501 copies
    // of 0x0000). Slice 0 carries c_pt and c_vacc, so both came back as zeros
    // that were never measured - which is what produced the impossible
    // "PVIOL=1 with VACC=0" word.
    //
    // Counting two sightings of slice 5 guarantees slices 0..5 have all been
    // emitted from the frozen, immutable word before the dump starts.
    // Trigger on the LAST slice (7), not on a captured-only field. The readout
    // now rotates either after a capture OR after a census timeout, so slice 7
    // appearing twice means a complete word is in the ring in BOTH cases -
    // including the case where nothing was captured and only the census has
    // anything to say.
    if ((s_xmic_dbg[15:13] == 3'd7) &&
        (s_xmic_dbg[15:13] != pf_slice_prev)) begin
      if (pf_s5_cnt == 2'd1) pf_seen <= 1'b1;
      else                   pf_s5_cnt <= pf_s5_cnt + 1'b1;
    end
  end

  wire [19:0] s_cap_src   = {4'd0, s_xmic_dbg[15:0]};
  // One sample per slice: the slice rotates far slower than clk_cpu, so a
  // change-detect records each slice exactly once instead of thousands of times.
  wire        s_cap_stb   = (s_xmic_dbg[15:13] != pf_slice_prev);
  wire        s_cap_arm   = cap_armed;
  /* verilator lint_off UNUSEDSIGNAL */
  wire        s_hang      = &csa_stable;   // deliberately NOT used - see below
  /* verilator lint_on UNUSEDSIGNAL */
  // TRIGGER ON THE CAPTURED FAULT ONLY. Do NOT or-in s_hang.
  //
  // Measured 21-AUG-2026: with `pf_seen || s_hang` the board emitted 7940
  // NUL bytes and nothing else. s_hang is "CSA unchanged for 2^22 cycles",
  // which is exactly what the WCS microcode load looks like, so the dump
  // triggered during the load, dump_fin latched permanently, and the dumper
  // held the TX pin forever. The console never spoke, the boot command was
  // never accepted, and no page fault could ever happen. The comment on
  // arm_cnt above warns about precisely this.
  //
  // This experiment NEEDS the console alive: a human types '20500&' and the
  // fault arrives 25+ minutes later. Only the real capture may take the pin.
  wire        s_cap_event = pf_seen;
  // All six slices must be recorded AFTER the trigger, and the rotation is
  // slow, so keep collecting well past the event.
  localparam [8:0] CAP_POST = 9'd2;
`elsif TANG_PTWR_CAPTURE
  // ---------------------------------------------------------------------------
  // PAGE-TABLE WRITE HISTORY (23-AUG-2026, zero-read campaign).
  //
  // Measured that day on silicon: the fault at the VA-064540 page dispatches
  // (TVEC=1) and the handler reads the CORRECT page in PGS - yet the page
  // never becomes resident and refaults forever. Surviving suspect: the
  // handler's page-table entry WRITE is lost or lands at the wrong map-RAM
  // index. This variant records the write stream itself.
  //
  // Source = DBG_PTW from CPU_MMU_24 (via CPU_15/ND3202D/ND120_CORE): on each
  // PT-chip write strobe (EPT_n low & WMAP_n low) two words appear on
  // consecutive sysclks, idle 16'h0000 otherwise:
  //   word A = {2'b10, addr[10:0], data[15:13]}   addr = raw LA_20_10 index
  //   word B = {2'b11, data[12:0], 1'b0}
  // The ring strobes only on nonzero-tag words, so 512 entries = the last
  // ~256 writes (addr+data pairs) before the trigger.
  //
  // Trigger = the ERRFATAL printer signature (PIL 14 with the microcode at
  // 004546, same event as TANG_PC_HISTORY) OR the frozen-CSA detector.
  // CAP_POST is small so post-trigger noise cannot evict the history.
  //
  // The decode: for each A/B pair, addr XOR 0o1400 (top two table bits are
  // complemented) = the SOFTWARE page-table index. The question the dump
  // answers: does any write land at software 0o432 (raw 0o1032, the page that
  // refaults), and if not, where DID the handler's writes go?
  wire [19:0] s_cap_src   = {4'hB, s_dbg_ptw};
  wire        s_cap_stb   = (s_dbg_ptw[15:14] != 2'b00);
  wire        s_cap_arm   = cap_armed;
  wire        s_hang      = &csa_stable;
  wire        s_cap_event = ((s_pil_3_0 == 4'd14) && (s_xmic_dbg[15:0] == 16'o004546))
                          || s_hang;
  localparam [8:0] CAP_POST = 9'd8;
`elsif TANG_STAGE_TIMER
  // ---------------------------------------------------------------------------
  // WHERE DOES A DISC OPERATION'S ~1 SECOND GO?  (24-AUG-2026)
  //
  // Measured: the oracle needs 221 disc operations to start S3; the Tang takes
  // 234 s for the same step, and led[0] blinks about once a second - so the
  // COUNT is sane and each operation costs ~1 s. The known terms account for
  // ~23 ms of that: 8 ms modelled drive latency (ND120_CORE.v:373), ~6 ms of
  // SD wire time, ~7.7 ms of DMA, ~1.4 ms of CDC handshake. ~977 ms per
  // operation is unexplained, and guessing which stage owns it has been wrong
  // twice, so this measures every stage instead.
  //
  // Free-running tick accumulators, all on clk_cpu, dumped as totals:
  //   t_wd    ticks while the Winchester controller is ACTIVE (a command is
  //           outstanding) - the wall clock a disc operation costs
  //   t_dma   ticks while that card's DMA master is busy
  //   t_sd    ticks while the SD writer is reading the card
  //   t_cache ticks while a cache lookup is outstanding
  //   n_ops   completed operations (falling edge of active)
  //   t_all   total ticks, so shares can be worked out
  //
  // READING IT: t_wd/n_ops is the true per-operation cost. If t_dma, t_sd and
  // t_cache together are far less than t_wd, the card is idle and WAITING -
  // and none of the three suspects is the answer.
  reg [39:0] t_all = 40'd0, t_wd = 40'd0, t_dma = 40'd0, t_sd = 40'd0, t_cache = 40'd0;
  reg [23:0] n_ops = 24'd0;
  reg        wd_act_d = 1'b0;
  always @(posedge clk_cpu) begin
    t_all <= t_all + 40'd1;
    if (s_dbg_wdstage[0])  t_wd    <= t_wd    + 40'd1;
    if (s_dbg_wdstage[1])  t_dma   <= t_dma   + 40'd1;
    if (s_dbg_sd_busy)     t_sd    <= t_sd    + 40'd1;
    if (s_dbg_cache_pend)  t_cache <= t_cache + 40'd1;
    wd_act_d <= s_dbg_wdstage[0];
    if (wd_act_d && !s_dbg_wdstage[0]) n_ops <= n_ops + 24'd1;
  end

  // Dump on the frozen-CSA detector or on a long timer, whichever comes first;
  // the operator simply lets S3 run and the totals cover the whole window.
  // 36 bits: bit 33 is the ~5.3 minute mark at 27 MHz (2^33 / 27e6 = 318 s),
  // long enough to cover a cold S3 start. A 32-bit counter cannot be indexed
  // at [33] - Gowin catches that as EX3784 where a -Wno-fatal lint does not.
  reg [35:0] st_tmr = 36'd0;
  always @(posedge clk_cpu) if (cap_armed && !cap_trig) st_tmr <= st_tmr + 36'd1;

  // Serialise the counters into the ring CONTINUOUSLY, on clk2x (the ring's
  // own domain), so the last 18 records before the freeze are always a fresh
  // snapshot. The first version emitted records only AFTER cap_trig, which
  // cannot work: this ring is a PRE-trigger recorder and `cap_post` counts
  // down in clk2x CYCLES, not strobes, so the window shuts microseconds after
  // the trigger and nothing was ever written. Measured 24-AUG-2026: zero
  // records dumped.
  reg [4:0]  st_idx = 5'd0;
  reg [9:0]  st_div = 10'd0;
  reg [19:0] st_word = 20'd0;
  reg        st_stb = 1'b0;
  always @(posedge clk2x) begin
    st_stb <= 1'b0;
    st_div <= st_div + 10'd1;
    if (st_div == 10'd0) begin          // one record per 1024 clk2x cycles
      st_stb <= 1'b1;
      st_idx <= (st_idx == 5'd17) ? 5'd0 : st_idx + 5'd1;
      case (st_idx)
        5'd0:  st_word <= {4'h1, t_all[15:0]};
        5'd1:  st_word <= {4'h1, t_all[31:16]};
        5'd2:  st_word <= {4'h1, 8'd0, t_all[39:32]};
        5'd3:  st_word <= {4'h2, t_wd[15:0]};
        5'd4:  st_word <= {4'h2, t_wd[31:16]};
        5'd5:  st_word <= {4'h2, 8'd0, t_wd[39:32]};
        5'd6:  st_word <= {4'h3, t_dma[15:0]};
        5'd7:  st_word <= {4'h3, t_dma[31:16]};
        5'd8:  st_word <= {4'h3, 8'd0, t_dma[39:32]};
        5'd9:  st_word <= {4'h4, t_sd[15:0]};
        5'd10: st_word <= {4'h4, t_sd[31:16]};
        5'd11: st_word <= {4'h4, 8'd0, t_sd[39:32]};
        5'd12: st_word <= {4'h5, t_cache[15:0]};
        5'd13: st_word <= {4'h5, t_cache[31:16]};
        5'd14: st_word <= {4'h5, 8'd0, t_cache[39:32]};
        5'd15: st_word <= {4'h6, n_ops[15:0]};
        5'd16: st_word <= {4'h6, 8'd0, n_ops[23:16]};
        default: st_word <= {4'h7, 16'hA5A5};   // frame marker
      endcase
    end
  end

  wire [19:0] s_cap_src   = st_word;
  wire        s_cap_stb   = st_stb;
  wire        s_cap_arm   = cap_armed;
  wire        s_hang      = &csa_stable;
  // freeze ~318 s after arming (2^33 clk_cpu ticks at 27 MHz) - mid-S3
  wire        s_cap_event = st_tmr[33] || s_hang;
  localparam [8:0] CAP_POST = 9'd2;
`elsif TANG_PGW_CAPTURE
  // ---------------------------------------------------------------------------
  // DID ANY WRITE EVER REACH THE PAGE THE CPU FETCHES ZEROS FROM?
  // (24-AUG-2026, run 16)
  //
  // Run 15 measured the fetch at 064544 resolving to PPN 0o3770 = physical
  // page 2040 = {BANK2, row 1016} - REAL, POPULATED memory that still reads
  // 000000. So the disc data was never stored there. This records every
  // access to that page's 8-page neighbourhood (rows 1016..1023, both banks)
  // AT THE SDRAM BRIDGE, the last point before the chip, so a transfer that
  // never arrives cannot be mistaken for one that did.
  //
  //   {4'hB, DBG_PGW}   tag 10 = write word A (bank+row+data[15:13])
  //                     tag 11 = write word B (data[12:0])
  //                     tag 01 = a READ of the same window
  //
  // Reading it: writes present with real data -> the data WAS stored and
  // something cleared or re-mapped it afterwards. Only reads, no writes ->
  // the Winchester transfer never targeted this page, and the next question
  // is the physical address the DMA presents against the one the MMU resolves.
  wire        pgw_act   = (s_dbg_pgw[15:14] != 2'b00);
  reg  [15:0] pgw_prev  = 16'd0;
  always @(posedge clk2x) pgw_prev <= s_dbg_pgw;
  wire [19:0] s_cap_src = {4'hB, s_dbg_pgw};
  wire        s_cap_stb = pgw_act && (s_dbg_pgw != pgw_prev);
  wire        s_cap_arm = cap_armed;
  wire        s_hang    = &csa_stable;
  wire        s_cap_event = ((s_pil_3_0 == 4'd14) && (s_xmic_dbg[15:0] == 16'o004546))
                          || s_hang;
  localparam [8:0] CAP_POST = 9'd8;
`elsif TANG_PTORD_CAPTURE
  // ---------------------------------------------------------------------------
  // ORDERING: PAGE-TABLE WRITES vs NO-PERMIT ACCESSES, ONE PAGE, ONE RING
  // (23-AUG-2026, Phase 1b of PLAN-pf-campaign-prio.md, git 043c460)
  //
  // THE CONTRADICTION THIS SETTLES. Both were measured on silicon on the same
  // boot, and they cannot both describe the same moment:
  //   - the handler WRITES a granting entry (raw index 0o1032, data 066001)
  //     for the page that keeps faulting;
  //   - the freeze register still catches a COMMITTED ACCESS at that same page
  //     whose entry grants nothing (PT[6:4] == 000).
  //
  // No shared timestamp is needed: one ring, written in time order, IS the
  // order. Both sources strobe the same ring.
  //   {4'hB, DBG_PTW}                 page-table write traffic at THIS index
  //                                   only (tag 10 = pair word A, 11 = word B,
  //                                   01 = IDB->PT attempt); same word format
  //                                   as TANG_PTWR_CAPTURE, so
  //                                   ptwr_capture_decode.py decodes it
  //   {4'hC, 4'd0, acc_cnt[11:0]}     a no-permit access at the matched page
  //   {4'hD, 4'd0, flt_cnt[11:0]}     a page-fault vector at the matched page
  //
  // READING THE DUMP: if a 4'hC marker appears AFTER a granting write pair,
  // the map RAM is not retaining or not returning the entry - a defect in the
  // page-table RAM read path (the Issue-D family). If every 4'hC precedes the
  // write, paging works and the halt cause is elsewhere.
  //
  // CLOCK NOTE: this ring runs on clk2x while the CPU core runs on clk_cpu
  // (half that rate), so every core-cycle event is visible for TWO ring edges.
  // Everything below is edge-detected on clk2x so one event makes one record -
  // otherwise every write word and every access would be counted twice.
  //
  // EVICTION GUARD. The page refaults forever, so raw access markers alone
  // would fill all 512 entries and push the writes out - and an evicted write
  // reads exactly like a write that never happened. So at most 4 consecutive
  // access markers are recorded between writes; the 12-bit counter carried in
  // every marker states the TRUE total, making any suppression visible rather
  // than silent.
  reg [15:0] po_ptw_d   = 16'd0;
  reg        po_acc_d   = 1'b0;
  reg        po_flt_d   = 1'b0;
  always @(posedge clk2x) begin
    po_ptw_d <= s_dbg_ptw;
    po_acc_d <= s_pf_captured[1];
    po_flt_d <= s_pf_captured[2];
  end
  wire       po_ptw_new = (s_dbg_ptw != po_ptw_d) && (s_dbg_ptw[15:14] != 2'b00);
  wire       po_acc_pls = s_pf_captured[1] && !po_acc_d;
  wire       po_flt_pls = s_pf_captured[2] && !po_flt_d;

  wire       po_ptw_a   = po_ptw_new && (s_dbg_ptw[15:14] == 2'b10);
  wire       po_ptw_b   = po_ptw_new && (s_dbg_ptw[15:14] == 2'b11);
  wire       po_ptw_att = po_ptw_new && (s_dbg_ptw[15:14] == 2'b01);
  // word A / attempt carry addr[10:0] at [13:3]; LA[19:10] = word bits [12:3]
  wire       po_idx_hit = (s_dbg_ptw[12:3] == 10'o1032);
  reg        po_pair_hit = 1'b0;   // word A of the current pair matched
  wire       po_wr_rec = (po_ptw_a   && po_idx_hit)
                      || (po_ptw_att && po_idx_hit)
                      || (po_ptw_b   && po_pair_hit);

  reg [11:0] po_acc_cnt = 12'd0;
  reg [11:0] po_flt_cnt = 12'd0;
  reg [ 2:0] po_run     = 3'd0;     // access markers recorded since last write
  reg        po_pend_acc = 1'b0;    // event arrived while the ring was busy
  reg        po_pend_flt = 1'b0;
  wire       po_emit_acc = !po_wr_rec && (po_pend_acc || po_acc_pls)
                                      && (po_run < 3'd4);
  wire       po_emit_flt = !po_wr_rec && !po_emit_acc && (po_pend_flt || po_flt_pls);
  always @(posedge clk2x) begin
    if (po_ptw_a) po_pair_hit <= po_idx_hit;
    if (po_acc_pls) po_acc_cnt <= po_acc_cnt + 12'd1;
    if (po_flt_pls) po_flt_cnt <= po_flt_cnt + 12'd1;
    // pending: set on the pulse, cleared when the word actually reaches the ring
    if (po_acc_pls && !po_emit_acc) po_pend_acc <= 1'b1;
    else if (po_emit_acc)           po_pend_acc <= 1'b0;
    if (po_flt_pls && !po_emit_flt) po_pend_flt <= 1'b1;
    else if (po_emit_flt)           po_pend_flt <= 1'b0;
    if (po_wr_rec)        po_run <= 3'd0;
    else if (po_emit_acc) po_run <= po_run + 3'd1;
  end

  wire [19:0] s_cap_src = po_wr_rec   ? {4'hB, s_dbg_ptw}
                        : po_emit_acc ? {4'hC, 4'd0, po_acc_cnt}
                                      : {4'hD, 4'd0, po_flt_cnt};
  wire        s_cap_stb   = po_wr_rec || po_emit_acc || po_emit_flt;
  wire        s_cap_arm   = cap_armed;
  wire        s_hang      = &csa_stable;
  wire        s_cap_event = ((s_pil_3_0 == 4'd14) && (s_xmic_dbg[15:0] == 16'o004546))
                          || s_hang;
  localparam [8:0] CAP_POST = 9'd8;
`elsif TANG_PFLOG_CAPTURE
  // ---------------------------------------------------------------------------
  // EVERY PAGE FAULT, ANY ADDRESS - THE LAST ONES ARE THE FATAL ONE
  // (23-AUG-2026, run 11, after Phase 1b closed the 0o432 question)
  //
  // WHAT RUN 10 SETTLED. For raw page 0o1032 (software 0o432) the ordering
  // ring showed: entry cleared to 000000, two no-permit accesses, two page
  // faults, then the handler writes 062001 and 066001 - both GRANTING. Every
  // no-permit access precedes the granting write. That page pages in exactly
  // as it should, so "a page is read as zeros instead of faulting" is dead as
  // an explanation of the halt.
  //
  // WHAT IS STILL UNKNOWN. The halt reports Perror 064406, level 1, IIC 3
  // Page Fault, with NPIT/APIT = 000012 / 000007. Which page does THAT fault
  // hit, and what did its page-table entry say? This ring answers exactly
  // that: one record per page-fault vector transition at ANY address,
  //
  //     record[19:17] = 0
  //     record[16:10] = PT[15:9] of that fault  (WPM RPM FPM WIP PGU ring1 ring0)
  //     record[ 9: 0] = LA[19:10] - the raw page-table index
  //
  // frozen by the ERRFATAL printer signature. The LAST records are the fault
  // that halts SINTRAN, named by page and by entry contents.
  //
  // CLOCK NOTE: the ring runs on clk2x, the core on clk_cpu (half the rate),
  // so the one-core-cycle pulse is edge-detected here to make one record.
  reg        pl_d = 1'b0;
  always @(posedge clk2x) pl_d <= s_pf_captured[3];
  wire       pl_pls = s_pf_captured[3] && !pl_d;

  wire [19:0] s_cap_src   = {3'd0, s_pf_captured[20:14], s_pf_captured[13:4]};
  wire        s_cap_stb   = pl_pls;
  wire        s_cap_arm   = cap_armed;
  wire        s_hang      = &csa_stable;
  wire        s_cap_event = ((s_pil_3_0 == 4'd14) && (s_xmic_dbg[15:0] == 16'o004546))
                          || s_hang;
  localparam [8:0] CAP_POST = 9'd0;   // the fatal fault is the LAST record
`elsif TANG_JPL_CAPTURE
  // ---------------------------------------------------------------------------
  // MICROCODE ADDRESS ACROSS THE TWO JPL I 111 INSTRUCTIONS (22-AUG-2026)
  //
  // WHAT IS BEING MEASURED, as opposed to inferred. The Tang executes
  // 064540..064547 straight through and takes neither of the two JPL I 111
  // calls at 064544/064545 that the oracle takes (to 004600 and 052031, 170
  // instructions). A story that FITS is that the decode lost instruction bit
  // 11, which is the only bit separating JPL (134000-137000) from the
  // conditional jumps (130000-133000): 135111 would become 131111, JAZ 111,
  // and with A = 144017 that does not jump - it falls through, exactly as
  // observed, on both instructions.
  //
  // That is a GUESS and it has a hole: SINTRAN executes JPL constantly for the
  // 150 seconds before this point, so bit 11 cannot be generally lost. This
  // probe replaces the guess with a measurement - it records the microcode
  // address the decode actually dispatched to.
  //
  // From Verilog/tests/instruction-verify/ND110-ND120-MIC-MAP.md:
  //
  //     007300 JAP   007304 JAN   007310 JAZ   007314 JAF
  //     007320 JPC   007324 JNC   007330 JXZ   007334 JXN
  //     007340 JPL
  //
  // CSA reaching 007340 means the decode produced JPL and the fault is inside
  // the JPL microroutine. CSA landing on one of 007300-007334 means the decode
  // produced a conditional jump instead, and the bit-11 story stops being a
  // guess. CSA going somewhere else again means neither.
  //
  // CSA_12_0 is already routed to this level and is used by the grant-capture
  // and trap-capture variants, so unlike the last two probes there is no new
  // bus or strobe whose timing has to be assumed.
  //
  // Two values are tagged into the ring so the microcode trail can be lined up
  // against the instruction boundaries. They are separately REGISTERED values
  // compared against their own previous copies - not two buses assumed valid in
  // the same cycle, which is the mistake that voided the address capture.
  //
  //     tag 0   CSA_12_0, the microcode address
  //     tag 1   the fetch-qualified program counter
  wire        csa_chg     = (CSA_12_0 != csa_cap_prev);
  wire        pc_chg      = (s_xmic_dbg[15:0] != pc_cap_prev);
  // 24-AUG: tag 2 = FIDBO, the word the CPU actually received. Run 13 measured
  // the DISPATCHED MICROADDRESS (006000 = STZ) and the instruction word was
  // INFERRED from it. That inference has to be replaced by a measurement:
  // FIDBO says whether memory really delivered 000000, or delivered 135111 and
  // the decode dropped it.
  wire        fid_chg     = (s_debug_fidbo != fid_cap_prev);
  // tag 3 = PPN[23:10], the PHYSICAL page the access reaches. PPN20 = [10] and
  // PPN21 = [11] are the bank-decode inputs (PAL_44445B.v:65-67):
  //   PPN[21:20] = 00 -> BANK0, 01 -> BANK2, 10 -> BANK1, 11 -> no bank.
  // On this board BANK1 IS NOT POPULATED - MEM_RAM_49_SDRAM.v:18 says
  // "never written, reads as 0" and line 415 drops the access. So a page whose
  // PPN lands in BANK1 is mapped, granted, writable-looking, and reads ZERO.
  wire        ppn_chg     = (s_dbg_ppn != ppn_cap_prev);
  wire [19:0] s_cap_src   = csa_chg ? {4'd0, 3'b000, CSA_12_0}
                          : pc_chg  ? {4'd1, s_xmic_dbg[15:0]}
                          : fid_chg ? {4'd2, s_debug_fidbo}
                                    : {4'd3, 2'b00, s_dbg_ppn};
  wire        s_cap_stb   = jpl_win && (csa_chg || pc_chg || fid_chg || ppn_chg);
  wire        s_cap_arm   = cap_armed;
  wire        s_cap_event = jpl_done && !jpl_win;
  localparam [8:0] CAP_POST = 9'd0;
`elsif TANG_PFPATH_CAPTURE
  // ---------------------------------------------------------------------------
  // HANDLER PATH AFTER THE 0o1032 FAULT (23-AUG-2026).
  // Measured so far on silicon: the fault dispatches (TVEC=1), PGS is correct,
  // page-table writes work, LIMCHECK's segment-table reads match the oracle -
  // so SINTRAN's PAGEFAULT must reach CALL SEGIN (034721) and yet the page is
  // never written. This ring records the {PIL,P} trail of the handler levels
  // (14 and 3) starting at the freeze register's capture of the first
  // no-permit access to raw page 0o1032, and freezes on the first return to
  // level 1 afterwards: the last 512 handler addresses before SINTRAN gave
  // the program back. Compare with the oracle's trail for the same fault
  // ($ND120_ORACLE_DIR/oracle_fault49_trail.txt): the first address where
  // the two paths split names the instruction whose result differs.
  // P register on XMIC_DBG via ND120_PC_ON_DBG_PORT (set in CGA.v for this
  // define); the freeze register is instantiated but its readout is not on
  // the port - only its `captured` flag comes up here.
  wire [19:0] pp_word     = {s_pil_3_0, s_xmic_dbg[15:0]};
  reg  [19:0] pp_prev = 20'd0, pp_r0 = 20'd0, pp_r1 = 20'd0, pp_r2 = 20'd0, pp_r3 = 20'd0;
  reg         pp_seen = 1'b0;     // freeze register has captured
  reg  [3:0]  pp_pil_prev = 4'd0;
  always @(posedge clk_cpu) begin
    pp_prev     <= pp_word;
    pp_pil_prev <= s_pil_3_0;
    if (s_pf_captured[0]) pp_seen <= 1'b1;
    if (pp_word != pp_prev) begin
      pp_r3 <= pp_r2; pp_r2 <= pp_r1; pp_r1 <= pp_r0; pp_r0 <= pp_word;
    end
  end
  wire        pp_dup      = (pp_word == pp_r0) || (pp_word == pp_r1)
                         || (pp_word == pp_r2) || (pp_word == pp_r3);
  wire        pp_handler  = (s_pil_3_0 == 4'd14) || (s_pil_3_0 == 4'd3);
  wire [19:0] s_cap_src   = pp_word;
  // RUN 3 (23-AUG): record ALL levels after the capture - the oracle services
  // this fault through levels 3, 2, 13, 11 and back to 1; the Tang's level
  // sequence after CALLMLEV's WAIT is the open question.
  /* verilator lint_off UNUSEDSIGNAL */
  wire        pp_handler_unused = pp_handler;
  /* verilator lint_on UNUSEDSIGNAL */
  wire        s_cap_stb   = pp_seen && (pp_word != pp_prev) && !pp_dup;
  wire        s_cap_arm   = cap_armed;
  /* verilator lint_off UNUSEDSIGNAL */
  wire        s_hang      = &csa_stable;   // not used as a trigger here
  /* verilator lint_on UNUSEDSIGNAL */
  // Freeze on the first EXIT FROM LEVEL 3 after the capture: PAGEFAULT runs
  // at level 3 (IPAGFAULT hands over via ACTMON) and ends with a WAIT, so
  // the ring then holds the tail of PAGEFAULT's own path. Run 1 froze on the
  // return to level 1 instead and caught SINTRAN's later segment-setup
  // sequence (034100-034310), with the fault handling already evicted.
  // Note: the P ring records PC+1 transients on jumps/calls/WAIT (the P
  // register steps before the target loads) - decode accordingly.
  // RUN 3: freeze on the first return to level 1 after the capture (where the
  // oracle re-executes 064540 with the page resident).
  // RUN 4: freeze on the first entry to level 3 FROM level 2 after the capture.
  // Oracle: CALLMLEV's WAIT drops 3->2, the level-2 routine at 035204 runs ~50
  // instructions, then 2->3 re-enters at 034020 (EXITA) and SEGIN continues.
  // The ring then holds PAGEFAULT, CALLMLEV, the level-2 routine and the
  // re-entry point - the last sample names where level 3 resumes on the Tang.
  // RUN 5: freeze a fixed number of samples AFTER the SEGIN call at P=036653,
  // so the ring holds the CALLMLEV(MLRESERVE) handoff and its return - does
  // control reach 036657, and with what A. pp_at_call latches on P=036653
  // (level 3); the ring then fills CAP_POST-limited and freezes.
  // RUN 6: latch when SEGIN issues CALL CALLMLEV(MLRESERVE) at 036653, then
  // freeze when control next reaches the RETURN region at level 3:
  //   036657 = returned, then IF <0 test;  036660 = A<0 wait path (MLRESERVE
  //   failed);  036724 = A>=0, disc read proceeds. If NONE is ever reached
  //   the capture falls back to the ring's timeout and none appears in the
  //   tail = control never returned from CALLMLEV to SEGIN (control-flow bug).
  reg pp_at_call = 1'b0;
  always @(posedge clk_cpu)
    if (pp_seen && (s_pil_3_0==4'd3) && (s_xmic_dbg[15:0]==16'o036653)) pp_at_call <= 1'b1;
  // RUN 8: the P ring samples P with a +-1 ambiguity, so a single address
  // cannot decide a branch. Trigger only on a RANGE well inside SEGIN's
  // disc-read body (036724..036736) - three words wide, so the ambiguity
  // cannot fake it. If this freezes, the Tang DOES take the disc-read path.
  // If instead the timeout fires, the ring tail shows what ran instead.
  // RUN 9: freeze the ring the moment the freeze register captures the
  // no-permit access to the FATAL page (raw 0o1360) - the ring then holds
  // the ~500 instructions that led to it, across all levels.
  wire        pp_ret = 1'b1;
  // own timeout counter: fallback_cnt is declared inside the PC_HISTORY block
  // and does not exist in this variant (build 6 failed on exactly that).
  reg [31:0] pp_to = 32'd0;
  always @(posedge clk_cpu) if (cap_armed && !pp_to[31]) pp_to <= pp_to + 1'b1;
  wire        s_cap_event = (pp_at_call && pp_ret) || pp_to[31];
  localparam [8:0] CAP_POST = 9'd2;
`elsif TANG_PC_HISTORY
  // ---------------------------------------------------------------------------
  // PC HISTORY - the trail of program counters leading into the ND-500 window
  // access (21-AUG-2026).
  //
  // WHAT IS RECORDED: {PIL[3:0], P[15:0]}. P arrives on the CGA debug port,
  // which carries s_pr_15_0 while TANG_PC_HISTORY is defined (CGA.v). PIL is
  // already routed here for the grant probe.
  //
  // TRIGGER: an ACCESS to the ND-500 window page - no fault needed. The nd100x
  // oracle NEVER touches this page: measured zero occurrences of PGS PNUMB
  // 0o760 in a complete 25,000,000-instruction boot, while the page next to it
  // (0o761) is the most-used page in table 7 (288 times). So our first access
  // to it IS the divergence and identifies itself; what is missing is how we
  // got there, which is exactly what the ring holds.
  //
  // RAW vs SOFTWARE PNUMB - the trap that has already cost this project a day:
  // the page-table index has its TOP TWO BITS COMPLEMENTED in hardware and the
  // microcode un-inverts them (TRA PGS XORs 0o1400). SINTRAN's WNDN5 is the
  // SOFTWARE value 0o760, which is RAW 0o1360. LA_23_10[9:0] carries the RAW
  // LA[19:10], so the comparison below MUST use 0o1360. Matching 0o760 here
  // would silently watch software page 0o360 and never fire.
  //
  // WHY NOT A STREAM COMPARISON AGAINST THE ORACLE: it cannot work. SINTRAN
  // multiplexes level 1 among programs from the scheduler at PIL-2 032037, and
  // which program is dispatched depends on device and clock timing, so two
  // machines diverge at the first scheduling decision however correct both
  // CPUs are. Measured: the first 673 normalised level-1 instructions match
  // exactly, then the ND-120 runs program 043503 - which the oracle also runs
  // 21 times, just at a different moment.
  //
  // CAP_POST is small on purpose: the ring is 512 deep and the value is in the
  // instructions BEFORE the access, not after.
  //
  // NOTE: TANG_PF_CAPTURE must NOT be defined together with this - there is one
  // 16-bit CGA debug port and the capture block wins (see CGA.v). This build
  // therefore has no page-fault readout, which is why the trigger is formed
  // from LA here instead.
  //! The PC word. During the post-trigger window this is replaced by the
  //! latched tick so the dump carries WHEN the halt happened as well as the
  //! trail leading to it - see tick_at_trig below.
  wire [19:0] pc_word     = {s_pil_3_0, s_xmic_dbg[15:0]};
  wire [19:0] s_cap_src   = !cap_trig ? pc_word
                          : (post_word == 2'd0) ? tick_at_trig[39:20]
                                                : tick_at_trig[19:0];
  //! True when this word is one of the last four already recorded.
  wire        pc_dup      = (pc_word == p_r0) || (pc_word == p_r1)
                         || (pc_word == p_r2) || (pc_word == p_r3);
  //! LEVEL-14 CODE IS NOT RECORDED (22-AUG-2026, after run 4).
  //!
  //! Run 4 came back with all 502 entries at PIL 14 and only 30 distinct
  //! addresses: SINTRAN's error-message printer, a 27-address loop at
  //! 0o31354..0o31451 spinning on the 9600-baud console. The last-4 filter had
  //! already collapsed the 3-instruction inner loop; this was the loop behind
  //! it, and no filter of a sensible width reaches past it.
  //!
  //! The code that actually touched the ND-500 window page ran at the
  //! INTERRUPTED level, not at 14, and none of it was in the ring. Dropping
  //! PIL 14 hands all 512 entries to the levels that matter, so the trail ends
  //! at the last instruction executed before the trap was taken - which is the
  //! address to compare against the oracle.
  //!
  //! What this gives up: the trap handler's own path is now invisible. That is
  //! an acceptable trade because run 4 already recorded it in full.
  //!
  //! NARROWED FURTHER TO LEVEL 1 ONLY (22-AUG-2026, after run 5).
  //!
  //! Run 5 answered the first question and posed the next one. Its trail ended:
  //!
  //!     494  PIL 2   032037   SINTRAN scheduler dispatch
  //!     495  PIL 2   032040
  //!     496  PIL 1   032040
  //!     497  PIL 1   064404   the oracle NEVER executes this
  //!     498  PIL 1   064405   "
  //!     499  PIL 1   064406   "   <- this run's Perror
  //!     500  PIL 1   064407   "
  //!     501  PIL 12  004377   "
  //!
  //! Measured against the oracle histogram: 064367 runs 35 times and 064540
  //! runs once, but every address from 064370 to 064537 runs ZERO times in a
  //! 25,000,000-instruction boot. Those four are the divergence.
  //!
  //! Only FIVE of the 502 entries were level 1 - the rest were level 3 (362)
  //! and level 2 (134) - so there is no level-1 history to say HOW P reached
  //! 064404. Recording level 1 alone turns all 512 entries into that history,
  //! across many scheduler dispatches. A trail showing 064403 -> 064404 means
  //! level 1 walked in; a trail showing some other address -> 064404 means
  //! level 1's P was set to it, and the routine that set it is in the same
  //! trail.
  wire        s_cap_stb   = !cap_trig ? ((pc_word != pc_prev) && !pc_dup
                                          && (s_pil_3_0 == 4'd1))
                                      : (post_word < 2'd2);
  wire        s_cap_arm   = cap_armed;
  // EDGE-DETECTED, not a level. LA_23_10 holds its last value between accesses,
  // so a bare `== 0o1360` would keep matching long after the access and could
  // freeze the ring at an arbitrary later moment. Firing only when LA BECOMES
  // that value pins the freeze to the access itself.
  //
  // The ideal qualifier is VACC, which is what ND120_PF_CAPTURE gates its
  // identical comparison on - but VACC is not routed out of the CGA, and
  // getting it here means adding a port through ND3202D and ND120_CORE as
  // well. The residual risk this leaves is a genuine transient in which LA
  // settles on 0o1360 at a clk2x edge without a translated access. That is
  // detectable after the fact: the decoded trail would not end anywhere near
  // the ERRFATAL, and the run can simply be repeated.
  // TRIGGER = ENTERING THE MEASURED HALT LOOP (22-AUG-2026, run 3).
  //
  // Run 2 handed us this for free: its dump was 512 entries containing exactly
  // two values, PIL 14 with P = 0o4546 and 0o4547, 252 each - SINTRAN's
  // post-ERRFATAL halt loop. Freezing on the FIRST entry into that loop keeps
  // the 512 program counters that came BEFORE it, which is the ERRFATAL path
  // and whatever led there. Run 2 could not see them because the loop itself
  // flushed the ring before the fallback fired at ~199 s.
  //
  // Why not the page match: run 1 tried the full raw address 0o1360 and never
  // fired, and page-only (0o60 in any table) has no fault qualifier here - the
  // proven ND120_PF_CAPTURE pairs its page-only match with a fault vector, and
  // no fault signal is routed to this level. The halt loop is an exact,
  // MEASURED signature and needs no such qualifier.
  //
  // Caveat to check in the result: 512 entries may not reach back past the
  // ERRFATAL console print, which is long. If the trail is all print routine,
  // the next step is the two-pass tick approach - which is why the tick is
  // captured below.
  wire        s_cap_event = ((s_pil_3_0 == 4'd14) && (s_xmic_dbg[15:0] == 16'o004546))
                         || fallback_cnt[31];   // ~199 s after reset, safety net
  localparam [8:0] CAP_POST = 9'd16;
`elsif TANG_GRANT_CAPTURE
  // Word = {PIL[3:0], INTRQ, CSA[10:0]}.  INTRQ = ~DEBUG_INTRQ_n (already routed
  // to this top) shows WHEN the interrupt-request FF is asserted relative to the
  // 00017 dispatch: held-from-early (a level-held PAN, i.e. the free-running RTC,
  // taken at the first interrupt-enable point => deterministic step 18) vs a late
  // pulse. CSA[10:0] still covers the whole dispatch/level-switch region
  // (00017 / 03740 / 01xxx); the 06xxx/07xxx SETUP context was captured in v1.
  // Word = the 16-bit interrupt-request vector, active-HIGH (bit n set = IREQ[n]
  // pending). Trigger = PIL entering level 10. The 448 pre-trigger samples cover
  // the 00214 dispatch and the 00017/00053 RVECT read, so this shows EXACTLY
  // which request bit (if any) is pending when the spurious grant fires:
  //   all-zero  => a phantom grant with NO real request (empty-vector -> level 10)
  //   bit 0 set => a real level-10 (BINT10 terminal) request
  //   bit 8-15  => a HIGH-group/internal request collapsing to a level-10 read
  // Word = {PIL[3:0], CSA[11:0]} - the microcode path. Trigger on EITHER the
  // PIL->10 wedge OR the microcode HANGING (CSA unchanged for 2^22 clk2x ~ 78 ms
  // = the free-run 0! cold start stalled). 480 pre + 32 post, so the dump shows
  // the CSA sequence LEADING INTO the stall/wedge - i.e. exactly where and how
  // the cold start dies. This is the free-run root-cause tool (single-stepping
  // injects its own panel-stop PAN pulses; free-run does not, so this catches
  // the REAL 0! failure).
  // Word = the MEMORY ADDRESS being accessed, bits [23:8] = {LA_23_10[13:0],
  // CA_9_0[9:8]}. When the cold start stalls at STZ (06000) waiting for a memory
  // write to terminate, this captures WHICH address the write targets - the high
  // bits show the region (in-range main mem < addr 21, vs bit22/23 = storage /
  // out-of-range), which points at the SDRAM-controller condition that never
  // asserts TERM. Trigger = microcode HANG (CSA stable) or PIL->10.
  // Word = the CYCLE-FSM / arbitration state that gates TERM_n (why the STZ
  // memory write never terminates). DEBUG_CC_TERM = {TERM_n,CC3_n,CC2_n,CC1_n,
  // CC0_n}. Plus INTRQ / REFRQ / FETCH / MR_n / LCS_n so we see if an interrupt
  // break, a refresh, or a stuck cycle state is holding TERM_n high at the hang.
  //   bit4:0 = CC_TERM {TERM_n,CC3_n,CC2_n,CC1_n,CC0_n}
  //   bit5=INTRQ(=~INTRQ_n) bit6=REFRQ(=~REFRQ_n) bit7=FETCH bit8=MR_n
  //   bit9=LCS_n bit10=CLEAR_n bit11=POWFAIL_n bit12=MCLK  bit15:13=0
  // Word = the microsequencer address-advance probe (from CGA_MIC XMIC_DBG):
  //   bit15=SC6  bit14=s_mclk_n (regW mux-select = ~mclk_pa routed LEVEL)
  //   bit13=MCLK_EN (microsequencer clock-tick pulse)  bit12:0=regIW (captured
  //   next-address). Captured at the 06000 hang: shows which signal is FROZEN.
  //   MCLK_EN stuck-low => word never retires (mem/CYC, case A); s_mclk_n stuck
  //   => regW mux frozen; regIW stuck 06000 vs jump target 0145 (case B).
  // STACK-hang investigation: capture the STALLED microcode address (CSA) so we
  // know WHERE the STACK test wedges (map to the microcode listing), the same
  // first step that located the boot hang at CSA 06000.
  //   bit12:0 = CSA_12_0 (octal microcode address at the stall)  bit15:13 = 0
  wire [19:0] s_cap_src   = {4'd0, 3'b0, CSA_12_0[12:0]};
  wire        s_cap_stb   = 1'b1;
  wire        s_cap_arm   = cap_armed;
  wire        s_hang      = &csa_stable;
  wire        s_cap_event = ((s_pil_3_0 == 4'd10) && (pil_prev != 4'd10)) || s_hang;
  localparam [8:0] CAP_POST = 9'd32;
`elsif TANG_TRAP_CAPTURE
  // Issue-D (PAGING test 3 eject) probe. Word = {TVEC[3:0], TRAPN, CSA[10:0]}:
  // TVEC/TRAPN arrive via the repacked XMIC_DBG (CGA_MIC.v, same define), CSA
  // is the local GAO net. Trigger = CSA held at octal 7 for 16 clk2x cycles
  // (the unimplemented-vector-7 self-jump; a transit through address 7 does
  // not persist) OR the frozen-CSA hang detector. 480 pre + 32 post so the
  // dump shows the trap dispatch LEADING INTO vector 7 - in particular
  // whether TVEC=7 at the jump (trap generator really computed 7 on silicon)
  // or TVEC!=7 (the CSA latch captured a mid-transition value = comb-path
  // setup failure in the TVEC->CSA path).
  wire [19:0] s_cap_src   = {4'd0, s_xmic_dbg[15:11], CSA_12_0[10:0]};
  wire        s_cap_stb   = 1'b1;
  wire        s_cap_arm   = cap_armed;
  reg  [4:0]  csa7_cnt;
  always @(posedge clk2x) begin
    if (!sys_rst_n) csa7_cnt <= 5'd0;
    else if (CSA_12_0 == 13'd7) begin
      if (!(&csa7_cnt)) csa7_cnt <= csa7_cnt + 1'b1;
    end else csa7_cnt <= 5'd0;
  end
  wire        s_hang      = &csa_stable;
  wire        s_cap_event = (csa7_cnt == 5'd16) || s_hang;
  localparam [8:0] CAP_POST = 9'd32;
`elsif TANG_WD_TRACE_DUMP
  // One ring entry per COMPLETED IOX access to the Winchester, not per clock:
  // s_cap_stb gates the ring write, so 128 entries are 128 register accesses
  // and the pointer wraps, always holding the MOST RECENT 128.
  //
  // TRIGGER ON THE END OF THE OPERATION, NOT ON A REGISTER. The first version
  // triggered on a read of +0 - but that is the diagnostic's very FIRST
  // access (op 1 of the captured oracle trace is `RD +0 -> 0`), so it fired
  // immediately, at the "Sector" prompt, and dumped a ring that was still
  // almost entirely empty. The interesting accesses are the LAST ones, so
  // wait until the card has been quiet for about a second instead: DISC-TEMA
  // stops touching it once the transfer is reported.
  // ND_WD_TRACE_DBUF: capture what the ADAPTER FORWARDS into the controller's
  // sector buffer, instead of the IOX register traffic.
  //
  // WHY, 09-AUG-2026. The silicon zero-read is now narrowed to exactly two
  // components: the c_buf_we -> dbuf_we forwarding window inside
  // nd_storage_disc_adapter (S_CWAIT / s_in_win), or the s_buffer BSRAM read
  // inside ND_WINCHESTER. Both pass in simulation, so only a probe on real
  // hardware can separate them, and black-box A/B has been exhausted:
  // the fault is independent of the file (BOOT.TAP reads fine through the
  // TAPE client and returns zeros through the WINCHESTER client), of the
  // cache, of the clock domain and of the LBA.
  //
  // This records every word the adapter writes into the buffer, tagged 4'hE.
  //   ring full of E<data> with REAL values -> the adapter forwards fine,
  //     so the fault is the controller's BSRAM read path.
  //   ring EMPTY, or full of E0000 -> the adapter never forwards, so the
  //     fault is the window in the adapter.
  // Either way it names the culprit in one run.
  // Fill-path diagnostic seam from nd_storage (see nd_storage_engine.v).
  wire [4:0] DBG_STATE;
  wire [31:0] DBG_LBA;
  wire [15:0] DBG_WDATA;
  wire [15:0] DBG_RDATA;
  wire [15:0] DBG_BUFW;
  wire DBG_BUFWE;
  wire [15:0] DBG_FSEC;
  wire       DBG_RX_STB;
  wire [7:0] DBG_RX_RAW, DBG_RX_BYTE;
  wire       DBG_PAST_EOF;
  wire [2:0] DBG_GRANT;

`ifdef ND_WD_TRACE_FILL
  // Capture CARD BYTES arriving during a fill, raw AND after the end-of-file
  // gate, tagged with the granted client:
  //   tag  = {1'b1, grant[2:0]}
  //   data = {raw byte from the card, byte actually written to staging}
  // raw != 0 while gated == 0 -> the s_past_eof gate is zeroing real data.
  // raw == 0                  -> the card itself delivered nothing.
  //
  // BIT 19 IS A WRITTEN-MARKER AND MUST STAY 1. It was 1'b0 until 09-AUG-2026,
  // which made the probe undecidable: the TAPE client is grant 0, so a genuine
  // record of a zero card byte encoded as 00000 - exactly what an unwritten
  // cap_mem entry reads back as (cap_mem has no reset). Two silicon runs came
  // back 64/64 zeros and neither could be interpreted. With bit 19 set, any
  // record that is still 00000 was never written, full stop.
  wire [19:0] s_cap_src   = {1'b1, DBG_GRANT, DBG_RX_RAW, DBG_RX_BYTE};
`elsif ND_WD_TRACE_REGION
  // Capture what the SDRAM REGION actually returns on a read completion.
  //
  // The dbuf probe (ND_WD_TRACE_DBUF) showed the adapter forwarding 63 words
  // that were ALL ZERO, which cleared both the adapter window and the
  // controller BSRAM and pushed the fault back inside nd_storage: the words
  // are already zero when the client buffer is written, i.e. s_bridge_rd_data
  // is zero, i.e. the REGION READ returned nothing. This probe tests that
  // directly at the SDRAM device port, which is the last place the data can
  // still be checked from outside nd_storage.
  //
  //   ring full of D<data> with REAL values -> SDRAM returns the data and it
  //     is lost between the region read and the client write (the bridge).
  //   ring full of D0000 -> the region itself holds zeros, so the staging ->
  //     region WRITE never landed, and the fault is on the write side.
  wire [19:0] s_cap_src   = {4'hD, s_mem_rdata[15:0]};
`elsif ND_WD_TRACE_DBUF
  wire [19:0] s_cap_src   = {4'hE, WDBUF_WDATA};
`elsif ND_WD_TRACE_ESTATE
  // Every CHANGE of the storage engine's state, tagged F, with the granted
  // client. Answers "which states does a Winchester read actually visit"
  // directly, instead of inferring it from a byte strobe that has now come
  // back empty three times for three different reasons.
  wire [19:0] s_cap_src   = {4'hF, 8'd0, DBG_GRANT, DBG_STATE};
`elsif ND_WD_TRACE_PIL
  // Every CHANGE of the CPU's priority interrupt level, tagged B. Added
  // 09-AUG-2026 because the Winchester-register trace had gone as far as it
  // can: the File System Investigator's device-open sequence matches the
  // nd100x oracle access for access, and then LI-FI issues no IOX at all and
  // reports the status it cached at open. That decision is taken CPU-side, so
  // the question is no longer what the controller returned but whether the
  // machine ever runs the level-11 handler. Recording only transitions keeps
  // the 64-entry ring useful - PIL sampled every clk2x would fill it in
  // microseconds and show nothing but the idle level.
  wire [19:0] s_cap_src   = {4'hB, 12'd0, s_pil_3_0};
`elsif ND_WD_TRACE_TVEC
  // TRAP DISPATCH. Word = {tag, TVEC[3:0], TRAPN, PIL[3:0]}, so each record
  // says WHICH internal interrupt was dispatched and at what level. TVEC is
  // the ND-100 internal interrupt code: 1 = monitor call, 2 = memory
  // protection violation, 3 = page fault.
  //
  // The reference number, measured 11-AUG-2026: in an nd100x boot of the SAME
  // WD0.IMG, the 65536-instruction window following the block-3643 read holds
  // EIGHT monitor calls (IID bit 1) and NO page fault and NO protection
  // violation. So a ring full of code 2 or code 3 here IS the divergence,
  // and a ring full of code 1 says the machine is re-taking monitor calls.
`elsif ND_WD_TRACE_CSATRAP
  // The microcode addresses LEADING INTO a trap. Sampling CSA at the trap
  // instant is useless - on a trap the micro-address bus IS the trap vector
  // (docs/SIGNALS.md: MA muxes WCA/W/TVEC/CD on EWCA/MAP_n/TRAP_n), so it
  // just reads back the vector, which is what the previous build measured.
  // Free-run CSA instead and let the TRAP be the TRIGGER, so the ring holds
  // the ~256 clk2x cycles (~19 us) of microcode before the fault.
  wire [19:0] s_cap_src   = {4'd0, 3'b0, CSA_12_0[12:0]};
`elsif ND_WD_TRACE_TVEC_CSA
  // CSA, not PIL, in the low bits: the PAGING test-3 analysis
  // (docs/HANDOFF-paging-test3-pof-dispatch-rootcause.md, retired - git
  // 202c606; the cause proved to be the PAL 44306A EIPL term, PAL_44306A.v)
  // pins its D2
  // defect to the overlapped COMM,AREAD dispatch word at CSA 04420. If
  // the SINTRAN hang traps at the same microcode address, it is the same
  // defect and not merely the same symptom.
  // CSA dropped: it was measured to equal TVEC in every one of 255 records, so
  // it carried nothing. CGA.v now packs the ring evidence into the low bits
  // instead - see the repack note there.
  //   [19:9] CSA[10:0]  [8:5] TVEC  [4] TRAPN  [3] VACC  [2] PGF  [1] TCLK  [0] 0
  //
  // The FULL low CSA is carried, not the top bits: the handlers that identify
  // the vector actually used sit at LOW addresses (vector 1 -> 00020 = 16,
  // vector 3 -> 00040 = 32), so CSA[10:0] is what distinguishes them and
  // CSA[10:7] would read 0 for both. PT_15_9 is dropped to make room - it is
  // already known to be 000 at the fault from the previous capture.
  // DEMAND-PAGING PROBE (17-AUG-2026). The oracle shows that servicing a page
  // fault ALWAYS runs the same microcode sequence, and 126 of 126 faults in a
  // healthy boot end in a disc read:
  //     PF -> CLPT (unmap victim) -> seek/read -> IDENT level 11
  //        -> ENPT (map the page in) -> resume
  // Our machine produces faults with NO disc read, so the question is whether
  // it ever reaches those routines at all. From the microcode listing
  // (the nd120uc repository, source/nd-120-delilah.uc):
  //     CLPT = CSA 0o5705,  ENPT = CSA 0o5706,  CLPT1 = 0o4071, CLPT3 = 0o4115
  //
  // NOTE 0o5705 = 3013 decimal - that needs TWELVE bits. The previous record
  // carried only CSA[10:0], which would have truncated bit 11 and shown 0o1705,
  // i.e. "never reached" for a routine that was in fact running. Carry the full
  // 13-bit CSA here.
  //
  // The sticky flags matter more than the live CSA: a 256-record window is only
  // microseconds, so it may simply miss a routine that does execute. Once set,
  // seen_enpt/seen_clpt stay set, so ANY dump answers "has this machine EVER
  // mapped a page in".
  //   [19:16] CSA[3:0]  [15:4] LA_21_10  [3] VACC  [2] PGF  [1] TRAPN  [0] TCLK
  //
  // PGS reconstruction: PGS is the last LA_21_10 loaded while VACC was high
  // (CGA_IDBCTL_PGSREG, drawing page 98 - self-holding scan FFs, TE = VACC).
  // SINTRAN masks PGS & 001777 to get PT<<6|VPN and keys its serviceable/fatal
  // decision on it. Oracle known-good at the first serviced fault: 040762
  // (PT=7, VPN 0o62). L-reg 072627 implies ours reported 000760.
  // Capturing LA + VACC free-running around the fault shows both the value AND
  // whether it is overwritten before SINTRAN reads it.
  wire [19:0] s_cap_src   = {CSA_12_0[3:0], s_xmic_dbg[15:0]};      // [0] PTM (STS bit 0)
`elsif ND_WD_TRACE_FSEC
  // The mount's first_sector for the granted client, low 16 bits, strobed on
  // entry to C_SEC_GO so it is captured at the moment a fetch is launched.
  // Compare against the ND_WD_TRACE_LBA capture of the SAME run configuration:
  // for block 0 they must be equal. Client 6 / WD0.IMG resolved to 64672 on
  // 10-AUG-2026; if first_sector is also 64672 the resolve is right and the
  // card really holds zeros there, and if it differs the resolve is wrong for
  // large files. Neither answer needs the card's layout.
  wire [19:0] s_cap_src   = {1'b1, DBG_GRANT, DBG_FSEC};
`elsif ND_WD_TRACE_BUFW
  // The word the engine hands to the granted client's buffer, strobed by that
  // buffer's own write enable. THE LAST UNMEASURED SIGNAL in the read chain.
  // Everything either side is now proven on silicon: the region reads back
  // real file text (10-AUG-2026), and the adapter forwards words that are
  // zero. If this is non-zero, the loss is inside nd_storage_disc_adapter -
  // which is the Winchester's adapter and NOT the tape's, matching a fault
  // that follows the client. If this is zero, the loss is in the engine's
  // clk_cpu client front-end between the bridge and the buffer.
  //
  // The strobe is safe here where the clk_stor ones were not: clk_cpu is HALF
  // of clk2x, so this write enable is two clk2x cycles wide.
  wire [19:0] s_cap_src   = {1'b1, DBG_GRANT, DBG_BUFW};
`elsif ND_WD_TRACE_RDATA
  // What the SDRAM region RETURNS on read-back, tagged with the client, taken
  // at R_PUSH_HI where mem_rdata is valid. The write side is now PROVEN good:
  // 10-AUG-2026, 63 of 63 region writes for client 6 were non-zero and carried
  // readable ASCII straight off the card. So staging is filled and the region
  // is written with real data; if it reads back zero, the fault is the region
  // read or the SDRAM device port, not the storage engine's fetch path.
  wire [19:0] s_cap_src   = {1'b1, DBG_GRANT, DBG_RDATA};
`elsif ND_WD_TRACE_WDATA
  // The word the engine writes into the SDRAM region, tagged with the client.
  // The resolve is now PROVEN correct (10-AUG-2026: pointed at BOOT.TAP,
  // client 6 fetched sectors 21920-21923 - four consecutive sectors, block 0
  // of the file, in the same card region the tape client reads). So the card
  // is read from the right place and the region still ends up zero. This
  // splits the last span: non-zero here means staging was filled and the
  // fault is in the region write or read-back; zero here means the received
  // bytes never reached staging.
  wire [19:0] s_cap_src   = {1'b1, DBG_GRANT, DBG_WDATA};
`elsif ND_WD_TRACE_LBA
  // The resolved card sector of each fetch, low 16 bits, tagged with the
  // granted client. Bit 19 is 1 so an unwritten ring slot (00000) stays
  // distinguishable from a genuine record.
  //
  // Why this value: the card fetch demonstrably RUNS for client 6 (four
  // C_SEC_WAIT per block in the engine-state trace) and completes with no
  // error, yet the region ends up zero. A read of the WRONG sector produces
  // exactly that - it lands in blank card space, sdw_err never asserts, and
  // every word is zero. BOOT.TAP reads correctly as client 0 and returns
  // zeros as client 6, same file and same card, so the fault follows the
  // CLIENT, and the sector is the per-client value.
  wire [19:0] s_cap_src   = {1'b1, DBG_GRANT, DBG_LBA[15:0]};
`else
  wire [19:0] s_cap_src   = wd_trace_rec;
`endif

  // RISING EDGE ONLY, and that is not a detail. This capture block is clocked
  // by clk2x while ND_WINCHESTER runs on clk_cpu, which is clk2x/2, so its
  // one-cycle trace_we pulse is TWO clk2x cycles
  // wide and the ring recorded every single event TWICE - halving the usable
  // depth from 64 entries to 32. Measured on silicon 09-AUG-2026: the whole
  // dump came back in exact pairs, including the interrupt-edge record, and
  // an edge-detected pulse cannot legitimately fire twice. Confined to this
  // trace mode: the other capture modes tie s_cap_stb high and sample every
  // clk2x cycle on purpose.
  reg         wd_trace_we_q;
  always @(posedge clk2x) begin
    if (!sys_rst_n) wd_trace_we_q <= 1'b0;
    else            wd_trace_we_q <= wd_trace_we;
  end
`ifdef ND_WD_TRACE_FILL
  // LEVEL change, not the strobe. DBG_RX_STB is a ONE-CYCLE pulse in the
  // clk_stor domain (27 MHz) and this ring samples on clk2x (13.5 MHz), so a
  // single-cycle pulse is simply not observable here - it can fall entirely
  // between two sampling edges. That, and not "the card delivered nothing",
  // is why three separate fill captures came back with an empty ring on
  // 09-AUG-2026. DBG_RX_RAW is held between bytes, so a change-detect on the
  // value samples reliably at half rate. It will MISS repeats of the same
  // byte value; that is fine, because the question is only whether the bytes
  // arriving from the card are non-zero.
  wire        s_cap_stb   = (DBG_RX_RAW != rxraw_prev);
`elsif ND_WD_TRACE_REGION
  wire        s_cap_stb   = s_mem_done && !s_mem_we;   // read completions only
`elsif ND_WD_TRACE_DBUF
  wire        s_cap_stb   = WDBUF_WE;
`elsif ND_WD_TRACE_ESTATE
  // The per-WORD serve loop is NOT recorded. R_WAIT(4) -> R_PUSH_HI(5) ->
  // R_PUSH_LO(6) repeats once per word, 512 times per block, and on 09-AUG-2026
  // it filled all 64 ring slots by itself - the capture showed nothing but the
  // tail of the serve loop and evicted every state that says HOW the block was
  // obtained (E_GRANT, C_SEC_GO/C_SEC_WAIT for a card read, W_MEM for the
  // staging->region write). Those are the states in question, so the loop is
  // filtered out exactly like the foreign IOX records were.
  // There are TWO per-word loops and both must go. The serve loop is
  // R_WAIT(4)/R_PUSH_HI(5)/R_PUSH_LO(6); the staging->region write loop is
  // W_MEM(10)/W_MEM_WAIT(11). Each runs once per word, 512 times per block.
  // Filtering only the first (09-AUG-2026) simply handed the whole ring to the
  // second. What is left is low-rate and is the actual question: E_GRANT,
  // C_SEC_GO/C_SEC_WAIT (four per fill - a real card read), C_ALLOC, R_MEM,
  // E_DONE.
  wire        s_cap_stb   = (DBG_STATE != estate_prev)
                            && (DBG_STATE != 5'd4)
                            && (DBG_STATE != 5'd5)
                            && (DBG_STATE != 5'd6)
                            && (DBG_STATE != 5'd10)
                            && (DBG_STATE != 5'd11);
`elsif ND_WD_TRACE_FSEC
  wire        s_cap_stb   = (DBG_STATE == 5'd14) && (estate_prev != 5'd14);
`elsif ND_WD_TRACE_BUFW
  wire        s_cap_stb   = DBG_BUFWE;
`elsif ND_WD_TRACE_RDATA
  wire        s_cap_stb   = (DBG_STATE == 5'd5) && (estate_prev != 5'd5);
`elsif ND_WD_TRACE_WDATA
  // Strobe on ENTRY TO W_MEM(10), not on a change of the data.
  //
  // A change-detector cannot answer this question, and 10-AUG-2026 proved it
  // the hard way: `DBG_WDATA != wdata_prev` produces an empty ring both when
  // the probe is broken AND when the value is constantly zero - which is the
  // exact hypothesis under test. Same undecidable design as the first fill
  // probe, made twice.
  //
  // W_MEM entry is a level transition the ESTATE trace already PROVED happens
  // (once per word, 512 per block). So records carrying 0000 mean the region
  // really is being written with zeros, and NO records at all means the probe
  // itself is dead. The two are finally distinguishable.
  wire        s_cap_stb   = (DBG_STATE == 5'd10) && (estate_prev != 5'd10);
`elsif ND_WD_TRACE_LBA
  wire        s_cap_stb   = (DBG_LBA != lba_prev);
`elsif ND_WD_TRACE_PIL
  wire        s_cap_stb   = (s_pil_3_0 != pil_prev);
`elsif ND_WD_TRACE_TVEC
  // One record per DISPATCHED trap: strobe on entry to level 14, the
  // internal-interrupt level, not on every clock. A machine re-taking the
  // same trap forever therefore fills the ring with that trap's code.
`elsif ND_WD_TRACE_CSATRAP
  wire        s_cap_stb   = 1'b1;
`elsif ND_WD_TRACE_TVEC_CSA
  // Falling edge of TRAPN = a trap is being DISPATCHED, which is the only
  // moment TVEC is valid. One record per trap - but ONLY a REAL one.
  //
  // TVEC 15 is excluded (measured 17-AUG-2026). A first capture came back with
  // all 256 slots holding the identical record AF00F = TVEC 15, CSA 0o17: the
  // TRAPN edge fires constantly with vector 15, so the ring filled with those
  // and evicted every real trap. This is not a new discovery - the repo's own
  // trap probe already treats it as noise and prints nothing for it
  // (DELILAH-CPU/CGA_TRAP/circuit/CGA_TRAP.v:117 tests
  // `s_tvec_3_0_out != 4'd15` on exactly this edge).
  //
  // With vector 15 filtered out, the OPCOM idle loop after the ERRFATAL halt
  // no longer consumes slots, so the card-idle trigger 45 s later still leaves
  // the ring holding the last 256 REAL traps - the ones taken on the way into
  // the fault.
  // FREE-RUN (17-AUG-2026). One record per trap has no time axis, and that is
  // now the limiting factor: the vector bits are TCLK-registered while TRAPN is
  // combinational (CGA_TRAP_BRKDET.v has no clock port), so a sample taken at
  // the TRAPN edge may read the vector BEFORE the capturing edge. That cannot
  // be told apart from a genuinely wrong dispatch without consecutive clocks.
  //
  // Free-running at clk2x makes each record one clock, so the sequence shows
  // TCLK rising, the vector settling, and - critically - which microcode
  // address the sequencer actually RUNS afterwards. Vector 3 jumps to 00040,
  // vector 1 jumps to 00020, so the handler entered names the vector that was
  // really used.
  wire        s_cap_stb   = 1'b1;
`else
  // FOREIGN ACCESSES ARE NOT RECORDED. They were excluded from the TRIGGER on
  // 09-AUG-2026 but still consumed ring slots, and with a 64-entry ring that
  // loses the evidence outright: the tape reader at IOX 400/402/403 polls
  // continuously, so a captured File System Investigator session came back
  // with 59 of 63 slots holding tape polls and only the last four Winchester
  // accesses surviving. The failing read had been evicted by the very traffic
  // this filter now drops. Foreign records still reach wd_trace_foreign for
  // the trigger logic below - only the ring write is gated. The tag test is
  // spelled out rather than reusing wd_trace_foreign, which is declared below
  // this point; module-level nets may legally be used before declaration but
  // there is no reason to lean on that through the Gowin front end.
`ifdef ND_WD_TRACE_OPSONLY
  // ONE OPERATION PER THREE RECORDS: the block address (+3), the word count
  // (+7), and the control word (+5) that actually ACTIVATES the card (bit 2).
  // The four device-clear words each operation writes to +5 carry bit 2 = 0
  // and are dropped, as are the status polls, the IDENT, the interrupt and
  // the address readback. 256 entries therefore hold 85 operations, which
  // reaches back past the block-0 retry loop the block-only capture found at
  // operation 121 of 158 - the full trace reaches back only thirteen.
  //
  // Tags: 4'hB = write to +3, 4'hF = write to +7, 4'hD = write to +5.
  wire        s_cap_stb   = wd_trace_we && !wd_trace_we_q
                            && ((wd_trace_rec[19:16] == 4'hB)
                             || (wd_trace_rec[19:16] == 4'hF)
                             || ((wd_trace_rec[19:16] == 4'hD)
                                 && wd_trace_rec[2]));
`elsif ND_WD_TRACE_BLKONLY
  // BLOCK ADDRESSES ONLY - one record per disc operation instead of the
  // nineteen a full operation costs (block address, two address halves, word
  // count, control word, status polls, IDENT, interrupt, address readback,
  // four device clears). The whole '20500&' SINTRAN load writes 251 block
  // addresses end to end, measured from the nd100x trace of the same WD0.IMG,
  // so a 256-entry ring holds EVERY disc address the boot asks for, from the
  // first to the last. The unfiltered trace cannot do that: at nineteen
  // records per operation a 256-entry ring reaches back only thirteen
  // operations, which is the last five percent of the load.
  //
  // Tag 4'hB is a WRITE (bit 19) to register 3 (bits 18:16), the block
  // address register.
  wire        s_cap_stb   = wd_trace_we && !wd_trace_we_q
                            && (wd_trace_rec[19:16] == 4'hB);
`else
  wire        s_cap_stb   = wd_trace_we && !wd_trace_we_q
                            && (wd_trace_rec[19:16] != 4'hC);
`endif
`endif

  // THE ARM DELAY DOES NOT APPLY HERE. cap_armed only goes high ~40 s after
  // reset, which exists so the CPU-debug capture modes do not trigger during
  // boot. For this trace it silently DISCARDS events: a mass-load hang three
  // seconds after reset trips the idle trigger at ~6 s, long before arming,
  // and the result is no dump at all - indistinguishable from "the card was
  // never touched". That ambiguity produced a wrong conclusion once already.
  wire        s_cap_arm = 1'b1;

  // COUNT ONLY REAL CARD ACCESSES. ND_WINCHESTER.v's trace_we bundles FIVE
  // kinds of record into one strobe (see its trace_rec assignment): register
  // write, register read, IDENT answer, interrupt edge, and - top nibble
  // 4'hC - a FOREIGN IOX, i.e. somebody ELSE'S device being addressed while
  // the Winchester just watches the bus go by. Feeding all five into the
  // trigger made the probe fire on traffic that has nothing to do with this
  // card: measured 09-AUG-2026 on silicon, a plain '400$' tape load filled
  // the entire 64-entry ring with C0100 / C0102 / C0103 - octal 400, 402,
  // 403, the PAPER TAPE READER's own registers - and tripped the trigger
  // before the Winchester had been touched at all. Twice that dump was read
  // as "the ring came back empty"; it never was, it was full of the tape.
  //
  // So the idle timer and the access count now advance ONLY on records that
  // are really this card's. Foreign records are still RECORDED - seeing the
  // surrounding IOX traffic is the whole point of having them - they simply
  // do not arm anything.
  wire wd_trace_foreign = (wd_trace_rec[19:16] == 4'hC);
  // The IOX one-shot, kept SEPARATE from s_cap_stb: when ND_WD_TRACE_DBUF
  // makes the ring capture buffer writes, the TRIGGER must still key off the
  // card's IOX activity going idle, not off buffer writes.
  wire wd_iox_own       = wd_trace_we && !wd_trace_we_q && !wd_trace_foreign;
  wire wd_trace_own     = wd_iox_own;

  reg [29:0] wd_idle;      // clk2x cycles since the last access to the card
  reg [7:0]  wd_count;     // OWN accesses seen (saturating)
  reg        wd_ran;       // a device OPERATION has completed
  always @(posedge clk2x) begin
    if (!sys_rst_n) begin
      wd_idle  <= 30'd0;
      wd_count <= 8'd0;
      wd_ran   <= 1'b0;
    end else begin
      if (wd_trace_done) wd_ran <= 1'b1;
      if (wd_trace_own) begin
        wd_idle <= 30'd0;
        if (!(&wd_count)) wd_count <= wd_count + 1'b1;
      end else if (!(&wd_idle)) begin
        wd_idle <= wd_idle + 1'b1;
      end
    end
  end

  // COMPARE WITH >=, NEVER ==. wd_idle saturates, so an equality test matches
  // for exactly one cycle and is lost if anything gates it that cycle - which
  // is the other half of how the last capture came back empty.
  //
  // Two ways in, because the two failures look different from here:
  //   - a COMPLETED operation then ~1 s of quiet: the DISC-TEMA case, where
  //     the memory-address readback is the last thing to happen;
  //   - at least 4 accesses then ~4 s of quiet with nothing completing: the
  //     mass-load hang. The count requirement stops a lone probe during the
  //     floppy boot from tripping it, which is what made an earlier version
  //     fire early and dump an empty ring.
  // The idle threshold has to exceed the LONGEST gap DISC-TEMA leaves while
  // collecting Disc name / Unit / Cylinder / Surface / Sector / Amount from
  // the operator. Measured: about 15 s with the scripted driver, longer with
  // a human. A 4 s window fired at the "Amount" prompt and dumped only the
  // seven opening probe accesses. 30 s clears it; the mass-load hang is
  // permanent, so any threshold works there.
  //
  // The completion path stays at ~1 s and fires FIRST in every healthy run,
  // so this long window only ever matters when nothing completes at all.
  // 09-AUG-2026: BOTH terms now also require a MINIMUM ACCESS COUNT, because
  // the old thresholds fired during the boot probe and dumped an empty ring -
  // exactly the failure this comment block warned about, just at a different
  // scale. Loading the File System Investigator from tape and getting it to
  // its first Winchester read takes well over the 30 s idle window, so a
  // 2-access boot probe armed the capture long before the traffic of
  // interest existed. The captured File System Investigator sequence
  // (nd100x, ND100X_WD_DEBUG=1) reaches its first completed transfer at
  // access 12 and runs to 72, so 8 and 40 sit clear of a probe and well
  // inside the real run.
  // THRESHOLDS MEASURED, NOT GUESSED. 09-AUG-2026 on silicon: a complete
  // File System Investigator session - load from tape, open DISC-74MB-1
  // unit 0, read the geometry, then LI-FI - produces only FOUR accesses of
  // this card's own (status read, the M7 return-to-zero, the completion
  // interrupt, status read). With the old >=8 requirement the trigger could
  // therefore NEVER fire, and a 180 s idle capture came back with no dump
  // at all - which reads exactly like "the probe is broken" and is why the
  // numbers are now tied to a measurement instead of a guess.
  //
  // The counts exist only to stop a ONE-access probe from arming, so 3 is
  // enough. The second term (no completion at all) drops in proportion.
  // 09-AUG-2026, SECOND correction, measured not guessed: the completion-path
  // idle window was 13_500_000 = ONE SECOND at 13.5 MHz, and that makes an
  // interactive File System Investigator session impossible to capture. The
  // console floor is 0.30 s per character (below 0.20 s OPCOM drops input), so
  // typing "LI-FI" alone takes 1.5 s, and the operator gap between answering
  // "Device unit :" and issuing the command is several seconds more. None of
  // that touches the Winchester, so wd_idle runs free and the capture closes
  // during the DIALOGUE - long before the read it exists to record. Observed
  // exactly that: the ring dumped while LI-FI was still printing, holding only
  // the three opening probe accesses. 45 s clears any scripted dialogue gap
  // and still fires well inside a listen window. Both terms move together so
  // the 30 s term cannot pre-empt the 45 s one.
`ifdef ND_WD_TRACE_CSATRAP
  // Trigger on the trap itself, keeping a few records after it so the ring
  // shows the dispatch as well as the run-up.
  // Gated on the card having gone QUIET first (same 45 s idle the register
  // trace uses). Traps happen constantly during a healthy boot; without the
  // gate the ring would capture the first one and be long gone by the time
  // the machine livelocks.
  wire        s_cap_event = (!s_xmic_dbg[11] && trapn_prev)
                            && (wd_count >= 8'd3)
                            && (wd_idle >= 30'd607_500_000);
  localparam [8:0] CAP_POST = 9'd16;
`elsif ND_WD_TRACE_TVEC_CSA
  // Keep the card-idle trigger; the STROBE above now filters vector 15, which
  // is what makes it work. Triggering on the page fault itself (TVEC == 1) was
  // tried on 17-AUG-2026 and produced ZERO records - the condition never became
  // true in the whole 146 s run up to the ERRFATAL halt, even though SINTRAN
  // then printed "IIC : 000003  Page Fault". That is worth knowing but it is
  // NOT usable as a trigger, and it is NOT yet proof of anything: it means
  // either page faults here are not delivered as microcode trap vector 1, or
  // PGF never asserted. Recording every REAL trap and reading what the machine
  // actually takes settles that without assuming the answer first.
  // Trigger on the PAGE-FAULT dispatch itself: TRAPN falling with PGF live.
  // The card-idle trigger is useless with a free-running strobe - 256 clk2x
  // cycles is a window of microseconds, and the idle trigger only becomes true
  // 45 s AFTER the halt.
  //
  // CAP_POST 200 of 256 puts most of the ring AFTER the trap, which is the
  // whole question: does the sequencer run the vector-1 handler (00020) or the
  // vector-3 handler (00040)? The ~56 records before the trigger still show the
  // vector settling across the TCLK edge.
  // BIT POSITIONS FOLLOW THE CGA REPACK. XMIC_DBG is now the PT/APT record
  //   [15:12] PT  [11:8] APT  [7:4] table used  [3] FETCH_n  [2] PTM
  //   [1] PGF  [0] TRAPN
  // so TRAPN is bit 0 and PGF is bit 1. Every earlier packing put them
  // somewhere else; leaving a stale index here makes the trigger test an
  // address bit and it silently never fires - that cost a build+boot on
  // 17-AUG-2026 and is what scratchpad/preflight_capture.py now checks.
  wire        s_cap_event = (!s_xmic_dbg[0] && trapn_prev) && s_xmic_dbg[1];
  localparam [8:0] CAP_POST = 9'd200;
`else
  wire        s_cap_event = (wd_ran && (wd_count >= 8'd3)
                             && (wd_idle >= 30'd607_500_000))
                         || ((wd_count >= 8'd6) && (wd_idle >= 30'd607_500_000));
  localparam [8:0] CAP_POST = 9'd0;   // the trigger IS the end - nothing after
`endif
`else
  wire [19:0] s_cap_src   = {4'd0, s_dbg_memw};
  wire        s_cap_stb   = 1'b1;
  wire        s_cap_arm   = cap_armed;
  wire        s_cap_event = !wdec_d2 && s_dbg_memw[7];
  localparam [8:0] CAP_POST = 9'd448;
`endif
  always @(posedge clk2x) begin
    if (!sys_rst_n) begin
      cap_wptr <= 0; cap_post <= 0; arm_cnt <= 0;
      cap_armed <= 0; cap_trig <= 0; cap_done <= 0; wdec_d2 <= 0;
      wdec_seen <= 0; write_seen <= 0; pil_prev <= 0; estate_prev <= 0; rxraw_prev <= 0; lba_prev <= 0; wdata_prev <= 0;
      trapn_prev <= 1'b1;
      csa_prev <= 0; csa_stable <= 0;
    end else begin
      if (!cap_armed) begin
        arm_cnt <= arm_cnt + 1'b1;
        if (arm_cnt == 29'h1FFFFFFF) cap_armed <= 1;  // ~40 s at 13.5 MHz
      end
      // microcode-hang detector: count clk2x cycles CSA stays unchanged
      csa_prev <= CSA_12_0;
      if (CSA_12_0 != csa_prev) csa_stable <= 0;
      else if (!(&csa_stable))  csa_stable <= csa_stable + 1'b1;
      wdec_d2 <= s_dbg_memw[7];
      pil_prev <= s_pil_3_0;
`ifdef TANG_PC_HISTORY
      pc_prev  <= s_cap_src;
      la_prev_pc <= s_debug_la_23_10[9:0];
      if (cap_armed && !cap_trig && !fallback_cnt[31]) fallback_cnt <= fallback_cnt + 1'b1;
      if (!cap_trig) tick_at_trig <= tickWide;      // tracks until the trigger freezes it
      if (cap_trig && post_word < 2'd2) post_word <= post_word + 1'b1;
      //! Shift only on an entry that really goes into the ring, so p_r0..p_r3
      //! are the last four RECORDED words and not the last four seen.
      if (!cap_done && !cap_trig && s_cap_stb) begin
        p_r3 <= p_r2; p_r2 <= p_r1; p_r1 <= p_r0; p_r0 <= pc_word;
      end
`endif
`ifdef TANG_JPL_CAPTURE
      //! Track only what actually went into the ring, so the "changed" test
      //! stays honest when both values change in the same cycle.
      if (jpl_win && csa_chg)            csa_cap_prev <= CSA_12_0;
      if (jpl_win && !csa_chg && pc_chg) pc_cap_prev  <= s_xmic_dbg[15:0];
      if (jpl_win && !csa_chg && !pc_chg && fid_chg) fid_cap_prev <= s_debug_fidbo;
      if (jpl_win && !csa_chg && !pc_chg && !fid_chg && ppn_chg) ppn_cap_prev <= s_dbg_ppn;
      //! Open on the fetch of 064544, close on 064547 or after 1023 cycles -
      //! the ring is 512 deep and CSA changes every few cycles, so a longer
      //! window would wrap and lose the JPL itself.
      if (cap_armed && !jpl_win && !jpl_done
          && (s_xmic_dbg[15:0] == 16'o064544)) begin
        jpl_win      <= 1'b1;
        jpl_cnt      <= 10'd0;
        csa_cap_prev <= ~CSA_12_0;            // force a first CSA entry
        pc_cap_prev  <= ~s_xmic_dbg[15:0];    // force a first PC entry
        fid_cap_prev <= ~s_debug_fidbo;       // force a first FIDBO entry
        ppn_cap_prev <= ~s_dbg_ppn;           // force a first PPN entry
      end else if (jpl_win) begin
        jpl_cnt <= jpl_cnt + 1'b1;
        if ((s_xmic_dbg[15:0] == 16'o064547) || (&jpl_cnt)) begin
          jpl_win  <= 1'b0;
          jpl_done <= 1'b1;
        end
      end
`endif
      trapn_prev <= s_xmic_dbg[0];   // TRAPN, see the repack note above
      estate_prev <= DBG_STATE;
      rxraw_prev  <= DBG_RX_RAW;
      lba_prev    <= DBG_LBA;
      wdata_prev  <= DBG_WDATA;
      if (cap_armed && s_dbg_memw[7]) wdec_seen <= 1;
      if (cap_armed && s_dbg_memw[6]) write_seen <= 1;
      if (!cap_done) begin
        if (s_cap_stb) begin
          cap_mem[cap_wptr] <= s_cap_src;
          cap_wptr <= cap_wptr + 1'b1;
        end
        if (!cap_trig && s_cap_arm && s_cap_event) begin
          cap_trig <= 1;
          cap_post <= CAP_POST;
        end else if (cap_trig) begin
          cap_post <= cap_post - 1'b1;
          if (cap_post == 0) cap_done <= 1;
        end
      end
    end
  end

  // hex dumper: 512 lines of 4 hex digits, oldest sample first
  function [7:0] hexd(input [3:0] n);
    hexd = (n < 4'd10) ? (8'h30 + {4'b0, n}) : (8'h37 + {4'b0, n});
  endfunction
  // Separate registered read port (simple-dual-port BRAM; write happens
  // only during capture, read only during dump - Gowin PA2122 otherwise)
  reg [CAP_AW-1:0] cap_raddr;
  reg [19:0] cap_rd;
  always @(posedge clk2x) cap_rd <= cap_mem[cap_raddr];

  reg [CAP_AW-1:0] dump_i;
  reg [2:0] dump_c;
  reg dump_run, dump_fin;
  reg [26:0] hold_cnt;  // ~10 s at 13.5 MHz: keep the console on the CPU
                        // after the trigger so the deposit echo and a
                        // post-deposit examine get through before the dump
  reg d_tx_valid;
  reg [7:0] d_tx_data;
  wire d_tx_busy, dbg_txd;
  assign dbg_dumping = dump_run | dump_fin;
  always @(posedge clk2x) begin
    if (!sys_rst_n) begin
      dump_i <= 0; dump_c <= 0; dump_run <= 0; dump_fin <= 0; d_tx_valid <= 0;
      cap_raddr <= 0; hold_cnt <= 0;
    end else begin
      d_tx_valid <= 0;
      if (cap_done && !dump_run && !dump_fin && hold_cnt != 27'h7FFFFFF)
        hold_cnt <= hold_cnt + 1'b1;
      if (cap_done && !dump_run && !dump_fin && hold_cnt == 27'h7FFFFFF) begin
        dump_run <= 1; dump_i <= 0; dump_c <= 0;
        cap_raddr <= cap_wptr;  // oldest sample first (cap_rd valid next cycle)
      end else if (dump_run && !d_tx_busy && !d_tx_valid) begin
        case (dump_c)
          3'd0: d_tx_data <= hexd(cap_rd[19:16]);  // rw + register offset
          3'd1: d_tx_data <= hexd(cap_rd[15:12]);
          3'd2: d_tx_data <= hexd(cap_rd[11:8]);
          3'd3: d_tx_data <= hexd(cap_rd[7:4]);
          3'd4: d_tx_data <= hexd(cap_rd[3:0]);
          3'd5: d_tx_data <= 8'h0D;
          default: d_tx_data <= 8'h0A;
        endcase
        d_tx_valid <= 1;
        if (dump_c == 3'd6) begin
          dump_c <= 0;
          dump_i <= dump_i + 1'b1;
          cap_raddr <= cap_raddr + 1'b1;  // settled long before next char
          if (dump_i == CAP_LAST) begin dump_run <= 0; dump_fin <= 1; end
        end else dump_c <= dump_c + 1'b1;
      end
    end
  end
  // FIXED 24-AUG-2026: this was hardcoded 1406, i.e. 13.5 MHz / 9600, which is
  // right ONLY for the slow bring-up variant. The dumper is clocked on clk2x,
  // which is 2 x BOARD_CLK_FREQ - so at -Variant full (clk2x 54 MHz) every
  // capture dump went out at ~38400 baud while the reader listened at 9600,
  // and arrived as garbage. Measured: two stage-timer runs produced "no
  // records" and looked like a stripped probe. Derive it instead.
  uart_tx #(
      .DELAY_FRAMES((2 * `BOARD_CLK_FREQ) / `UART_BAUD_RATE)
  ) u_dbg_tx (
      .clk(clk2x),
      .rst_n(sys_rst_n),
      .tx_data(d_tx_data),
      .tx_valid(d_tx_valid),
      .tx_busy(d_tx_busy),
      .txd(dbg_txd)
  );
  wire cpu_txd;
  // The write-path analyzer served its purpose: with the P3 strobe
  // conversion the write decode fires during normal boot, so the dump
  // would trigger every boot and hold the TX pin forever (dump_fin never
  // clears). Only let it take the console when explicitly enabled.
`ifdef TANG_PC_HISTORY
  // PC history: once an access to the ND-500 window page has frozen the ring,
  // the dumper takes the TX pin and streams the preceding {PIL,P} samples as
  // hex. The console is dead afterwards - expected, and the machine is about
  // to halt in ERRFATAL anyway.
  //
  // WITHOUT this branch the dump never reaches a pin, the ring is dead logic
  // and synthesis removes everything feeding it - including the probe. That
  // exact failure was measured on 21-AUG-2026 for TANG_PF_CAPTURE: the netlist
  // contained no capture registers at all and the readout streamed constants.
  assign uart_txp = dbg_dumping ? dbg_txd : cpu_txd;
`elsif TANG_JPL_CAPTURE
  // JPL-read capture: once the two-instruction window at 064544/064545 has
  // closed, the dumper takes the TX pin and streams the 512 tagged samples.
  //
  // THIS BRANCH IS NOT OPTIONAL AND ITS ABSENCE IS SILENT. Measured 22-AUG-2026
  // on the first JPL-capture run: without it uart_txp falls through to the
  // `else` below, the dumper drives nothing, the ring becomes dead logic and
  // synthesis removes the whole probe. The board booted normally straight to
  // the ERRFATAL and produced ZERO dump bytes, which looks exactly like "the
  // trigger never fired" - 20 minutes of build and boot spent on nothing. The
  // same trap is recorded for TANG_PF_CAPTURE two branches down; adding a
  // capture variant means adding a branch HERE as well.
  assign uart_txp = dbg_dumping ? dbg_txd : cpu_txd;
`elsif TANG_PF_CAPTURE
  // Page-fault freeze readout: once ND120_PF_CAPTURE has frozen a fault the
  // dumper takes the TX pin and streams the ring, which holds the four 14-bit
  // slices of the frozen word. The console is dead afterwards - expected, the
  // machine has already halted in ERRFATAL.
  //
  // WITHOUT this branch the dump never reaches a pin, so the ring is dead
  // logic and synthesis removes everything feeding it - including the capture
  // block itself. Measured 21-AUG-2026: the netlist contained no PF_CAPTURE
  // registers at all and the readout would have streamed constants.
  assign uart_txp = dbg_dumping ? dbg_txd : cpu_txd;
`elsif TANG_PTWR_CAPTURE
  // Page-table write history: after the ERRFATAL/frozen-CSA trigger the
  // dumper takes the TX pin and streams the ring of {tag,addr,data} write
  // records. WITHOUT this branch the ring is dead logic and synthesis strips
  // the whole probe (the TANG_PF_CAPTURE lesson two branches up).
  assign uart_txp = dbg_dumping ? dbg_txd : cpu_txd;
`elsif TANG_STAGE_TIMER
  // Stage timer: the dumper streams the tick totals once the window closes.
  // Without this branch synthesis strips the whole probe.
  assign uart_txp = dbg_dumping ? dbg_txd : cpu_txd;
`elsif TANG_PGW_CAPTURE
  // Page-write watch: after the ERRFATAL/frozen-CSA trigger the dumper takes
  // the TX pin and streams the ring of bridge-level accesses to the failing
  // page's neighbourhood. Without this branch synthesis strips the probe.
  assign uart_txp = dbg_dumping ? dbg_txd : cpu_txd;
`elsif TANG_PTORD_CAPTURE
  // Ordering capture: after the ERRFATAL/frozen-CSA trigger the dumper takes
  // the TX pin and streams the interleaved ring of page-table writes and
  // no-permit-access markers for raw page 0o1032. WITHOUT this branch the ring
  // is dead logic and synthesis strips the whole probe (the TANG_PF_CAPTURE
  // lesson three branches up).
  assign uart_txp = dbg_dumping ? dbg_txd : cpu_txd;
`elsif TANG_PFLOG_CAPTURE
  // Fault stream: after the ERRFATAL trigger the dumper takes the TX pin and
  // streams the last 512 page faults (page + entry). Without this branch
  // synthesis strips the probe (the TANG_PF_CAPTURE lesson).
  assign uart_txp = dbg_dumping ? dbg_txd : cpu_txd;
`elsif TANG_PFPATH_CAPTURE
  // Handler-path capture: dumper takes the TX pin after the first return to
  // level 1 following the 0o1032 fault. Without this branch synthesis strips
  // the probe (the TANG_PF_CAPTURE lesson).
  assign uart_txp = dbg_dumping ? dbg_txd : cpu_txd;
`elsif TANG_WRITE_ANALYZER_DUMP
  assign uart_txp = dbg_dumping ? dbg_txd : cpu_txd;
`elsif TANG_WD_TRACE_DUMP
  // The dump takes the console only after the diagnostic has printed.
  assign uart_txp = dbg_dumping ? dbg_txd : cpu_txd;
`elsif TANG_GRANT_CAPTURE
  // Grant-capture: after PIL->10 fires the capture, the dumper takes the TX
  // pin and streams the 512 {PIL,CSA} samples as hex. The console is dead
  // after that (expected - the CPU has wedged at level 10 anyway).
  assign uart_txp = dbg_dumping ? dbg_txd : cpu_txd;
`elsif TANG_TRAP_CAPTURE
  // Trap-capture: after the vector-7 / hang trigger fires, the dumper takes
  // the TX pin and streams the 512 {TVEC,TRAPN,CSA} samples as hex. Console
  // dead afterwards (expected - the CPU is spinning at vector 7 / wedged).
  assign uart_txp = dbg_dumping ? dbg_txd : cpu_txd;
`else
  /* verilator lint_off UNUSEDSIGNAL */
  wire unused_dbg_txd = dbg_txd;
  /* verilator lint_on UNUSEDSIGNAL */
  assign uart_txp = cpu_txd;
`endif

  // Phase 2 of the ND120_CORE extraction (14-JUL-2026): the core now carries
  // the ND-BUS tape device (INCLUDE_TAPE=1) and nd_storage_devices hangs off
  // the TAPE_BYTE_* seam, so '400$' at the console boots BOOT.BPUN off the real
  // SD card - the same RTL proven in the runSim harness against a simulated
  // card. Floppy and SMD stay out until their phases (they need the sync-read
  // buffer refactor first - see BSRAM-BUDGET.md).
  //
  // installation_number, the s_high/s_low helpers, OC_1_0, SEL_TESTMUX and
  // the baud-rate switch now live INSIDE ND120_CORE (CPU-board constants),
  // so this board no longer declares them.
  //
  // installation_number is no longer a constant: ND120_CORE holds a real
  // 16-byte BACK-WIRING PROM (BACKWIRING_PROM, addressed by PIL 3:0) that
  // SINTRAN reads with VERSN / IDBS,INR=35. To bake a CPU NUMBER / CPU TYPE /
  // legal-user count into THIS bitstream, add the `defines to
  // src/tang20k_defines.v (compiled first, so it wins over the defaults):
  //   `define ND120_SYSNO   16'd42
  //   `define ND120_HWINFO2 16'd102
  //   `define ND120_NLEGU   8'd32
  // Defaults + "not present" sentinels live in
  // ../../Shared/support/nd120_backwiring_defaults.vh; mechanism in
  // ../../docs/backwiring-prom-installation-number.md.

  /**********************************************
  *  Storage: BOOT.BPUN off the SD card         *
  ***********************************************/
  // Clock choice - deliberate, do not "simplify" to clk_cpu/clk2x:
  // sd_file_reader's identification clock is a HARDCODED divide (INIT_HALF=99,
  // sd_file_reader.v:124), i.e. clk/198. The SD spec requires 100-400 kHz for
  // card identification, so ONLY the 27 MHz crystal is legal:
  //     27.00 MHz -> 136.4 kHz  OK   (and what sd-fat-test proved on silicon)
  //     13.50 MHz ->  68.2 kHz  OUT OF SPEC
  //      6.75 MHz ->  34.1 kHz  OUT OF SPEC
  // Running the stack from sys_clk also keeps the card at one fixed speed for
  // every VARIANT (slow/crawl/full only move the CPU). The SDRAM device port
  // is built for exactly this: stor_clk is its own domain, toggle-CDC'd into
  // clk2x inside MEM_RAM_49_SDRAM.
  wire clk_stor = sys_clk;

  // sys_rst_n is generated in the clk_cpu domain; 2-FF synchronize its release
  // into the storage domain (async assert, synchronous deassert).
  reg stor_rst_r1, stor_rst_r2;
  always @(posedge clk_stor or negedge sys_rst_n)
    if (!sys_rst_n) begin
      stor_rst_r1 <= 1'b0;
      stor_rst_r2 <= 1'b0;
    end else begin
      stor_rst_r1 <= 1'b1;
      stor_rst_r2 <= stor_rst_r1;
    end
  wire rst_stor_n = stor_rst_r2;

  wire        TAPE_BYTE_REQ, TAPE_REWIND;
  wire        s_tape_byte_valid;
  wire [7:0]  s_tape_byte_data;

  wire        s_sd_clk_o;
  wire        s_sd_cmd_o, s_sd_cmd_oe;
  wire        s_sd_dat0_o, s_sd_dat0_oe;
  wire        s_sd_dat1_o, s_sd_dat1_oe;
  wire        s_sd_dat2_o, s_sd_dat2_oe;
  wire        s_sd_dat3_o, s_sd_dat3_oe;
  wire [ 1:0] s_sd_status;

  // SDRAM device port (clk_stor domain) into MEM_RAM_49_SDRAM's upper half
  wire        s_mem_start, s_mem_we;
  wire [19:0] s_mem_addr;
  wire [31:0] s_mem_wdata, s_mem_rdata;
  wire        s_mem_busy, s_mem_done;

  // Storage device select: TANG_FLOPPY adds the floppy (1560&); the tape
  // (400$) is carried in EVERY build - Ronny 06-AUG-2026: the tape driver is
  // small, keep it alongside the floppy. What stays dropped in a floppy build
  // is LONG-FILENAME parsing (SDFAT_NO_LFN, ~1800 LUT), which is why the
  // tape's boot file on the Tang is the 8.3 name BOOT.TAP, not BOOT.BPUN -
  // see the BOOT_NAME override at the nd_storage_devices instantiation.
`ifdef TANG_FLOPPY
  localparam TANG_INC_TAPE   = 1;
  localparam TANG_INC_FLOPPY = 1;
`else
  localparam TANG_INC_TAPE   = 1;
  localparam TANG_INC_FLOPPY = 0;
`endif
  // TANG_SMD (src/tang20k_defines.v) adds the SMD disk at 1540 (SMD0.IMG via
  // nd_storage client 3) on top of whichever base build is selected. It is a
  // separate gate so it can be dropped alone if resources overflow.
`ifdef TANG_SMD
  localparam TANG_INC_SMD    = 1;
`else
  localparam TANG_INC_SMD    = 0;
`endif
  // TANG_WD adds the Winchester at 500 (WD0.IMG via nd_storage client 6)
  // INSTEAD of the SMD - the two are mutually exclusive on BSRAM, which
  // tang20k_defines.v enforces at elaboration.
`ifdef TANG_WD
  localparam TANG_INC_WD     = 1;
 `ifdef TANG_SMD
  // Both disc controllers selected. There is no 47th BSRAM block: each owns a
  // 1024x16 buffer and TANG_SMD alone already sits at 46/46 (see the note on
  // `define TANG_SMD in tang20k_defines.v). Pick one. This deliberately
  // instantiates a module that does not exist, so the build stops with a
  // message that names the problem rather than failing later in place-and-
  // route with an opaque resource error.
  TANG_SMD_AND_TANG_WD_ARE_MUTUALLY_EXCLUSIVE_ONLY_ONE_BSRAM_BUFFER_IS_FREE u_pick_one();
 `endif
`else
  localparam TANG_INC_WD     = 0;
`endif
  // ---------------------------------------------------------------------
  // STORAGE SEAM NETS - declared HERE, ABOVE their first use.
  //
  // Moved 24-AUG-2026. These were declared several hundred lines BELOW the
  // instantiations that use them, so every one was created as an IMPLICIT
  // 1-bit net at first use and the real declaration came too late - 42
  // EX3638 warnings in the Gowin log. Implicit nets are 1 bit wide, which
  // silently truncates every multi-bit member of this group, and it left
  // the block-activity LEDs reading a net nothing drove.
  // ---------------------------------------------------------------------
  // Floppy seam (1560&) is WIRED to the SD-FAT stack (FLOPPY1.IMG via the
  // nd_storage floppy adapter inside TAPE_SDFAT_SOURCE). The SMD seam (1540&)
  // is wired the same way (SMD0.IMG via nd_storage_disc_adapter, client 3)
  // when TANG_SMD is defined; otherwise the source ties it idle and the core
  // leaves the device out.
  wire [15:0] DMA_RDATA;
  wire        DMA_ACK, DMA_ERR, DMA_BUSY;
  // core -> floppy backend (request)
  wire        FDISK_REQ, FDISK_WR;
  wire [15:0] FDISK_LSECT;
  wire [1:0]  FDISK_FORMAT, FDISK_DRIVE;
  wire [10:0] FDISK_WORDCOUNT;
  wire [15:0] FDBUF_RDATA;
  // floppy backend -> core (completion + buffer fill + media format)
  wire        FDISK_DONE, FDISK_ERR;
  wire [ 3:0] FDISK_ERR_CODE;
  wire [3:0]  FDISK_MEDIA_FMT;
  wire [9:0]  FDBUF_ADDR;
  wire [15:0] FDBUF_WDATA;
  wire        FDBUF_WE;
  wire        SDISK_START, SDISK_REQ, SDISK_WR;
  wire        WDISK_START, WDISK_REQ, WDISK_WR;
  wire [15:0] SDISK_BLKADDR1, SDISK_BLKADDR2;
  wire [2:0]  SDISK_UNIT;
  wire [10:0] SDISK_WORDCOUNT;
  wire [15:0] SDBUF_RDATA;
  wire [15:0] WDISK_BLKADDR1, WDISK_BLKADDR2;
  wire [ 2:0] WDISK_UNIT;
  wire [10:0] WDISK_WORDCOUNT;
  wire        WDISK_DONE, WDISK_ERR;
  wire [ 3:0] WDISK_ERR_CODE;
  wire [ 9:0] WDBUF_ADDR;
  wire [15:0] WDBUF_WDATA;
  wire        WDBUF_WE;
  wire [15:0] WDBUF_RDATA;
  // SMD backend -> core (completion + buffer fill); tied idle by the
  // source's gen_no_smd when TANG_SMD is absent
  wire        SDISK_DONE, SDISK_ERR;
  wire [ 3:0] SDISK_ERR_CODE;
  wire [9:0]  SDBUF_ADDR;
  wire [15:0] SDBUF_WDATA;
  wire        SDBUF_WE;


  nd_storage_devices #(
      // 24-AUG-2026: 4-bit did NOT boot (no banner in 420 s; the CPU was alive -
      // OPCOM echoed 20500& - so the SD read path failed). Two variables had
      // been changed together, the bit clock 2.7->13.5 MHz AND the width.
      // Back to 1-bit to test the clock change ALONE.
      // 4-bit DAT bus. DAT1-3 are wired on the board (J7 -> FPGA 85/80/81,
      // schematic table in doc/SD-SLOT-WIRING.md). This was 0 until 25-AUG
      // because nd_storage tied the writer's CMD55 argument to rca=0, so the
      // card never accepted the ACMD6 width switch and every 4-bit transfer
      // read garbage; the reader's published card_rca is now wired through
      // (gate: make test-nds-mount-4bit in SD-FAT/sim).
      .USE_4BIT      (1),
      .SIMULATE(0),                     // real card: full-length SD init
      .INCLUDE_TAPE(TANG_INC_TAPE),
      .INCLUDE_FLOPPY(TANG_INC_FLOPPY),
      .INCLUDE_SMD(TANG_INC_SMD),
      .INCLUDE_WD(TANG_INC_WD),
      // The Tang boots the tape from BOOT.TAP, not the default BOOT.BPUN:
      // ".BPUN" is a 4-character extension, which FAT can only store via a
      // VFAT long-filename entry, and the floppy/WD builds strip LFN parsing
      // (SDFAT_NO_LFN). An 8.3 name is readable by every build variant.
      .BOOT_NAME("BOOT.TAP"),
      .BOOT_LEN(8'd8)
  ) TAPE_SDFAT_SOURCE (
      .clk_stor  (clk_stor),
      .rst_stor_n(rst_stor_n),
      .clk_cpu   (clk_cpu),
      .rst_cpu_n (sys_rst_n),

      .byte_req     (TAPE_BYTE_REQ),
      .byte_valid   (s_tape_byte_valid),
      .byte_data    (s_tape_byte_data),
      .source_rewind(TAPE_REWIND),

      // floppy disk-image backend seam (client 1 = FLOPPY1.IMG) <-> the core
      .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),

      // SMD disk-image backend seam (client 3 = SMD0.IMG) <-> the core
      .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),

      // Winchester disk-image backend (client 6 = WD0.IMG)
      .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     (WDISK_DONE),
      .WDISK_ERR      (WDISK_ERR),
      .WDISK_ERR_CODE (WDISK_ERR_CODE),
      .WDBUF_ADDR     (WDBUF_ADDR),
      .WDBUF_WDATA    (WDBUF_WDATA),
      .WDBUF_WE       (WDBUF_WE),
      .WDBUF_RDATA    (WDBUF_RDATA),

      .sd_clk_o  (s_sd_clk_o),
      .sd_cmd_i  (sd_cmd),
      .sd_cmd_o  (s_sd_cmd_o),
      .sd_cmd_oe (s_sd_cmd_oe),
      .sd_dat0_i (sd_dat0),
      .sd_dat0_o (s_sd_dat0_o),
      .sd_dat0_oe(s_sd_dat0_oe),
      .dbg_sd_busy   (s_dbg_sd_busy),
      .dbg_cache_pend(s_dbg_cache_pend),
      .sd_dat1_i (sd_dat1),
      .sd_dat1_o (s_sd_dat1_o),
      .sd_dat1_oe(s_sd_dat1_oe),
      .sd_dat2_i (sd_dat2),
      .sd_dat2_o (s_sd_dat2_o),
      .sd_dat2_oe(s_sd_dat2_oe),
      .sd_dat3_i (sd_dat3),
      .sd_dat3_o (s_sd_dat3_o),
      .sd_dat3_oe(s_sd_dat3_oe),

      .mem_start(s_mem_start),
      .mem_we   (s_mem_we),
      .mem_addr (s_mem_addr),
      .mem_wdata(s_mem_wdata),
      .mem_rdata(s_mem_rdata),
      .mem_busy (s_mem_busy),
      .mem_done (s_mem_done),

      .DBG_STATE   (DBG_STATE),
      .DBG_LBA     (DBG_LBA),
      .DBG_WDATA   (DBG_WDATA),
      .DBG_RDATA   (DBG_RDATA),
      .DBG_BUFW    (DBG_BUFW),
      .DBG_BUFWE   (DBG_BUFWE),
      .DBG_FSEC    (DBG_FSEC),
      .DBG_RX_STB  (DBG_RX_STB),
      .DBG_RX_RAW  (DBG_RX_RAW),
      .DBG_RX_BYTE (DBG_RX_BYTE),
      .DBG_PAST_EOF(DBG_PAST_EOF),
      .DBG_GRANT   (DBG_GRANT),
      .sd_status(s_sd_status)
  );

  /**********************************************
  *  SD pads - the ONLY tristates (repo rule)   *
  ***********************************************/
  // Single-ternary form  oe ? val : 1'bz  is mandatory: it is the only idiom
  // yosys maps to a real IOBUF. A 'z' in an INNER ternary branch silently
  // collapses to a plain driver and shorts the bus - the silicon-only bug
  // documented in sd-fat-test/src/sd_fat_test_top.v. Verified by 'make check'
  // (check_tristate.py) below. DAT1-3 are not driven at all; the slot's
  // external 10K pull-ups hold them high, which is what keeps the card out of
  // SPI mode at CMD0.
  assign sd_clk  = s_sd_clk_o;
  assign sd_cmd  = s_sd_cmd_oe  ? s_sd_cmd_o  : 1'bz;
  assign sd_dat0 = s_sd_dat0_oe ? s_sd_dat0_o : 1'bz;
  // EVERY pad below MUST stay in this single-ternary  oe ? val : 1'bz  form.
  // A nested ternary (e.g. park-driven-high with 1'bz in the inner branch) is
  // silently collapsed by yosys into an always-driving OBUF: the FPGA then
  // fights the card on DAT1-3 through every 4-bit read data phase, which
  // simulates perfectly and fails on silicon. Proven from the synthesis
  // netlist 12-JUL-2026 - see fpga/tang-nano-20k/sd-fat-test/README.md. The pads idle
  // released and the slot's external 10K pull-ups hold the lines high,
  // including DAT3 at CMD0 (SD-native mode select).
  assign sd_dat1 = s_sd_dat1_oe ? s_sd_dat1_o : 1'bz;
  assign sd_dat2 = s_sd_dat2_oe ? s_sd_dat2_o : 1'bz;
  assign sd_dat3 = s_sd_dat3_oe ? s_sd_dat3_o : 1'bz;

  /**********************************************
  *  STORAGE BRING-UP LED SET (ACTIVE LOW)      *
  ***********************************************/
  // The board's only window into the SD stack. It answers "did the card
  // mount?" and "did the CPU ever ask the tape for a byte?" SEPARATELY, so a
  // silent console can be attributed to the CPU or to storage instead of
  // guessed at.
  //
  //   led[5] heartbeat ~0.8 Hz        - clk_cpu alive at all
  //   led[4] sd_status[1]  \  00 = NOTCHK (the mount never ran)  01 = NOCARD
  //   led[3] sd_status[0]  /  10 = ERROR (mount/FAT failed)      11 = OK
  //   led[2] a tape byte was served   - the SD->tape path delivered data
  //   led[1] the SD clock has toggled - the card is being talked to at all
  //   led[0] the CPU asked the tape for a byte - '400$' reached ND_TAPE_400
  //
  // Reading it: led[4] AND led[3] both lit = the whole SD-FAT chain works
  // (card init, FAT walk, BOOT.BPUN located and preloaded into the SDRAM
  // region). led[0] dark after typing 400$ = the CPU never drove the device,
  // i.e. a CPU/bus problem and NOT a storage one.
  //
  // sd_status is a clk_stor signal sampled into clk_cpu here with no CDC: it
  // drives an LED for a human eye, where a torn sample is unobservable.
  // The write-path analyzer keeps its stickies (still live behind
  // TANG_WRITE_ANALYZER_DUMP) - they just no longer own the LEDs.
  reg s_tape_byte_seen, s_tape_req_seen, s_sdclk_seen, s_sdclk_d;
  always @(posedge clk_cpu or negedge sys_rst_n)
    if (!sys_rst_n) begin
      s_tape_byte_seen <= 1'b0;
      s_tape_req_seen  <= 1'b0;
      s_sdclk_seen     <= 1'b0;
      s_sdclk_d        <= 1'b0;
    end else begin
      s_sdclk_d <= s_sd_clk_o;
      if (s_tape_byte_valid)       s_tape_byte_seen <= 1'b1;
      if (TAPE_BYTE_REQ)           s_tape_req_seen  <= 1'b1;
      if (s_sd_clk_o != s_sdclk_d) s_sdclk_seen     <= 1'b1;
    end

  // led[0]/led[1] repurposed 07-AUG-2026 (Ronny): storage BLOCK activity.
  // A block op lasts microseconds, so each event loads a ~150 ms stretcher
  // (22 bits at 27 MHz) - one flash per block, a solid glow under load.
  // Sources are the device-side backend seams (clk_cpu domain): floppy and
  // Winchester block requests, direction from the WR flag. The old meanings
  // (tape-req-seen / sd-clk-seen) served bring-up and are retired.
  //
  // 24-AUG-2026: widened from 22 to 24 bits (~155 ms -> ~620 ms at 27 MHz).
  // A block op is microseconds and SINTRAN's disc bursts are short, so the
  // old stretch was easy to miss by eye. A THIRD stretcher watches the SD
  // card clock itself, so there is an indicator that lights for ANY card
  // traffic even if a device seam is mis-wired.
  reg [23:0] s_led_rd_stretch, s_led_wr_stretch, s_led_sd_stretch;
  wire s_blk_rd_ev = (FDISK_REQ & ~FDISK_WR) | (WDISK_REQ & ~WDISK_WR);
  wire s_blk_wr_ev = (FDISK_REQ &  FDISK_WR) | (WDISK_REQ &  WDISK_WR);
  // s_sdclk_d is already registered at line 1989 for s_sdclk_seen - reuse it
  // rather than declaring a second copy of the same delayed sample.
  wire s_sd_activity = (s_sd_clk_o != s_sdclk_d);
  always @(posedge clk_cpu or negedge sys_rst_n)
    if (!sys_rst_n) begin
      s_led_rd_stretch <= 24'd0;
      s_led_wr_stretch <= 24'd0;
      s_led_sd_stretch <= 24'd0;
    end else begin
      if (s_blk_rd_ev)                 s_led_rd_stretch <= {24{1'b1}};
      else if (|s_led_rd_stretch)      s_led_rd_stretch <= s_led_rd_stretch - 24'd1;
      if (s_blk_wr_ev)                 s_led_wr_stretch <= {24{1'b1}};
      else if (|s_led_wr_stretch)      s_led_wr_stretch <= s_led_wr_stretch - 24'd1;
      if (s_sd_activity)               s_led_sd_stretch <= {24{1'b1}};
      else if (|s_led_sd_stretch)      s_led_sd_stretch <= s_led_sd_stretch - 24'd1;
    end

  assign led[0] = ~|s_led_rd_stretch; // ON = storage BLOCK READ in the last ~150 ms
  assign led[1] = ~|s_led_wr_stretch; // ON = storage BLOCK WRITE in the last ~150 ms
`ifdef TANG_PC_HISTORY
  // Repurposed for the PC-history probe: the board otherwise gives NO indication
  // of whether the dumper ever started, which is exactly what the first run
  // could not determine. ON = dbg_dumping (the dumper has taken the TX pin).
  assign led[2] = ~dbg_dumping;
`elsif TANG_JPL_CAPTURE
  assign led[2] = ~dbg_dumping;
`else
  // 24-AUG-2026: was ~s_tape_byte_seen (a bring-up indicator). Now RAW SD CARD
  // TRAFFIC: ON = the SD clock toggled in the last ~620 ms. led[0]/led[1] say
  // a device asked for a block; this one says the card was actually talked to,
  // so a dark led[2] with a lit led[0] means the request never reached the
  // card.
  assign led[2] = ~|s_led_sd_stretch;
`endif
  assign led[3] = ~s_sd_status[0];    // sd_status low bit
  assign led[4] = ~s_sd_status[1];    // sd_status high bit (both lit = OK)
  assign led[5] = clockTicks[24];     // heartbeat ~0.8 Hz (clock alive)

  /* verilator lint_off UNUSEDSIGNAL */
  wire unused_analyzer_leds = &{1'b0, s_dbg_memw, dbg_dumping, wdec_seen,
                                write_seen, s_cpu_led[2], s_tape_req_seen,
                                s_sdclk_seen, 1'b0};
  /* verilator lint_on UNUSEDSIGNAL */


  /* verilator lint_off UNUSEDSIGNAL */
  // FDISK_*/FDBUF_* and (under TANG_SMD) SDISK_*/SDBUF_* are used; only the
  // external DMA test-client port stays unused in this build.
  wire unused_core_seam = &{1'b0, DMA_RDATA,
                            DMA_ACK, DMA_ERR, DMA_BUSY, 1'b0};
  /* verilator lint_on UNUSEDSIGNAL */

  ND120_CORE #(
      .INCLUDE_TAPE  (TANG_INC_TAPE),
      .INCLUDE_FLOPPY(TANG_INC_FLOPPY),
      .INCLUDE_SMD   (TANG_INC_SMD),
      .INCLUDE_WD    (TANG_INC_WD)
  ) CORE (
      .BAUD_9600(1'b0),   // no baud switch on this board - console fixed 115200
      .clk_cpu(clk_cpu),  // CPU core, OSC and bus all on 27 MHz
      .sys_rst_n(sys_rst_n),
      .CACHE_SW(1'b1),    // console SW1: no free switch here; ND120_NO_CACHE overrides it to off

      // C-PLUG bus: no external bus on this board (tied off above)
      .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(),

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

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

      // UART console (BL616 USB serial)
      .RXD(uart_rxp),
      .TXD(cpu_txd),

      // Storage seam: the tape reads BOOT.BPUN off the SD card
      .TAPE_BYTE_REQ(TAPE_BYTE_REQ),
      .TAPE_BYTE_VALID(s_tape_byte_valid),
      .TAPE_BYTE_DATA(s_tape_byte_data),
      .TAPE_REWIND(TAPE_REWIND),

      .DMA_REQ(1'b0),
      .DMA_WR(1'b0),
      .DMA_ADDR(24'd0),
      .DMA_WDATA(16'd0),
      .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),

      // Winchester disk-image backend (TANG_WD; tied idle otherwise)
      .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(WDISK_DONE),
      .WDISK_ERR(WDISK_ERR),
      .WDISK_ERR_CODE(WDISK_ERR_CODE),
      .WDBUF_ADDR(WDBUF_ADDR),
      .WDBUF_WDATA(WDBUF_WDATA),
      .WDBUF_WE(WDBUF_WE),
      .WDBUF_RDATA(WDBUF_RDATA),

      // Debug / status
      .LED(s_cpu_led[6:0]),
      .RUN_n(s_run),
      .CSA_12_0(CSA_12_0),
      .PIL(s_pil_3_0),
      .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_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),
      .DEBUG_IREQ_15_0_N(s_ireq_15_0_n),
      .XMIC_DBG_15_0(s_xmic_dbg),

      // SDRAM main memory (MAIN_RAM_SDRAM, threaded down to MEM_RAM_49_SDRAM)
      .clk2x(clk2x),
      .clk2x_sdram(clk2x_sdram),
      .O_sdram_clk(O_sdram_clk),
      .O_sdram_cke(O_sdram_cke),
      .O_sdram_cs_n(O_sdram_cs_n),
      .O_sdram_cas_n(O_sdram_cas_n),
      .O_sdram_ras_n(O_sdram_ras_n),
      .O_sdram_wen_n(O_sdram_wen_n),
      .IO_sdram_dq(IO_sdram_dq),
      .O_sdram_addr(O_sdram_addr),
      .O_sdram_ba(O_sdram_ba),
      .O_sdram_dqm(O_sdram_dqm),
      .DBG_MEMW(s_dbg_memw),
      .DBG_PTW(s_dbg_ptw),   // page-table write stream (23-AUG, zero-read campaign)
      .PF_CAPTURED(s_pf_captured),
      .DBG_WDSTAGE(s_dbg_wdstage),
      .DBG_PPN(s_dbg_ppn),
      .DBG_PGW(s_dbg_pgw),

      // nd_storage device port -> MEM_RAM_49_SDRAM's upper-half region
      .stor_clk  (clk_stor),
      .stor_rst_n(rst_stor_n),
      .mem_start (s_mem_start),
      .mem_we    (s_mem_we),
      .mem_addr  (s_mem_addr),
      .mem_wdata (s_mem_wdata),
      .mem_rdata (s_mem_rdata),
      .mem_busy  (s_mem_busy),
      .mem_done  (s_mem_done)
`ifdef TANG_WD_TRACE_DUMP
      ,
      .wd_trace_rec (wd_trace_rec),
      .wd_trace_we  (wd_trace_we),
      .wd_trace_done(wd_trace_done)
`endif
  );

endmodule