Skip to content

Commit 8e39bb7

Browse files
committed
spi: Allow multi-word transfer via FIFO
1 parent 4f38c13 commit 8e39bb7

5 files changed

Lines changed: 593 additions & 333 deletions

File tree

ip/communication/spi/spi_interface.vhd

Lines changed: 157 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@
2222
--! 3. If implementing the SPI controller, consider using clock enables at registers
2323
--! rather than gating the SPI clock output
2424
--!
25-
--!
2625
--! @license This project is released under the terms of the MIT License. See LICENSE for more details.
2726
--!
2827

2928
library ieee;
3029
use ieee.std_logic_1164.all;
3130
use ieee.numeric_std.all;
3231

32+
use work.utils_pkg.all;
33+
3334
entity spi_interface is
3435
generic (
3536
SPI_CLK_POLARITY: bit := '0'; -- Clock polarity
@@ -39,36 +40,160 @@ entity spi_interface is
3940
CONTROLLER_AND_NOT_PERIPHERAL: boolean := true;
4041
MSB_FIRST_AND_NOT_LSB: boolean := true;
4142
ENABLE_INTERNAL_CLOCK_GATING: boolean := true;
42-
USE_XILINX_CLK_GATE_AND_NOT_INTERNAL: boolean := false -- Use Xilinx clock gating instead of internal logic
43+
USE_XILINX_CLK_GATE_AND_NOT_INTERNAL: boolean := false;
44+
TX_FIFO_DEPTH_IN_BITS: natural := 4 -- Set to 0 for single-word mode
4345
);
4446
port (
4547
spi_clk_in: in std_ulogic;
4648
rst_n: in std_ulogic;
4749

48-
selected_chips: in std_ulogic_vector;
50+
selected_chips: in std_ulogic_vector(SPI_CHIPS_AMOUNT - 1 downto 0);
51+
52+
tx_fifo_write_clk: in std_ulogic;
53+
tx_fifo_write_enable: in std_ulogic;
54+
tx_fifo_write_data: in std_ulogic_vector(DATA_WIDTH - 1 downto 0);
55+
tx_fifo_write_blocked: out std_ulogic;
56+
tx_fifo_full: out std_ulogic;
57+
tx_fifo_empty: out std_ulogic;
58+
tx_fifo_words_stored: out natural range 0 to 2**TX_FIFO_DEPTH_IN_BITS;
4959

50-
tx_data: in std_ulogic_vector(DATA_WIDTH - 1 downto 0);
51-
tx_data_valid: in std_ulogic;
60+
-- Streaming control
61+
tx_trigger: in std_ulogic;
62+
spi_busy: out std_ulogic;
5263

5364
rx_data: out std_ulogic_vector(DATA_WIDTH - 1 downto 0);
5465
rx_data_valid: out std_ulogic;
5566

5667
spi_clk_out: out std_ulogic;
5768
serial_data_out: out std_logic;
5869
serial_data_in: in std_ulogic;
59-
spi_chip_select_n: inout std_ulogic_vector(SPI_CHIPS_AMOUNT - 1 downto 0);
60-
61-
tx_is_ongoing: out std_ulogic
70+
spi_chip_select_n: inout std_ulogic_vector(SPI_CHIPS_AMOUNT - 1 downto 0)
6271
);
6372
end entity;
6473

