uart: Add a simulation model for the 16550 compatible UART

Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
pull/213/head
Benjamin Herrenschmidt 4 years ago
parent 4eae29801b
commit cc10f6b289

@ -56,7 +56,8 @@ soc_files = $(core_files) wishbone_arbiter.vhdl wishbone_bram_wrapper.vhdl sync_


soc_sim_files = $(soc_files) sim_console.vhdl sim_pp_uart.vhdl sim_bram_helpers.vhdl \
sim_bram.vhdl sim_jtag_socket.vhdl sim_jtag.vhdl dmi_dtm_xilinx.vhdl
sim_bram.vhdl sim_jtag_socket.vhdl sim_jtag.vhdl dmi_dtm_xilinx.vhdl \
sim_16550_uart.vhdl

soc_sim_c_files = sim_vhpi_c.c sim_bram_helpers_c.c sim_console_c.c \
sim_jtag_socket_c.c

@ -0,0 +1,421 @@
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

library work;
use work.sim_console.all;

entity uart_top is
port(
wb_clk_i : in std_ulogic;
wb_rst_i : in std_ulogic;
wb_adr_i : in std_ulogic_vector(2 downto 0);
wb_dat_i : in std_ulogic_vector(7 downto 0);
wb_dat_o : out std_ulogic_vector(7 downto 0);
wb_we_i : in std_ulogic;
wb_stb_i : in std_ulogic;
wb_cyc_i : in std_ulogic;
wb_ack_o : out std_ulogic;
int_o : out std_ulogic;
stx_pad_o : out std_ulogic;
srx_pad_i : in std_ulogic;
rts_pad_o : out std_ulogic;
cts_pad_i : in std_ulogic;
dtr_pad_o : out std_ulogic;
dsr_pad_i : in std_ulogic;
ri_pad_i : in std_ulogic;
dcd_pad_i : in std_ulogic
);
end entity uart_top;

architecture behaviour of uart_top is

-- Call POLL every N clocks to generate interrupts
constant POLL_DELAY : natural := 100;

-- Register definitions
subtype reg_adr_t is std_ulogic_vector(2 downto 0);

constant REG_IDX_RXTX : reg_adr_t := "000";
constant REG_IDX_IER : reg_adr_t := "001";
constant REG_IDX_IIR_FCR : reg_adr_t := "010";
constant REG_IDX_LCR : reg_adr_t := "011";
constant REG_IDX_MCR : reg_adr_t := "100";
constant REG_IDX_LSR : reg_adr_t := "101";
constant REG_IDX_MSR : reg_adr_t := "110";
constant REG_IDX_SCR : reg_adr_t := "111";

-- IER bits
constant REG_IER_RDI_BIT : natural := 0;
constant REG_IER_THRI_BIT : natural := 1;
constant REG_IER_RLSI_BIT : natural := 2;
constant REG_IER_MSI_BIT : natural := 3;

-- IIR bit
constant REG_IIR_NO_INT : natural := 0;
-- IIR values for bit 3 downto 0
constant REG_IIR_RDI : std_ulogic_vector(3 downto 1) := "010";
constant REG_IIR_THRI : std_ulogic_vector(3 downto 1) := "001";
constant REG_IIR_RLSI : std_ulogic_vector(3 downto 1) := "011";
constant REG_IIR_MSI : std_ulogic_vector(3 downto 1) := "000";

-- FCR bits
constant REG_FCR_EN_FIFO_BIT : natural := 0; -- Always 1
constant REG_FCR_CLR_RCVR_BIT : natural := 1;
constant REG_FCR_CLR_XMIT_BIT : natural := 2;
constant REG_FCR_DMA_SEL_BIT : natural := 3; -- Not implemented
-- FCR values for FIFO threshold in bits 7 downto 6
constant REG_FCR_FIFO_TRIG1 : std_ulogic_vector(7 downto 6) := "00";
constant REG_FCR_FIFO_TRIG4 : std_ulogic_vector(7 downto 6) := "01";
constant REG_FCR_FIFO_TRIG8 : std_ulogic_vector(7 downto 6) := "10";
constant REG_FCR_FIFO_TRIG14 : std_ulogic_vector(7 downto 6) := "11";

