diff --git a/uart_reg/rtl/uart_ctrl.vhd b/uart_reg/rtl/uart_ctrl.vhd new file mode 100644 index 0000000..9db2eff --- /dev/null +++ b/uart_reg/rtl/uart_ctrl.vhd @@ -0,0 +1,135 @@ +-- UART register + +-- Register file with 8 registers storing values of one byte each. +-- +-- The first received byte on the axis in port contains command & address: +-- +-- 7 reserved +-- 6:4 register address +-- 3:0 command +-- 0x0 read +-- 0x1 write +-- +-- In case of a write command, the payload has to follow +-- with the next byte. +-- +-- In case of a read command, the value of the addressed +-- register is returned on the axis out port. +-- +-- Register at address 0 is special. It contains the version +-- and is read-only. Writes to that register are ignored. + + +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + + +entity uart_ctrl is + port ( + -- globals + rst_n_i : in std_logic; + clk_i : in std_logic; + -- axis in + tdata_i : in std_logic_vector(7 downto 0); + tvalid_i : in std_logic; + tready_o : out std_logic; + -- axis out + tdata_o : out std_logic_vector(7 downto 0); + tvalid_o : out std_logic; + tready_i : in std_logic + ); +end entity uart_ctrl; + + +architecture rtl of uart_ctrl is + + type t_state is (IDLE, GET_CMD, RECV_DATA, SEND_DATA); + signal s_state : t_state; + + subtype t_reg is std_logic_vector(7 downto 0); + + type t_reg_file is array (1 to 7) of t_reg; + signal s_reg_file : t_reg_file; + + constant c_version : t_reg := x"01"; + + signal s_reg_addr : natural range 0 to 7; + signal s_reg_data : t_reg; + + subtype t_cmd is std_ulogic_vector(3 downto 0); + constant c_read : t_cmd := x"0"; + constant c_write : t_cmd := x"1"; + + alias a_tdata_cmd is tdata_i(3 downto 0); + alias a_tdata_addr is tdata_i(6 downto 4); + +begin + + -- Register memory, omitted reset of memory during synthesis + -- for better RAM detection + process (clk_i, rst_n_i) is + begin + if (not rst_n_i) then + -- synthesis translate_off + s_reg_file <= (others => (others => '0')); + -- synthesis translate_on + s_reg_data <= (others => '0'); + elsif (rising_edge(clk_i)) then + -- Write + if (s_state = RECV_DATA and tvalid_i = '1') then + -- Ignore writes to version register + if (s_reg_addr /= 0) then + s_reg_file(s_reg_addr) <= tdata_i; + end if; + end if; + -- Always read, regardless of write or read command + if (s_state = GET_CMD) then + if (s_reg_addr /= 0) then + s_reg_data <= s_reg_file(s_reg_addr); + end if; + end if; + end if; + end process; + + -- Control state machine + process (clk_i, rst_n_i) is + begin + if (not rst_n_i) then + s_state <= IDLE; + s_reg_addr <= 0; + elsif (rising_edge(clk_i)) then + case s_state is + when IDLE => + if (tvalid_i) then + s_state <= GET_CMD; + s_reg_addr <= to_integer(unsigned(a_tdata_addr)); + end if; + when GET_CMD => + if (a_tdata_cmd = c_read) then + s_state <= SEND_DATA; + elsif (a_tdata_cmd = c_write) then + s_state <= RECV_DATA; + else + s_state <= IDLE; + end if; + when RECV_DATA => + if (tvalid_i) then + s_state <= IDLE; + end if; + when SEND_DATA => + if (tready_i) then + s_state <= IDLE; + end if; + when others => + null; + end case; + end if; + end process; + + tready_o <= '1' when s_state = GET_CMD or s_state = RECV_DATA else '0'; + + tdata_o <= c_version when s_reg_addr = 0 else s_reg_data; + tvalid_o <= '1' when s_state = SEND_DATA else '0'; + +end architecture; diff --git a/uart_reg/rtl/uart_reg.vhd b/uart_reg/rtl/uart_reg.vhd index dc31c5d..89bc0b2 100644 --- a/uart_reg/rtl/uart_reg.vhd +++ b/uart_reg/rtl/uart_reg.vhd @@ -1,4 +1,8 @@ --- This design implements a simple UART loop with 9600 baud +-- This design implements a register file which can +-- be accessed by an UART with 9600 baud +-- +-- See into uart_ctrl.vhd for documentation of the protocol +-- used to read / write the register file. library ieee ; @@ -31,6 +35,10 @@ architecture rtl of uart_reg is signal s_uart_rx_tvalid : std_logic; signal s_uart_rx_tready : std_logic; + signal s_uart_tx_tdata : std_logic_vector(7 downto 0); + signal s_uart_tx_tvalid : std_logic; + signal s_uart_tx_tready : std_logic; + begin pll : CC_PLL @@ -74,6 +82,21 @@ begin rx_i => uart_rx_i ); + uart_ctrl : entity work.uart_ctrl + port map ( + -- globals + rst_n_i => s_rst_n, + clk_i => s_pll_clk, + -- uart rx interface + tdata_i => s_uart_rx_tdata, + tvalid_i => s_uart_rx_tvalid, + tready_o => s_uart_rx_tready, + -- uart tx interface + tdata_o => s_uart_tx_tdata, + tvalid_o => s_uart_tx_tvalid, + tready_i => s_uart_tx_tready + ); + uart_tx : entity work.uart_tx generic map ( CLK_DIV => 104 @@ -83,9 +106,9 @@ begin rst_n_i => s_rst_n, clk_i => s_pll_clk, -- axis user interface - tdata_i => s_uart_rx_tdata, - tvalid_i => s_uart_rx_tvalid, - tready_o => s_uart_rx_tready, + tdata_i => s_uart_tx_tdata, + tvalid_i => s_uart_tx_tvalid, + tready_o => s_uart_tx_tready, -- uart interface tx_o => uart_tx_o ); diff --git a/uart_reg/sim/Makefile b/uart_reg/sim/Makefile index 3ee810a..24f0d80 100644 --- a/uart_reg/sim/Makefile +++ b/uart_reg/sim/Makefile @@ -1,6 +1,6 @@ DESIGN_NAME := uart_reg LIB_SRC := ../../lib/rtl_components.vhd ../../lib/sim_components.vhd -RTL_SRC := ../rtl/uart_tx.vhd ../rtl/uart_rx.vhd ../rtl/${DESIGN_NAME}.vhd +RTL_SRC := ../rtl/uart_tx.vhd ../rtl/uart_rx.vhd ../rtl/uart_ctrl.vhd ../rtl/${DESIGN_NAME}.vhd SIM_SRC := tb_${DESIGN_NAME}.vhd SIM_FLAGS := --std=08 -fpsl --workdir=work diff --git a/uart_reg/sim/tb_uart_reg.vhd b/uart_reg/sim/tb_uart_reg.vhd index 6af49dc..dbeaaaa 100644 --- a/uart_reg/sim/tb_uart_reg.vhd +++ b/uart_reg/sim/tb_uart_reg.vhd @@ -20,6 +20,33 @@ architecture sim of tb_uart_reg is constant c_baudrate : natural := 9600; constant c_period_ns : time := 1000000000 / c_baudrate * ns; + procedure uart_send ( data : in std_logic_vector(7 downto 0); + signal tx : out std_logic) is + begin + report "UART send: 0x" & to_hstring(data); + tx <= '0'; + wait for c_period_ns; + for i in 0 to 7 loop + tx <= data(i); + wait for c_period_ns; + end loop; + tx <= '1'; + wait for c_period_ns; + end procedure; + + procedure uart_recv ( data : out std_logic_vector(7 downto 0); + signal rx : in std_logic) is + begin + wait until not rx; + wait for c_period_ns; -- Skip start bit + wait for c_period_ns/2; + for i in 0 to 7 loop + data(i) := rx; + wait for c_period_ns; + end loop; + report "UART recv: 0x" & to_hstring(data); + end procedure; + begin dut : entity work.uart_reg @@ -39,37 +66,46 @@ begin wait until s_rst_n; wait until rising_edge(s_clk); wait for 200 us; - for tx in 0 to 255 loop - v_data := std_logic_vector(to_unsigned(tx, 8)); - report "UART send: 0x" & to_hstring(v_data); - s_uart_rx <= '0'; - wait for c_period_ns; - for i in 0 to 7 loop - s_uart_rx <= v_data(i); - wait for c_period_ns; - end loop; - s_uart_rx <= '1'; - wait for c_period_ns; + -- First read all registers + for i in 0 to 7 loop + v_data := std_logic_vector(to_unsigned(i, 4)) & x"0"; + uart_send(v_data, s_uart_rx); + end loop; + -- Then write all registers + for i in 0 to 7 loop + v_data := std_logic_vector(to_unsigned(i, 4)) & x"1"; + uart_send(v_data, s_uart_rx); + uart_send(x"FF", s_uart_rx); + end loop; + -- Finally read all registers again after write + for i in 0 to 7 loop + v_data := std_logic_vector(to_unsigned(i, 4)) & x"0"; + uart_send(v_data, s_uart_rx); end loop; wait; end process; ReceiveP : process is + type t_exp is array (0 to 7) of std_logic_vector(7 downto 0); + variable v_exp : t_exp; variable v_data : std_logic_vector(7 downto 0); begin wait until s_rst_n; wait until rising_edge(s_clk); - for rx in 0 to 255 loop - wait until not s_uart_tx; - wait for c_period_ns; -- Skip start bit - wait for c_period_ns/2; - for i in 0 to 7 loop - v_data(i) := s_uart_tx; - wait for c_period_ns; - end loop; - report "UART recv: 0x" & to_hstring(v_data); - assert v_data = std_logic_vector(to_unsigned(rx, 8)) - report "UART receive error, got 0x" & to_hstring(v_data) & ", expected 0x" & to_hstring(v_data) + -- First read all registers + v_exp := (0 => x"01", others => x"00"); + for i in 0 to 7 loop + uart_recv(v_data, s_uart_tx); + assert v_data = v_exp(i) + report "UART receive error, got 0x" & to_hstring(v_data) & ", expected 0x" & to_hstring(v_exp(i)) + severity failure; + end loop; + -- Finally read all registers again after write + v_exp := (0 => x"01", others => x"FF"); + for i in 0 to 7 loop + uart_recv(v_data, s_uart_tx); + assert v_data = v_exp(i) + report "UART receive error, got 0x" & to_hstring(v_data) & ", expected 0x" & to_hstring(v_exp(i)) severity failure; end loop; wait for 200 us; diff --git a/uart_reg/syn/Makefile b/uart_reg/syn/Makefile index 96dae39..c94102c 100644 --- a/uart_reg/syn/Makefile +++ b/uart_reg/syn/Makefile @@ -1,5 +1,5 @@ DESIGN_NAME := uart_reg -WORK_FILES := ../rtl/uart_tx.vhd ../rtl/uart_rx.vhd ../rtl/uart_reg.vhd +WORK_FILES := ../rtl/uart_ctrl.vhd ../rtl/uart_tx.vhd ../rtl/uart_rx.vhd ../rtl/uart_reg.vhd GM_FILES := ../../lib/rtl_components.vhd GHDL_FLAGS := --std=08 --workdir=build -Pbuild YOSYSPIPE := -nomx8 -retime @@ -22,7 +22,6 @@ build/gatemate-obj08.cf: ${GM_FILES} # Synthesis target for implementation ${DESIGN_NAME}.v: build/work-obj08.cf - ghdl --synth ${GHDL_FLAGS} ${DESIGN_NAME} > ${DESIGN_NAME}.vhd yosys -m ghdl -p 'ghdl ${GHDL_FLAGS} --warn-no-binding --no-formal ${DESIGN_NAME}; synth_gatemate -top $(DESIGN_NAME) ${YOSYSPIPE} -vlog $@' \ 2>&1 | tee build/yosys-report.txt