// // Very simple asynchronous tty. Not really a "UART" since it isn't // very "universal". It doesn't have to be. // // Currently only transmit is supported, 8-N-1, at a fixed speed. // The baud rate is tty_clk/((divisor+1)*16). // // The registers are as follows: // // 0 - WO - data register (will be RW) // 1 - RW - baud rate register (binary fraction of TTY_CLK/16 - 1) // 2 - RO - status register // 0 - transmitter idle (FIFO and shift register empty) // 1 - transmit FIFO 3/4 empty // 2 - transmit FIFO 1/2 empty // 3 - transmit FIFO 1/2 full (inverse of bit 2) // 4 - transmit FIFO 3/4 full // 3 - RW - interrupt enable register (status register mask) module tty ( input rst_n, input clk, input valid, input [3:0] wstrb, input [31:0] wdata, input [1:0] addr, output reg [31:0] rdata, output reg irq, output tty_txd ); `include "functions.sv" // For ModelSim // // Baud rate generator; produces a clock enable synchronous // with clk. This is based on a numerically controlled oscillator // (NCO); the baudrate is given as a binary fraction of the input // clock rate divided by the oversampling rate (16); for practical // reasons represented minus one LSB so 0x0.ffffff -> 1 // and 0 -> 0x0.000001. // // The term "divisor" here is probably misleading, since it is // actually a fixed-point *multiplier* which is <= 1. // parameter [31:0] BAUDRATE = 115200; parameter [31:0] TTY_CLK = 84000000; parameter NCO_BITS = 24; reg [NCO_BITS-1:0] divisor = round_div(BAUDRATE << NCO_BITS, TTY_CLK >> 4) - 1'b1; reg [NCO_BITS-1:0] nco_q; reg tty_clk_en; // tty clock tick (clock enable) always @(posedge clk) { tty_clk_en, nco_q } <= nco_q + divisor + 1'b1; // // Tx FIFO // reg tx_rdack; wire tx_wrreq; wire tx_rdempty; wire [7:0] tx_data; wire [8:0] tx_usedw; fifo txfifo ( .aclr ( ~rst_n ), .clock ( clk ), .data ( wdata ), .rdreq ( tx_rdack ), .sclr ( 1'b0 ), // Flush FIFO command .wrreq ( tx_wrreq ), .empty ( tx_rdempty ), .full ( ), .q ( tx_data ), .usedw ( tx_usedw ) ); // // Transmitter // reg [3:0] tx_phase; reg [3:0] tx_bits; reg [9:0] tx_sr = ~10'b0; // Shift register assign tty_txd = tx_sr[0]; always @(negedge rst_n or posedge clk) if (~rst_n) begin tx_phase <= 4'h0; tx_bits <= 4'd0; tx_sr <= ~10'b0; // Line idle tx_rdack <= 1'b0; end else begin tx_rdack <= 1'b0; if ( tty_clk_en ) begin tx_phase <= tx_phase + 1'b1; if (tx_phase == 4'hF) begin tx_sr[8:0] <= tx_sr[9:1]; tx_sr[9] <= 1'b1; // Stop bit/idle if (tx_bits == 4'd0) begin if ( ~tx_rdempty ) begin tx_sr[9:2] <= tx_data; tx_sr[1] <= 1'b0; // Start bit // 10 = start bit + data + stop bit tx_bits <= 4'd10; tx_rdack <= 1'b1; // Remove from FIFO end end else begin tx_bits <= tx_bits - 1'b1; end // else: !if(tx_bits == 4'd0) end // if (tx_phase == 4'hF) end // if ( tty_clk_en ) end // else: !if(~rst_n) // // CPU interface // // Data (FIFO) register; normally addressed as byte // Protect against long pulses (edge detect) reg old_wstrb; always @(posedge clk) old_wstrb <= wstrb[0]; assign tx_wrreq = valid & wstrb[0] & ~old_wstrb & (addr == 2'b00); // Status register definition localparam status_bits = 5; wire [status_bits-1:0] status; assign status[0] = tx_rdempty & (tx_bits == 4'd0); assign status[1] = tx_usedw[8:7] == 2'b00; assign status[2] = ~tx_usedw[8]; assign status[3] = tx_usedw[8]; assign status[4] = tx_usedw[8:7] == 2'b11; reg [status_bits-1:0] irq_en; // // Control register writes. // Only full word writes are supported. // always @(negedge rst_n or posedge clk) if (~rst_n) begin irq_en <= 5'b0; end else if (valid & (&wstrb)) case (addr) 2'b01: divisor <= wdata[NCO_BITS-1:0]; 2'b11: irq_en <= wdata[status_bits-1:0]; endcase // case (addr) // Read MUX always @(*) begin rdata = 32'b0; case (addr) 2'b01: rdata[NCO_BITS-1:0] = divisor; 2'b10: rdata[status_bits-1:0] = status; 2'b11: rdata[status_bits-1:0] = irq_en; default: rdata = 32'b0; endcase // case (addr) end // Interrupt always @(negedge rst_n or posedge clk) if (~rst_n) irq <= 1'b0; else irq <= |(status & irq_en); endmodule // tty