-- LCR bits
constant REG_LCR_STOP_BIT : natural := 2;
constant REG_LCR_PARITY_BIT : natural := 3;
constant REG_LCR_EPAR_BIT : natural := 4;
constant REG_LCR_SPAR_BIT : natural := 5;
constant REG_LCR_SBC_BIT : natural := 6;
constant REG_LCR_DLAB_BIT : natural := 7;
-- LCR values for data length (bits 1 downto 0)
constant REG_LCR_WLEN5 : std_ulogic_vector(1 downto 0) := "00";
constant REG_LCR_WLEN6 : std_ulogic_vector(1 downto 0) := "01";
constant REG_LCR_WLEN7 : std_ulogic_vector(1 downto 0) := "10";
constant REG_LCR_WLEN8 : std_ulogic_vector(1 downto 0) := "11";

-- MCR bits
constant REG_MCR_DTR_BIT : natural := 0;
constant REG_MCR_RTS_BIT : natural := 1;
constant REG_MCR_OUT1_BIT : natural := 2;
constant REG_MCR_OUT2_BIT : natural := 3;
constant REG_MCR_LOOP_BIT : natural := 4;

-- LSR bits
constant REG_LSR_DR_BIT : natural := 0;
constant REG_LSR_OE_BIT : natural := 1;
constant REG_LSR_PE_BIT : natural := 2;
constant REG_LSR_FE_BIT : natural := 3;
constant REG_LSR_BI_BIT : natural := 4;
constant REG_LSR_THRE_BIT : natural := 5;
constant REG_LSR_TEMT_BIT : natural := 6;
constant REG_LSR_FIFOE_BIT : natural := 7;

-- MSR bits
constant REG_MSR_DCTS_BIT : natural := 0;
constant REG_MSR_DDSR_BIT : natural := 1;
constant REG_MSR_TERI_BIT : natural := 2;
constant REG_MSR_DDCD_BIT : natural := 3;
constant REG_MSR_CTS_BIT : natural := 4;
constant REG_MSR_DSR_BIT : natural := 5;
constant REG_MSR_RI_BIT : natural := 6;
constant REG_MSR_DCD_BIT : natural := 7;

-- Wishbone signals decode:
signal reg_idx : reg_adr_t;
signal wb_phase : std_ulogic;
signal reg_write : std_ulogic;
signal reg_read : std_ulogic;

-- Register storage
signal reg_ier : std_ulogic_vector(3 downto 0);
signal reg_iir : std_ulogic_vector(3 downto 0);
signal reg_fcr : std_ulogic_vector(7 downto 6);
signal reg_lcr : std_ulogic_vector(7 downto 0);
signal reg_mcr : std_ulogic_vector(4 downto 0);
signal reg_lsr : std_ulogic_vector(7 downto 0);
signal reg_msr : std_ulogic_vector(7 downto 0);
signal reg_scr : std_ulogic_vector(7 downto 0);

signal reg_div : std_ulogic_vector(15 downto 0);

-- Control signals
signal rx_fifo_clr : std_ulogic;
signal tx_fifo_clr : std_ulogic;

-- Pending interrupts
signal int_rdi_pending : std_ulogic;
signal int_thri_pending : std_ulogic;
signal int_rlsi_pending : std_ulogic;
signal int_msi_pending : std_ulogic;

-- Actual data output
signal data_out : std_ulogic_vector(7 downto 0) := x"00";

-- Incoming data pending signal
signal data_in_pending : std_ulogic := '0';

-- Useful aliases
alias dlab : std_ulogic is reg_lcr(REG_LCR_DLAB_BIT);

alias clk : std_ulogic is wb_clk_i;
alias rst : std_ulogic is wb_rst_i;
alias cyc : std_ulogic is wb_cyc_i;
alias stb : std_ulogic is wb_stb_i;
alias we : std_ulogic is wb_we_i;
begin

-- Register index shortcut
reg_idx <= wb_adr_i(2 downto 0);

