Skip to content

nd120_console_mega65

Source: Verilog/fpga/mega65/rtl/nd120_console_mega65.v

Where it sits (MEGA65 R6): nd120_mega65_machine > nd120_console_mega65 - instance path: CONSOLE

Used in: nd120_mega65_machine (MEGA65 R6, MEGA65 R3)

Contains: key_tdv2200, m65_keys_to_ps2, ps2_decoder_tdv, term_console_feed, terminal_top

Module hierarchy - All modules

nd120_console_mega65 symbol

Schematic

Drawn from the Verilog: the yosys netlist of the MEGA65 R4 R5 R6 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_mega65 schematic

Parameters

Parameter Default
FONT_FILE "embedded"
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 [6:0] key_num
input 1 key_pressed_n (active low)
input [1:0] text_colour
input 1 cpu_byte_valid one clock per received byte (from console_uart_rx.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
input 1 panel_cpu_green
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; ignored unless ready (to console_uart_tx.byte_valid)
output [7:0] kbd_data
output [7:0] video_r
output [7:0] video_g
output [7:0] video_b
output 1 hsync
output 1 vsync
output 1 hblank
output 1 vblank
output 1 de
output 1 bell

Verilog source

Verilog/fpga/mega65/rtl/nd120_console_mega65.v on GitHub.

Show the Verilog of nd120_console_mega65 (278 lines)
//============================================================================
//! MEGA65 glue for the board-independent terminal core.
//!
//! Full path: Verilog/fpga/mega65/rtl/nd120_console_mega65.v
//!
//! The MEGA65 counterpart of fpga/mister/rtl/nd120_console_mister.v: the
//! terminal core in Verilog/Terminals/ is shared and unchanged, and this
//! file is the two ends that differ per board - where keystrokes come from
//! and where pixels go - plus the palette, because colour depth is a board
//! property.
//!
//! THE KEYBOARD END. MiSTer2MEGA65 gives the core a 1 kHz SCAN of the
//! MEGA65's 80 key numbers (key_num + key_pressed_n), not events, and the
//! keys are a C64 layout. m65_keys_to_ps2 turns that into PS/2 set-2
//! events whose codes type what the MEGA65 KEYCAPS say, so the shared
//! ps2_decoder_tdv (modifiers, caps, ctrl, Alt markers, the TDV
//! function/cursor keys) is reused as-is - the same decoder the MiSTer and
//! Nexys builds use. Then key_tdv2200 expands the TDV markers into their
//! ESC[nn_ sequences, exactly as on the other two boards.
//!
//! THE VIDEO END. The framework wants a pixel clock, a pixel enable, RGB
//! 8:8:8, syncs and SEPARATE hblank/vblank in the video clock domain, and
//! scales whatever it is given for HDMI while passing it straight out on
//! VGA. We hand it the terminal's own 800x600@60 at one pixel per clock
//! (40.000 MHz, ce = 1), the same mode the Nexys and MiSTer builds use - one
//! tested configuration rather than one per board. hblank/vblank come from
//! the terminal core (ports added 02-SEP-2026 for this board).
//!
//! B1 (02-SEP-2026) has no ND-120 in it. `cpu_*` is wired but nothing
//! drives it: the banner prints, then the keyboard echoes locally. That
//! proves the framework flow, the clock, the video timing, the font, the
//! character RAM and the keyboard on a MEGA65 with NOTHING of the CPU able
//! to be blamed - and the MEGA65 is tested by friends, days per round
//! trip, so one thing at a time matters more here than anywhere.
//!
//! Written 02-SEP-2026.
//============================================================================

`default_nettype none

module nd120_console_mega65 #(
    //! Ignored since 02-SEP-2026: font_rom.v has the glyphs EMBEDDED
    //! (commit 4eb3220) - nothing is read from disk at synthesis. Kept so
    //! this glue instantiates terminal_top the same way the other boards do.
    parameter FONT_FILE = "embedded",

    //! B1: echo keystrokes to the screen so the console is testable with no
    //! machine behind it. B3 sets this to 0 - the ND-120 echoes, and doing
    //! both shows every character twice.
    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

    //! The framework's keyboard scan (main_kb_key_num_i / _pressed_n_i)
    input wire [6:0] key_num,
    input wire       key_pressed_n,

    //! Console text colour, 0 green / 1 amber / 2 white / 3 cyan - an OSD
    //! option later; B1 ties it to green.
    input wire [1:0] text_colour,

    //! The machine seam. Bytes from the ND-120's console UART come in here;
    //! keystrokes go back out. Unused in B1.
    input  wire       cpu_byte_valid,  //! one clock per received byte (from console_uart_rx.byte_valid)
    input  wire [7:0] cpu_byte_data,
    output wire       cpu_byte_ready,

    //! Operator panel, threaded straight to terminal_top (see
    //! nd120_console_mister.v for the field meanings). B1: all constant 0.
    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,
    input wire        panel_cpu_green,
    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. kbd_ready
    //! is the console UART TX's idle flag; B1 ties it high.
    input  wire       kbd_ready,  //! high when idle (from console_uart_tx.ready)
    output wire       kbd_valid,  //! one clock; ignored unless ready (to console_uart_tx.byte_valid)
    output wire [7:0] kbd_data,

    //! Video, in the framework's shape. `de` is kept for testbenches.
    output wire [7:0] video_r,
    output wire [7:0] video_g,
    output wire [7:0] video_b,
    output wire       hsync,
    output wire       vsync,
    output wire       hblank,
    output wire       vblank,
    output wire       de,

    output wire bell
);

  //--------------------------------------------------------------------------
  // Keyboard: scan -> PS/2 events -> the shared decoder -> the TDV expander
  //--------------------------------------------------------------------------

  wire       s_code_valid;
  wire [7:0] s_code_data;
  wire       s_code_release;
  wire       s_code_extended;

  m65_keys_to_ps2 KEYS (
      .clk          (clk),
      .rst_n        (rst_n),
      .key_num      (key_num),
      .key_pressed_n(key_pressed_n),
      .code_valid   (s_code_valid),
      .code_data    (s_code_data),
      .code_release (s_code_release),
      .code_extended(s_code_extended)
  );

  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_code_valid),
      .code_data    (s_code_data),
      .code_release (s_code_release),
      .code_extended(s_code_extended),

      //! US table, always: m65_keys_to_ps2 chooses its codes against the
      //! US column, and the MEGA65 keycaps are what they are regardless of
      //! the terminal's national font page.
      .layout_no(1'b0),

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

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

  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: banner, then machine, then echo -
  // the priority lives in the shared term_console_feed.
  //--------------------------------------------------------------------------

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

  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),

      .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()
  );

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

  wire [2:0] s_colour;

  terminal_top #(
      .FONT_FILE(FONT_FILE)
  ) TERMINAL (
      .byte_clk  (clk),
      .byte_rst_n(rst_n),
      .byte_valid(s_src_valid),
      .byte_data (s_src_data),
      .byte_ready(s_term_ready),

      .national (1'b0),
      .mode     (1'b0),

      .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   (s_colour),

      .pix_clk  (clk),
      .pix_rst_n(rst_n),
      .pixel    (),        // `colour` already says ink/paper - see the palette
      .hsync    (hsync),
      .vsync    (vsync),
      .de       (de),
      .hblank   (hblank),
      .vblank   (vblank),

      .bell(bell),
      .leds(),
      .dbg_box_mode(),
      .dbg_saw_esc6()
  );

  //--------------------------------------------------------------------------
  // Palette: the core says WHICH of eight things a pixel is; the board picks
  // the colour. Same values as the MiSTer build (nd120.sv), whose panel
  // colours were sampled from a photograph of the real folio panel.
  //--------------------------------------------------------------------------

  reg [23:0] s_text_rgb;
  always @(*) begin
    case (text_colour)
      2'd0:    s_text_rgb = 24'h00FF00;  // green - the Tandberg default
      2'd1:    s_text_rgb = 24'hFFBF00;  // amber
      2'd2:    s_text_rgb = 24'hFFFFFF;  // white
      default: s_text_rgb = 24'h00FFFF;  // cyan
    endcase
  end

  reg [23:0] s_rgb;
  always @(*) begin
    case (s_colour)
      3'd0:    s_rgb = 24'h000000;   // black
      3'd1:    s_rgb = s_text_rgb;   // console text
      3'd2:    s_rgb = 24'h191b19;   // panel fascia
      3'd3:    s_rgb = 24'hd6d9d2;   // silkscreen
      3'd4:    s_rgb = 24'hb6c2a4;   // LCD ground
      3'd5:    s_rgb = 24'h2a3226;   // LCD segment
      3'd6:    s_rgb = 24'he04a63;   // lit legend
      default: s_rgb = 24'h444444;   // unlit legend
    endcase
  end

  assign video_r = de ? s_rgb[23:16] : 8'h00;
  assign video_g = de ? s_rgb[15:8]  : 8'h00;
  assign video_b = de ? s_rgb[7:0]   : 8'h00;

endmodule

`default_nettype wire