Skip to content

nd120_console_mister

Source: Verilog/fpga/mister/rtl/nd120_console_mister.v

Where it sits (MiSTer): emu > nd120_console_mister - instance path: CONSOLE

Used in: emu (MiSTer)

Contains: key_tdv2200, ps2_decoder_tdv, term_console_feed, terminal_top

Module hierarchy - All modules

nd120_console_mister symbol

Schematic

Drawn from the Verilog: the yosys netlist of the MiSTer build, instance CONSOLE. 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_console_mister schematic

Parameters

Parameter Default
FONT_FILE "font8x16.hex"
LOCAL_ECHO 1

Ports

Direction Width Name Description
input 1 clk pixel clock, 40.000 MHz for 800x600@60
input 1 rst_n (active low) async reset, active low, in the pixel domain
input [10:0] ps2_key
input 1 layout_no
input 1 cpu_byte_valid
input [7:0] cpu_byte_data
output 1 cpu_byte_ready
input 1 panel_enable
input [3:0] panel_pil Processor interrupt level (for debug capture) (from ND120_CORE.PIL)
input [15:0] panel_actlv ACTIVE LEVEL word from the panel processor (from ND120_CORE.PANEL_ACTLV)
input [15:0] panel_mips
input 1 panel_cpu_red CPU board LED[0] - MACL in progress
input 1 panel_cpu_green CPU board LED[1] - init complete (self-test passed)
input 1 panel_lev0
input 1 panel_hit
input [1:0] panel_ring
input 1 panel_paging_on
input 1 panel_interrupt_on
input 1 panel_running
input 1 panel_hdd_rd
input 1 panel_hdd_wr
input 1 panel_flp_rd
input 1 panel_flp_wr
input 1 kbd_ready high when idle (from console_uart_tx.ready)
output 1 kbd_valid one clock per byte to the machine, gated by kbd_ready
output [7:0] kbd_data
output [2:0] colour palette index - see terminal_top.v
output 1 panel_active 1 = panel pixel (board keeps its lamp green)
output 1 pixel 1 = ink
output 1 hsync
output 1 vsync
output 1 de
output 1 bell

Verilog source

Verilog/fpga/mister/rtl/nd120_console_mister.v on GitHub.

Show the Verilog of nd120_console_mister (292 lines)
//============================================================================
//! MiSTer glue for the board-independent terminal core.
//!
//! Full path: Verilog/fpga/mister/rtl/nd120_console_mister.v
//!
//! Everything MiSTer-specific about the console lives here, and nothing
//! MiSTer-specific lives in Verilog/Terminals/. The terminal core is shared
//! with the Nexys 4 DDR and (later) the MEGA65; the only things that differ
//! per board are the two ends - where keystrokes come from and where pixels
//! go - which is exactly what this file is.
//!
//! THE KEYBOARD END. hps_io gives us `ps2_key = {toggle, pressed, extended,
//! code[7:0]}` (sys/hps_io.sv:102-103, 306) - Linux has already done the USB
//! work AND the PS/2 framing, so there is no serial line to decode. That is
//! why this instantiates `ps2_decoder` and NOT `ps2_keyboard`: the framing
//! half of the Nexys keyboard has no job here, while the half with the actual
//! logic in it - modifiers, caps lock, control characters, the TDV cursor
//! keys - is shared. Bit 10 toggles per event rather than pulsing, so it needs
//! an edge detector to become a strobe.
//!
//! THE VIDEO END. MiSTer wants CLK_VIDEO + CE_PIXEL + VGA_DE/HS/VS/R/G/B and
//! scales whatever it is given. We hand it the terminal's own 800x600@60
//! timing at one pixel per clock, so CE_PIXEL is simply 1 - the same video
//! mode the Nexys build uses, which means one tested configuration rather than
//! one per board.
//!
//! BUILD 1 (28-AUG-2026) has no ND-120 in it at all. `cpu_*` is wired but
//! nothing drives it, so what you get is the banner plus local echo: type and
//! the characters appear. That is the whole point of build 1 - it proves the
//! clock, the video timing, the font, the character RAM and the keyboard on
//! real MiSTer hardware with NOTHING of the CPU able to be blamed. Build 2
//! drops ND3202D in behind the same seam.
//!
//! Written 28-AUG-2026.
//============================================================================

