From cc10f6b2892e2790726511d6d450405d27111fce Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Thu, 18 Jun 2020 14:00:28 +1000 Subject: [PATCH] uart: Add a simulation model for the 16550 compatible UART Signed-off-by: Benjamin Herrenschmidt --- Makefile | 3 +- sim_16550_uart.vhdl | 421 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 423 insertions(+), 1 deletion(-) create mode 100644 sim_16550_uart.vhdl diff --git a/Makefile b/Makefile index dcceff6..96631e7 100644 --- 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 index 0000000..5f8330e --- /dev/null +++ b/sim_16550_uart.vhdl @@ -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; -- 2.30.2