-- 2 phases WB process.
--
-- Among others, this gives us a "free" cycle for the
-- side effects of some accesses percolate in the form
-- of status bit changes in other registers.
wb_cycle: process(clk)
variable phase : std_ulogic := '0';
begin
if rising_edge(clk) then
if wb_phase = '0' then
if cyc = '1' and stb = '1' then
wb_ack_o <= '1';
wb_phase <= '1';
end if;
else
wb_ack_o <= '0';
wb_phase <= '0';
end if;
end if;
end process;

-- Reg read/write signals
reg_write <= cyc and stb and we and not wb_phase;
reg_read <= cyc and stb and not we and not wb_phase;

-- Register read is synchronous to avoid collisions with
-- read-clear side effects
do_reg_read: process(clk)
begin
if rising_edge(clk) then
wb_dat_o <= x"00";
if reg_read = '1' then
case reg_idx is
when REG_IDX_RXTX =>
if dlab = '1' then
wb_dat_o <= reg_div(7 downto 0);
else
wb_dat_o <= data_out;
end if;
when REG_IDX_IER =>
if dlab = '1' then
wb_dat_o <= reg_div(15 downto 8);
else
wb_dat_o <= "0000" & reg_ier;
end if;
when REG_IDX_IIR_FCR =>
-- Top bits always set as FIFO is always enabled
wb_dat_o <= "1100" & reg_iir;
when REG_IDX_LCR =>
wb_dat_o <= reg_lcr;
when REG_IDX_LSR =>
wb_dat_o <= reg_lsr;
when REG_IDX_MSR =>
wb_dat_o <= reg_msr;
when REG_IDX_SCR =>
wb_dat_o <= reg_scr;
when others =>
end case;
end if;
end if;
end process;

-- Receive/send synchronous process
rxtx: process(clk)
variable dp : std_ulogic;
variable poll_cnt : natural;
variable sim_tmp : std_ulogic_vector(63 downto 0);
begin
if rising_edge(clk) then
if rst = '0' then
dp := data_in_pending;
if dlab = '0' and reg_idx = REG_IDX_RXTX then
if reg_write = '1' then
-- FIFO write
-- XXX Simulate the FIFO and delays for more
-- accurate behaviour & interrupts
sim_console_write(x"00000000000000" & wb_dat_i);
end if;
if reg_read = '1' then
dp := '0';
data_out <= x"00";
end if;
end if;

-- Poll for incoming data
if poll_cnt = 0 or (reg_read = '1' and reg_idx = REG_IDX_LSR) then
sim_console_poll(sim_tmp);
poll_cnt := POLL_DELAY;
if dp = '0' and sim_tmp(0) = '1' then
dp := '1';
sim_console_read(sim_tmp);
data_out <= sim_tmp(7 downto 0);
end if;
poll_cnt := poll_cnt - 1;
end if;
data_in_pending <= dp;
end if;
end if;
end process;

-- Interrupt pending bits
int_rdi_pending <= data_in_pending;
int_thri_pending <= '1';
int_rlsi_pending <= reg_lsr(REG_LSR_OE_BIT) or
reg_lsr(REG_LSR_PE_BIT) or
reg_lsr(REG_LSR_FE_BIT) or
reg_lsr(REG_LSR_BI_BIT);
int_msi_pending <= reg_msr(REG_MSR_DCTS_BIT) or
reg_msr(REG_MSR_DDSR_BIT) or
reg_msr(REG_MSR_TERI_BIT) or
reg_msr(REG_MSR_DDCD_BIT);

-- Derive interrupt output from IIR
int_o <= not reg_iir(REG_IIR_NO_INT);

-- Divisor register
div_reg_w: process(clk)
begin
if rising_edge(clk) then
if rst = '1' then
reg_div <= (others => '0');
elsif reg_write = '1' and dlab = '1' then
if reg_idx = REG_IDX_RXTX then
reg_div(7 downto 0) <= wb_dat_i;
elsif reg_idx = REG_IDX_IER then
reg_div(15 downto 8) <= wb_dat_i;
end if;
end if;
end if;
end process;