6574
architecture behavioural of spi_interface is
75+
constant CHIP_INDEX_OUT_OF_RANGE: natural := selected_chips'length;
76+
signal current_chip_index: natural range 0 to CHIP_INDEX_OUT_OF_RANGE;
77+
78+
-- FSM state
79+
type state_t is (idle, fetch_data, wait_for_data, wait_for_acknowledge, reset_for_next_chip, wait_for_transfer_end);
80+
signal state: state_t;
81+
82+
-- FIFO signals
83+
signal tx_fifo_write_enable_internal: std_ulogic;
84+
85+
signal tx_fifo_read_enable: std_ulogic;
86+
signal tx_fifo_read_data: std_ulogic_vector(DATA_WIDTH - 1 downto 0);
87+
signal tx_fifo_read_data_valid: std_ulogic;
88+
signal tx_fifo_reset_read_pointer: std_ulogic;
89+
90+
-- TX interface signals
91+
signal tx_is_ongoing: std_ulogic;
92+
signal tx_data: std_ulogic_vector(DATA_WIDTH - 1 downto 0);
93+
signal tx_data_valid: std_ulogic;
94+
signal tx_data_ack: std_ulogic;
95+
96+
signal spi_chip_select_n_internal: std_ulogic;
6697
begin
98+
fsm: process (spi_clk_in)
99+
variable current_chip_index_v: current_chip_index'subtype;
100+
variable selected_chips_reg: selected_chips'subtype;
101+
102+
-- Select only one chip at a time
103+
impure function get_next_selected_chip return natural is begin
104+
if current_chip_index_v >= CHIP_INDEX_OUT_OF_RANGE then
105+
return get_lowest_active_bit(selected_chips);
106+
end if;
107+
108+
for i in spi_chip_select_n'subtype'low to spi_chip_select_n'subtype'high loop
109+
current_chip_index_v := current_chip_index_v + 1;
110+
-- Out of range or found the next active chip
111+
exit when current_chip_index_v >= CHIP_INDEX_OUT_OF_RANGE or (?? selected_chips_reg(current_chip_index_v));
112+
end loop;
113+
114+
return current_chip_index_v;
115+
end function;
116+
begin
117+
if rising_edge(spi_clk_in) then
118+
tx_data_valid <= '0';
119+
tx_fifo_read_enable <= '0';
120+
tx_fifo_reset_read_pointer <= '0';
121+
spi_busy <= '1';
122+
123+
if rst_n = '0' then
124+
tx_fifo_write_blocked <= '0';
125+
state <= idle;
126+
current_chip_index <= 0;
127+
else
128+
case state is
129+
when idle =>
130+
spi_busy <= '0';
131+
tx_fifo_write_blocked <= '0';
132+
current_chip_index_v := CHIP_INDEX_OUT_OF_RANGE;
133+
134+
if tx_trigger and not tx_fifo_empty then
135+
selected_chips_reg := selected_chips;
136+
current_chip_index_v := get_next_selected_chip;
137+
if current_chip_index_v /= CHIP_INDEX_OUT_OF_RANGE then
138+
current_chip_index <= current_chip_index_v;
139+
tx_fifo_write_blocked <= '1';
140+
state <= fetch_data;
141+
end if;
142+
end if;
143+
when fetch_data =>
144+
if not tx_fifo_empty then
145+
tx_fifo_read_enable <= '1';
146+
state <= wait_for_data;
147+
else
148+
current_chip_index_v := get_next_selected_chip;
149+
if current_chip_index_v /= CHIP_INDEX_OUT_OF_RANGE then
150+
state <= reset_for_next_chip;
151+
elsif not tx_is_ongoing then
152+
state <= idle; -- All chips done
153+
else
154+
state <= wait_for_transfer_end; -- Wait for ongoing transfer to finish
155+
end if;
156+
end if;
157+
when wait_for_data =>
158+
if tx_fifo_read_data_valid then
159+
tx_data_valid <= '1';
160+
tx_data <= tx_fifo_read_data;
161+
state <= wait_for_acknowledge;
162+
end if;
163+
when wait_for_acknowledge =>
164+
tx_data_valid <= '1';
165+
if tx_data_ack then
166+
state <= fetch_data;
167+
end if;
168+
when reset_for_next_chip =>
169+
tx_fifo_reset_read_pointer <= '1'; -- Reset read pointer to replay data
170+
current_chip_index <= current_chip_index_v;
171+
if not tx_fifo_empty then
172+
state <= fetch_data;
173+
end if;
174+
when wait_for_transfer_end =>
175+
if not tx_is_ongoing then
176+
state <= idle; -- All chips done
177+
end if;
178+
when others =>
179+
state <= idle;
180+
end case;
181+
end if;
182+
end if;
183+
end process;
184+
185+
chip_select: process (all)
186+
begin
187+
spi_chip_select_n <= (others => '1');
188+
spi_chip_select_n(current_chip_index) <= spi_chip_select_n_internal;
189+
end process;
190+
191+
tx_fifo_write_enable_internal <= rst_n and not tx_fifo_full and not tx_fifo_write_blocked and tx_fifo_write_enable;
192+
67193
spi_tx_inst: entity work.spi_tx
68194
generic map (
69195
SPI_CLK_POLARITY => SPI_CLK_POLARITY,
70196
SPI_CLK_PHASE => SPI_CLK_PHASE,
71-
SPI_CHIPS_AMOUNT => SPI_CHIPS_AMOUNT,
72197
DATA_WIDTH => DATA_WIDTH,
73198
CONTROLLER_AND_NOT_PERIPHERAL => CONTROLLER_AND_NOT_PERIPHERAL,
74199
MSB_FIRST_AND_NOT_LSB => MSB_FIRST_AND_NOT_LSB,
@@ -78,12 +203,12 @@ begin
78203
port map (
79204
spi_clk_in => spi_clk_in,
80205
rst_n => rst_n,
81-
selected_chips => selected_chips,
82206
tx_data => tx_data,
83207
tx_data_valid => tx_data_valid,
208+
tx_data_ack => tx_data_ack,
84209
spi_clk_out => spi_clk_out,
85210
serial_data_out => serial_data_out,
86-
spi_chip_select_n => spi_chip_select_n,
211+
spi_chip_select_n => spi_chip_select_n_internal,
87212
tx_is_ongoing => tx_is_ongoing
88213
);
89214

@@ -98,8 +223,28 @@ begin
98223
spi_clk => spi_clk_in,
99224
rst_n => rst_n,
100225
serial_data_in => serial_data_in,
101-
spi_chip_select_n => and(spi_chip_select_n),
226+
spi_chip_select_n => spi_chip_select_n_internal,
102227
rx_data => rx_data,
103228
rx_data_valid => rx_data_valid
104229
);
230+
231+
fifo_async_inst: entity work.fifo_async(own_behavioural_async_fifo)
232+
generic map (
233+
FIFO_DEPTH_IN_BITS => TX_FIFO_DEPTH_IN_BITS,
234+
CDC_SYNC_STAGES => 2
235+
)
236+
port map (
237+
aclr => not rst_n,
238+
write_clk => tx_fifo_write_clk,
239+
read_clk => spi_clk_in,
240+
write_enable => tx_fifo_write_enable_internal,
241+
write_data => tx_fifo_write_data,
242+
read_enable => tx_fifo_read_enable,
243+
read_data => tx_fifo_read_data,
244+
read_data_valid => tx_fifo_read_data_valid,
245+
full => tx_fifo_full,
246+
empty => tx_fifo_empty,
247+
words_stored => tx_fifo_words_stored,
248+
reset_read_pointer => tx_fifo_reset_read_pointer
249+
);
105250
end architecture;

