Skip to content

console_uart_tx

Source: Verilog/Terminals/rtl/console_uart_tx.v

Where it sits (Nexys): nd120_nexys4ddr_top > console_uart_tx - instance path: CONSOLE_TX

Used in: emu (MiSTer), nd120_mega65_machine (MEGA65 R6, MEGA65 R3), nd120_nexys4ddr_top (Nexys)

Contains: no other modules.

Module hierarchy - All modules

console_uart_tx symbol

Schematic

Drawn from the Verilog: the yosys netlist of the Nexys 4 DDR build, instance CONSOLE_TX. 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).

console_uart_tx schematic

Parameters

Parameter Default
CLK_HZ 40_000_000
BAUD 115200
DATA_BITS 7
PARITY 1'b1
PARITY_ODD 1'b0 //! 0 = even parity

Ports

Direction Width Name Description
input 1 clk
input 1 rst_n (active low)
input [15:0] divisor_ovr
input 1 byte_valid one clock; ignored unless ready
input [7:0] byte_data
output 1 ready high when idle
output 1 txd the serial line, idle high

Verilog source

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

Show the Verilog of console_uart_tx (125 lines)
//============================================================================
//! Console UART transmitter - sends keyboard bytes into the machine's RX line
//!
//! Part of the board-independent terminal core (Verilog/Terminals/).
//!
//! The mirror of console_uart_rx: a key press becomes a serial character on
//! the ND-120's console RX pin, so the machine cannot tell the difference
//! between the local keyboard and the PC terminal.
//!
//! MERGING WITH THE PC'S LINE. Both sources idle HIGH, so two transmitters can
//! share one receive pin by ANDing them - whichever one is sending pulls the
//! line, the idle one contributes 1s. That is exactly the idiom the Nexys top
//! already uses on the way out (`uart_rxd_out = cpu_txd & s_errfa_txd &
//! s_wdiox_txd`). It garbles a character only if somebody types on the
//! keyboard at the same moment the PC sends one, which is a person's problem,
//! not a design flaw.
//!
//! Framing must match console_uart_rx and the machine - see the long note in
//! that file about the SC2661 being software programmable.
//!
//! Written 27-AUG-2026.
//============================================================================

`default_nettype none

module console_uart_tx #(
    parameter integer CLK_HZ    = 40_000_000,
    parameter integer BAUD      = 115200,
    parameter integer DATA_BITS = 7,      //! 7 or 8
    parameter         PARITY    = 1'b1,   //! 1 = send a parity bit
    parameter         PARITY_ODD = 1'b0   //! 0 = even parity, 1 = odd
) (
    input wire clk,
    input wire rst_n,

    //! CLOCKS PER BIT, AT RUN TIME. Zero means "use the CLK_HZ/BAUD parameters",
    //! which is what every board did until the Nexys gained a second video mode.
    //!
    //! WHY THIS EXISTS. The console shares one clock domain with the video, so
    //! switching the pixel clock between 800x600 (40 MHz) and 1920x1080 (148.5
    //! MHz) also changes what a bit time is worth. A compile-time divisor would
    //! be right in one mode and produce garbage in the other - and it would look
    //! like a terminal bug rather than a clock bug, which is the expensive kind.
    input wire [15:0] divisor_ovr,

    input  wire       byte_valid,  //! one clock; ignored unless ready
    input  wire [7:0] byte_data,
    output wire       ready,       //! high when idle

    output reg txd  //! the serial line, idle high
);

  localparam integer DIVISOR = CLK_HZ / BAUD;

  wire [15:0] s_divisor = (divisor_ovr != 16'd0) ? divisor_ovr : DIVISOR[15:0];
  //! start + data + optional parity + stop
  localparam integer FRAME_BITS = 1 + DATA_BITS + (PARITY ? 1 : 0) + 1;

  reg [15:0] s_count;
  reg [ 3:0] s_bit_index;
  reg [11:0] s_frame;   //! the whole frame, shifted out LSB first
  reg        s_busy;

  assign ready = !s_busy;

  //! Build the frame where it is used, inside the clocked block. An earlier
  //! version assembled it in a wide continuous assignment off the byte_data
  //! port; in simulation the frame came out holding the PREVIOUS byte while
  //! byte_data already held the new one, so every character went out one
  //! transmission late. Building it here removes the question entirely - the
  //! frame is made from the byte at the instant the byte is accepted.
  //!
  //! Frame is LSB-first: start(0), data, parity if enabled, stop(1). Unused
  //! high bits are 1 so the line stays idle-high after the stop bit.
  always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
      s_count      <= 16'd0;
      s_bit_index  <= 4'd0;
      s_frame      <= {12{1'b1}};
      s_busy       <= 1'b0;
      txd          <= 1'b1;
    end else begin
      if (!s_busy) begin
        txd <= 1'b1;
        if (byte_valid) begin
          if (PARITY) begin
            s_frame <= {{(12 - (DATA_BITS + 3)){1'b1}},          // idle padding
                        1'b1,                                     // stop
                        (PARITY_ODD ? ~(^byte_data[DATA_BITS-1:0])
                                    :  (^byte_data[DATA_BITS-1:0])),
                        byte_data[DATA_BITS-1:0],                 // data, LSB first
                        1'b0};                                    // start
          end else begin
            s_frame <= {{(12 - (DATA_BITS + 2)){1'b1}},
                        1'b1,
                        byte_data[DATA_BITS-1:0],
                        1'b0};
          end

          s_busy      <= 1'b1;
          s_count     <= 16'd0;
          s_bit_index <= 4'd0;
          txd         <= 1'b0;  // start bit goes out immediately
        end
      end else begin
        if (s_count == s_divisor - 16'd1) begin
          s_count <= 16'd0;
          if (s_bit_index == FRAME_BITS - 1) begin
            s_busy <= 1'b0;
            txd    <= 1'b1;
          end else begin
            s_bit_index <= s_bit_index + 4'd1;
            s_frame     <= {1'b1, s_frame[11:1]};
            txd         <= s_frame[1];
          end
        end else begin
          s_count <= s_count + 16'd1;
        end
      end
    end
  end

endmodule

`default_nettype wire