// Copyright (c) 2016-2020 Bluespec, Inc. All Rights Reserved // //- // RVFI_DII + CHERI modifications: // Copyright (c) 2020 Alexandre Joannou // Copyright (c) 2020 Jonathan Woodruff // All rights reserved. // // This software was developed by SRI International and the University of // Cambridge Computer Laboratory (Department of Computer Science and // Technology) under DARPA contract HR0011-18-C-0016 ("ECATS"), as part of the // DARPA SSITH research programme. // // This work was supported by NCSC programme grant 4212611/RFA 15971 ("SafeBet"). //- package UART_Model; // ================================================================ // This package implements a slave IP, a UART model. // // This is a basic (and somewhat incomplete) model of a classic 16550 // UART, enough to do basic character reads and writes, interrupts, // etc. Just sends/receives the chars into Get/Put interfaces, // leaving it to external logic to manage actual physical // tranmit/receive. In particular, this module does nothing about // clock speed, baud rates, etc. // // Bus interface width: This slave IP can be attached to fabrics with // 32b- or 64b-wide data channels. The type parameter 'Wd_Data' in // Fabric_Defs.bsv specifies this. // // Address stride: the 16550 UART's registers are just 1-byte wide. // As a slave IP in a system, this IP places them at aligned addresses // with a gap of 4 or 8 bytes. This is controlled by the Integer // parameter 'address_stride' in this file. // Some of the 'truncate()'s and 'zeroExtend()'s below are no-ops but // necessary to satisfy type-checking, to manage these width // variations. // ================================================================ export UART_IFC (..), mkUART; // ================================================================ // BSV library imports import Vector :: *; import FIFOF :: *; import GetPut :: *; import ClientServer :: *; import ConfigReg :: *; // ---------------- // BSV additional libs import Cur_Cycle :: *; import GetPut_Aux :: *; import Semi_FIFOF :: *; import AXI4 :: *; import SourceSink :: *; // ================================================================ // Project imports import Fabric_Defs :: *; import SoC_Map :: *; // ================================================================ // UART registers and their address offsets Bit #(3) addr_UART_rbr = 3'h_0; // receiver buffer register (read only) Bit #(3) addr_UART_thr = 3'h_0; // transmitter holding register (write only) Bit #(3) addr_UART_ier = 3'h_1; // interrupt enable register Bit #(3) addr_UART_iir = 3'h_2; // interrupt id register (read-only) Bit #(3) addr_UART_lcr = 3'h_3; // line control reg Bit #(3) addr_UART_mcr = 3'h_4; // modem control reg Bit #(3) addr_UART_lsr = 3'h_5; // line status reg (read-only) Bit #(3) addr_UART_msr = 3'h_6; // modem status reg (read-only) Bit #(3) addr_UART_scr = 3'h_7; // scratch pad reg // Aliased registers, depending on control bits Bit #(3) addr_UART_dll = 3'h_0; // divisor latch low Bit #(3) addr_UART_dlm = 3'h_1; // divisor latch high Bit #(3) addr_UART_fcr = 3'h_2; // fifo control reg (write-only) // Bit fields of ier (Interrupt Enable Register) Bit #(8) uart_ier_erbfi = 8'h_01; // Enable Received Data Available Interrupt Bit #(8) uart_ier_etbei = 8'h_02; // Enable Transmitter Holding Register Empty Interrupt Bit #(8) uart_ier_elsi = 8'h_04; // Enable Receiver Line Status Interrupt Bit #(8) uart_ier_edssi = 8'h_08; // Enable Modem Status Interrupt // iir values (Interrupt Identification Register) in decreasing priority of interrupts Bit #(8) uart_iir_none = 8'h_01; // None (no interrupts pending) Bit #(8) uart_iir_rls = 8'h_06; // Receiver Line Status Bit #(8) uart_iir_rda = 8'h_04; // Received Data Available Bit #(8) uart_iir_cti = 8'h_0C; // Character Timeout Indication Bit #(8) uart_iir_thre = 8'h_02; // Transmitter Holding Register Empty Bit #(8) uart_iir_ms = 8'h_00; // Modem Status // Bit fields of LCR Bit #(8) uart_lcr_dlab = 8'h_80; // Divisor latch access bit Bit #(8) uart_lcr_bc = 8'h_40; // Break control Bit #(8) uart_lcr_sp = 8'h_20; // Stick parity Bit #(8) uart_lcr_eps = 8'h_10; // Even parity Bit #(8) uart_lcr_pen = 8'h_08; // Parity enable Bit #(8) uart_lcr_stb = 8'h_04; // # of stop bits (0=1b,1=2b) Bit #(8) uart_lcr_wls = 8'h_03; // word len (0:5b,1:6b,2:7b,3:8b) // Bit fields of LSR Bit #(8) uart_lsr_rxfe = 8'h_80; // Receiver FIFO error Bit #(8) uart_lsr_temt = 8'h_40; // Transmitter empty Bit #(8) uart_lsr_thre = 8'h_20; // THR empty Bit #(8) uart_lsr_bi = 8'h_10; // Break interrupt Bit #(8) uart_lsr_fe = 8'h_08; // Framing Error Bit #(8) uart_lsr_pe = 8'h_04; // Parity Error Bit #(8) uart_lsr_oe = 8'h_02; // Overrun Error Bit #(8) uart_lsr_dr = 8'h_01; // Data Ready Bit #(8) uart_lsr_reset_value = (uart_lsr_temt | uart_lsr_thre); // ================================================================ // THIS MODULE's INTERFACE interface UART_IFC; // Reset interface Server #(Bit #(0), Bit #(0)) server_reset; // set_addr_map should be called after this module's reset method Action set_addr_map (Fabric_Addr addr_base, Fabric_Addr addr_lim); // Main Fabric Reqs/Rsps interface AXI4_Slave #(Wd_SId, Wd_Addr, Wd_Data, 0, 0, 0, 0, 0) slave; // To external console interface Get #(Bit #(8)) get_to_console; interface Put #(Bit #(8)) put_from_console; // Interrupt pending (* always_ready *) method Bool intr; endinterface // ================================================================ // Local types and constants // Module state typedef enum {STATE_START, STATE_READY } Module_State deriving (Bits, Eq, FShow); // ================================================================ // Addressing of UART registers // ---------------------------------------------------------------- // UART reg addresses should be at stride 4 or 8. Integer address_stride = 4; // Integer address_stride = 8; // ---------------------------------------------------------------- // Split a bus address into (offset, lsbs), based on the address // stride. function Tuple2 #(Bit #(64), Bit #(3)) split_addr (Bit #(64) addr); Bit #(64) offset = ((address_stride == 4) ? (addr >> 2) : (addr >> 3)); Bit #(3) lsbs = ((address_stride == 4) ? { 1'b0, addr [1:0] } : addr [2:0]); return tuple2 (offset, lsbs); endfunction // ---------------------------------------------------------------- // Extract data from AXI4 byte lanes, based on the AXI4 'strobe' // (byte-enable) bits. function Bit #(64) fn_extract_AXI4_data (Bit #(64) data, Bit #(8) strb); Bit #(64) result = 0; case (strb) 8'b_0000_0001: result = zeroExtend (data [ 7:0]); 8'b_0000_0010: result = zeroExtend (data [15:8]); 8'b_0000_0100: result = zeroExtend (data [23:16]); 8'b_0000_1000: result = zeroExtend (data [31:24]); 8'b_0001_0000: result = zeroExtend (data [39:32]); 8'b_0010_0000: result = zeroExtend (data [47:40]); 8'b_0100_0000: result = zeroExtend (data [55:48]); 8'b_1000_0000: result = zeroExtend (data [63:56]); 8'b_0000_0011: result = zeroExtend (data [15:0]); 8'b_0000_1100: result = zeroExtend (data [31:16]); 8'b_0011_0000: result = zeroExtend (data [47:32]); 8'b_1100_0000: result = zeroExtend (data [63:48]); 8'b_0000_1111: result = zeroExtend (data [31:0]); 8'b_1111_0000: result = zeroExtend (data [63:32]); 8'b_1111_1111: result = zeroExtend (data [63:0]); endcase return result; endfunction // ================================================================ // THIS MODULE's IMPLEMENTATION (* synthesize *) module mkUART (UART_IFC); Reg #(Bit #(8)) cfg_verbosity <- mkConfigReg (0); Reg #(Module_State) rg_state <- mkReg (STATE_START); // These regs represent where this UART is placed in the address space. Reg #(Fabric_Addr) rg_addr_base <- mkRegU; Reg #(Fabric_Addr) rg_addr_lim <- mkRegU; FIFOF #(Bit #(0)) f_reset_reqs <- mkFIFOF; FIFOF #(Bit #(0)) f_reset_rsps <- mkFIFOF; // ---------------- // Connector to AXI4 fabric let slave_shim <- mkAXI4Shim; // ---------------- // character queues to and from external circuitry for the console FIFOF #(Bit #(8)) f_from_console <- mkFIFOF; FIFOF #(Bit #(8)) f_to_console <- mkFIFOF; // ---------------- // These are the 16550 UART registers // See fn_addr_offset() above for meaning of 'addr offset' Reg #(Bit #(8)) rg_rbr <- mkRegU; // addr offset 0 Reg #(Bit #(8)) rg_thr <- mkRegU; // addr offset 0 Reg #(Bit #(8)) rg_dll <- mkReg (0); // addr offset 0 Reg #(Bit #(8)) rg_ier <- mkReg (0); // addr offset 1 Reg #(Bit #(8)) rg_dlm <- mkReg (0); // addr offset 1 // IIR is a virtual read-only register computed from other regs Reg #(Bit #(8)) rg_fcr <- mkReg (0); // addr offset 2 Reg #(Bit #(8)) rg_lcr <- mkReg (0); // addr offset 3 Reg #(Bit #(8)) rg_mcr <- mkReg (0); // addr offset 4 Reg #(Bit #(8)) rg_lsr <- mkReg (uart_lsr_reset_value); // addr offset 5 Reg #(Bit #(8)) rg_msr <- mkReg (0); // addr offset 6 Reg #(Bit #(8)) rg_scr <- mkReg (0); // addr offset 7 // ---------------- // Virtual read-only register IIR function Bit #(8) fn_iir (); Bit #(8) iir = 0; if ( ((rg_ier & uart_ier_erbfi) != 0) // Rx interrupt enabled && ((rg_lsr & uart_lsr_dr) != 0)) // data ready iir = uart_iir_rda; else if ((rg_ier & uart_ier_etbei) != 0) // Tx Holding Reg Empty intr enabled iir = uart_iir_thre; return iir; endfunction // ---------------- // Test if an interrupt is pending function Bool fn_intr (); let iir = fn_iir (); Bool eip = ((iir & uart_iir_none) == 0); return eip; endfunction // ================================================================ // BEHAVIOR // ---------------------------------------------------------------- // Soft reset (on token in f_reset_reqs) rule rl_reset; f_reset_reqs.deq; rg_dll <= 0; rg_ier <= 0; rg_dlm <= 0; rg_fcr <= 0; rg_lcr <= 0; rg_mcr <= 0; rg_lsr <= uart_lsr_reset_value; rg_msr <= 0; rg_scr <= 0; slave_shim.clear; rg_state <= STATE_READY; f_reset_rsps.enq (?); if (cfg_verbosity != 0) $display ("%0d: UART.rl_reset", cur_cycle); endrule // ---------------------------------------------------------------- // Handle fabric read requests rule rl_process_rd_req (rg_state == STATE_READY); let rda <- get(slave_shim.master.ar); let byte_addr = rda.araddr - rg_addr_base; match { .offset, .lsbs } = split_addr (zeroExtend (byte_addr)); Bit #(8) rdata_byte = 0; AXI4_Resp rresp = OKAY; if ((rda.araddr < rg_addr_base) || (rda.araddr >= rg_addr_lim)) begin $display ("%0d: %m.rl_process_rd_req: ERROR: UART addr out of bounds", cur_cycle); $display (" UART base addr 0x%0h limit addr 0x%0h", rg_addr_base, rg_addr_lim); $display (" AXI4 request: ", fshow (rda)); rresp = DECERR; end else if (lsbs != 0) begin $display ("%0d: %m.rl_process_rd_req: ERROR: UART misaligned addr", cur_cycle); $display (" ", fshow (rda)); rresp = SLVERR; end else if (offset [63:3] != 0) begin $display ("%0d: %m.rl_process_rd_req: ERROR: UART unsupported addr", cur_cycle); $display (" ", fshow (rda)); rresp = DECERR; end // offset 0: RBR else if ((offset [2:0] == addr_UART_rbr) && ((rg_lcr & uart_lcr_dlab) == 0)) begin // Read an input char rg_lsr <= (rg_lsr & (~ uart_lsr_dr)); // Reset data-ready rdata_byte = rg_rbr; end // offset 0: DLL else if ((offset [2:0] == addr_UART_dll) && ((rg_lcr & uart_lcr_dlab) != 0)) rdata_byte = rg_dll; // offset 1: IER else if ((offset [2:0] == addr_UART_ier) && ((rg_lcr & uart_lcr_dlab) == 0)) rdata_byte = rg_ier; // offset 1: DLM else if ((offset [2:0] == addr_UART_dlm) && ((rg_lcr & uart_lcr_dlab) != 0)) rdata_byte = rg_dlm; // offset 2: IIR (read-only) else if (offset [2:0] == addr_UART_iir) rdata_byte = fn_iir(); // offset 3: LCR else if (offset [2:0] == addr_UART_lcr) rdata_byte = { 0, rg_lcr }; // offset 4: MCR else if (offset [2:0] == addr_UART_mcr) rdata_byte = { 0, rg_mcr }; // offset 5: LSR else if (offset [2:0] == addr_UART_lsr) rdata_byte = { 0, rg_lsr }; // offset 6: MSR else if (offset [2:0] == addr_UART_msr) rdata_byte = { 0, rg_msr }; // offset 7: SCR else if (offset [2:0] == addr_UART_scr) rdata_byte = { 0, rg_scr }; else begin $display ("%0d: %m.rl_process_rd_req: ERROR: UART unsupported addr", cur_cycle); $display (" ", fshow (rda)); rresp = DECERR; end // Align data byte for AXI4 data bus based on fabric-width Fabric_Data rdata = zeroExtend (rdata_byte); if ((valueOf (Wd_Data) == 64) && (byte_addr [2:0] == 3'b100)) rdata = rdata << 32; // Send read-response to bus let rdr = AXI4_RFlit {rid: rda.arid, rdata: rdata, rresp: rresp, rlast: True, ruser: rda.aruser}; // XXX This requires that Wd_AR_User == Wd_R_User slave_shim.master.r.put(rdr); if (cfg_verbosity > 1) begin $display ("%0d: %m.rl_process_rd_req", cur_cycle); $display (" ", fshow (rda)); $display (" ", fshow (rdr)); end endrule // ---------------------------------------------------------------- // Handle fabric write requests rule rl_process_wr_req (rg_state == STATE_READY); let wra <- get(slave_shim.master.aw); let wrd <- get(slave_shim.master.w); Bit #(64) wdata = zeroExtend (wrd.wdata); Bit #(8) wstrb = zeroExtend (wrd.wstrb); Bit #(8) data_byte = truncate (fn_extract_AXI4_data (wdata, wstrb)); let byte_addr = wra.awaddr - rg_addr_base; match { .offset, .lsbs } = split_addr (zeroExtend (byte_addr)); AXI4_Resp bresp = OKAY; if ((wra.awaddr < rg_addr_base) || (wra.awaddr >= rg_addr_lim)) begin $display ("%0d: %m.rl_process_rd_req: ERROR: UART addr out of bounds", cur_cycle); $display (" UART base addr 0x%0h limit addr 0x%0h", rg_addr_base, rg_addr_lim); $display (" AXI4 request: ", fshow (wra)); bresp = DECERR; end else if (lsbs != 0) begin $display ("%0d: %m.rl_process_wr_req: ERROR: UART misaligned addr", cur_cycle); $display (" ", fshow (wra)); $display (" ", fshow (wrd)); bresp = SLVERR; end else if (offset [63:3] != 0) begin $display ("%0d: %m.rl_process_wr_req: ERROR: UART unsupported addr", cur_cycle); $display (" ", fshow (wra)); $display (" ", fshow (wrd)); bresp = DECERR; end // offset 0: THR else if ((offset [2:0] == addr_UART_thr) && ((rg_lcr & uart_lcr_dlab) == 0)) begin // Write a char to the serial line rg_thr <= data_byte; f_to_console.enq (data_byte); end // offset 0: DLL else if ((offset [2:0] == addr_UART_dll) && ((rg_lcr & uart_lcr_dlab) != 0)) rg_dll <= data_byte; // offset 1: IER else if ((offset [2:0] == addr_UART_ier) && ((rg_lcr & uart_lcr_dlab) == 0)) rg_ier <= data_byte; // offset 1: DLM else if ((offset [2:0] == addr_UART_dlm) && ((rg_lcr & uart_lcr_dlab) != 0)) rg_dlm <= data_byte; // offset 2: FCR (write-only) else if (offset [2:0] == addr_UART_fcr) rg_fcr <= data_byte; // offset 3: LCR else if (offset [2:0] == addr_UART_lcr) rg_lcr <= data_byte; // offset 4: MCR else if (offset [2:0] == addr_UART_mcr) rg_mcr <= data_byte; // offset 5: LSR else if (offset [2:0] == addr_UART_lsr) noAction; // LSR is read-only // offset 6: MSR else if (offset [2:0] == addr_UART_msr) noAction; // MSR is read-only // offset 7: SCR else if (offset [2:0] == addr_UART_scr) rg_scr <= data_byte; else begin $display ("%0d: %m.rl_process_wr_req: ERROR: UART unsupported addr", cur_cycle); $display (" ", fshow (wra)); $display (" ", fshow (wrd)); bresp = DECERR; end // Send write-response to bus let wrr = AXI4_BFlit {bid: wra.awid, bresp: bresp, buser: wra.awuser}; // XXX This requires that Wd_AW_User == Wd_B_User slave_shim.master.b.put(wrr); if (cfg_verbosity > 1) begin $display ("%0d: %m.rl_process_wr_req", cur_cycle); $display (" ", fshow (wra)); $display (" ", fshow (wrd)); $display (" ", fshow (wrr)); end endrule // ---------------------------------------------------------------- // Receive a char from the serial line when RBR is empty (i.e., LSR.DR is 0), // and deposit it into RBR // and set it full (LSR.DR = 1) (* descending_urgency = "rl_receive, rl_process_rd_req" *) rule rl_receive ((rg_lsr & uart_lsr_dr) == 0); let ch <- pop (f_from_console); rg_rbr <= ch; let new_lsr = (rg_lsr | uart_lsr_dr); // Set data-ready rg_lsr <= new_lsr; if (cfg_verbosity > 1) $display ("UART_Model.rl_receive: received char 0x%0h; new_lsr = 0x%0h", ch, new_lsr); endrule // ================================================================ // INTERFACE // Reset interface server_reset = toGPServer (f_reset_reqs, f_reset_rsps); // set_addr_map should be called after this module's reset method Action set_addr_map (Fabric_Addr addr_base, Fabric_Addr addr_lim); if (addr_base [2:0] != 0) $display ("%0d: WARNING: UART.set_addr_map: addr_base 0x%0h is not 8-Byte-aligned", cur_cycle, addr_base); if (addr_lim [2:0] != 0) $display ("%0d: WARNING: UART.set_addr_map: addr_lim 0x%0h is not 8-Byte-aligned", cur_cycle, addr_lim); rg_addr_base <= addr_base; rg_addr_lim <= addr_lim; endmethod // Main Fabric Reqs/Rsps interface slave = slave_shim.slave; // To external console interface put_from_console = toPut (f_from_console); interface get_to_console = toGet (f_to_console); // Interrupt pending method Bool intr; return fn_intr (); endmethod endmodule // ================================================================ endpackage