diff --git a/cores/generic/fancy_spi_master.vhd b/cores/generic/fancy_spi_master.vhd new file mode 100644 index 0000000..11f0e78 --- /dev/null +++ b/cores/generic/fancy_spi_master.vhd @@ -0,0 +1,240 @@ +library ieee; +use ieee.std_logic_1164.all; +use ieee.numeric_std.all; + +entity fancy_spi_master is + generic( + TX_WIDTH_MAX : natural := 8; -- In bits + RX_WIDTH_MAX : natural := 8; -- In bits + RX_IGNORE_MAX : natural := 8; -- In bits + SPI_CPOL : std_logic; -- SPI clock polarity. '0': Idle low, '1': Idle high + SPI_CPHA : std_logic; -- SPI clock phase. '0': Sample on first edge, '1': Sample on second edge + CKDIV : natural := 8; -- Clock divider for SPI clock, minimum 2 + CS_INACTIVE_DELAY : natural := 1 -- In system clock cycles + ); + port( + -- System Signals + clk : in std_logic; -- System clock + rst : in std_logic; -- Asynchronous reset + clr : in std_logic; -- Synchronous reset + + -- TX + tx_width : in integer range 0 to TX_WIDTH_MAX; -- Width of the next word in bits, sampled when asserting tx_next and at the beginning + tx_enable : in std_logic; -- Enable transmission, taking this low for at least one clock cycle will trigger a CS inactive sequence + tx_data : in std_logic_vector(TX_WIDTH_MAX - 1 downto 0); -- Data to be transmitted, MSB first, aligned to the left when tx_width /= TX_WIDTH_MAX + tx_next : out std_logic; -- Strobe to request new data or to deassert tx_enable + + -- RX + rx_width : in integer range 0 to RX_WIDTH_MAX; -- Width of the next word in bits, sampled when asserting rx_valid and at the beginning + rx_valid : out std_logic; -- rx_data is valid, a new word has been received. + rx_data : out std_logic_vector(RX_WIDTH_MAX - 1 downto 0); -- Received data, MSB first, aligned to the right when rx_width /= RX_WIDTH_MAX + rx_ignore : in integer range 0 to RX_IGNORE_MAX; -- Bits to be ignored at the beginning of the transaction + + -- SPI HW Signals + spi_clk : out std_logic; -- SPI clock output + spi_cs_n : out std_logic; -- SPI chip select (low active) + spi_mosi : out std_logic; -- SPI MOSI output + spi_miso : in std_logic -- SPI MISO input + ); +end entity fancy_spi_master; + +architecture RTL of fancy_spi_master is + -- CKDIV + signal spi_clk_en : std_logic; + signal spi_clk_en_last : std_logic; + signal spi_clk_i : std_logic; + signal ckdiv_cnt : integer range 0 to CKDIV; + signal spi_clk_event : boolean; + + -- MAIN FSM + type state_t is (IDLE, ACTIVE, STOP); + signal state : state_t; + signal tx_width_i : integer range 0 to TX_WIDTH_MAX; + signal shiftreg_out : std_logic_vector(TX_WIDTH_MAX - 1 downto 0); + signal shiftreg_in : std_logic_vector(RX_WIDTH_MAX - 1 downto 0); + signal bit_cnt_tx : integer range 0 to CS_INACTIVE_DELAY - 1; + signal bit_cnt_rx : integer range 0 to RX_IGNORE_MAX; -- TODO: or RX_WIDTH_MAX or CS_INACTIVE_DELAY if bigger + signal rx_past_ignore : boolean; + signal rx_width_i : integer range 0 to RX_WIDTH_MAX; + signal active_cycles : boolean; + +begin + clk_gen : process(rst, clk) is + procedure default_state is + begin + null; + end procedure default_state; + procedure reset_state is + begin + default_state; + ckdiv_cnt <= 0; + spi_clk_i <= SPI_CPOL; + spi_clk_en_last <= '0'; + end procedure reset_state; + begin + if (rst = '1') then + reset_state; + elsif (rising_edge(clk)) then + default_state; + if (clr = '1') then + reset_state; + else + if (ckdiv_cnt = 0) then + spi_clk_en_last <= spi_clk_en; + if (spi_clk_en_last /= spi_clk_en) then + ckdiv_cnt <= CKDIV / 4; + else + if (spi_clk_i /= SPI_CPOL or spi_clk_en = '1') then + spi_clk_i <= not spi_clk_i; + end if; + if (spi_clk_en = '1' or spi_clk_i = SPI_CPOL) then + ckdiv_cnt <= CKDIV / 4; + end if; + end if; + else + ckdiv_cnt <= ckdiv_cnt - 1; + end if; + end if; + end if; + end process clk_gen; + spi_clk <= spi_clk_i; + spi_clk_event <= true when ckdiv_cnt = 0 and (spi_clk_en_last = spi_clk_en) else false; + + fancy_spi : process(rst, clk) is + variable tx_break : boolean; + variable tx_word_done : boolean; + variable rx_word_done : boolean; + + procedure default_state is + begin + tx_next <= '0'; + tx_word_done := false; + rx_word_done := false; + rx_valid <= '0'; + end procedure default_state; + procedure reset_state is + begin + default_state; + state <= IDLE; + tx_break := true; + bit_cnt_tx <= 0; + shiftreg_out <= (others => '0'); + shiftreg_in <= (others => '0'); + spi_cs_n <= '1'; + rx_past_ignore <= false; + rx_width_i <= 0; + tx_width_i <= 0; + bit_cnt_rx <= 0; + active_cycles <= false; + spi_clk_en <= '0'; + end procedure reset_state; + + procedure load_new_tx_word is + begin + tx_width_i <= tx_width; + shiftreg_out <= tx_data; + bit_cnt_tx <= 0; + tx_next <= '1'; + end procedure load_new_tx_word; + + procedure shift_out is + begin + shiftreg_out <= shiftreg_out(shiftreg_out'high - 1 downto 0) & '0'; + if (bit_cnt_tx = tx_width_i - 1) then + bit_cnt_tx <= 0; + tx_word_done := true; + else + bit_cnt_tx <= bit_cnt_tx + 1; + end if; + end procedure shift_out; + + procedure shift_in is + begin + if (rx_past_ignore) then + shiftreg_in <= shiftreg_in(shiftreg_in'high - 1 downto 0) & spi_miso; + if (bit_cnt_rx = rx_width_i - 1) then + bit_cnt_rx <= 0; + rx_word_done := true; + else + bit_cnt_rx <= bit_cnt_rx + 1; + end if; + else + if (bit_cnt_rx = rx_ignore - 1) then + bit_cnt_rx <= 0; + rx_past_ignore <= true; + else + bit_cnt_rx <= bit_cnt_rx + 1; + end if; + end if; + + end procedure shift_in; + + begin + if (rst = '1') then + reset_state; + elsif (rising_edge(clk)) then + default_state; + if (clr = '1') then + reset_state; + else + if (tx_enable = '0') then + tx_break := true; + end if; + case state is + when IDLE => + if (tx_enable = '1') then + if (tx_break) then + rx_width_i <= rx_width; + end if; + spi_clk_en <= '1'; + state <= ACTIVE; + tx_break := false; + load_new_tx_word; + spi_cs_n <= '0'; + end if; + when ACTIVE => + if (spi_clk_event) then + active_cycles <= true; + end if; + if (active_cycles or SPI_CPHA = '0') then + if (spi_clk_event and spi_clk_i /= SPI_CPOL) then + shift_out; + if (tx_word_done) then + if (tx_break) then + spi_clk_en <= '0'; + else + load_new_tx_word; + end if; + end if; + elsif (spi_clk_event and spi_clk_i = SPI_CPOL) then + if (spi_clk_en = '1') then + shift_in; + if (rx_word_done) then + rx_width_i <= rx_width; + rx_valid <= '1'; + end if; + else + bit_cnt_tx <= 0; + state <= STOP; + spi_cs_n <= '1'; + end if; + end if; + end if; + when STOP => + if (bit_cnt_tx = CS_INACTIVE_DELAY - 1) then + state <= IDLE; + rx_past_ignore <= false; + tx_break := true; + active_cycles <= false; + bit_cnt_rx <= 0; + else + bit_cnt_tx <= bit_cnt_tx + 1; + end if; + end case; + end if; + end if; + end process fancy_spi; + spi_mosi <= shiftreg_out(shiftreg_out'high); + rx_data <= shiftreg_in; + +end architecture RTL;