Files
Toooba/src_Testbench/SoC/UART_Model.bsv

513 lines
18 KiB
Plaintext

// Copyright (c) 2016-2020 Bluespec, Inc. All Rights Reserved
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 :: *;
// ================================================================
// Project imports
import AXI4_Types :: *;
import Fabric_Defs :: *;
// ================================================================
// 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_IFC #(Wd_Id, Wd_Addr, Wd_Data, Wd_User) 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
AXI4_Slave_Xactor_IFC #(Wd_Id, Wd_Addr, Wd_Data, Wd_User) slave_xactor <- mkAXI4_Slave_Xactor;
// ----------------
// 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_xactor.reset;
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 <- pop_o (slave_xactor.o_rd_addr);
let byte_addr = rda.araddr - rg_addr_base;
match { .offset, .lsbs } = split_addr (zeroExtend (byte_addr));
Bit #(8) rdata_byte = 0;
AXI4_Resp rresp = axi4_resp_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 = axi4_resp_decerr;
end
else if (lsbs != 0) begin
$display ("%0d: %m.rl_process_rd_req: ERROR: UART misaligned addr", cur_cycle);
$display (" ", fshow (rda));
rresp = axi4_resp_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 = axi4_resp_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 = axi4_resp_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_Rd_Data {rid: rda.arid,
rdata: rdata,
rresp: rresp,
rlast: True,
ruser: rda.aruser};
slave_xactor.i_rd_data.enq (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 <- pop_o (slave_xactor.o_wr_addr);
let wrd <- pop_o (slave_xactor.o_wr_data);
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 = axi4_resp_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 = axi4_resp_decerr;
end
else if (lsbs != 0) begin
$display ("%0d: %m.rl_process_wr_req: ERROR: UART misaligned addr", cur_cycle);
$display (" ", fshow (wra));
bresp = axi4_resp_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));
bresp = axi4_resp_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 = axi4_resp_decerr;
end
// Send write-response to bus
let wrr = AXI4_Wr_Resp {bid: wra.awid,
bresp: bresp,
buser: wra.awuser};
slave_xactor.i_wr_resp.enq (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_xactor.axi_side;
// 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