-- IER register
ier_reg_w: process(clk)
begin
if rising_edge(clk) then
if rst = '1' then
reg_ier <= "0000";
else
if reg_write = '1' and dlab = '0' and reg_idx = REG_IDX_IER then
reg_ier <= wb_dat_i(3 downto 0);
end if;
end if;
end if;
end process;

-- IIR (read only) generation
iir_reg_w: process(clk)
begin
if rising_edge(clk) then
reg_iir <= "0001";
if int_rlsi_pending = '1' and reg_ier(REG_IER_RLSI_BIT) = '1' then
reg_iir <= REG_IIR_RLSI & "0";
elsif int_rdi_pending = '1' and reg_ier(REG_IER_RDI_BIT) = '1' then
reg_iir <= REG_IIR_RDI & "0";
elsif int_thri_pending = '1' and reg_ier(REG_IER_THRI_BIT) = '1' then
reg_iir <= REG_IIR_THRI & "0";
elsif int_msi_pending = '1' and reg_ier(REG_IER_MSI_BIT) = '1' then
reg_iir <= REG_IIR_MSI & "0";
end if;

-- It *seems* like reading IIR should clear THRI for
-- some amount of time until it gets set again a few
-- clocks later if the transmitter is still empty. We
-- don't do that at this point.
end if;
end process;

-- FCR (write only) register
fcr_reg_w: process(clk)
begin
if rising_edge(clk) then
if rst = '1' then
reg_fcr <= "11";
rx_fifo_clr <= '1';
tx_fifo_clr <= '1';
elsif reg_write = '1' and reg_idx = REG_IDX_IIR_FCR then
reg_fcr <= wb_dat_i(7 downto 6);
rx_fifo_clr <= wb_dat_i(REG_FCR_CLR_RCVR_BIT);
tx_fifo_clr <= wb_dat_i(REG_FCR_CLR_XMIT_BIT);
else
rx_fifo_clr <= '0';
tx_fifo_clr <= '0';
end if;
end if;
end process;

-- LCR register
lcr_reg_w: process(clk)
begin
if rising_edge(clk) then
if rst = '1' then
reg_lcr <= "00000011";
elsif reg_write = '1' and reg_idx = REG_IDX_LCR then
reg_lcr <= wb_dat_i;
end if;
end if;
end process;

-- MCR register
mcr_reg_w: process(clk)
begin
if rising_edge(clk) then
if rst = '1' then
reg_mcr <= "00000";
elsif reg_write = '1' and reg_idx = REG_IDX_MCR then
reg_mcr <= wb_dat_i(4 downto 0);
end if;
end if;
end process;

-- LSR register
lsr_reg_w: process(clk)
begin
if rising_edge(clk) then
if rst = '1' then
reg_lsr <= "00000000";
else
reg_lsr(REG_LSR_DR_BIT) <= data_in_pending;

-- Clear error bits on read. Those bits are
-- always 0 in sim for now.
-- if reg_read = '1' and reg_idx = REG_IDX_LSR then
-- reg_lsr(REG_LSR_OE_BIT) <= '0';
-- reg_lsr(REG_LSR_PE_BIT) <= '0';
-- reg_lsr(REG_LSR_FE_BIT) <= '0';
-- reg_lsr(REG_LSR_BI_BIT) <= '0';
-- reg_lsr(REG_LSR_FIFOE_BIT) <= '0';
-- end if;

-- Tx FIFO empty indicators. Always empty in sim
reg_lsr(REG_LSR_THRE_BIT) <= '1';
reg_lsr(REG_LSR_TEMT_BIT) <= '1';
end if;
end if;
end process;

-- MSR register
msr_reg_w: process(clk)
begin
if rising_edge(clk) then
if rst = '1' then
reg_msr <= "00000000";
elsif reg_read = '1' and reg_idx = REG_IDX_MSR then
reg_msr <= "00000000";
-- XXX TODO bit setting machine...
end if;
end if;
end process;

-- SCR register
scr_reg_w: process(clk)
begin
if rising_edge(clk) then
if rst = '1' then
reg_scr <= "00000000";
elsif reg_write = '1' and reg_idx = REG_IDX_SCR then
reg_scr <= wb_dat_i;
end if;
end if;
end process;

end architecture behaviour;
Loading…
Cancel
Save