///////////////////////////////////////////
// uart.sv
//
// Written: David_Harris@hmc.edu 21 January 2021
// Modified: 
//
// Purpose: Universial Asynchronous Receiver/ Transmitter with FIFOs
//          Emulates interface of Texas Instruments PC16550D
//          https://media.digikey.com/pdf/Data%20Sheets/Texas%20Instruments%20PDFs/PC16550D.pdf
//          Compatible with UART in Imperas Virtio model
//
//  Compatible with most of PC16550D with the following known exceptions:
//   Generates 2 rather than 1.5 stop bits when 5-bit word length is slected and LCR[2] = 1
//   Timeout not yet implemented
// 
// Documentation: RISC-V System on Chip Design
//
// A component of the CORE-V-WALLY configurable RISC-V project.
// https://github.com/openhwgroup/cvw
// 
// Copyright (C) 2021-23 Harvey Mudd College & Oklahoma State University
//
// SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1
//
// Licensed under the Solderpad Hardware License v 2.1 (the “License”); you may not use this file 
// except in compliance with the License, or, at your option, the Apache License version 2.0. You 
// may obtain a copy of the License at
//
// https://solderpad.org/licenses/SHL-2.1/
//
// Unless required by applicable law or agreed to in writing, any work distributed under the 
// License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 
// either express or implied. See the License for the specific language governing permissions 
// and limitations under the License.
////////////////////////////////////////////////////////////////////////////////////////////////

/* verilator lint_off UNOPTFLAT */

