Skip to content

emu

Source: Verilog/fpga/mister/nd120.sv

Where it sits (MiSTer): emu

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

Contains: console_uart_rx, console_uart_tx, hps_io (vendor), mips_counter, nd120_console_mister, ND120_CORE, nd_storage_mister_devices, pll (vendor), pll_cpu

Module hierarchy - All modules

emu symbol

Schematic

Drawn from the Verilog: the yosys netlist of the MiSTer 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).

emu schematic

Ports

Direction Width Name Description
input 1 CLK_50M
input 1 RESET
inout [45:0] HPS_BUS
output 1 CLK_VIDEO
output 1 CE_PIXEL
output [12:0] VIDEO_ARX
output [12:0] VIDEO_ARY
output [7:0] VGA_R
output [7:0] VGA_G
output [7:0] VGA_B
output 1 VGA_HS
output 1 VGA_VS
output 1 VGA_DE = ~(VBlank | HBlank)
output 1 VGA_F1
output [1:0] VGA_SL
output 1 VGA_SCALER Force VGA scaler
output 1 VGA_DISABLE analog out is off
input [11:0] HDMI_WIDTH
input [11:0] HDMI_HEIGHT
output 1 HDMI_FREEZE
output 1 HDMI_BLACKOUT
output 1 HDMI_BOB_DEINT
output 1 FB_EN
output [4:0] FB_FORMAT
output [11:0] FB_WIDTH
output [11:0] FB_HEIGHT
output [31:0] FB_BASE
output [13:0] FB_STRIDE
input 1 FB_VBL
input 1 FB_LL
output 1 FB_FORCE_BLANK
output 1 FB_PAL_CLK
output [7:0] FB_PAL_ADDR
output [23:0] FB_PAL_DOUT
input [23:0] FB_PAL_DIN
output 1 FB_PAL_WR
output 1 LED_USER 1 - ON, 0 - OFF.
output [1:0] LED_POWER
output [1:0] LED_DISK
output [1:0] BUTTONS
input 1 CLK_AUDIO 24.576 MHz
output [15:0] AUDIO_L
output [15:0] AUDIO_R
output 1 AUDIO_S 1 - signed audio samples, 0 - unsigned
output [1:0] AUDIO_MIX 0 - no mix, 1 - 25%, 2 - 50%, 3 - 100% (mono)
inout [3:0] ADC_BUS
output 1 SD_SCK
output 1 SD_MOSI
input 1 SD_MISO
output 1 SD_CS
input 1 SD_CD
output 1 DDRAM_CLK
input 1 DDRAM_BUSY
output [7:0] DDRAM_BURSTCNT
output [28:0] DDRAM_ADDR
input [63:0] DDRAM_DOUT
input 1 DDRAM_DOUT_READY
output 1 DDRAM_RD
output [63:0] DDRAM_DIN
output [7:0] DDRAM_BE
output 1 DDRAM_WE
output 1 SDRAM_CLK
output 1 SDRAM_CKE
output [12:0] SDRAM_A
output [1:0] SDRAM_BA
inout [15:0] SDRAM_DQ
output 1 SDRAM_DQML
output 1 SDRAM_DQMH
output 1 SDRAM_nCS
output 1 SDRAM_nCAS
output 1 SDRAM_nRAS
output 1 SDRAM_nWE
input 1 SDRAM2_EN
output 1 SDRAM2_CLK
output [12:0] SDRAM2_A
output [1:0] SDRAM2_BA
inout [15:0] SDRAM2_DQ
output 1 SDRAM2_nCS
output 1 SDRAM2_nCAS
output 1 SDRAM2_nRAS
output 1 SDRAM2_nWE
input 1 UART_CTS
output 1 UART_RTS
input 1 UART_RXD
output 1 UART_TXD
output 1 UART_DTR
input 1 UART_DSR
input [6:0] USER_IN
output [6:0] USER_OUT
input 1 OSD_STATUS

Verilog source

Verilog/fpga/mister/nd120.sv on GitHub.

Show the Verilog of emu (1087 lines)
//============================================================================
//
//  This program is free software; you can redistribute it and/or modify it
//  under the terms of the GNU General Public License as published by the Free
//  Software Foundation; either version 2 of the License, or (at your option)
//  any later version.
//
//  This program is distributed in the hope that it will be useful, but WITHOUT
//  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
//  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
//  more details.
//
//  You should have received a copy of the GNU General Public License along
//  with this program; if not, write to the Free Software Foundation, Inc.,
//  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
//============================================================================

