library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.spi_pkg.all;
entity spi_master_ctrl is
generic (
SIZES : natural_vector := (8, 16);
SIZES_2LOG : natural := 1;
DIVISORS : natural_vector := (2, 4, 6, 8, 16, 32, 64, 128, 256);
DIVISORS_LOG2 : natural := 3;
CSN_PULSE_CYCLES : natural := 1
);
port (
clk_i : in std_logic;
rst_in : in std_logic;
en_i : in std_logic;
size_sel_i : in std_logic_vector(SIZES_2LOG - 1 downto 0);
div_sel_i : in std_logic_vector(DIVISORS_LOG2 - 1 downto 0);
pulse_csn_i : in std_logic;
clock_phase_i : in std_logic;
counter_overflow_i : in std_logic;
rx_block_on_full_i : in std_logic;
rx_en_i : in std_logic;
rx_valid_o : out std_logic;
rx_ready_i : in std_logic;
tx_en_i : in std_logic;
tx_valid_i : in std_logic;
tx_ready_o : out std_logic;
busy_o : out std_logic;
err_lost_rx_data_o : out std_logic;
clear_lost_rx_data_i : in std_logic;
rst_on : out std_logic;
csn_o : out std_logic;
csn_en_o : out std_logic;
mosi_en_o : out std_logic;
miso_en_o : out std_logic;
sck_mask_o : out std_logic;
sck_en_o : out std_logic;
gen_clk_en_o : out std_logic;
latch_tx_data_o : out std_logic
);
end entity spi_master_ctrl;
architecture a1 of spi_master_ctrl is
constant MAX_SIZE : natural := get_max_natural(SIZES);
constant MAX_DIVISOR : natural := get_max_natural(DIVISORS);
type states_t is (RESET, IDLE, SHIFTING, NEXT_DATA, CSN_RISING);
type tx_states_t is (IDLE, TX_LATCHING_DATA, TX_LATCHED, TX_WAITING);
type rx_states_t is (IDLE, RX_GOT_DATA, RX_INVALID_DATA);
signal rx_block : std_logic;
signal curr_rx_state : rx_states_t;
signal next_rx_state : rx_states_t;
signal curr_tx_state : tx_states_t;
signal next_tx_state : tx_states_t;
signal curr_state : states_t;
signal next_state : states_t;
signal curr_counter : natural;
signal next_counter : natural;
signal set_lost_rx_data : std_logic;
signal tx_got_data : std_logic;
signal ack_tx_got_data : std_logic;
signal transmission_done : std_logic;
signal shifting_length : integer range 0 to MAX_SIZE * 2;
signal selected_divisor : integer range 0 to MAX_DIVISOR;
signal clear_lost_rx_data : std_logic;
begin -- architecture a1
registers: process (clk_i) is
begin -- process registers
if rising_edge(clk_i) then -- rising clock edge
if rst_in = '0' then -- synchronous reset (active low)
curr_counter <= 0;
curr_state <= RESET;
curr_tx_state <= IDLE;
curr_rx_state <= IDLE;
else
curr_counter <= next_counter;
curr_state <= next_state;
curr_tx_state <= next_tx_state;
curr_rx_state <= next_rx_state;
end if;
end if;
end process registers;
state: process (all) is
procedure switch_to (
constant state : in states_t;
constant counter : in natural) is
begin -- procedure switch_to
next_state <= state;
next_counter <= counter;
end procedure switch_to;
procedure switch_to_shifting(constant is_next_data: boolean) is
variable count : natural;
begin -- procedure switch_to_shifting
if is_next_data then
if selected_divisor = 2 then
count := shifting_length * 2 - 2;
else
count := shifting_length * 2;
end if;
else
count := shifting_length * 2 - 1;
if clock_phase_i = '1' then
count := count + 1;
end if;
end if;
switch_to(SHIFTING, count);
end procedure switch_to_shifting;
variable zero : std_logic;
begin -- process state_sel
next_counter <= curr_counter;
if curr_counter /= 0 and counter_overflow_i = '1' then
next_counter <= curr_counter - 1;
end if;
if curr_counter = 0 then
zero := '1';
else
zero := '0';
end if;
transmission_done <= '0';
next_state <= curr_state;
gen_clk_en_o <= '1';
ack_tx_got_data <= '0';
rst_on <= '1';
sck_mask_o <= '1';
busy_o <= '1';
csn_o <= '1';
case curr_state is
when RESET =>
switch_to(IDLE, 0);
next_state <= IDLE;
rst_on <= '0';
gen_clk_en_o <= '0';
csn_o <= '1';
when IDLE =>
busy_o <= '0';
gen_clk_en_o <= '0';
if zero = '1' and tx_got_data = '1' then
switch_to_shifting(false);
gen_clk_en_o <= '1';
ack_tx_got_data <= '1';
end if;
when SHIFTING =>
csn_o <= '0';
sck_mask_o <= '1';
if zero = '1' then
transmission_done <= '1';
if counter_overflow_i = '0' then
switch_to(NEXT_DATA, 2);
else
switch_to(NEXT_DATA, 0);
end if;
end if;
when NEXT_DATA =>
csn_o <= '0';
sck_mask_o <= '0';
if pulse_csn_i = '1' then
switch_to(IDLE, CSN_PULSE_CYCLES - 1);
elsif tx_got_data = '1' then
sck_mask_o <= '1';
switch_to_shifting(true);
ack_tx_got_data <= '1';
elsif zero = '1' then
switch_to(IDLE, 0);
end if;
when others =>
next_state <= RESET;
end case;
if en_i = '0' then
next_state <= RESET;
end if;
end process state;
tx_state: process(all) is
begin -- process tx_state
next_tx_state <= curr_tx_state;
latch_tx_data_o <= '0';
tx_got_data <= '0';
tx_ready_o <= '0';
case curr_tx_state is
when IDLE =>
next_tx_state <= TX_LATCHING_DATA;
when TX_LATCHING_DATA =>
tx_ready_o <= '1';
if tx_valid_i = '1' then
latch_tx_data_o <= '1';
next_tx_state <= TX_LATCHED;
if ack_tx_got_data = '1' then
next_tx_state <= TX_WAITING;
end if;
end if;
when TX_LATCHED =>
tx_got_data <= '1';
if ack_tx_got_data = '1' then
next_tx_state <= TX_WAITING;
end if;
when TX_WAITING =>
if (transmission_done = '1' or curr_state /= SHIFTING) and rx_block = '0' then
-- prevent pulse...
if rx_ready_i = '1' or rx_block_on_full_i = '0' then
tx_ready_o <= '1';
end if;
next_tx_state <= TX_LATCHING_DATA;
if tx_valid_i = '1' then
next_tx_state <= TX_LATCHED;
latch_tx_data_o <= '1';
end if;
end if;
when others =>
next_tx_state <= IDLE;
end case;
if curr_state = RESET then
next_tx_state <= IDLE;
end if;
if tx_en_i = '0' then
next_tx_state <= IDLE;
tx_got_data <= not rx_block and tx_valid_i; -- simulate always receiving new data
end if;
end process tx_state;
rx_state: process(all) is
begin -- process rx_state
next_rx_state <= curr_rx_state;
rx_block <= rx_block_on_full_i;
rx_valid_o <= '0';
set_lost_rx_data <= '0';
case curr_rx_state is
when IDLE =>
next_rx_state <= RX_INVALID_DATA;
rx_block <= '0';
when RX_GOT_DATA =>
rx_valid_o <= '1';
if rx_ready_i = '1' or tx_got_data = '1' then
next_rx_state <= RX_INVALID_DATA;
rx_block <= '0';
if tx_got_data = '1' then
rx_valid_o <= '0';
end if;
if rx_ready_i = '0' then
set_lost_rx_data <= '1';
end if;
end if;
when RX_INVALID_DATA =>
rx_block <= '0';
if transmission_done = '1' then
if rx_ready_i = '0' then
rx_block <= rx_block_on_full_i;
end if;
if rx_ready_i = '0' then
next_rx_state <= RX_GOT_DATA;
end if;
rx_valid_o <= '1'; -- TODO check
end if;
when others =>
next_rx_state <= IDLE;
end case;
if curr_state = RESET then
next_rx_state <= IDLE;
end if;
if rx_en_i = '0' then
next_rx_state <= IDLE;
rx_block <= '0'; -- do not block if disabled
end if;
end process rx_state;
error_rx_lost : entity work.rs_latch
port map (
set_i => set_lost_rx_data,
reset_i => clear_lost_rx_data,
q_o => err_lost_rx_data_o);
-- Internal
clear_lost_rx_data <= '1' when clear_lost_rx_data_i = '1' or curr_state = RESET else '0';
shifting_length <= SIZES(to_integer(unsigned(size_sel_i)));
selected_divisor <= DIVISORS(to_integer(unsigned(div_sel_i)));
-- Enable Outputs
miso_en_o <= '0';
sck_en_o <= en_i;
mosi_en_o <= en_i and tx_en_i;
csn_en_o <= en_i;
sck_en_o <= en_i; -- TODO make it configurable so sck can be Z when not commnicating
end architecture a1;