Skip to content

Terminals - the ND-120's console on a screen and a keyboard

Full path: Verilog/Terminals/ Created 27-AUG-2026.

Every ND-120 FPGA target so far puts the OPCOM/SINTRAN console on a UART and you attach a PC terminal program to the other end. Two of the new targets - MiSTer (fpga/mister/) and MEGA65 (fpga/mega65/) - are whole machines with their own HDMI/VGA output and their own keyboard. What is missing is the piece in between: a terminal. That is what lives here.

First test rig: the Nexys 4 DDR, not MiSTer or MEGA65 - it is the only board that today has a booting SINTRAN, a VGA connector AND a USB keyboard host all at once, so the terminal was the only new thing in the build.

That work is DONE. The screen-and-keyboard console is built and running on the Nexys, the MiSTer and the MEGA65. There is no plan document to read: fpga/nexys4ddr/PLAN-vga-console.md was deleted in commit c4896a4 because it was finished, and docs/PLAN-vt100-terminal-core.md says of itself that its stages are "history, not plan". This README is the current state.

This folder is board-independent RTL. It knows nothing about MiSTer, MEGA65, Tang or Nexys. Each board supplies the two ends - a source of key events and a video sink - and the board folder owns that wiring.

Module list (31-AUG-2026)

File What it is Used by
rtl/terminal_top.v the whole terminal: bytes in, pixels out. Selects VT100 vs TDV2200 at compile time - see below every board
rtl/terminal_ctrl.v VT100 (type 6): ESC/CSI parser, CUP/ED/EL, SGR, DECSTBM, charsets every board (via -DND120_TERMINAL_VT100)
rtl/terminal_ctrl_tdv.v TDV2200 (type 93, DEFAULT): TDV-native C0 table, DLE binary cursor addressing, generic mode/DCS swallow every board (default, no define)
rtl/text_screen.v the pixel pipeline: RAM -> font -> pixel, attributes every board
rtl/char_ram.v cells (80x24 VT100 / 80x25 TDV2200), 8 bits character + 8 bits attribute every board
rtl/byte_fifo.v 16-byte slack in front of the controller (region scroll > 1 byte time) every board
rtl/vga_timing.v parameterised sync generator, 800x600@60 default every board
rtl/font_rom.v 8x16 glyphs from font/font8x16.hex: four 128-glyph pages - US, Norwegian, DEC Special Graphics (VT100 build), TDV2200 character set 2 (TDV build - both ESC 6 Box and SS2/ESC N, what PED draws frames with) every board
rtl/cdc_byte.v one-byte clock crossing, full valid/ready handshake every board
rtl/term_banner.v power-on self-test message sender every board
rtl/term_banner_rom.v the message itself - GENERATED, see below every board
rtl/ps2_decoder.v scancode -> byte, VT100 marker scheme: modifiers, caps, ctrl; arrows/HOME emit sequence markers Nexys and MiSTer
rtl/ps2_decoder_tdv.v sibling of the above for the TDV2200 table - same logic, different ps2_ascii_table_* instance Nexys (default)
rtl/key_vt100.v expands VT100 markers to ESC [ x toward the UART, with the FIFO that makes 3-5 bytes per keypress survive Nexys/MiSTer (via -DND120_TERMINAL_VT100)
rtl/key_tdv2200.v expands TDV2200 markers to ESC [ nn _ (shift = n+1) toward the UART Nexys (default)
rtl/ps2_ascii_table.v the VT100 scancode lookup tables Nexys and MiSTer
rtl/ps2_ascii_table_tdv.v the TDV2200 scancode lookup tables - bare C0 bytes for arrows/Home/Delete, ESC[nn_ markers for F-keys Nexys (default)
rtl/ps2_keyboard.v PS/2 serial front end (11-bit framing, prefixes) + VT100 decoder Nexys only, via -DND120_TERMINAL_VT100
rtl/ps2_keyboard_tdv.v same framing, TDV2200 decoder Nexys only, default
rtl/console_uart_rx.v / _tx.v the machine seam, 7/8 bits + optional parity Nexys only

VT100 and TDV2200 are two genuinely separate module pairs, never both elaborated in the same build - selected by `ifdef ND120_TERMINAL_VT100 in terminal_top.v and each board's top level (Nexys: -VT100Terminal build flag). Default is TDV2200 (type 93) since 31-AUG-2026: PED and LED are built for the Tandberg keyboard's own key set, not VT100 CSI input - see docs/SPEC-tdv2200.md. The MiSTer runs TDV2200 too: its TDV keyboard path (ps2_decoder_tdv + key_tdv2200) was wired 02-SEP-2026, and fpga/mister/rtl/nd120_console_mister.v warns not to re-add the VT100 define.

The keyboard is split in two on purpose. MiSTer's hps_io delivers scancodes that Linux has already framed, and the MEGA65 has a matrix behind a CPLD - neither has a serial line to decode. Keeping the part with the actual logic in it (ps2_decoder.v) separate from the framing means a keyboard bug found on one board is fixed on all of them.

term_banner_rom.v is generated by font/make_banner.py. Edit the text there and re-run it; do not edit the ROM. The generator exists so that the message and its length can never disagree - and in fact no length constant survives at all, because the ROM returns 0x00 past the end and the sender stops there.

Layout

Path What
rtl/ the terminal RTL (Verilog, not VHDL - see the plan for why)
sim/ testbenches, per the repo convention; every one gets registered in Verilog/tests/run_all_tests.sh with a strict pass pattern
font/ character-generator ROM data + the script that builds it
docs/ the plan and the spec notes

History: the VT100 decision of 30-AUG-2026 (reversed 31-AUG-2026)

TDV2200 has been the default since 31-AUG-2026 (see the module table above). The text below is the 30-AUG reasoning, kept as a record; the "80x24" and "TDV build REMOVED" statements are no longer true of the default build.

Decision (Ronny): plain VT100, matching SINTRAN terminal type 6 (@SET-TERMINAL-TYPE <n> 6), not TDV2200. RetroTerm's own class hierarchy settled the question: its TDV2200 emulator derives from the same ECMA-48 core its VT100 is, adding ND private CSI finals, rectangles, work areas and nine character sets on top - so a TDV2200 is a VT100 plus a delta, and the delta is not needed to give SINTRAN a working console. The earlier TDV-native C0 build (FF=roll up, EM=erase page, DLE cursor addressing, 80x25) was REMOVED with the 30-AUG rewrite of terminal_ctrl.v; the geometry is now the VT100's 80x24 and the C0 codes have their ASCII meanings. If TDV2200 is ever wanted it is a delta on this parser (the header of terminal_ctrl.v says exactly where).

What the controller implements - the list, with the reasons, is the header of rtl/terminal_ctrl.v:

  • CSI: CUP/HVP, CUU/CUD/CUF/CUB, CHA, VPA, ED 0/1/2, EL 0/1/2, SGR (reverse, bold, underline, blink), DECSTBM scroll regions, DECLL lamps, modes DECSCNM / DECOM / DECAWM / DECTCEM / LNM.
  • ESC: IND, NEL, RI, DECSC/DECRC, RIS, charset designation.
  • The VT100 last-column flag (autowrap done right).
  • Character sets: G0/G1 with DEC Special Graphics (ESC ( 0 / ESC ) 0
    • SO/SI) - font page 2, generated by font/make_font.py, so line-drawn boxes render. Page 0 US, page 1 Norwegian NS 4551-1 (the national pin).
  • NOT implemented, deliberately: anything needing a transmit path (DA/DSR/CPR reports - SINTRAN is configured, it does not probe), VT102 IL/DL/ICH/DCH, 132 columns, double-size lines. Unknown sequences are swallowed, never printed.

Scrolling: full-screen scroll is still the top_row ring; a DECSTBM region scroll is a real row copy through the character RAM's second port (~96 us worst case at 40 MHz), which is why byte_fifo.v now sits in front of the controller - one 115200 byte time is only 87 us and the UART does not wait.

Keyboard: the full DEC key set (30-AUG-2026). Every special key leaves ps2_ascii_table.v as a sequence marker (bit 7 flag, 2 family bits, 5 payload bits) and key_vt100.v expands it on the way to the console UART: arrows ESC[A-D; Home/Insert/Delete/End/PgUp/PgDn as the DEC editing six ESC[1~..6~ (Home=FIND, End=SELECT - ESC[H was xterm's Home, not DEC's, and is gone); F1-F4 as DEC PF1-PF4 ESC O P/Q/R/S; F5-F12 as the VT220 codes ESC[15~..24~. Modifiers per the standard: Shift/Ctrl on a special key send the base sequence (no modifier encoding existed in this era), Alt sends nothing. Sequences up to five bytes ride a 16-deep FIFO. Full analysis + the VTM-side evidence: docs/SPEC-vt100-keys.md. Wired on the Nexys top; MiSTer build 1 has no UART, its local-echo path drops the markers harmlessly (noted in nd120_console_mister.v).

Verilator lint clean (make lint in sim/, using the SAME suppression set as Verilog/sim and Verilog/runSim - deviating would make "clean" mean something different here than everywhere else), and zero inferred latches with -Wno-LATCH removed, checked separately because the repo suppresses that warning by default and latches are the one thing this project cannot have.

All nine testbenches pass (cd Terminals/sim && make, iverilog under WSL) and are registered in Verilog/tests/run_all_tests.sh. sim/terminal_ctrl_tb.v was rewritten with the VT100 controller: it checks the parser against sequences split across gaps, CAN aborts, the last-column flag, region scrolls through the copy engine, SGR attribute bits landing in the cells, charset shifts, DECOM addressing and RIS - seventeen sections. Synthesized and timing-clean on the Nexys 4 DDR (30-AUG-2026): first Vivado run failed the 1080p pixel clock (139.7 MHz) on the cursor-move paths; two rounds of fixes (defer the moves into the apply state, then latch their operands before use - commits 191bdee, e26f44b) closed it at WNS +0.211 ns, all endpoints met, better margin than the build before the rewrite. Utilization delta vs the old TDV terminal: +695 LUTs, +494 regs, +2.5 BRAM. Flashed to the board 30-AUG 06:22 as part of the CACHEFIX session's cache build 8; first hardware exercise (TPE console traffic) ran right after - what a human sees on the screen is still unreported.

The scancode table is verified against the standard, not against fingers. Every set-2 code in ps2_ascii_table.v was cross-checked 30-AUG-2026 against two independent published references - the OSDev wiki set-2 table and Vetra Systems' translation table - and both agree with every entry, F7=0x83 included. Scan code set 2 is a fixed standard, so the codes are settled by documentation. What still needs checking on real hardware: that the Nexys USB-HID bridge behaves as the standard says, and the Norwegian layout positions, which come from RetroTerm's KBD-ND-246 grid rather than from any scancode standard - parentheses, colon, comma, full stop and slash are the ones SINTRAN needs first.

Plan: docs/PLAN-vt100-terminal-core.md.

The seam

The ND-120 console UART is IO_UART_42 inside the board; ND120_TOP.v:890 brings .TXD(uartTx) out, and boards wire that to cpu_txd / RXD. The terminal attaches with two byte FIFOs:

  • TX: ideally tap the console byte before serialization, so baud and framing never reach the screen. Note the framing is not a constant: the console chip is a programmable SC2661_UART (IO_UART_42.v:179) and this repo's own console.ps1:17 says the OPCOM console is "7E1 in some configurations", board checks 8N1. The "7E2" in the MEGA65 plan is not confirmed anywhere. On the Nexys test rig we instead deserialize cpu_txd, which needs no shared-RTL change and proves the real framing.
  • RX: keyboard bytes are injected as if they had been received.

Identical on every board. Only the ends differ:

Nexys 4 DDR (Artix-7) MiSTer (Cyclone V) MEGA65 (Artix-7)
Keys in onboard USB host hands the FPGA plain PS/2 on F4/B2 - a ~50-line receiver, no USB stack hps_io gives ps2_key[10:0] - USB keyboard handled by Linux vendored mega65-core keyboard files (LGPLv3, own directory)
Video out 12-bit VGA connector straight off the pins, 40 MHz pixel clock free from the existing MMCM CLK_VIDEO + CE_PIXEL + VGA_* into the framework video_mixer; HDMI free VGA timing straight to the board's VDAC
Console switch build define ND120_CONSOLE_VGA; the serial console stays live in parallel an OSD status bit picks screen vs framework UART build-time or a config switch

Where the specification comes from

RetroTerm (its own repository) is Ronny's own MIT-licensed terminal emulator and it already implements VT100, VT52, ECMA-48, TDV and Tektronix. That makes it a clean, unrestricted specification source - unlike PDP2011's VT100, whose files are non-commercial-only and cannot be touched (details in docs/PLAN-vt100-terminal-core.md, "The licensing finding"). Verified paths, 27-AUG-2026:

  • src/RetroTerm.Core/Terminal/Emulators/ - TerminalEmulatorBase.cs (360 KB, the real behaviour), VT100Emulator.cs, Vt52Emulator.cs, Ecma48Emulator.cs, TDV/, Tektronix/
  • spec/TDV2200/Term and keyboard info/TDV-Keys.md - implementation guide (control sequences, screen model, the TDV-specific keys)
  • spec/TDV2200/OCR/*.md - the OCR'ed Tandberg/ND manuals. These are the primary source; TDV-Keys.md is a derived guide that hedges in places ("firmware-specific", "may vary"), so anything load-bearing gets checked against the manual.
  • spec/TDV2200/DOC TDV2200/ - keyboard documentation + a ROM disassembly
  • spec/TDV2200/Export2215Code/ - Ghidra export of TDV2215 firmware

What that source already tells us (read 27-AUG-2026)

Read the ordering warning first. Two of the facts below came from TDV-Keys.md, which is a derived implementation guide, and one of them was wrong. The RetroTerm session (retroterm-09, which owns that code) corrected it the same day. Treat the OCR text and the derived guides as claims until they agree with the ROM disassembly or with RetroTerm's actual behaviour - that repo has had three of its own documents turn out wrong, two of which would have hidden a real defect.

Corrected by retroterm-09, 27-AUG-2026 - the important one

Overtaken 30-AUG-2026: the paragraphs below are the case for a TDV layer, kept as history. The decision went the other way - the terminal is a plain VT100 and SINTRAN's line is set to terminal type 6, where VT100 IS the right grammar. The TDV facts stay true; they just describe type 53, which we no longer emulate.

TDV 2200 is NOT "ANSI with extras". It is a genuinely different grammar, and a plain VT100 pointed at SINTRAN will not do. Keys come back as TDV grid positions - e.g. ESC [ 4 6 _, note the underscore terminator, which is not in the ANSI final-byte set at all. RetroTerm resolves every key through a TDV key registry with deliberately no VT100 fallback: a key with no TDV equivalent returns null rather than degrading to something ANSI.

So the plan stands - build the VT100/ECMA-48 core first - but the TDV layer is not a small delta on top of it. Budget it as its own parser and its own key registry.

(What TDV-Keys.md ยง1 says - "conforms largely to ISO 6429/VT100 with proprietary enhancements" - is about the TDV 1200, and is a derived claim. Do not carry it over to the 2200.)

Where to read the keyboard truth (paths from retroterm-09)

Do not start from TerminalEmulatorBase.cs - 360 KB, and it is the ECMA-48 core, not the TDV story. For keys:

  • src\RetroTerm.Core\Terminal\Input\KeyboardMapper.cs - TDV2200KeyboardMapper, keyed by VK code. This is the one the factory actually builds.
  • src\RetroTerm.Core\Terminal\Emulators\TDV\TDVKeyboardMapper.cs - keyed by key name. A DIFFERENT class. Conflating the two has already cost time over there; do not repeat it.
  • the TDV2200KeyRegistry both resolve through.

From TDV-Keys.md - useful, but still claims

  • Screen model: 80x25 cells (corrected 27-AUG by retroterm-09 from RetroTerm's EmulatorFactory - the TDV-Keys.md figure of 80x24 is the TDV 1200); per-cell attributes are character code, bold/reverse/underline, a protection bit (SPA/EPA) and a G0-G3 character-set selector. Double height/width lines take two lines of memory.
  • Mode switching exists: CSI > 0 c answers with firmware ID 115 (the TDV signature); CSI ? 40 h/l selects TDV 2115 compatibility.
  • The unique keys are the hard part (Ronny, 27-AUG). HELP, DO, CANCEL, COMMAND, FIND, SELECT, REMOVE, INSERT HERE, PREV/NEXT, KEY 21-32, plus soft keys reprogrammable over DCS. The sequences listed in that guide are in ANSI ESC [ n ~ form, which the correction above says is not how a 2200 actually reports keys - so read them as "these functions exist", not as the wire format. Mapping them onto a PS/2 or a MEGA65 keyboard is a design problem either way - stretch goal, not the first build.

Still coming from retroterm-09

Asked 27-AUG-2026; it is mid-session with Ronny on a Sixel test and will send: the must-have / nice-to-have / never-seen split of what SINTRAN III actually exercises, the geometry and attribute answer, and the traps list. Folded in here when it lands.

Scope - where the stages landed (30-AUG-2026)

  • Stage A (TDV C0 glass TTY) - built 27-AUG, removed 30-AUG when the VT100 decision landed. Its history and the TDV C0 facts live on in docs/SPEC-tdv2200.md.
  • Stage B (VT100 proper) - BUILT, and it is the terminal now. See "The terminal is a VT100" above.
  • Stage C (TDV 2200) - not planned. If it ever returns it is a delta on the VT100 parser; terminal_ctrl.v's header says where the ND finals and private modes would hook in.
  • Open: eyes on the real screen (rendering confirmed good by Ronny 30-AUG) and the Norwegian layout positions at the real keyboard - the set-2 scancodes themselves are settled against published references.