ip/communication/spi/spi_tx.vhd

Lines changed: 30 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ entity spi_tx is
3232
generic (
3333
SPI_CLK_POLARITY: bit := '0'; -- Clock polarity
3434
SPI_CLK_PHASE: bit := '0'; -- Clock phase
35-
SPI_CHIPS_AMOUNT: natural := 1;
3635
DATA_WIDTH: natural := 8;
3736
CONTROLLER_AND_NOT_PERIPHERAL: boolean := true;
3837
MSB_FIRST_AND_NOT_LSB: boolean := true;
@@ -43,14 +42,13 @@ entity spi_tx is
4342
spi_clk_in: in std_ulogic;
4443
rst_n: in std_ulogic;
4544

46-
selected_chips: in std_ulogic_vector;
47-
4845
tx_data: in std_ulogic_vector(DATA_WIDTH - 1 downto 0);
4946
tx_data_valid: in std_ulogic;
47+
tx_data_ack: out std_ulogic;
5048

5149
spi_clk_out: out std_ulogic;
5250
serial_data_out: out std_logic; -- For tri-state output
53-
spi_chip_select_n: inout std_ulogic_vector(SPI_CHIPS_AMOUNT - 1 downto 0);
51+
spi_chip_select_n: inout std_ulogic;
5452

5553
tx_is_ongoing: out std_ulogic
5654
);
@@ -65,88 +63,45 @@ architecture behavioural of spi_tx is
6563
use spi_pkg_constrained.all;
6664

6765
signal serial_data_out_internal: std_logic;
68-
signal spi_chip_select_n_internal: spi_chip_select_n'subtype;
66+
signal spi_chip_select_n_internal: std_ulogic;
6967
begin
70-
spi_fsm: process (spi_clk_in)
71-
type state_t is (idle, transmission, wait_for_chip_selects_deassertion);
72-
73-
constant CHIP_INDEX_OUT_OF_RANGE: natural := selected_chips'length;
74-
75-
variable state: state_t;
76-
68+
spi_tx_logic: process(spi_clk_in)
7769
variable bit_index: natural range 0 to tx_data'subtype'high;
78-
variable current_chip_index: natural range 0 to CHIP_INDEX_OUT_OF_RANGE;
7970

