uart: Add a simulation model for the 16550 compatible UART
authorBenjamin Herrenschmidt <benh@kernel.crashing.org>
Thu, 18 Jun 2020 04:00:28 +0000 (14:00 +1000)
committerBenjamin Herrenschmidt <benh@kernel.crashing.org>
Tue, 23 Jun 2020 23:53:46 +0000 (09:53 +1000)
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Makefile
sim_16550_uart.vhdl [new file with mode: 0644]

index dcceff6d6d07486ed21454aa3110e20b527281e9..96631e77ab71c139f179de7732f280473e3a41c8 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/sim_16550_uart.vhdl b/sim_16550_uart.vhdl
new file mode 100644 (file)
index 0000000..5f8330e
--- /dev/null
@@ -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;