module emu
(
    `include "sys/emu_ports.vh"
);

///////// Default values for ports not used in this core /////////

assign ADC_BUS  = 'Z;
assign USER_OUT = '1;
// UART_TXD is driven from the CPU's console further down - see "SERIAL
// CONSOLE". RTS/DTR stay inactive; the ND-120 console is 3-wire.
assign {UART_RTS, UART_DTR} = 0;
assign {SD_SCK, SD_MOSI, SD_CS} = 'Z;
// SDRAM: main memory (01-SEP-2026) - driven by the sheet-49 SDRAM bridge
// through ND120_CORE, see the CLOCKS and MAIN MEMORY notes below.
assign {DDRAM_CLK, DDRAM_BURSTCNT, DDRAM_ADDR, DDRAM_DIN, DDRAM_BE, DDRAM_RD, DDRAM_WE} = '0;  

assign VGA_SL = 0;
assign VGA_F1 = 0;
assign VGA_SCALER  = 0;
assign VGA_DISABLE = 0;
assign HDMI_FREEZE = 0;
assign HDMI_BLACKOUT = 0;
assign HDMI_BOB_DEINT = 0;

assign AUDIO_S = 0;
assign AUDIO_L = 0;
assign AUDIO_R = 0;
assign AUDIO_MIX = 0;

// LED_DISK / LED_POWER are driven from the CPU's own status lamps further
// down, once s_core_led is declared - see "CPU status lamps".
assign BUTTONS = 0;

//////////////////////////////////////////////////////////////////

wire [1:0] ar = status[122:121];

assign VIDEO_ARX = (!ar) ? 12'd4 : (ar - 1'd1);
assign VIDEO_ARY = (!ar) ? 12'd3 : 12'd0;

`include "build_id.v" 
localparam CONF_STR = {
    "ND120;;",
    "-;",
    "O[122:121],Aspect ratio,Original,Full Screen,[ARC1],[ARC2];",
    "-;",
    "O[6],Keyboard,US,Norwegian;",
    // Panel default ON (build 2, 31-AUG-2026): status bits reset to 0, so
    // bit 0 = "On" and bit 1 = "Off" gives default-on with no extra default-
    // value machinery. Same DBG_PANEL signals the Nexys build's sw[3] shows.
    "O[7],Operator panel,On,Off;",
    // Console font colour (02-SEP-2026). The terminal core emits a palette
    // INDEX; this only re-tints the console-text index in the RGB map below,
    // so it is a board-only cosmetic - the panel colours and the terminal RTL
    // are untouched. Default Green (the green Tandberg look the ND grew up
    // with). status[9:8], freed when the baud selector was removed.
    "O[9:8],Console colour,Green,Amber,White,Cyan;",
    "-;",
    // Disk and tape images (01-SEP-2026, PLAN-mister-storage.md). One OSD
    // mount slot per drive, served through hps_io's block interface by
    // rtl/nd_storage_hps.v - the pattern the mainstream MiSTer computer
    // cores use (Atari ST, Apple II, C64, BK0011M, TRS-80...). The slot
    // number IS the hps_io index IS the storage client: see the slot map in
    // rtl/nd_storage_mister_devices.v. Slot 4 is outside the documented 0-3
    // range but inside what the code accepts (hps_io VDNUM 1..10) and is
    // what TRS-80_MiSTer ships; it carries the tape so the four disc slots
    // stay in the documented range. Extensions are 3-character groups; a
    // 4-letter ".BPUN" cannot be named, so tapes are ".BPU" or ".TAP" here.
    // The HPS automounts boot0.vhd..boot3.vhd / ND120.VHD from the core's
    // folder into slots 0-3 at start - never ship such files.
    "S0,IMG,Floppy drive 0;",
    "S1,IMG,Floppy drive 1;",
    "S2,IMG,Winchester unit 0;",
    "S3,IMG,Winchester unit 1;",
    "S4,BPUTAP,Paper tape;",
    "-;",
    "T[0],Reset;",
    "R[0],Reset and close OSD;",
    "v,0;",
    "V,v",`BUILD_DATE 
};

wire forced_scandoubler;
wire   [1:0] buttons;
wire [127:0] status;
wire  [10:0] ps2_key;

// ---- the block interface to the mounted images (clk_sys) -----------------
// VDNUM 5 = the five S slots above. BLKSZ 2 = 512-byte HPS blocks; the
// storage backend asks for four at a time (sd_blk_cnt = 3) so one ND-120
// storage block (2048 bytes) is one HPS transaction. WIDE = 16-bit words on
// the buffer bus. The ND side of this is plain Verilog with flattened
// per-slot vectors; the unpacking into hps_io's arrays is right here.
localparam VDNUM = 5;
wire [VDNUM-1:0] img_mounted;
wire             img_readonly;
wire      [63:0] img_size;
wire      [31:0] sd_lba[VDNUM];
wire       [5:0] sd_blk_cnt[VDNUM];
wire [VDNUM-1:0] sd_rd, sd_wr, sd_ack;
wire      [12:0] sd_buff_addr;
wire      [15:0] sd_buff_dout;
wire      [15:0] sd_buff_din[VDNUM];
wire             sd_buff_wr;
wire [VDNUM*32-1:0] sd_lba_flat;
wire [VDNUM*6-1:0]  sd_blk_cnt_flat;
wire      [15:0] sd_buff_din_one;   // one transaction in flight: same word to every slot
genvar vd;
generate
    for (vd = 0; vd < VDNUM; vd = vd + 1) begin : g_vd
        assign sd_lba[vd]      = sd_lba_flat[vd*32 +: 32];
        assign sd_blk_cnt[vd]  = sd_blk_cnt_flat[vd*6 +: 6];
        assign sd_buff_din[vd] = sd_buff_din_one;
    end
endgenerate

hps_io #(.CONF_STR(CONF_STR), .VDNUM(VDNUM), .BLKSZ(2), .WIDE(1)) hps_io
(
    .clk_sys(clk_sys),
    .HPS_BUS(HPS_BUS),
    .EXT_BUS(),
    .gamma_bus(),

    .forced_scandoubler(forced_scandoubler),

    .buttons(buttons),
    .status(status),
    .status_menumask({status[5]}),

    .img_mounted(img_mounted),
    .img_readonly(img_readonly),
    .img_size(img_size),
    .sd_lba(sd_lba),
    .sd_blk_cnt(sd_blk_cnt),
    .sd_rd(sd_rd),
    .sd_wr(sd_wr),
    .sd_ack(sd_ack),
    .sd_buff_addr(sd_buff_addr),
    .sd_buff_dout(sd_buff_dout),
    .sd_buff_din(sd_buff_din),
    .sd_buff_wr(sd_buff_wr),

    .ps2_key(ps2_key)
);

///////////////////////   CLOCKS   ///////////////////////////////

// 50 MHz in -> 40.000 MHz out: the pixel clock for 800x600@60. See the note
// at the top of rtl/pll.v - the frequency is a string parameter in the
// generated IP, changed there rather than through the Quartus GUI.
wire clk_sys;
wire pll_locked;
pll pll
(
    .refclk(CLK_50M),
    .rst(0),
    .outclk_0(clk_sys),
    .locked(pll_locked)
);

wire reset = RESET | status[0] | buttons[1];

//////////////////////   RESET, TIED TO PLL LOCK   ///////////////////////////

// Hold the console in reset until the PLL has locked AND a few thousand
// clocks have passed. Releasing logic onto a clock that is still moving is a
// good way to get a design that works on one board and not the next, and it
// costs one counter to rule out.
reg [11:0] rst_cnt = 0;
reg        pix_rst_n = 0;
always @(posedge clk_sys) begin
    if (reset || !pll_locked) begin
        rst_cnt   <= 0;
        pix_rst_n <= 0;
    end else if (!rst_cnt[11]) begin
        rst_cnt   <= rst_cnt + 1'd1;
        pix_rst_n <= 0;
    end else begin
        pix_rst_n <= 1;
    end
end

//////////////////////   CPU CLOCK - 20 MHz, clk_sys / 2   ///////////////////
//
// MEASURED, not chosen by feel (31-AUG-2026). Running the CPU on clk_sys
// itself (40 MHz) MISSES TIMING on this device: TimeQuest reported
// Fmax = 38.65 MHz with the 24 KB memory and 36.91 MHz once main memory grew
// to 192 KB, against a 40 MHz requirement - setup slack -0.870 ns, TNS
// -111.6 ns. A CPU that misses setup latches wrong values, which on silicon
// looks exactly like "it does not run", while the shallow console and panel
// logic keeps working - the symptom this board actually showed.
//
// 20 MHz is an exact divide of the existing PLL output, so it needs no
// second PLL, and it leaves ~2x margin (50 ns period against the ~27 ns the
// longest path needs). It is also the speed the Tang is timing-clean at, so
// it is not an unreasonable number for this design on this class of fabric.
//
// The video side stays at 40 MHz - 800x600@60 needs exactly that - and the
// only link between the two domains is the console SERIAL line, which is
// asynchronous by nature. That is the same split nd120_nexys4ddr_top.v uses
// (console UART on the pixel clock, CPU on its own clock).
//
// nd120.qsf's BOARD_CLK_FREQ MUST equal this: SC2661_UART.v derives the
// console baud divisor from it, so a mismatch garbles the console.
// A REAL PLL OUTPUT, not a fabric divider (31-AUG-2026). The divider version
//
//     reg clk_cpu_div = 1'b0;
//     always @(posedge clk_sys) clk_cpu_div <= ~clk_cpu_div;
//
// put the whole CPU's clock on ORDINARY ROUTING: Quartus listed
// emu:emu|clk_cpu_div under "Non-Global High Fan-Out Signals" in the fitter
// report. Its edge therefore reached thousands of registers at different
// times and the CPU could not run on silicon - while the console, panel and
// uptime kept working, because those sit on the 40 MHz PLL output which IS
// on a global network (CLKCTRL_G7). Simulation cannot see this at all, and
// TimeQuest did not object: it met the 50 ns period on the routing it was
// given and reported Fmax 28.9 MHz.
//
// A PLL output is global by construction, which is what the other boards
// already do (Nexys clk_cpu from an MMCM output, Tang from an rPLL output).
wire clk_cpu;
wire clk2x;         // 40 MHz, edge-aligned with clk_cpu - the SDRAM bridge
wire clk2x_sdram;   // 40 MHz, 180 degrees - the SDRAM chip's clock pin
wire cpu_pll_locked;
pll_cpu PLL_CPU (
    .refclk  (CLK_50M),
    .rst     (1'b0),
    .outclk_0(clk_cpu),
    .outclk_1(clk2x),
    .outclk_2(clk2x_sdram),
    .locked  (cpu_pll_locked)
);

// Reset for the CPU domain, released after the pixel domain is already out
// of reset, and synchronised onto clk_cpu so the core never sees a reset
// edge that violates its own setup.
reg [1:0] cpu_rst_sync = 2'b00;
// Also gated on the CPU PLL's own lock - releasing the core onto a clock
// that has not settled is the other half of the same class of bug.
always @(posedge clk_cpu) cpu_rst_sync <= {cpu_rst_sync[0], pix_rst_n & cpu_pll_locked};
wire cpu_rst_n = cpu_rst_sync[1];

////////////////////////////   CONSOLE   /////////////////////////////////////

// BUILD 2 (31-AUG-2026): ND3202D lives behind the same console proven on real
// hardware as build 1. LOCAL_ECHO is 0 here - the ND-120 echoes what you
// type, and a terminal that echoes as well shows every character twice.

wire con_pixel, con_hs, con_vs, con_de, con_bell;
wire [2:0] con_colour;
wire       con_panel_active;  //! 1 = this pixel is the operator panel

// Machine seam: bytes from the CPU's console UART TX line, deserialized;
// keystrokes serialized back out to its RX line. Byte-level ports only -
// nd120_console_mister does no UART framing itself, same split as the Nexys
// top (nd120_nexys4ddr_top.v's CONSOLE_RX/CONSOLE_TX).
wire       s_cpu_byte_valid;
wire [7:0] s_cpu_byte_data;
wire       s_kbd_valid;
wire [7:0] s_kbd_data;
wire       s_kbd_ready;   // console UART TX idle -> key_tdv2200 expander backpressure
wire       s_cpu_txd, s_cpu_rxd;
// Disc activity for the panel line, derived from the storage seams exactly
// as fpga/nexys4ddr does (nd120_nexys4ddr_top.v: WDISK_REQ & ~WDISK_WR ...).
// Declared here because the console sits above the storage block in this
// file; assigned right after the STORAGE instance. Until 02-SEP-2026 these
// four were tied to 0 - a leftover from build 2, which had no storage - so
// the HDD/FLOPPY lamps never lit while SINTRAN was booting off WD0.
wire       s_lamp_hdd_rd, s_lamp_hdd_wr, s_lamp_flp_rd, s_lamp_flp_wr;

// Operator panel sources off CORE's debug ports (build 2, 31-AUG-2026).
// Declared here, ahead of both instances, because CONSOLE consumes what
// CORE and MIPS produce; Verilog doesn't care about declaration order for
// wires used across instances, only that every wire is declared once.
// Bit layout matches ND3202D's DBG_PANEL (same as the Nexys build):
// [1:0] PCR ring, [2] PONI, [3] IONI, [4] LHIT, [5] LEV0.
wire [ 6:0] s_core_led;
wire        s_core_run_n;
wire [ 3:0] s_core_pil;
wire [ 7:0] s_core_dbg_panel;
wire [15:0] s_core_panel_actlv;
wire        s_core_debug_cfetch;
wire [15:0] s_panel_mips;

// status[7]: bit=0 "On" (default, status resets to 0), bit=1 "Off".
wire s_panel_enable = !status[7];

// Console receive/transmit divisor, from the OSD. 0 means "use the module's
// own CLK_HZ/BAUD parameters", which is the 115200 default; the rest are
// 40 MHz / baud, the pixel clock these two run on.
// Console baud is FIXED at 115200 (01-SEP-2026, Ronny: "it's always
// 115200"). The OSD selector that used to feed a runtime divisor into the
// terminal's deserialiser (23040 / 9600 / 57600 for the bring-up hunt) is
// gone; 0 = use the terminal's own 115200 parameters. status[9:8] is free.
wire [15:0] s_con_divisor = 16'd0;

// --- CPU status lamps ------------------------------------------------------
// The ND-120's own two CPU lamps, straight onto the board's status LEDs
// (build 2, 31-AUG-2026). ND3202D.v:143 gives LED[0] = CPU RED, LED[1] =
// CPU GREEN; boot-sequence.md section 10 says RED = Master Clear / MACL in
// progress, GREEN = "initialization complete", written only when the
// microcode reaches MACL2 - i.e. only after the self-test PASSES.
//
// This is the one-glance boot verdict: GREEN lit = self-test passed and
// OPCOM should be alive; RED alone = still in MACL, or halted in STERR on a
// failed self-test, which never reaches OPCOM and so prints no prompt.
// Active low at the source (IO_REG_41.v:145-148), so inverted here.
assign LED_DISK  = ~s_core_led[0];   // CPU RED   - MACL in progress
assign LED_POWER = ~s_core_led[1];   // CPU GREEN - initialisation complete

// Counts CFETCH, which lives in the CPU domain - so it runs on clk_cpu and
// its CLOCK_HZ must match that, or the MIPS figure is wrong by the ratio.
// The crossing into the pixel domain happens inside terminal_top.
mips_counter #(
    .CLOCK_HZ(20_000_000)
) MIPS (
    .clk     (clk_cpu),
    .rst_n   (cpu_rst_n),
    .fetch   (s_core_debug_cfetch),
    .mips_bcd(s_panel_mips)
);

// --- CPU liveness probe, printed on the console ---------------------------
//
// Scaffolding, not part of the machine: compiled in only when
// ND120_DIAG_PRINT is defined (see nd120.qsf). It answers "what is the CPU
// actually doing" from a screenshot, which can be pulled off the board over
// ssh, instead of from another 15-minute rebuild per guess. Full reasoning is
// in the header of rtl/nd120_diag_print.v.
wire [15:0] s_diag_ticks;
wire [12:0] s_core_csa;
wire [19:0] s_core_wrfb;      // register-file B port {LBA_3_0, B_15_0}
wire [15:0] s_core_ireq_n;    // interrupt debug word, ACTIVE HIGH (see CGA_INTR.v:167)
wire [15:0] s_core_fidbo;     // FIDBO internal data bus
wire [ 4:0] s_core_cc_term;   // condition + terminate lines the sequencer branches on
wire [11:0] s_core_cyc;      // cycle-controller inputs + clock enables (CYC_36)
wire        s_sterr_hit;
wire [ 7:0] s_sterr_count;
wire [19:0] s_sterr_first;
wire        s_diag_valid;
wire [ 7:0] s_diag_data;
wire        s_console_byte_ready;

`ifdef ND120_DIAG_PRINT
// Free-running counter in the CPU domain. Its only job is to prove that
// clk_cpu is toggling in silicon and that cpu_rst_n has been released -
// neither of which simulation or the timing report can tell us.
reg [15:0] r_cpu_ticks = 16'd0;
always @(posedge clk_cpu) begin
    if (!cpu_rst_n) r_cpu_ticks <= 16'd0;
    else            r_cpu_ticks <= r_cpu_ticks + 16'd1;
end

// ALUCLK_EN pulse COUNTER (01-SEP-2026). The windowed dump showed the cycle
// controller's clock enables asserting at different microinstructions here
// than in a machine that boots - but those are ONE-SYSCLK pulses sampled at
// an address-change instant, which is exactly the phase-sensitive shape that
// produced a false result earlier today with FIDBO. Counting cannot be fooled
// that way: over the same number of CPU clocks, two machines running the same
// microcode must step the cycle controller the same number of times.
//
// Compare this against CK on the status line. The RATIO is the measurement -
// ALUCLK_EN pulses per clk_cpu - and it is directly comparable to the
// simulator's, which counts the same signal the same way.
// Counted over a FIXED WINDOW of 65536 clk_cpu and latched, not free-running:
// a free-running 16-bit counter wraps every ~360k clocks at this rate, so two
// wrapped values cannot be turned back into a ratio. Latching per window makes
// the printed number the ratio directly - divide by 65536.
// The simulator measures 0.1820 (ALUCLK_EN per clk_cpu), i.e. 11930 expected.
// ONLY THE FIRST WINDOW, then frozen. Measuring continuously compares the
// board's steady-state loop against the simulator's boot - different microcode
// runs different cycle types, so a rate difference there proves nothing about
// a fault. The FIRST 65536 CPU clocks after reset are the same microcode on
// both machines (they agree all the way to 001006), so that window is
// comparable. The simulator measures 0.1820 enables per clk_cpu over its first
// 50k-150k clocks, i.e. 11930 expected here.
reg [15:0] r_alu_run = 16'd0;   // enables so far in the first window
reg [15:0] r_aluclk_cnt = 16'd0;  // the first window's total, then held
reg        r_alu_done = 1'b0;
always @(posedge clk_cpu) begin
    if (!cpu_rst_n) begin
        r_alu_run    <= 16'd0;
        r_aluclk_cnt <= 16'd0;
        r_alu_done   <= 1'b0;
    end else if (!r_alu_done) begin
        if (r_cpu_ticks == 16'hFFFF) begin
            r_aluclk_cnt <= r_alu_run + (s_core_cyc[10] ? 16'd1 : 16'd0);
            r_alu_done   <= 1'b1;      // freeze: this window is the measurement
        end else if (s_core_cyc[10]) begin
            r_alu_run    <= r_alu_run + 16'd1;
        end
    end
end
assign s_diag_ticks = r_cpu_ticks;

// Self-test failure catcher. The microcode's STERR branch (002156) puts the
// error number, held in R2, on the register-file B port - so latching that
// port there reads the number straight out. See rtl/nd120_sterr_catch.v.
nd120_sterr_catch STERR (
    .clk_cpu      (clk_cpu),
    .cpu_rst_n    (cpu_rst_n),
    .csa          (s_core_csa),
    .wrfb         (s_core_wrfb),
    .hit          (s_sterr_hit),
    .hit_count    (s_sterr_count),
    .first_capture(s_sterr_first),
    .last_capture ()
);

// PIE PROBE (01-SEP-2026). The first divergence from a booting machine is at
// microcode 001013, which does NOT take its conditional jump to NOTI2 here.
// The condition is formed at 001012 from the value read one microinstruction
// earlier at 001011:
//     001011   AB,PIE  ALUF,PASSD  ALUD,Q  IDBS,REG
// i.e. the Priority Interrupt Enable register, driven onto the internal data
// bus. Capturing FIDBO at that address gives the value the branch is decided
// on, to compare against the same instant in the simulator.
//
// Same module as the STERR catcher - it is an address-triggered capture of a
// 20-bit bus, which is exactly what is wanted; only the address differs. The
// bus here is 16 bits, so the top 4 are padded and ignored.
wire        s_pie_hit;
wire [ 7:0] s_pie_count;
wire [19:0] s_pie_first;

nd120_sterr_catch #(
    .STERR_ADDR(13'o01011)      // the PIE read, not STERR
) PIEPROBE (
    .clk_cpu      (clk_cpu),
    .cpu_rst_n    (cpu_rst_n),
    .csa          (s_core_csa),
    .wrfb         ({4'd0, s_core_fidbo}),
    .hit          (s_pie_hit),
    .hit_count    (s_pie_count),
    .first_capture(s_pie_first),
    .last_capture ()
);

nd120_diag_print #(
    .CLK_HZ(40_000_000)      // clk_sys, the domain this module runs in
) DIAG (
    .clk        (clk_sys),
    .rst_n      (pix_rst_n),
    .cpu_ticks  (s_diag_ticks),
    .csa        (s_core_csa),
    .pil        (s_core_pil),           // which level the CPU is running on
    .actlv      (s_core_panel_actlv),   // which levels are active
    .sterr_hit  (s_sterr_hit),
    .sterr_count(s_sterr_count),
    .sterr_r2   (s_sterr_first[15:0]),  // R2 at the FIRST STERR entry
    .sterr_lba  (s_sterr_first[19:16]), // and which slot "R2" decoded to
    // NOT a raw request vector, despite the _N name - CGA_INTR.v:167 packs a
    // debug word that is ACTIVE HIGH ("the Tang top captures it directly, no
    // invert"):  bit0=PAN  bit1=IRQ  bit2=INTRQ  bit5:3=PICV  bit9:6=MIREQ.
    // Passed straight through. An earlier build inverted it here and printed a
    // double negative, which read as "14 levels requesting" when the truth was
    // bits 0 and 2 set - PAN plus a grant, and PICV empty.
    .ireq       (s_core_ireq_n),
    .pie_hit    (s_pie_hit),
    .pie_count  (s_pie_count),
    // REPURPOSED: the PE field now carries the ALUCLK_EN pulse COUNT, not PIE.
    // The PIE comparison was retired - it was a single sample and proved
    // nothing. Read this against CK on the line above: the RATIO of cycle-
    // controller steps to CPU clocks is what is being compared, and a count
    // cannot be fooled by sampling phase the way that read was.
    .pie        (r_aluclk_cnt),
    .cpu_rst_n  (cpu_rst_n),
    .run_n      (s_core_run_n),
    .byte_valid (s_diag_valid),
    .byte_data  (s_diag_data),
    .byte_ready (s_diag_ready)
);
`elsif ND120_STORAGE_PROBE
// storage mount + Winchester activity, on the console (see the module header).
// Reuses the diag-print slot in the console priority mux below (CPU > trace >
// this), so it prints only in the quiet a hung boot leaves.
assign s_diag_ticks = 16'd0;
nd120_storage_probe #(.CLK_HZ(40_000_000)) STORPROBE (
    .clk       (clk_sys),
    .rst_n     (pix_rst_n),
    .mounted   (s_img_mounted_cpu),
    .wd_req    (s_wd_req),
    .wd_done   (s_wd_done),
    .wd_err    (s_wd_err),
    .byte_valid(s_diag_valid),
    .byte_data (s_diag_data),
    .byte_ready(s_diag_ready)
);
`else
assign s_diag_ticks = 16'd0;
assign s_diag_valid = 1'b0;
assign s_diag_data  = 8'h00;
`endif

// Microcode trace buffer: the consecutive CSA sequence, which the once-a-
// second status line cannot show (it aliases against a tight loop - see the
// header of rtl/nd120_csa_trace.v).
wire       s_trace_valid;
wire [7:0] s_trace_data;

`ifdef ND120_DIAG_PRINT
// TRIGGERED at 002026 (01-SEP-2026). That is MACL's call into RIIE1:
//     002026   ALUF,ZERO ALUD,Q IDBS,ALU  T,JMP T,PUSH RIIE1;
//     002027   <- the return address it pushes
// The working Verilator boot returns from NOTI2 (001020, an UNCONDITIONAL
// T,RETURN) to 002027. This board goes to 001021 instead - the next
// sequential address. Arming here captures the call, the RIIE1 body and where
// the return actually lands, which the free-running buffer cannot show: it
// only ever holds the steady-state loop the machine settles into afterwards.
//
// If NOTHING prints, that is a result too - it means 002026 is never reached
// and MACL never gets that far.
// DEPTH 64 at 4 per line = 16 lines, which fits the screen alongside the two
// status lines. Each entry is now "csa:fidbo", 13 characters, so fewer fit per
// line than when only the address was printed. DEPTH MUST stay a power of two:
// the buffer wraps on {AW{1'b1}}, so 48 would run past the end of the array.
nd120_csa_trace #(
    .DEPTH       (64),
    .PERLINE     (4),
    .CLK_HZ      (40_000_000),
    .TRIGGERED   (1),
    .PER_CLOCK   (1),   // record every clock: shows the CYCLE WAVEFORM
    .TRIGGER_ADDR(13'o02026)
) CSATRACE (
    .clk_cpu   (clk_cpu),
    .cpu_rst_n (cpu_rst_n),
    .csa       (s_core_csa),
    // {cycle-controller inputs, condition lines} - 13 bits, printed as 6 octal
    // digits. Both halves travel with the address so neither can be misread as
    // a phase artifact.
    // {3'b0, 4 clock enables, 4 key cycle inputs, 5 condition bits} = 16.
    // enables = {MCLK_FALL_EN, ALUCLK_EN, MCLK_EN, CLK_EN}
    // inputs  = {BRK_n, HIT, SLOW_n, SHORT_n}
    .aux       ({3'd0, s_core_cyc[11:8], s_core_cyc[3:0], s_core_cc_term}),
    .clk       (clk_sys),
    .rst_n     (pix_rst_n),
    .byte_valid(s_trace_valid),
    .byte_data (s_trace_data),
    .byte_ready(s_trace_ready)
);
`else
assign s_trace_valid = 1'b0;
assign s_trace_data  = 8'h00;
`endif

// Priority: the CPU's own console output always wins, then the trace block
// (kept contiguous so a dump is not split by a status line), then the
// once-a-second status line. Each producer holds its byte until its own ready
// goes high, so a stalled one resumes rather than dropping characters.
wire       s_console_valid = s_cpu_byte_valid ? 1'b1
                           : s_trace_valid    ? 1'b1
                                              : s_diag_valid;
wire [7:0] s_console_data  = s_cpu_byte_valid ? s_cpu_byte_data
                           : s_trace_valid    ? s_trace_data
                                              : s_diag_data;
wire s_trace_ready = s_console_byte_ready & ~s_cpu_byte_valid;
wire s_diag_ready  = s_trace_ready        & ~s_trace_valid;

nd120_console_mister #(
    .FONT_FILE("font8x16.hex"),   // found via SEARCH_PATH or the make font copy
    .LOCAL_ECHO(0)          // the ND-120 echoes; a local echo too would double every character
) CONSOLE (
    .clk  (clk_sys),
    .rst_n(pix_rst_n),

    .ps2_key(ps2_key),

    // Keyboard/font national variant, from the OSD. One bit drives both, which
    // is the point - see Terminals/rtl/ps2_ascii_table.v.
    .layout_no(status[6]),

    .cpu_byte_valid(s_console_valid),
    .cpu_byte_data (s_console_data),
    .cpu_byte_ready(s_console_byte_ready),

    .panel_enable      (s_panel_enable),
    .panel_pil         (s_core_pil),
    .panel_actlv       (s_core_panel_actlv),
    .panel_mips        (s_panel_mips),
    // The CPU board's own lamps - GREEN lit means the microcode reached
    // MACL2, i.e. the self-test PASSED. ND3202D.v:143 bit order.
    // INVERTED: both are ACTIVE LOW at the source. IO_REG_41.v:145-148
    // drives IOLED[0] from s_emcl_n and IOLED[1] from s_led3_green_n, and
    // its own comments say "active low" on both. Passing them straight
    // through showed every lamp backwards (31-AUG-2026) - which also
    // inverted the boot verdict they exist to give.
    .panel_cpu_red     (~s_core_led[0]),
    .panel_cpu_green   (~s_core_led[1]),
    .panel_lev0        (s_core_dbg_panel[5]),
    .panel_hit         (s_core_dbg_panel[4]),
    .panel_ring        (s_core_dbg_panel[1:0]),
    .panel_paging_on   (s_core_dbg_panel[2]),
    .panel_interrupt_on(s_core_dbg_panel[3]),
    .panel_running     (~s_core_run_n),   // RUN_n is active low
    // one pulse per disc request, read or write, from the storage seams
    .panel_hdd_rd (s_lamp_hdd_rd),
    .panel_hdd_wr (s_lamp_hdd_wr),
    .panel_flp_rd (s_lamp_flp_rd),
    .panel_flp_wr (s_lamp_flp_wr),

    .kbd_valid(s_kbd_valid),
    .kbd_data (s_kbd_data),
    .kbd_ready(s_kbd_ready),

    .colour(con_colour),
    .panel_active(con_panel_active),
    .pixel(con_pixel),
    .hsync(con_hs),
    .vsync(con_vs),
    .de   (con_de),
    .bell (con_bell)
);

// --- UART bridge: byte-level console <-> the CPU's real serial pins --------
//
// The emulated SC2661 is hardwired 8N1 (Verilog/Shared/support/SC2661_UART.v
// - "Not implemented, we use constant ... 8N1"; HARDWARE.md, commit
// 773651f). NEVER 7E1 - a 7E1 deserializer against an 8N1 sender reads the
// 8th data bit as a parity bit and corrupts every odd-population character.
//
// Both run on clk_sys because CORE.clk_cpu IS clk_sys here (one clock domain
// for the whole build, see the note below) - the 2-flop synchronizer inside
// console_uart_rx is harmless and left in place rather than special-cased
// out, the same call nd120_console_mister already makes for its own CDC.
// 7 DATA BITS + PARITY, like the Nexys - NOT 8N1 (02-SEP-2026, measured).
// The CPU's SC2661 frames 8 bits, but what SINTRAN puts in bit 7 during its
// boot text is SOFTWARE PARITY: a raw capture of the serial line on the HPS
// (/dev/ttyS1) during the first SINTRAN boot on this board shows CR as 8D,
// space as A0, '4' as B4 - every character with an odd number of ones has
// bit 7 set - while later output is plain 7-bit. The terminal controller
// drops any byte >= 7F, so with an 8N1 receiver every other character of
// those lines vanished ("SNAN-VS500M" for "SINTRAN III - VSX/500 M") and
// so did every CR (the staircase). A 7E1 receiver treats bit 7 as the
// parity bit and discards it, which is what the real TDV2200 did on a real
// 7E1 line and what fpga/nexys4ddr does (ND120_CONSOLE_DATA_BITS 7,
// ND120_CONSOLE_PARITY 1). Gate: sim/console_burst_tb.v sends the
// parity-tagged stream and requires the 7-bit text on the screen.
console_uart_rx #(
    .CLK_HZ   (40_000_000),
    .BAUD     (115_200),
    .DATA_BITS(7),
    .PARITY   (1'b1)
) CONSOLE_UART_RX (
    .clk        (clk_sys),
    .rst_n      (pix_rst_n),
    .divisor_ovr(s_con_divisor),
    .rxd        (s_cpu_txd),
    .byte_valid (s_cpu_byte_valid),
    .byte_data  (s_cpu_byte_data)
);

////////////////////////   SERIAL CONSOLE   /////////////////////////////////
//
// The SAME console the terminal shows, also brought out on the board's real
// serial port (31-AUG-2026). Two reasons:
//
//   * MEASUREMENT. When the on-screen console showed nothing, there was no
//     way to tell "the CPU is not transmitting" from "it is transmitting
//     and the deserialiser cannot decode it". A real serial line can be
//     watched with a terminal that sets its own baud, which settles that in
//     one attempt instead of a 15-minute rebuild per guess.
//   * PARITY WITH THE OTHER BOARDS. Nexys and Tang both have a serial
//     console; this board had only the screen.
//
// DO NOT put a [ND120] uartmode= section in /media/fat/MiSTer.ini for this.
// Newer MiSTer main binaries can bridge a core's UART to the HPS that way,
// but the one on this board does not know the option and answers with an
// "ini error: uartmode unknown" popup on every core start (reported and
// removed 01-SEP-2026; the board's MiSTer.ini is back to its original
// content). Nothing here needs it: the console is on the video output, this
// line drives the physical pins directly, and the link is always 8N1 at
// 115200 - there is no mode to select. See Shared/support/SC2661_UART.v.
assign UART_TXD = s_cpu_txd;

// Both sources idle HIGH, so the CPU's RX line is the wired-AND of the local
// keyboard and the serial port - whichever is sending pulls it low, the idle
// one contributes ones. Same idiom as the Nexys top's uart_rxd_out.
wire s_cpu_rxd_merged = s_cpu_rxd & UART_RXD;

console_uart_tx #(
    .CLK_HZ    (40_000_000),
    .BAUD      (115_200),
    .DATA_BITS (8),
    .PARITY    (1'b0),
    .PARITY_ODD(1'b0)
) CONSOLE_UART_TX (
    .clk        (clk_sys),
    .rst_n      (pix_rst_n),
    .divisor_ovr(s_con_divisor),
    .byte_valid (s_kbd_valid),
    .byte_data  (s_kbd_data),
    .ready      (s_kbd_ready),
    .txd        (s_cpu_rxd)
);

//////////////////////////////   ND-120 CPU   /////////////////////////////////

// clk_cpu IS clk_sys - one clock domain for the whole build. Lower risk than
// a second PLL or a divided clock for a first Cyclone V synthesis of the
// CPU, and 40 MHz is well inside what this design already meets on a
// comparable fabric (Nexys 4 DDR runs the same CPU at 45.45 MHz). A second,
// truly independent CPU clock is future work once this boots.
//
// -DBOARD_CLK_FREQ=40000000 / -DUART_BAUD_RATE=115200 in nd120.qsf must match
// this exactly - SC2661_UART.v's baud generator divides the real clk_cpu
// rate by those defines, not by anything measured at run time.

// C-PLUG bus: no external ND bus on this board, same tie-off as the Tang
// (ND120_TANG20K_TOP.v).
wire [23:0] s_bd_in = 24'hFFFFFF;

// ---- storage: the floppy, Winchester and tape seams served from the ------
// ---- images mounted in the OSD (01-SEP-2026, PLAN-mister-storage.md) ----
// MiSTer serves images from the HPS/Linux side, not a wired SD card, so the
// Tang/Nexys SD-FAT stack does not apply; rtl/nd_storage_mister_devices.v
// is its counterpart - the same three controller-facing adapters on top of
// rtl/nd_storage_hps.v, which speaks hps_io's block interface. Two floppy
// drives, two Winchester units, one tape. The seams below are pin-for-pin
// what ND120_CORE exposes; before this they were tied off and any disk or
// tape access hung the guest (no done ever came).
wire        s_tape_req, s_tape_valid, s_tape_rewind;
wire [7:0]  s_tape_data;
wire        s_tdisk_fault;
wire [3:0]  s_tdisk_code;
wire        s_fd_req, s_fd_wr, s_fd_done, s_fd_err, s_fdb_we;
wire [15:0] s_fd_lsect, s_fdb_wdata, s_fdb_rdata;
wire [1:0]  s_fd_format, s_fd_drive;
wire [10:0] s_fd_wc;
wire [3:0]  s_fd_code, s_fd_media;
wire [9:0]  s_fdb_addr;
wire        s_wd_start, s_wd_req, s_wd_wr, s_wd_done, s_wd_err, s_wdb_we;
wire [15:0] s_wd_ba1, s_wd_ba2, s_wdb_wdata, s_wdb_rdata;
wire [2:0]  s_wd_unit;
wire [10:0] s_wd_wc;
wire [3:0]  s_wd_code;
wire [9:0]  s_wdb_addr;
wire [4:0]  s_img_mounted_cpu;   // per-slot "a file is there" (clk_cpu)

// SDRAM pin adapters for the 16-bit module (see the MAIN MEMORY note in the
// core instance): the bridge's upper DQ/DQM bits go nowhere, A[12:11] = 0.
wire [15:0] s_sdram_dq_hi;    // bridge DQ[31:16]: not driven, not read
wire [10:0] s_sdram_a11;
wire [ 3:0] s_sdram_dqm4;
assign SDRAM_A    = {2'b00, s_sdram_a11};
assign SDRAM_DQML = s_sdram_dqm4[0];
assign SDRAM_DQMH = s_sdram_dqm4[1];

nd_storage_mister_devices STORAGE (
    .clk_cpu   (clk_cpu),
    .rst_cpu_n (cpu_rst_n),
    .clk_sys   (clk_sys),
    .rst_sys_n (pix_rst_n),

    .byte_req      (s_tape_req),
    .byte_valid    (s_tape_valid),
    .byte_data     (s_tape_data),
    .source_rewind (s_tape_rewind),
    .TDISK_FAULT   (s_tdisk_fault),
    .TDISK_ERR_CODE(s_tdisk_code),

    .FDISK_REQ      (s_fd_req),
    .FDISK_WR       (s_fd_wr),
    .FDISK_LSECT    (s_fd_lsect),
    .FDISK_FORMAT   (s_fd_format),
    .FDISK_DRIVE    (s_fd_drive),
    .FDISK_WORDCOUNT(s_fd_wc),
    .FDISK_DONE     (s_fd_done),
    .FDISK_ERR      (s_fd_err),
    .FDISK_ERR_CODE (s_fd_code),
    .FDISK_MEDIA_FMT(s_fd_media),
    .FDBUF_ADDR     (s_fdb_addr),
    .FDBUF_WDATA    (s_fdb_wdata),
    .FDBUF_WE       (s_fdb_we),
    .FDBUF_RDATA    (s_fdb_rdata),

    .WDISK_START    (s_wd_start),
    .WDISK_REQ      (s_wd_req),
    .WDISK_WR       (s_wd_wr),
    .WDISK_BLKADDR1 (s_wd_ba1),
    .WDISK_BLKADDR2 (s_wd_ba2),
    .WDISK_UNIT     (s_wd_unit),
    .WDISK_WORDCOUNT(s_wd_wc),
    .WDISK_DONE     (s_wd_done),
    .WDISK_ERR      (s_wd_err),
    .WDISK_ERR_CODE (s_wd_code),
    .WDBUF_ADDR     (s_wdb_addr),
    .WDBUF_WDATA    (s_wdb_wdata),
    .WDBUF_WE       (s_wdb_we),
    .WDBUF_RDATA    (s_wdb_rdata),

    .img_mounted (img_mounted),
    .img_readonly(img_readonly),
    .img_size    (img_size),
    .sd_lba      (sd_lba_flat),
    .sd_blk_cnt  (sd_blk_cnt_flat),
    .sd_rd       (sd_rd),
    .sd_wr       (sd_wr),
    .sd_ack      (sd_ack),
    .sd_buff_addr(sd_buff_addr),
    .sd_buff_dout(sd_buff_dout),
    .sd_buff_din (sd_buff_din_one),
    .sd_buff_wr  (sd_buff_wr),

    .MOUNTED(s_img_mounted_cpu)
);

// panel lamps: the same four expressions the Nexys top uses
assign s_lamp_hdd_rd = s_wd_req & ~s_wd_wr;
assign s_lamp_hdd_wr = s_wd_req &  s_wd_wr;
assign s_lamp_flp_rd = s_fd_req & ~s_fd_wr;
assign s_lamp_flp_wr = s_fd_req &  s_fd_wr;

// ND-BUS DEVICES ON (01-SEP-2026, Ronny's call). This board built with all of
// them OFF, which made it the ONLY target running a device-less machine -
// Nexys builds tape+floppy+WD, and the Tang builds them behind TANG_INC_*.
// A configuration no booting board has ever run is not a safe place to be
// while hunting a boot failure, whatever the eventual cause turns out to be.
//
// INCLUDE_SMD stays 0 to match the Nexys, which builds the Winchester (WD)
// rather than the SMD card.
//
// Note that turning these on also instantiates ND_BUS_SLAVE inside the core
// (ND120_CORE.v:566, "present with any device"), which is what carries the
// device interrupt lines BINT10..13 and the IDENT chain - so this changes the
// interrupt wiring, not just which peripherals exist.
ND120_CORE #(
    .INCLUDE_TAPE  (1),
    .INCLUDE_FLOPPY(1),
    .INCLUDE_SMD   (0),
    .INCLUDE_WD    (1)
) CORE (
    .BAUD_9600(1'b0),      // no baud switch - console fixed 115200
    .clk_cpu  (clk_cpu),    // 20 MHz - see the CPU CLOCK note above
    .sys_rst_n(cpu_rst_n),
    // Cache ON, matching BOTH proven configurations (31-AUG-2026):
    // ND120_TOP.v:853 hardcodes 1'b1 ("cache on, as it always was in sim"),
    // and the Nexys reads it from sw[4] where the default position gives
    // s_cache_on = 1. This board ran it OFF, which I chose to "reduce risk
    // on a first bring-up" - the wrong instinct: cache-disabled is a path
    // NO booting configuration exercises, and the self-test walks the
    // memory path it changes. An OSD switch for it is future work, but the
    // DEFAULT has to be what the working boards do.
    // Cache ON. The Nexys reads this from slide switch sw[4] whose default
    // position gives 1; tied high here for the same effect. ND120_NO_CACHE is
    // no longer defined (01-SEP-2026), so this switch now genuinely selects a
    // cache that exists, rather than one Quartus had already optimised away.
    .CACHE_SW (1'b1),

    .BREQ_n(1'b1),
    .BINT10_n(1'b1),
    .BINT11_n(1'b1),
    .BINT12_n(1'b1),
    .BINT13_n(1'b1),
    .BINT15_n(1'b1),
    .POWSENSE_n(1'b1),

    .BD_23_0_n_IN(s_bd_in),
    .BD_23_0_n_OUT(),

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

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

    .RXD(s_cpu_rxd_merged),
    .TXD(s_cpu_txd),

    .TAPE_BYTE_REQ(s_tape_req),
    .TAPE_BYTE_VALID(s_tape_valid),
    .TAPE_BYTE_DATA(s_tape_data),
    .TAPE_REWIND(s_tape_rewind),

    .DMA_REQ(1'b0),
    .DMA_WR(1'b0),
    .DMA_ADDR(24'd0),
    .DMA_WDATA(16'd0),
    .DMA_RDATA(),
    .DMA_ACK(),
    .DMA_ERR(),
    .DMA_BUSY(),

    .FDISK_REQ(s_fd_req),
    .FDISK_WR(s_fd_wr),
    .FDISK_LSECT(s_fd_lsect),
    .FDISK_FORMAT(s_fd_format),
    .FDISK_DRIVE(s_fd_drive),
    .FDISK_WORDCOUNT(s_fd_wc),
    .FDISK_DONE(s_fd_done),
    .FDISK_ERR(s_fd_err),
    .FDISK_ERR_CODE(s_fd_code),
    .FDISK_MEDIA_FMT(s_fd_media),
    .FDBUF_ADDR(s_fdb_addr),
    .FDBUF_WDATA(s_fdb_wdata),
    .FDBUF_WE(s_fdb_we),
    .FDBUF_RDATA(s_fdb_rdata),

    .SDISK_START(),
    .SDISK_REQ(),
    .SDISK_WR(),
    .SDISK_BLKADDR1(),
    .SDISK_BLKADDR2(),
    .SDISK_UNIT(),
    .SDISK_WORDCOUNT(),
    .SDISK_DONE(1'b0),
    .SDISK_ERR(1'b0),
    .SDISK_ERR_CODE(4'd0),
    .SDBUF_ADDR(10'd0),
    .SDBUF_WDATA(16'd0),
    .SDBUF_WE(1'b0),
    .SDBUF_RDATA(),

    .WDISK_START(s_wd_start),
    .WDISK_REQ(s_wd_req),
    .WDISK_WR(s_wd_wr),
    .WDISK_BLKADDR1(s_wd_ba1),
    .WDISK_BLKADDR2(s_wd_ba2),
    .WDISK_UNIT(s_wd_unit),
    .WDISK_WORDCOUNT(s_wd_wc),
    .WDISK_DONE(s_wd_done),
    .WDISK_ERR(s_wd_err),
    .WDISK_ERR_CODE(s_wd_code),
    .WDBUF_ADDR(s_wdb_addr),
    .WDBUF_WDATA(s_wdb_wdata),
    .WDBUF_WE(s_wdb_we),
    .WDBUF_RDATA(s_wdb_rdata),

    // ---- MAIN MEMORY: the DE10-Nano's SDRAM module (01-SEP-2026) ---------
    // MAIN_RAM_SDRAM + ND_SDRAM_PACK16 + ND_SDRAM_DQ16 (nd120.qsf): the
    // Tang's proven sheet-49 bridge (fpga/tang-nano-20k/sdram-bridge/
    // MEM_RAM_49_SDRAM.v + sdram18.v) in its 16-bit-module mode. 2M words =
    // 4 MB as BANK0 + BANK2, the same map as the Tang and the Nexys; parity
    // regenerated on read. The bridge runs on clk2x from the CPU's own PLL
    // (edge-aligned, see pll_cpu.v) and meets the ND-120 DRAM read deadline
    // (data at OSC N+4) exactly as on the Tang. This replaces the block-RAM
    // main memory, whose 32K-word banks wrapped every address >= 0o100000
    // (the LIST-FILE-NAMES runaway, build v48) and whose 64K-word variant
    // did not fit the device (v49). The WCS is untouched: it stays in block
    // RAM exactly as before.
    // The module is 16 bits wide with two DQM pins and 13 address lines; the
    // bridge's ports are the Tang's 32-bit shape, so the upper 16 DQ bits and
    // upper 2 DQM bits are simply left unconnected and A[12:11] are 0.
    .clk2x        (clk2x),
    .clk2x_sdram  (clk2x_sdram),
    .O_sdram_clk  (SDRAM_CLK),
    .O_sdram_cke  (SDRAM_CKE),
    .O_sdram_cs_n (SDRAM_nCS),
    .O_sdram_cas_n(SDRAM_nCAS),
    .O_sdram_ras_n(SDRAM_nRAS),
    .O_sdram_wen_n(SDRAM_nWE),
    .IO_sdram_dq  ({s_sdram_dq_hi, SDRAM_DQ}),
    .O_sdram_addr (s_sdram_a11),
    .O_sdram_ba   (SDRAM_BA),
    .O_sdram_dqm  (s_sdram_dqm4),
    .DBG_MEMW     (),
    .DBG_PTW      (),
    .PF_CAPTURED  (),
    .DBG_WDSTAGE  (),
    .DBG_PPN      (),
    .DBG_PGW      (),

    .LED(s_core_led),
    .RUN_n(s_core_run_n),
    .CSA_12_0(s_core_csa),   // microcode address - read by the diag printer
    .PIL(s_core_pil),
    .LA_23_10(),
    .CA_9_0(),
    .DEBUG_CC_TERM(s_core_cc_term),   // {TERM_n,CC3_n,CC2_n,CC1_n,CC0_n} - the branch condition
    .DEBUG_MCLK(),
    .DEBUG_LCS_n(),
    .DEBUG_FETCH(),
    .DEBUG_MAP_n(),
    .DEBUG_CFETCH(s_core_debug_cfetch),
    .DEBUG_MR_n(),
    .DEBUG_CLEAR_n(),
    .DEBUG_REFRQ_n(),
    .DEBUG_INTRQ_n(),
    .DEBUG_POWFAIL_n(),
    .DEBUG_FIDBO_15_0(s_core_fidbo),     // carries PIE at microcode 001011
    .DEBUG_IREQ_15_0_N(s_core_ireq_n),   // which levels are requesting
    .XMIC_DBG_15_0(),
    .XWRFB_DBG_19_0(s_core_wrfb),        // register-file B port, for STERR's R2
    .XCYC_DBG_7_0(s_core_cyc),           // cycle-controller terminate-plane inputs
    .DBG_PTW_LVL(),
    .DBG_PANEL(s_core_dbg_panel),
    .PANEL_ACTLV(s_core_panel_actlv),
    .DBG_CACHE()
);

assign CLK_VIDEO = clk_sys;

// One pixel per clock: the terminal generates real 800x600@60 timing from the
// 40 MHz clock, so there is nothing to gate. CE_PIXEL exists for cores whose
// pixel rate is a fraction of their system clock; ours is not.
assign CE_PIXEL = 1'b1;

assign VGA_DE = con_de;
assign VGA_HS = con_hs;
assign VGA_VS = con_vs;

// Console text colour, chosen in the OSD (status[9:8]). Full-brightness CRT
// phosphor looks: green (the ND's Tandberg default), amber, white, cyan.
// Amber is red plus a reduced green, the same recipe the PDP2011 core uses.
reg [23:0] s_con_text_rgb;
always @(*) begin
    case (status[9:8])
        2'd0: s_con_text_rgb = 24'h00FF00;  // green
        2'd1: s_con_text_rgb = 24'hFFBF00;  // amber
        2'd2: s_con_text_rgb = 24'hFFFFFF;  // white
        2'd3: s_con_text_rgb = 24'h00FFFF;  // cyan
        default: s_con_text_rgb = 24'h00FF00;
    endcase
end

// The core says WHICH of eight things a pixel is; this board picks the colour.
// Panel colours are sampled from the photograph of the real folio panel.
reg [23:0] rgb;
always @(*) begin
    case (con_colour)
        3'd0: rgb = 24'h000000;   // black
        // Green, matching the Nexys build exactly (nd120_nexys4ddr_top.v: "this
    // is a 1988 minicomputer and the Tandberg terminals it grew up with
    // were green") - was white here, the one colour that did not match
    // (31-AUG-2026). The other seven are full 8-bit precision on this
    // board's true-colour RGB path; Nexys rounds the same measured panel
    // colours to its 12-bit VGA DAC, which is why those hex values differ
    // slightly without being a mismatch.
    // Index 1 is BOTH the console-text ink and the panel's green CPU lamp
    // (term_panel C_TEXT). Recolour it only where it is console text; on a
    // panel pixel keep it fixed green so the CPU G lamp stays green while the
    // console colour is amber/white/cyan (02-SEP-2026, Ronny).
    3'd1: rgb = con_panel_active ? 24'h00FF00 : s_con_text_rgb;
        3'd2: rgb = 24'h191b19;   // panel fascia
        3'd3: rgb = 24'hd6d9d2;   // silkscreen
        3'd4: rgb = 24'hb6c2a4;   // LCD ground
        3'd5: rgb = 24'h2a3226;   // LCD segment
        3'd6: rgb = 24'he04a63;   // lit legend
        3'd7: rgb = 24'h444444;   // unlit legend
        default: rgb = 24'h000000;
    endcase
end

assign VGA_R = con_de ? rgb[23:16] : 8'h00;
assign VGA_G = con_de ? rgb[15:8]  : 8'h00;
assign VGA_B = con_de ? rgb[7:0]   : 8'h00;

// Heartbeat off the PLL output. This is the ONE signal that still means
// something when the screen is black: if it breathes, the PLL locked and
// clk_sys is running, so the fault is in the video logic and not the clock.
reg  [26:0] act_cnt;
always @(posedge clk_sys) act_cnt <= act_cnt + 1'd1; 
assign LED_USER    = act_cnt[26]  ? act_cnt[25:18]  > act_cnt[7:0]  : act_cnt[25:18]  <= act_cnt[7:0];

endmodule