80-
variable selected_chips_reg: selected_chips'subtype;
8171
variable tx_data_reg: tx_data'subtype;
82-
83-
-- Select only one chip at a time
84-
impure function select_next_chip_index return natural is begin
85-
if current_chip_index >= CHIP_INDEX_OUT_OF_RANGE then
86-
return get_lowest_active_bit(selected_chips);
87-
end if;
88-
89-
for i in spi_chip_select_n'subtype'low to spi_chip_select_n'subtype'high loop
90-
current_chip_index := current_chip_index + 1;
91-
-- Out of range or found the next active chip
92-
exit when current_chip_index >= CHIP_INDEX_OUT_OF_RANGE or (?? selected_chips_reg(current_chip_index));
93-
end loop;
94-
95-
return current_chip_index;
96-
end function;
72+
variable tx_started: boolean := false;
9773
begin
74+
-- NOTE: Order of the operations is important here
9875
if rising_edge(spi_clk_in) then
99-
tx_is_ongoing <= '0';
10076
serial_data_out_internal <= 'Z';
101-
spi_chip_select_n_internal <= (others => '1');
77+
tx_data_ack <= '0';
78+
spi_chip_select_n_internal <= '1';
10279

10380
if rst_n = '0' then
104-
state := idle;
105-
current_chip_index := CHIP_INDEX_OUT_OF_RANGE;
10681
reset_bit_index(bit_index);
82+
tx_started := false;
10783
else
108-
case state is
109-
when idle =>
110-
selected_chips_reg := selected_chips;
111-
112-
if tx_data_valid then
113-
tx_data_reg := tx_data;
114-
current_chip_index := select_next_chip_index;
115-
116-
if current_chip_index /= CHIP_INDEX_OUT_OF_RANGE then
117-
state := transmission;
118-
end if;
119-
end if;
120-
when transmission =>
121-
tx_is_ongoing <= '1';
122-
serial_data_out_internal <= tx_data_reg(bit_index);
123-
spi_chip_select_n_internal(current_chip_index) <= '0';
124-
125-
if last_bit_index(bit_index) then
126-
current_chip_index := select_next_chip_index; -- Move to the next chip
127-
128-
if current_chip_index >= CHIP_INDEX_OUT_OF_RANGE then
129-
state := idle; -- Finished transmission for all chips
130-
else
131-
state := wait_for_chip_selects_deassertion;
132-
end if;
133-
end if;
134-
135-
-- This construct should optimise current_chip_index and spi_chip_select_n_internal away
136-
if CONTROLLER_AND_NOT_PERIPHERAL or (not CONTROLLER_AND_NOT_PERIPHERAL and (and(spi_chip_select_n) = '0')) then
137-
update_bit_index(bit_index);
138-
end if;
139-
when wait_for_chip_selects_deassertion =>
140-
tx_is_ongoing <= '1';
141-
142-
if and(spi_chip_select_n) = '1' then
143-
state := transmission;
144-
end if;
145-
when others =>
146-
state := idle;
147-
end case;
84+
if (?? tx_data_valid) and not tx_started then
85+
tx_data_ack <= '1';
86+
tx_data_reg := tx_data;
87+
tx_started := true;
88+
reset_bit_index(bit_index);
89+
end if;
90+
91+
if tx_started then
92+
spi_chip_select_n_internal <= '0';
93+
serial_data_out_internal <= tx_data_reg(bit_index);
94+
95+
if last_bit_index(bit_index) then
96+
tx_started := false;
97+
elsif CONTROLLER_AND_NOT_PERIPHERAL or (not CONTROLLER_AND_NOT_PERIPHERAL and (spi_chip_select_n = '0')) then
98+
update_bit_index(bit_index);
99+
end if;
100+
end if;
148101
end if;
149102
end if;
103+
104+
tx_is_ongoing <= '1' when tx_started else '0';
150105
end process;
151106

152107
-- NOTE: Xilinx doesn't recognise generic functions that have the same definitions like rising_edge/falling_edge, thus, it creates latches instead of FFs
@@ -187,7 +142,7 @@ begin
187142
end if;
188143
end process;
189144
when '1' =>
190-
alignment: process (spi_clk_in)
145+
alignment: process (spi_clk_in, spi_chip_select_n_internal)
191146
begin
192147
pass_through: spi_chip_select_n_assertion <= spi_chip_select_n_internal;
193148

@@ -217,7 +172,7 @@ begin
217172
)
218173
port map (
219174
clk_in => spi_clk_in,
220-
clk_enable => not (and(spi_chip_select_n)),
175+
clk_enable => not spi_chip_select_n,
221176
clk_out => spi_clk_out
222177
);
223178
else generate

0 commit comments

Comments
 (0)