Skip to content

ps2_ascii_table

Source: Verilog/Terminals/rtl/ps2_ascii_table.v

Hierarchy: not instantiated by any of the 9 build tops (elaborated by yosys).

Module hierarchy - All modules

ps2_ascii_table symbol

Schematic

Drawn from the Verilog: no build top uses this module, so it was elaborated from its own file with no defines and default parameters. 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).

ps2_ascii_table schematic

Ports

Direction Width Name Description
input [7:0] code
input 1 layout_no
output [7:0] unshifted
output [7:0] shifted
output [7:0] extended

Verilog source

Verilog/Terminals/rtl/ps2_ascii_table.v on GitHub.

Show the Verilog of ps2_ascii_table (253 lines)
//============================================================================
//! PS/2 scancode set 2 -> ASCII, unshifted and shifted
//!
//! Part of the board-independent terminal core (Verilog/Terminals/).
//! Pure combinational lookup; 0x00 out means "this key has no character".
//!
//! SCANCODES CROSS-CHECKED 30-AUG-2026 against two independent published
//! references, which agree with every entry below including the F7=0x83
//! oddity: the OSDev wiki's set-2 table (wiki.osdev.org/PS/2_Keyboard) and
//! Vetra Systems' scan-code translation table (vetra.com/scancodes.html).
//! Scan code set 2 is a fixed standard every PS/2-compatible keyboard and
//! the Nexys board's USB-HID bridge implement, so the codes are facts, not
//! transcription hopes. What documentation CANNOT settle - and typing on
//! the real board still can (fpga/nexys4ddr/PLAN-vga-console.md phase 3):
//! whether the board's bridge really emits set 2 unsurprisingly, and the
//! NORWEGIAN LAYOUT POSITIONS below, which come from RetroTerm's
//! KBD-ND-246.md rather than from a scancode standard.
//!
//! Extended (E0-prefixed) keys ARE here, in their own `extended` output and
//! their own case block below. Since the 30-AUG-2026 VT100 decision the
//! arrows and HOME emit SEQUENCE MARKERS (0x80 | final byte) that
//! key_vt100.v expands into the real ESC [ x bytes on the way to the UART -
//! see the block comment on the case below.
//!
//! Written 27-AUG-2026.
//============================================================================