module uartPC16550D #(parameter UART_PRESCALE) (
  // Processor Interface
  input  logic       PCLK, PRESETn,                  // UART clock and active low reset
  input  logic [2:0] A,                              // address input (8 registers)
  input  logic [7:0] Din,                            // 8-bit WriteData
  output logic [7:0] Dout,                           // 8-bit ReadData
  input  logic       MEMRb, MEMWb,                   // Active low memory read/write
  output logic       INTR, TXRDYb, RXRDYb,           // interrupt and ready lines
  // Clocks
  output logic       BAUDOUTb,                       // active low baud clock
  input logic        RCLK,                           // usually BAUDOUTb tied to RCLK externally
  // E1A Driver
  input  logic       SIN, DSRb, DCDb, CTSb, RIb,     // UART external serial and flow-control inputs
  output logic       SOUT, RTSb, DTRb, OUT1b, OUT2b  // UART external serial and flow-control outputs
);

  // register map
  localparam UART_DLL_RBR = 3'b000;
  localparam UART_DLM_IER = 3'b001;
  localparam UART_IIR = 3'b010;
  localparam UART_LCR = 3'b011;
  localparam UART_MCR = 3'b100;
  localparam UART_LSR = 3'b101;
  localparam UART_MSR = 3'b110;
  localparam UART_SCR = 3'b111;
  
  // transmit and receive states 
  typedef enum logic [1:0] {UART_IDLE, UART_ACTIVE, UART_DONE, UART_BREAK} statetype;

  // Registers
  logic [10:0] RBR;
  logic [7:0]  FCR, LCR, LSR, SCR, DLL, DLM;
  logic [3:0]  IER, MSR;
  logic [4:0]  MCR;

  // Syncrhonized and delayed UART signals
  logic        SINd, DSRbd, DCDbd, CTSbd, RIbd;
  logic        SINsync, DSRbsync, DCDbsync, CTSbsync, RIbsync;
  logic        DSRb2, DCDb2, CTSb2, RIb2;
  logic        SOUTbit;

  // Control signals
  logic        loop; // loopback mode
  logic        DLAB; // Divisor Latch Access Bit (LCR bit 7)

  // Baud and rx/tx timing
  logic                         baudpulse, txbaudpulse, rxbaudpulse; // high one system clk cycle each baud/16 period
  logic [16+UART_PRESCALE-1:0]  baudcount;
  logic [3:0]                   rxoversampledcnt, txoversampledcnt;  // count oversampled-by-16
  logic [3:0]                   rxbitsreceived, txbitssent;
  statetype rxstate, txstate;

  // shift registrs and FIFOs
  logic [9:0]                   rxshiftreg;
  logic [10:0]                  rxfifo[15:0];
  logic [7:0]                   txfifo[15:0];
  logic [4:0]                   rxfifotailunwrapped;
  logic [3:0]                   rxfifohead, rxfifotail, txfifohead, txfifotail, rxfifotriggerlevel;
  logic [3:0]                   rxfifoentries;
  logic [3:0]                   rxbitsexpected, txbitsexpected;

  // receive data
  logic [10:0]                  RXBR;
  logic [9:0]                   rxtimeoutcnt;
  logic                         rxcentered;
  logic                         rxparity, rxparitybit, rxstopbit;
  logic                         rxparityerr, rxoverrunerr, rxframingerr, rxbreak, rxfifohaserr;
  logic                         rxdataready;
  logic                         rxfifoempty, rxfifotriggered, rxfifotimeout;
  logic                         rxfifodmaready;
  logic [8:0]                   rxdata9;
  logic [7:0]                   rxdata;
  logic [15:0]                  RXerrbit, rxfullbit;
  logic [31:0]                  rxfullbitunwrapped;

  // transmit data
  logic [7:0]                   TXHR, nexttxdata;
  logic [11:0]                  txdata, txsr;
  logic                         txnextbit, txhrfull, txsrfull;
  logic                         txparity;
  logic                         txfifoempty, txfifofull, txfifodmaready;

  // control signals
  logic                         fifoenabled, fifodmamodesel, evenparitysel;

  // interrupts
  logic                         RXerr, RXerrIP, squashRXerrIP, prevSquashRXerrIP, setSquashRXerrIP, resetSquashRXerrIP;
  logic                         THRE, THRE_IP, squashTHRE_IP, prevSquashTHRE_IP, setSquashTHRE_IP, resetSquashTHRE_IP;
  logic                         rxdataavailintr, modemstatusintr, intrpending;
  logic [2:0]                   intrID;

  logic                         baudpulseComb;
  logic                         HeadPointerLastMove;

  ///////////////////////////////////////////
  // Input synchronization: 2-stage synchronizer
  ///////////////////////////////////////////
  
  always_ff @(posedge PCLK) begin
    {SINd, DSRbd, DCDbd, CTSbd, RIbd} <= {SIN, DSRb, DCDb, CTSb, RIb};
    {SINsync, DSRbsync, DCDbsync, CTSbsync, RIbsync} <= loop ? {SOUTbit, ~MCR[0], ~MCR[3], ~MCR[1], ~MCR[2]} : 
                            {SINd, DSRbd, DCDbd, CTSbd, RIbd}; // syncrhonized signals, handle loopback testing
    {DSRb2, DCDb2, CTSb2, RIb2} <= {DSRbsync, DCDbsync, CTSbsync, RIbsync}; // for detecting state changes
  end

  ///////////////////////////////////////////
  // Register interface (Table 1, note some are read only and some write only)
  ///////////////////////////////////////////
  
  always_ff @(posedge PCLK) 
    if (~PRESETn) begin // Table 3 Reset Configuration
      IER <= 4'b0;
      FCR <= 8'b0;
      LCR <= 8'b11; // PC16550D datasheet resets to 0, but all modern systems will use 8-bit data.  Wally resets to 3 for 8-bit data.
      MCR <= 5'b0;
      LSR <= 8'b0110_0000;
      MSR <= 4'b0;
      DLL <= 8'd1; // this cannot be zero with DLM also zero.
      DLM <= 8'b0;
      SCR <= 8'b0; // not strictly necessary to reset
    end else begin
      if (~MEMWb) begin
        /* verilator lint_off CASEINCOMPLETE */
        case (A)
          UART_DLL_RBR: if (DLAB) DLL <= Din; // else TXHR <= Din; // TX handled in TX register/FIFO section
          UART_DLM_IER: if (DLAB) DLM <= Din; else IER <= Din[3:0];
          UART_IIR: FCR <= {Din[7:6], 2'b0, Din[3], 2'b0, Din[0]}; // Write only FIFO Control Register; 4:5 reserved and 2:1 self-clearing
          UART_LCR: LCR <= Din;
          UART_MCR: MCR <= Din[4:0];
          UART_SCR: SCR <= Din;
        endcase
        /* verilator lint_on CASEINCOMPLETE */
      end

      // Line Status Register (8.6.3)
      // Ben 6/9/21 I don't like how this is a register. A lot of the individual bits have clocked components, so this just adds unecessary delay.
      if (~MEMWb & (A == UART_LSR))
        LSR[6:1] <= Din[6:1]; // recommended only for test, see 8.6.3
      else begin
        LSR[0]   <= rxdataready; // Data ready
        LSR[1]   <= (LSR[1] | RXBR[10]) & ~squashRXerrIP; // overrun error
        LSR[2]   <= (LSR[2] | RXBR[9])  & ~squashRXerrIP; // parity error
        LSR[3]   <= (LSR[3] | RXBR[8])  & ~squashRXerrIP; // framing error
        LSR[4]   <= (LSR[4] | rxbreak)  & ~squashRXerrIP; // break indicator
        LSR[5]   <= THRE; // THRE
        LSR[6]   <= ~txsrfull & THRE; //  TEMT
        if (rxfifohaserr) LSR[7] <= 1'b1; // any bits in FIFO have error
      end

      // Modem Status Register (8.6.8)
      if (~MEMWb & (A == UART_MSR))
        MSR <= Din[3:0];
      else if (~MEMRb & (A == UART_MSR)) 
        MSR <= 4'b0; // Reading MSR clears the flags in MSR bits 3:0
      else begin
        MSR[0] <= MSR[0] | CTSb2 ^ CTSbsync; // Delta Clear to Send
        MSR[1] <= MSR[1] | DSRb2 ^ DSRbsync; // Delta Data Set Ready
        MSR[2] <= MSR[2] | (~RIb2 & RIbsync); // Trailing Edge of Ring Indicator
        MSR[3] <= MSR[3] | DCDb2 ^ DCDbsync; // Delta Data Carrier Detect
      end
    end
  
  always_comb
    if (~MEMRb)
      case (A)
        UART_DLL_RBR: if (DLAB) Dout = DLL; else Dout = RBR[7:0];
        UART_DLM_IER: if (DLAB) Dout = DLM; else Dout = {4'b0, IER[3:0]};
        UART_IIR: Dout = {{2{fifoenabled}}, 2'b00, intrID[2:0], ~intrpending}; // Read only Interupt Ident Register
        UART_LCR: Dout = LCR;
        UART_MCR: Dout = {3'b000, MCR};
        UART_LSR: Dout = LSR;
        UART_MSR: Dout = {~DCDbsync, ~RIbsync, ~DSRbsync, ~CTSbsync, MSR[3:0]};
        UART_SCR: Dout = SCR;      
      endcase
    else Dout = 8'b0;

  ///////////////////////////////////////////
  // Baud rate generator
  // consider switching to same fixed-frequency reference clock used for TIME register
  // prescale by factor of 2^UART_PRESCALE to allow for high-frequency reference clock
  // Unlike PC16550D, this unit is hardwired with same rx and tx baud clock
  // For example, with PCLK = 320 MHz, UART_PRESCALE = 5, DLM = 0, DLL = 65, 
  // 320 MHz system clock is divided by 65 x 2^5.  The UART clock 16x oversamples
  // the data, so the baud rate is 320x10^6 / (65 x 2^5 x 16) = 9615 Hz, which is
  // close enough to 9600 baud to stay synchronized over the duration of one character.
  ///////////////////////////////////////////
  always_ff @(posedge PCLK) 
    if (~PRESETn) begin
      baudcount <= 1;
      baudpulse <= 1'b0;
    end else if (~MEMWb & DLAB & (A == 3'b0 | A == 3'b1)) begin
      baudcount <= 1;
    end else begin
      // the baudpulse is too long by 2 clock cycles.
      // This is cause baudpulse is registered adding 1 cycle and
      // baudcount is reset when baudcount equals the threshold {DLM, DLL, UART_PRESCALE}
      // rather than 1 less than that value.  Alternatively the reset value could be 1 rather 
      // than 0.
      baudpulse <= baudpulseComb;
      baudcount <= baudpulseComb ? 1 :  baudcount +1;
    end

  assign baudpulseComb = (baudcount == {DLM, DLL, {(UART_PRESCALE){1'b0}}});
  
  assign txbaudpulse = baudpulse;
  assign BAUDOUTb = ~baudpulse;
  assign rxbaudpulse = ~RCLK; // usually BAUDOUTb tied to RCLK externally

  ///////////////////////////////////////////
  // receive timing and control
  ///////////////////////////////////////////
  
  always_ff @(posedge PCLK)
    if (~PRESETn) begin
      rxoversampledcnt   <= '0;
      rxstate            <= UART_IDLE;
      rxbitsreceived     <= '0;
      rxtimeoutcnt       <= '0;
    end else begin
      if (rxstate == UART_IDLE & ~SINsync) begin // got start bit
        rxstate          <= UART_ACTIVE;
        rxoversampledcnt <= '0;
        rxbitsreceived   <= '0;
        if (~rxfifotimeout) rxtimeoutcnt <= '0; // reset timeout when new character is arriving. Jacob Pease: Only if the timeout was not already reached. p.16 PC16550D.pdf
      end else if (rxbaudpulse & (rxstate == UART_ACTIVE)) begin
        rxoversampledcnt <= rxoversampledcnt + 4'b1;  // 16x oversampled counter
        if (rxcentered) rxbitsreceived <= rxbitsreceived + 1;
        if (rxbitsreceived == rxbitsexpected) rxstate <= UART_DONE; // pulse rxdone for a cycle
      end else if (rxstate == UART_DONE | rxstate == UART_BREAK) begin
        if (rxbreak & ~SINsync) rxstate <= UART_BREAK;
        else rxstate <= UART_IDLE;
      end
      // timeout counting
      if (~MEMRb & A == 3'b000 & ~DLAB) rxtimeoutcnt <= '0; // reset timeout on read
      else if (fifoenabled & ~rxfifoempty & rxbaudpulse & ~rxfifotimeout) rxtimeoutcnt <= rxtimeoutcnt+1; // may not be right
    end

  assign rxcentered = rxbaudpulse & (rxoversampledcnt == 4'b1000);     // implies rxstate = UART_ACTIVE      
 
  assign rxbitsexpected = 4'd1 + (4'd5 + {2'b00, LCR[1:0]}) + {3'b000, LCR[3]} + 4'd1; // start bit + data bits + (parity bit) + stop bit 
  
  ///////////////////////////////////////////
  // receive shift register, buffer register, FIFO
  ///////////////////////////////////////////
  
  always_ff @(posedge PCLK)
    if (~PRESETn) rxshiftreg <= 10'b0000000001; // initialize so that there is a valid stop bit
    else if (rxcentered) rxshiftreg <= {rxshiftreg[8:0], SINsync}; // capture bit
  assign rxparitybit = rxshiftreg[1]; // parity, if it exists, in bit 1 when all done
  assign rxstopbit = rxshiftreg[0];
  always_comb
    case(LCR[1:0]) // check how many bits used.  Grab all bits including possible parity
      2'b00: rxdata9 = {3'b0, rxshiftreg[1], rxshiftreg[2], rxshiftreg[3], rxshiftreg[4], rxshiftreg[5], rxshiftreg[6]}; // 5-bit character
      2'b01: rxdata9 = {2'b0, rxshiftreg[1], rxshiftreg[2], rxshiftreg[3], rxshiftreg[4], rxshiftreg[5], rxshiftreg[6], rxshiftreg[7]}; // 6-bit 
      2'b10: rxdata9 = {1'b0, rxshiftreg[1], rxshiftreg[2], rxshiftreg[3], rxshiftreg[4], rxshiftreg[5], rxshiftreg[6], rxshiftreg[7], rxshiftreg[8]}; // 7-bit
      2'b11: rxdata9 = {      rxshiftreg[1], rxshiftreg[2], rxshiftreg[3], rxshiftreg[4], rxshiftreg[5], rxshiftreg[6], rxshiftreg[7], rxshiftreg[8], rxshiftreg[9]}; // 8-bit
    endcase
  assign rxdata = LCR[3] ? rxdata9[7:0] : rxdata9[8:1]; // discard parity bit

  // ERROR CONDITIONS
  assign rxparity     = ^rxdata;
  assign rxparityerr  = (rxparity ^ rxparitybit ^ ~evenparitysel) & LCR[3]; // Check even/odd parity
  assign rxoverrunerr = fifoenabled ? (rxfifoentries == 15) : rxdataready; // overrun if FIFO or receive buffer register full 
  assign rxframingerr = ~rxstopbit; // framing error if no stop bit
  assign rxbreak      = rxframingerr & (rxdata9 == 9'b0); // break when 0 for start + data + parity + stop time

  // receive FIFO and register
  always_ff @(posedge PCLK)
    if (~PRESETn) begin
      rxfifohead <= '0; rxfifotail <= '0; rxdataready <= 1'b0; RXBR <= '0;
    end else begin
      if (~MEMWb & (A == 3'b010) & Din[1]) begin
        rxfifohead <= '0; rxfifotail <= '0; rxdataready <= 1'b0;
      end else if (rxstate == UART_DONE) begin
        RXBR <= {rxoverrunerr, rxparityerr, rxframingerr, rxdata}; // load recevive buffer register
//        if (rxoverrunerr) $warning("UART RX Overrun Err\n");
//        if (rxparityerr)  $warning("UART RX Parity Err\n");
//        if (rxframingerr) $warning("UART RX Framing Err\n");
        if (fifoenabled) begin
          rxfifo[rxfifohead] <= {rxoverrunerr, rxparityerr, rxframingerr, rxdata};
          rxfifohead <= rxfifohead + 1'b1;
        end
        rxdataready <= 1'b1;
      end else if (~MEMRb & A == 3'b000 & ~DLAB) begin // reading RBR updates ready / pops fifo 
        if (fifoenabled) begin
          if (~rxfifoempty) rxfifotail <= rxfifotail + 1;
          // if (rxfifoempty) rxdataready <= 1'b0;
          if (rxfifoentries == 1) rxdataready <= 1'b0; // When reading the last entry, data ready becomes zero
        end else begin
          rxdataready <= 1'b0;
          RXBR <= {1'b0, RXBR[9:0]}; // Ben 31 March 2022: I added this so that rxoverrunerr permanently goes away upon reading RBR (when not in FIFO mode)
        end
      end else if (~MEMWb & A == 3'b010)  // writes to FIFO Control Register
        if (Din[1] | ~Din[0]) begin // rx FIFO reset or FIFO disable clears FIFO contents
          rxfifohead <= '0; rxfifotail <= '0;
        end
    end

  assign rxfifoempty = (rxfifohead == rxfifotail);
  /* verilator lint_off WIDTH */
  assign rxfifoentries = (rxfifohead >= rxfifotail) ? (rxfifohead-rxfifotail) : 
                         (rxfifohead + 16 - rxfifotail);
  /* verilator lint_on WIDTH */
  assign rxfifotriggered = rxfifoentries >= rxfifotriggerlevel;
  assign rxfifotimeout = rxtimeoutcnt == {rxbitsexpected, 6'b0}; // time out after 4 character periods; probably not right yet
  //assign rxfifotimeout = 0; // disabled pending fix

  // detect any errors in rx fifo
  // although rxfullbit looks like a combinational loop, in one bit rxfifotail == i and breaks the loop
  // tail is normally higher than head, but might wrap around.  unwrapped variable adds 16 to eliminate wrapping
  assign rxfifotailunwrapped = rxfifotail < rxfifohead ? {1'b1, rxfifotail} : {1'b0, rxfifotail};
  genvar i;
  for (i=0; i<32; i++) begin:rxfull
    if (i == 0) assign rxfullbitunwrapped[i] = (rxfifohead==0) & (rxfifotail != 0);
    else        assign rxfullbitunwrapped[i] = ({1'b0,rxfifohead}==i | rxfullbitunwrapped[i-1]) & (rxfifotailunwrapped != i);
  end
  for (i=0; i<16; i++) begin:rx
    assign RXerrbit[i]  = |rxfifo[i][10:8]; // are any of the error conditions set?
    assign rxfullbit[i] = rxfullbitunwrapped[i] | rxfullbitunwrapped[i+16];
  /*      if (i > 0)
      assign rxfullbit[i] = ((rxfifohead==i) | rxfullbit[i-1]) & (rxfifotail != i);
      else
      assign rxfullbit[0] = ((rxfifohead==i) | rxfullbit[15]) & (rxfifotail != i);*/
  end
  assign rxfifohaserr   = |(RXerrbit & rxfullbit);

  // receive buffer register and ready bit
  always_ff @(posedge PCLK) // track rxrdy for DMA mode (FCR3 = FCR0 = 1)
    if (~PRESETn) rxfifodmaready <= 1'b0;
    else if (rxfifotriggered | rxfifotimeout) rxfifodmaready <= 1'b1;
    else if (rxfifoempty) rxfifodmaready <= 1'b0;

  always_comb
    if (fifoenabled) begin
      if (rxfifoempty) RBR = 11'b0;
      else             RBR = rxfifo[rxfifotail];
      if (fifodmamodesel) RXRDYb = ~rxfifodmaready;
      else                RXRDYb = rxfifoempty;
    end else begin
      RBR    = RXBR;
      RXRDYb = ~rxdataready;
    end

  ///////////////////////////////////////////
  // transmit timing and control
  ///////////////////////////////////////////
  
  always_ff @(posedge PCLK)
    if (~PRESETn) begin
      txoversampledcnt <= '0;
      txstate          <= UART_IDLE;
      txbitssent       <= '0;
    end else if ((txstate == UART_IDLE) & txsrfull) begin // start transmitting
      txstate          <= UART_ACTIVE;
      txoversampledcnt <= 4'b1;
      txbitssent       <= '0;
    end else if (txbaudpulse & (txstate == UART_ACTIVE)) begin
      txoversampledcnt <= txoversampledcnt + 1'b1; 
      if (txnextbit) begin // transmit at end of phase
        txbitssent <= txbitssent + 1'b1;
        if (txbitssent == txbitsexpected) txstate <= UART_DONE;
      end
    end else if (txstate == UART_DONE) begin
      txstate <= UART_IDLE;
    end

  assign txbitsexpected = 4'd1 + (4'd5 + {2'b00, LCR[1:0]}) + {3'b000, LCR[3]} + 4'd1 + {3'b000, LCR[2]} - 4'd1; // start bit + data bits + (parity bit) + stop bit(s) - 1
  assign txnextbit = txbaudpulse & (txoversampledcnt == 4'b0000);  // implies txstate = UART_ACTIVE

  ///////////////////////////////////////////
  // transmit holding register, shift register, FIFO
  ///////////////////////////////////////////
  
  always_comb begin // compute value for parity and tx holding register
    nexttxdata = fifoenabled ? txfifo[txfifotail] : TXHR; // pick from FIFO or holding register
    case (LCR[1:0]) // compute parity from appropriate number of bits
      2'b00: txparity = ^nexttxdata[4:0] ^ ~evenparitysel; 
      2'b01: txparity = ^nexttxdata[5:0] ^ ~evenparitysel; 
      2'b10: txparity = ^nexttxdata[6:0] ^ ~evenparitysel; 
      2'b11: txparity = ^nexttxdata[7:0] ^ ~evenparitysel; 
    endcase
    case({LCR[3], LCR[1:0]}) // parity, data bits
      // load up start bit (0), 5-8 data bits, 0-1 parity bits, 2 stop bits (only one sometimes used), padding
      3'b000: txdata = {1'b0, nexttxdata[0], nexttxdata[1], nexttxdata[2], nexttxdata[3], nexttxdata[4], 6'b111111};                                                       // 5 data, no parity
      3'b001: txdata = {1'b0, nexttxdata[0], nexttxdata[1], nexttxdata[2], nexttxdata[3], nexttxdata[4], nexttxdata[5], 5'b11111};                                         // 6 data, no parity
      3'b010: txdata = {1'b0, nexttxdata[0], nexttxdata[1], nexttxdata[2], nexttxdata[3], nexttxdata[4], nexttxdata[5], nexttxdata[6], 4'b1111};                           // 7 data, no parity
      3'b011: txdata = {1'b0, nexttxdata[0], nexttxdata[1], nexttxdata[2], nexttxdata[3], nexttxdata[4], nexttxdata[5], nexttxdata[6], nexttxdata[7], 3'b111};             // 8 data, no parity
      3'b100: txdata = {1'b0, nexttxdata[0], nexttxdata[1], nexttxdata[2], nexttxdata[3], nexttxdata[4], txparity, 5'b11111};                                              // 5 data, parity
      3'b101: txdata = {1'b0, nexttxdata[0], nexttxdata[1], nexttxdata[2], nexttxdata[3], nexttxdata[4], nexttxdata[5], txparity, 4'b1111};                                // 6 data, parity
      3'b110: txdata = {1'b0, nexttxdata[0], nexttxdata[1], nexttxdata[2], nexttxdata[3], nexttxdata[4], nexttxdata[5], nexttxdata[6], txparity, 3'b111};                  // 7 data, parity
      3'b111: txdata = {1'b0, nexttxdata[0], nexttxdata[1], nexttxdata[2], nexttxdata[3], nexttxdata[4], nexttxdata[5], nexttxdata[6], nexttxdata[7], txparity, 2'b11};    // 8 data, parity
    endcase
  end
  
  // registers & FIFO
  always_ff @(posedge PCLK)
    if (~PRESETn) begin
      txfifohead <= '0; txfifotail <= '0; txhrfull <= 1'b0; txsrfull <= 1'b0; TXHR <= '0; txsr <= 12'hfff;
    end else if (~MEMWb & (A == 3'b010) & Din[2]) begin
      txfifohead <= '0; txfifotail <= '0;
    end else begin
      if (~MEMWb & A == 3'b000 & ~DLAB) begin // writing transmit holding register or fifo
        if (fifoenabled) begin
          txfifo[txfifohead] <= Din;
          txfifohead         <= txfifohead + 4'b1;          
        end else begin 
          TXHR     <= Din;
          txhrfull <= 1'b1;
        end
        $write("%c",Din); // for testbench
      end
      if (txstate == UART_IDLE) begin // move data into tx shift register if available
        if (fifoenabled) begin 
          if (~txfifoempty & ~txsrfull) begin
            txsr       <= txdata;
            txfifotail <= txfifotail+1;
            txsrfull   <= 1'b1;
          end
        end else if (txhrfull) begin
          txsr     <= txdata;
          txhrfull <= 1'b0;
          txsrfull <= 1'b1;
        end
      end else if (txstate == UART_DONE) txsrfull <= 1'b0; // done transmitting shift register
      else if (txstate == UART_ACTIVE & txnextbit) txsr <= {txsr[10:0], 1'b1}; // shift txhr
      if (!MEMWb & A == 3'b010) // writes to FIFO control register
        if (Din[2] | ~Din[0]) begin // tx FIFO reste or FIFO disable clears FIFO contents
          txfifohead <= '0; txfifotail <= '0;
        end
    end

  always_ff @(posedge PCLK) begin
  // special condition to check if the fifo is empty or full.  Because the head
  // pointer indicates where the next write goes and not the location of the
  // current head, the head and tail pointer being equal imply two different
  // things.  First it could mean the fifo is empty and second it could mean
  // the fifo is full.  To differenciate we need to know which pointer moved
  // to cause them to be equal.  If the head pointer moved then it is full.
  // If the tail pointer moved then it is empty.  it resets to empty so
  // if reset with the tail pointer indicating the last update.
  if(~PRESETn) 
    HeadPointerLastMove <= 1'b0;
  else if(fifoenabled & ~MEMWb & A == 3'b000 & ~DLAB)
    HeadPointerLastMove <= 1'b1;
  else if(fifoenabled & ~txfifoempty & ~txsrfull & txstate == UART_IDLE)
    HeadPointerLastMove <= 1'b0;
  end

  assign txfifoempty = (txfifohead == txfifotail) & ~HeadPointerLastMove;
  assign txfifofull  = (txfifohead == txfifotail) & HeadPointerLastMove;

  // transmit buffer ready bit
  always_ff @(posedge PCLK) // track txrdy for DMA mode (FCR3 = FCR0 = 1)
    if (~PRESETn)         txfifodmaready <= 1'b0;
    else if (txfifoempty) txfifodmaready <= 1'b1;
    else if (txfifofull)  txfifodmaready <= 1'b0;

  always_comb
    if (fifoenabled & fifodmamodesel) TXRDYb = ~txfifodmaready;
    else TXRDYb  = ~THRE;

  // Transmitter pin 
  assign SOUTbit = txsr[11]; // transmit most significant bit
  assign SOUT    = loop ? 1 : (LCR[6] ? '0 : SOUTbit); // tied to 1 during loopback or 0 during break 

  ///////////////////////////////////////////
  // interrupts
  ///////////////////////////////////////////
  
  assign RXerr = |LSR[4:1]; // LS interrupt if any of the flags are true
  assign RXerrIP = RXerr & ~squashRXerrIP; // intr squashed upon reading LSR
  assign rxdataavailintr = fifoenabled ? rxfifotriggered : rxdataready; 
  assign THRE = fifoenabled ? txfifoempty : ~txhrfull;
  assign THRE_IP = THRE & ~squashTHRE_IP; // THRE_IP squashed upon reading IIR
  assign modemstatusintr = |MSR[3:0]; // set interrupt when modem pins change
  
  // IIR: interrupt priority (Table 5)
  // set intrID based on highest priority pending interrupt source; otherwise, no interrupt is pending
  always_comb begin
    intrpending = 1'b1;
    if      (RXerrIP & IER[2])                     intrID = 3'b011;
    else if (rxdataavailintr & IER[0])             intrID = 3'b010;
    else if (rxfifotimeout & fifoenabled & IER[0]) intrID = 3'b110;
    else if (THRE_IP & IER[1])                     intrID = 3'b001;
    else if (modemstatusintr & IER[3])             intrID = 3'b000;
    else begin
      intrID = 3'b000;
      intrpending = 1'b0;
    end
  end
  always_ff @(posedge PCLK) INTR <= intrpending; // prevent glitches on interrupt pin

  // Side effect of reading LSR is lowering overrun, parity, framing, break intr's
  assign setSquashRXerrIP = ~MEMRb & (A==3'b101);
  assign resetSquashRXerrIP = (rxstate == UART_DONE);
  assign squashRXerrIP = (prevSquashRXerrIP | setSquashRXerrIP) & ~resetSquashRXerrIP;
  flopr #(1) squashRXerrIPreg(PCLK, ~PRESETn, squashRXerrIP, prevSquashRXerrIP);
  // Side effect of reading IIR is lowering THRE_IP if most significant intr
  assign setSquashTHRE_IP = ~MEMRb & (A==3'b010) & (intrID==3'h1); // there's a 1-cycle delay on set squash so that THRE_IP doesn't change during the process of reading IIR (otherwise combinational loop)
  assign resetSquashTHRE_IP = ~THRE;
  assign squashTHRE_IP = prevSquashTHRE_IP & ~resetSquashTHRE_IP;
  flopr #(1) squashTHRE_IPreg(PCLK, ~PRESETn, squashTHRE_IP | setSquashTHRE_IP, prevSquashTHRE_IP);

  ///////////////////////////////////////////
  // modem control logic
  ///////////////////////////////////////////

  assign loop  = MCR[4]; 
  assign DTRb  = ~MCR[0] | loop; // disable modem signals in loopback mode
  assign RTSb  = ~MCR[1] | loop;
  assign OUT1b = ~MCR[2] | loop;
  assign OUT2b = ~MCR[3] | loop;

  assign DLAB = LCR[7];
  assign evenparitysel  = LCR[4];
  assign fifoenabled    = FCR[0];
  assign fifodmamodesel = FCR[3];
  always_comb
    case (FCR[7:6]) 
      2'b00: rxfifotriggerlevel = 4'd1;
      2'b01: rxfifotriggerlevel = 4'd4;
      2'b10: rxfifotriggerlevel = 4'd8;
      2'b11: rxfifotriggerlevel = 4'd14;
    endcase

endmodule

/* verilator lint_on UNOPTFLAT */