`default_nettype none

// TDV2200 (the shared core's default, undefined) - matching the Nexys and
// what SINTRAN drives both boards as. This file USED to `define
// ND120_TERMINAL_VT100 because its keyboard end only wired ps2_decoder +
// key_vt100 and "there is no TDV keyboard path here yet". That path is wired
// now (02-SEP-2026): ps2_decoder_tdv + the key_tdv2200 marker expander below,
// exactly as nd120_nexys4ddr_top.v does. Display and keyboard now speak the
// same terminal type - TDV2200 - so the box glyphs (font page 2) and the TDV
// cursor/function keys work here as they do on the Nexys. Do NOT re-add the
// VT100 `define without also switching the keyboard back, or the two ends
// would disagree again.

module nd120_console_mister #(
    //! A BARE FILENAME on purpose. Vivado resolves $readmemh next to the .v
    //! that contains it and Quartus is believed to resolve it relative to the
    //! project directory - so instead of betting on one, the build puts the
    //! font where both rules look: nd120.qsf adds a SEARCH_PATH and `make
    //! font` copies the hex into the project directory. Blank boxes on screen
    //! instead of glyphs means neither worked.
    parameter FONT_FILE = "font8x16.hex",

    //! Build 1 default: echo keystrokes straight to the screen so the console
    //! is testable with no machine behind it. Build 2 sets this to 0, because
    //! once the ND-120 is there IT echoes - a terminal that echoes locally as
    //! well shows every character twice, which is the classic first symptom.
    parameter LOCAL_ECHO = 1
) (
    input wire clk,    //! pixel clock, 40.000 MHz for 800x600@60
    input wire rst_n,  //! async reset, active low, in the pixel domain

    //! From hps_io. {toggle, pressed, extended, code[7:0]}
    input wire [10:0] ps2_key,

    //! 0 = US ANSI, 1 = Norwegian (NS 4551-1). Drives the keyboard table AND
    //! the font page from one bit - see the note in ps2_ascii_table.v. On the
    //! Nexys this is a slide switch; here it should become an OSD option
    //! (CONF_STR) once build 2 has a machine behind the console. Wired now so
    //! the two boards do not drift.
    input wire        layout_no,

    //! The machine seam. Bytes from the ND-120's console UART come in here;
    //! keystrokes go back out. Unused in build 1.
    input  wire       cpu_byte_valid,
    input  wire [7:0] cpu_byte_data,
    output wire       cpu_byte_ready,

    //! Operator panel (build 2, 31-AUG-2026). Real ports now, threaded
    //! straight to terminal_top - see the port comment there and
    //! nd120_nexys4ddr_top.v's wiring for the same DBG_PANEL bit layout
    //! ([1:0] PCR ring, [2] PONI, [3] IONI, [4] LHIT, [5] LEV0). Build 1 had
    //! no CPU, so every field would have been a constant; the board (not
    //! this module) decides whether it is on, via the OSD.
    input wire        panel_enable,
    input wire [ 3:0] panel_pil,  //! Processor interrupt level (for debug capture) (from ND120_CORE.PIL)
    input wire [15:0] panel_actlv,  //! ACTIVE LEVEL word from the panel processor (from ND120_CORE.PANEL_ACTLV)
    input wire [15:0] panel_mips,
    input wire        panel_cpu_red,     //! CPU board LED[0] - MACL in progress
    input wire        panel_cpu_green,   //! CPU board LED[1] - init complete (self-test passed)
    input wire        panel_lev0,
    input wire        panel_hit,
    input wire [ 1:0] panel_ring,
    input wire        panel_paging_on,
    input wire        panel_interrupt_on,
    input wire        panel_running,
    input wire        panel_hdd_rd,
    input wire        panel_hdd_wr,
    input wire        panel_flp_rd,
    input wire        panel_flp_wr,

    //! Keystrokes to the machine, AFTER the key_tdv2200 expander (so a TDV
    //! function/cursor key is already its full ESC[nn_ sequence). kbd_ready is
    //! the console UART TX's idle flag - the expander only emits while it is
    //! high, exactly as on the Nexys (nd120_nexys4ddr_top.v CONSOLE_TX.ready).
    input  wire       kbd_ready,  //! high when idle (from console_uart_tx.ready)
    output wire       kbd_valid,  //! one clock per byte to the machine, gated by kbd_ready
    output wire [7:0] kbd_data,

    // Video, straight onto the framework's VGA_* ports
    output wire [2:0] colour,  //! palette index - see terminal_top.v
    output wire       panel_active,  //! 1 = panel pixel (board keeps its lamp green)
    output wire pixel,  //! 1 = ink
    output wire hsync,
    output wire vsync,
    output wire de,

    output wire bell
);

  //--------------------------------------------------------------------------
  // Keyboard: toggle -> strobe, then the shared decoder
  //--------------------------------------------------------------------------

  reg s_key_toggle_d;

  wire s_key_strobe = (ps2_key[10] != s_key_toggle_d);

  //! No reset on purpose. Resetting this to a CONSTANT would manufacture a
  //! phantom keystroke whenever the incoming toggle happened to sit at the
  //! other value when reset released - a stray character at power-on that
  //! looks exactly like a keyboard fault. Tracking the input unconditionally
  //! cannot produce a false edge; hps_io declares `ps2_key = 0` at power-up
  //! (sys/hps_io.sv:103) so both sides start agreeing anyway.
  always @(posedge clk) s_key_toggle_d <= ps2_key[10];

  wire       s_kbd_ascii_valid;
  wire [7:0] s_kbd_ascii_data;

  ps2_decoder_tdv DECODER (
      .clk  (clk),
      .rst_n(rst_n),

      .code_valid   (s_key_strobe),
      .code_data    (ps2_key[7:0]),
      //! hps_io reports `pressed`; the decoder wants `release`.
      .code_release (~ps2_key[9]),
      .code_extended(ps2_key[8]),

      .layout_no(layout_no),

      .ascii_valid(s_kbd_ascii_valid),
      .ascii_data (s_kbd_ascii_data),

      .shift_active(),
      .ctrl_active (),
      .caps_active (),
      .alt_active  ()
  );

  //! Marker expander. A TDV function/cursor key leaves the table above as a
  //! single marker byte (0x80|final); key_tdv2200 turns it into the ESC[nn_
  //! byte sequence the machine expects, one byte at a time, gated by the UART
  //! TX's idle (kbd_ready). Plain characters pass straight through. This is
  //! the exact KEYEXP the Nexys top wires (nd120_nexys4ddr_top.v) - without
  //! it, arrows and function keys are single bytes >= 0x80 that the machine
  //! never sees. HPS keyboards send the proper E0 extended codes (unlike the
  //! Nexys USB-PS/2 bridge that drops E0), so the extended table handles the
  //! arrows here; the bare-code entries added for the Nexys are harmless.
  key_tdv2200 KEYEXP (
      .clk      (clk),
      .rst_n    (rst_n),
      .key_valid(s_kbd_ascii_valid),
      .key_data (s_kbd_ascii_data),
      .out_valid(kbd_valid),
      .out_data (kbd_data),
      .out_ready(kbd_ready)
  );

  //--------------------------------------------------------------------------
  // What the screen is told to display
  //
  // Three possible sources, in strict priority: the banner first (it owns the
  // screen until it has finished and then never speaks again), then the
  // machine, then the local echo. The banner going first matters - if it had
  // to share, a machine that starts talking immediately would interleave with
  // it and the self-test message would be unreadable exactly when something is
  // wrong.
  //--------------------------------------------------------------------------

  wire s_term_ready;
  wire s_src_valid;
  wire [7:0] s_src_data;

  //! The banner and the source priority live in the shared core, not here -
  //! every board needs exactly the same rules and one of them was already got
  //! wrong once when it was inline. See term_console_feed.v.
  term_console_feed FEED (
      .clk  (clk),
      .rst_n(rst_n),

      .cpu_valid(cpu_byte_valid),
      .cpu_data (cpu_byte_data),
      .cpu_ready(cpu_byte_ready),

      // Build 1 has no machine behind the seam, so the terminal echoes for
      // itself. Build 2 ties these off: the ND-120 echoes, and doing both
      // shows every character twice.
      // NOTE 30-AUG-2026: the arrows/HOME now come out of the decoder as
      // VT100 sequence markers (0x80|final, see ps2_ascii_table.v). This
      // echo path passes them RAW and terminal_ctrl drops bytes >= 0x7F, so
      // in build 1 the arrow keys simply do nothing locally. Build 2, which
      // has a real UART to the machine, must route keys through key_vt100
      // the way the Nexys top does - the expander's handshake is written
      // for a UART's ready, not for this feed's.
      .echo_valid((LOCAL_ECHO != 0) && s_kbd_ascii_valid),
      .echo_data (s_kbd_ascii_data),

      .term_valid(s_src_valid),
      .term_data (s_src_data),
      .term_ready(s_term_ready),

      .banner_done()
  );

  //! NOTE on the echo path: a keystroke is a one-clock strobe with no queue
  //! behind it, so a character typed while the terminal is busy is lost rather
  //! than delayed. The busy window is a clear-screen, 1920 pixel clocks = 48 us
  //! at 40 MHz, and the banner is ~320 clocks = 8 us; a human cannot type into
  //! either. It stops being good enough when Stage B adds escape sequences and
  //! the busy windows get longer. Written down rather than left as a surprise.

  //--------------------------------------------------------------------------
  // The shared terminal core
  //--------------------------------------------------------------------------

  terminal_top #(
      .FONT_FILE(FONT_FILE)
  ) TERMINAL (
      // One clock domain here: the whole console runs on the pixel clock, so
      // the CDC inside terminal_top is a no-op. Left in place rather than
      // bypassed - it is what lets this core drop onto a board where the
      // machine runs on its own clock, and it costs three flops.
      .byte_clk  (clk),
      .byte_rst_n(rst_n),
      .byte_valid(s_src_valid),
      .byte_data (s_src_data),
      .byte_ready(s_term_ready),

      .national (layout_no),
      .mode     (1'b0),   // MiSTer runs one video mode; the scaler does the rest

      // Real signals now (build 2) - see the port comment above.
      .panel_enable      (panel_enable),
      .panel_pil         (panel_pil),
      .panel_actlv       (panel_actlv),
      .panel_mips        (panel_mips),
      .panel_cpu_red     (panel_cpu_red),
      .panel_cpu_green   (panel_cpu_green),
      .panel_lev0        (panel_lev0),
      .panel_hit         (panel_hit),
      .panel_ring        (panel_ring),
      .panel_paging_on   (panel_paging_on),
      .panel_interrupt_on(panel_interrupt_on),
      .panel_running     (panel_running),
      .panel_hdd_rd      (panel_hdd_rd),
      .panel_hdd_wr      (panel_hdd_wr),
      .panel_flp_rd      (panel_flp_rd),
      .panel_flp_wr      (panel_flp_wr),

      .colour   (colour),
      .panel_active(panel_active),

      .pix_clk  (clk),
      .pix_rst_n(rst_n),
      .pixel    (pixel),
      .hsync    (hsync),
      .vsync    (vsync),
      .de       (de),

      .bell(bell),
      .leds()
  );

endmodule

`default_nettype wire