`default_nettype none

module ps2_ascii_table (
    input  wire [7:0] code,

    //! 0 = US ANSI, 1 = Norwegian (NS 4551-1). See the block comment below.
    input  wire       layout_no,
    output wire [7:0] unshifted,
    output wire [7:0] shifted,

    //! Byte for the same scancode when it arrived with the E0 (extended)
    //! prefix - the arrows, HOME and DELETE. 0x00 = this extended key sends
    //! nothing; 0x80|f = key_vt100.v expands it to ESC [ f (see below).
    output reg  [7:0] extended
);

  //--------------------------------------------------------------------------
  // Extended (E0-prefixed) keys -> VT100 (30-AUG-2026)
  //
  // A VT100 sends multi-byte escape sequences for the special keys - three
  // to five bytes per keypress. One lookup cannot produce that, so the table
  // emits a MARKER byte that key_vt100.v expands. Bit 7 is the marker flag
  // (safe: the ND-120 is a 7-bit machine, no typed character has it, and a
  // marker that leaks unexpanded is visible instead of vanishing); bits 6:5
  // select the sequence FAMILY, bits 4:0 the payload
  // (SPEC-vt100-keys.md has the full story):
  //
  //   100f_ffff  ESC [ <0x40+f>    arrows: f 1..4 = A B C D
  //   101f_ffff  ESC O <0x40+f>    DEC PF keys: f 16..19 = P Q R S
  //   110n_nnnn  ESC [ <n> ~       editing keys n=1..6, F5-F12 n=15..24
  //   111x_xxxx  reserved - key_vt100 drops it
  //
  // (Until 30-AUG-2026 these were the TDV bare C0 bytes - UP=0x1C, DOWN=0x0B,
  // LEFT=0x08, RIGHT=0x18, HOME=0x1D - correct for terminal type 53, whose
  // evidence trail is in docs/SPEC-tdv2200.md and the git history. The
  // terminal is a VT100 now.)
  //
  // Scancodes cross-checked against OSDev + Vetra 30-AUG-2026 - see the
  // module header. Both agree on every code below.
  //--------------------------------------------------------------------------

  always @(*) begin
    case (code)
      8'h75:   extended = 8'h81;  // Up     -> ESC [ A
      8'h72:   extended = 8'h82;  // Down   -> ESC [ B
      8'h74:   extended = 8'h83;  // Right  -> ESC [ C
      8'h6B:   extended = 8'h84;  // Left   -> ESC [ D
      // Home is ESC [ H, MEASURED (30-AUG-2026, live PED on a type-6
      // SINTRAN line): ESC[H toggles to/from the PED: command line, while
      // ESC[1~ (DEC FIND, which the spec first chose) does NOTHING in PED.
      // The doc-derived guess pointed both ways; the live editor settled it.
      8'h6C:   extended = 8'h88;  // Home   -> ESC [ H
      8'h70:   extended = 8'hC2;  // Insert -> ESC [ 2 ~  (DEC INSERT HERE)
      8'h71:   extended = 8'hC3;  // Delete -> ESC [ 3 ~  (DEC REMOVE)
      8'h69:   extended = 8'hC4;  // End    -> ESC [ 4 ~  (DEC SELECT)
      8'h7D:   extended = 8'hC5;  // PgUp   -> ESC [ 5 ~  (DEC PREV SCREEN)
      8'h7A:   extended = 8'hC6;  // PgDn   -> ESC [ 6 ~  (DEC NEXT SCREEN)
      8'h5A:   extended = 8'h0D;  // keypad Enter -> CR
      default: extended = 8'h00;  // no VT100 equivalent (GUI keys, menu...):
                                  // send NOTHING rather than invent bytes.
    endcase
  end

  //--------------------------------------------------------------------------
  // Norwegian (NS 4551-1) - the layout_no overrides
  //
  // TWO THINGS DIFFER, and getting only one right produces nonsense:
  //
  //   1. WHICH KEY carries the character. Positions taken from RetroTerm's
  //      docs/KBD-ND-246.md (the ND 246 keyboard grid): D11, where US has [,
  //      is AA; C10 (US ;) is OE; C11 (US ') is AE; B8 (US ,) is , / ; ;
  //      B9 (US .) is . / : ; B10 (US /) is - / _ ; E11 (US -) is + / ?.
  //      Those are the same positions a modern PC Norwegian keyboard uses,
  //      which is the corroboration - two independent sources agreeing beats
  //      one source trusted.
  //
  //   2. WHICH BYTE it sends. NOT Latin-1. The ND-120 is a 7-BIT machine
  //      speaking ISO 646, where AE OE AA REPLACE [ \ ] and ae oe aa replace
  //      { | }. RetroTerm's FontTDV2200.cs says it outright: "The host sends
  //      7-bit ASCII position bytes (e.g. '[' = 0x5B for AE)". Send 0xC6 for
  //      AE and SINTRAN sees a byte with no meaning; send 0x5B and it is right.
  //
  // The SCREEN has to agree, which is why font_rom carries a second page (see
  // font/make_font.py). Keyboard and font are selected by the SAME bit -
  // letting them disagree would draw '[' for a typed AE.
  //
  // DEAD KEYS ARE NOT IMPLEMENTED. On a PC Norwegian layout the ¨/^ and ´/`
  // keys compose with the next keystroke, which needs state this table does
  // not have. They send their shifted character where that is plain ISO 646,
  // and nothing unshifted. Sending a plausible wrong byte would be worse than
  // sending none.
  //--------------------------------------------------------------------------

  reg [7:0] no_unshifted, no_shifted;
  reg       no_override;

  always @(*) begin
    no_override  = 1'b1;
    no_unshifted = 8'h00;
    no_shifted   = 8'h00;
    case (code)
      // number row - only the SHIFTED symbols move
      8'h1E: begin no_unshifted = "2"; no_shifted = 8'h22; end  // " where US has @
      8'h25: begin no_unshifted = "4"; no_shifted = 8'h24; end  // 0x24 is the currency sign in ISO 646 NO
      8'h36: begin no_unshifted = "6"; no_shifted = "&";   end
      8'h3D: begin no_unshifted = "7"; no_shifted = "/";   end
      8'h3E: begin no_unshifted = "8"; no_shifted = "(";   end
      8'h46: begin no_unshifted = "9"; no_shifted = ")";   end
      8'h45: begin no_unshifted = "0"; no_shifted = "=";   end

      // the three Norwegian letters, in their ISO 646 positions
      8'h54: begin no_unshifted = 8'h7D; no_shifted = 8'h5D; end  // D11  aa / AA
      8'h4C: begin no_unshifted = 8'h7C; no_shifted = 8'h5C; end  // C10  oe / OE
      8'h52: begin no_unshifted = 8'h7B; no_shifted = 8'h5B; end  // C11  ae / AE

      // the punctuation that moves
      8'h4E: begin no_unshifted = "+";   no_shifted = "?";   end  // E11
      8'h55: begin no_unshifted = 8'h5C; no_shifted = 8'h60; end  // \ and `
      8'h5D: begin no_unshifted = 8'h27; no_shifted = "*";   end  // C12  ' / *
      8'h41: begin no_unshifted = ",";   no_shifted = ";";   end  // B8
      8'h49: begin no_unshifted = ".";   no_shifted = ":";   end  // B9
      8'h4A: begin no_unshifted = "-";   no_shifted = 8'h5F; end  // B10  - / _
      8'h61: begin no_unshifted = "<";   no_shifted = ">";   end  // the extra ISO key US ANSI does not have

      // dead keys - shifted only, see the note above
      8'h5B: begin no_unshifted = 8'h00; no_shifted = 8'h5E; end  // D12  dead diaeresis / ^

      default: no_override = 1'b0;   // this key is the same in both layouts
    endcase
  end

  reg [7:0] us_unshifted, us_shifted;

  assign unshifted = (layout_no && no_override) ? no_unshifted : us_unshifted;
  assign shifted   = (layout_no && no_override) ? no_shifted   : us_shifted;

  always @(*) begin
    case (code)
      // --- letters ---------------------------------------------------------
      8'h1C: begin us_unshifted = "a"; us_shifted = "A"; end
      8'h32: begin us_unshifted = "b"; us_shifted = "B"; end
      8'h21: begin us_unshifted = "c"; us_shifted = "C"; end
      8'h23: begin us_unshifted = "d"; us_shifted = "D"; end
      8'h24: begin us_unshifted = "e"; us_shifted = "E"; end
      8'h2B: begin us_unshifted = "f"; us_shifted = "F"; end
      8'h34: begin us_unshifted = "g"; us_shifted = "G"; end
      8'h33: begin us_unshifted = "h"; us_shifted = "H"; end
      8'h43: begin us_unshifted = "i"; us_shifted = "I"; end
      8'h3B: begin us_unshifted = "j"; us_shifted = "J"; end
      8'h42: begin us_unshifted = "k"; us_shifted = "K"; end
      8'h4B: begin us_unshifted = "l"; us_shifted = "L"; end
      8'h3A: begin us_unshifted = "m"; us_shifted = "M"; end
      8'h31: begin us_unshifted = "n"; us_shifted = "N"; end
      8'h44: begin us_unshifted = "o"; us_shifted = "O"; end
      8'h4D: begin us_unshifted = "p"; us_shifted = "P"; end
      8'h15: begin us_unshifted = "q"; us_shifted = "Q"; end
      8'h2D: begin us_unshifted = "r"; us_shifted = "R"; end
      8'h1B: begin us_unshifted = "s"; us_shifted = "S"; end
      8'h2C: begin us_unshifted = "t"; us_shifted = "T"; end
      8'h3C: begin us_unshifted = "u"; us_shifted = "U"; end
      8'h2A: begin us_unshifted = "v"; us_shifted = "V"; end
      8'h1D: begin us_unshifted = "w"; us_shifted = "W"; end
      8'h22: begin us_unshifted = "x"; us_shifted = "X"; end
      8'h35: begin us_unshifted = "y"; us_shifted = "Y"; end
      8'h1A: begin us_unshifted = "z"; us_shifted = "Z"; end

      // --- digits and their shifted symbols (US layout) --------------------
      8'h45: begin us_unshifted = "0"; us_shifted = ")"; end
      8'h16: begin us_unshifted = "1"; us_shifted = "!"; end
      8'h1E: begin us_unshifted = "2"; us_shifted = "@"; end
      8'h26: begin us_unshifted = "3"; us_shifted = "#"; end
      8'h25: begin us_unshifted = "4"; us_shifted = "$"; end
      8'h2E: begin us_unshifted = "5"; us_shifted = "%"; end
      8'h36: begin us_unshifted = "6"; us_shifted = "^"; end
      8'h3D: begin us_unshifted = "7"; us_shifted = "&"; end
      8'h3E: begin us_unshifted = "8"; us_shifted = "*"; end
      8'h46: begin us_unshifted = "9"; us_shifted = "("; end

      // --- punctuation -----------------------------------------------------
      8'h0E: begin us_unshifted = "`";  us_shifted = "~"; end
      8'h4E: begin us_unshifted = "-";  us_shifted = "_"; end
      8'h55: begin us_unshifted = "=";  us_shifted = "+"; end
      8'h54: begin us_unshifted = "[";  us_shifted = "{"; end
      8'h5B: begin us_unshifted = "]";  us_shifted = "}"; end
      8'h5D: begin us_unshifted = 8'h5C; us_shifted = "|"; end  // 0x5C = backslash
      8'h4C: begin us_unshifted = ";";  us_shifted = ":"; end
      8'h52: begin us_unshifted = "'";  us_shifted = 8'h22; end  // 0x22 = double quote
      8'h41: begin us_unshifted = ",";  us_shifted = "<"; end
      8'h49: begin us_unshifted = ".";  us_shifted = ">"; end
      8'h4A: begin us_unshifted = "/";  us_shifted = "?"; end

      // --- the control keys a console needs --------------------------------
      8'h29: begin us_unshifted = 8'h20; us_shifted = 8'h20; end  // space
      8'h5A: begin us_unshifted = 8'h0D; us_shifted = 8'h0D; end  // Enter -> CR
      8'h66: begin us_unshifted = 8'h08; us_shifted = 8'h08; end  // Backspace -> BS
      8'h0D: begin us_unshifted = 8'h09; us_shifted = 8'h09; end  // Tab -> HT
      8'h76: begin us_unshifted = 8'h1B; us_shifted = 8'h1B; end  // Esc

      // 0x00 = no character for this key. Modifiers land here too, which is
      // correct: the decoder handles them before it ever consults this table.
      // --- function keys - marker in BOTH columns: a VT220 sends the same
      // code shifted or not, and there is no Shift-F encoding in this era
      // (SPEC-vt100-keys.md, "Modifiers"). F1-F4 are DEC's keypad PF1-PF4
      // by universal emulator convention; F5-F12 the VT220 top-row codes
      // 15,17,18,19,20,21,23,24 - the gaps at 16 and 22 are DEC's.
      // NOTE F7 = scancode 0x83, the one printable-range oddity in set 2.
      8'h05: begin us_unshifted = 8'hB0; us_shifted = 8'hB0; end  // F1  ESC O P
      8'h06: begin us_unshifted = 8'hB1; us_shifted = 8'hB1; end  // F2  ESC O Q
      8'h04: begin us_unshifted = 8'hB2; us_shifted = 8'hB2; end  // F3  ESC O R
      8'h0C: begin us_unshifted = 8'hB3; us_shifted = 8'hB3; end  // F4  ESC O S
      8'h03: begin us_unshifted = 8'hCF; us_shifted = 8'hCF; end  // F5  ESC [ 15 ~
      8'h0B: begin us_unshifted = 8'hD1; us_shifted = 8'hD1; end  // F6  ESC [ 17 ~
      8'h83: begin us_unshifted = 8'hD2; us_shifted = 8'hD2; end  // F7  ESC [ 18 ~
      8'h0A: begin us_unshifted = 8'hD3; us_shifted = 8'hD3; end  // F8  ESC [ 19 ~
      8'h01: begin us_unshifted = 8'hD4; us_shifted = 8'hD4; end  // F9  ESC [ 20 ~
      8'h09: begin us_unshifted = 8'hD5; us_shifted = 8'hD5; end  // F10 ESC [ 21 ~
      8'h78: begin us_unshifted = 8'hD7; us_shifted = 8'hD7; end  // F11 ESC [ 23 ~
      8'h07: begin us_unshifted = 8'hD8; us_shifted = 8'hD8; end  // F12 ESC [ 24 ~

      default: begin us_unshifted = 8'h00; us_shifted = 8'h00; end
    endcase
  end

endmodule

`default_nettype wire