-
Notifications
You must be signed in to change notification settings - Fork 100
/
spi_rxtx.vhdl
394 lines (343 loc) · 12.3 KB
/
spi_rxtx.vhdl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
library work;
use work.wishbone_types.all;
entity spi_rxtx is
generic (
DATA_LINES : positive := 1; -- Number of data lines
-- 1=MISO/MOSI, otherwise 2 or 4
INPUT_DELAY : natural range 0 to 1 := 1 -- Delay latching of SPI input:
-- 0=no delay, 1=clk/2
);
port (
clk : in std_ulogic;
rst : in std_ulogic;
--
-- Clock divider
-- SCK = CLK/((CLK_DIV+1)*2) : 0=CLK/2, 1=CLK/4, 2=CLK/6....
--
-- This need to be changed before a command.
-- XX TODO add handshake
clk_div_i : in natural range 0 to 255;
--
-- Command port (includes write data)
--
-- Valid & ready: command sampled when valid=1 and ready=1
cmd_valid_i : in std_ulogic;
cmd_ready_o : out std_ulogic;
-- Command modes:
-- 000 : Single bit read+write
-- 010 : Single bit read
-- 011 : Single bit write
-- 100 : Dual read
-- 101 : Dual write
-- 110 : Quad read
-- 111 : Quad write
cmd_mode_i : in std_ulogic_vector(2 downto 0);
-- # clocks-1 in a command (#bits-1)
cmd_clks_i : in std_ulogic_vector(2 downto 0);
-- Write data (sampled with command)
cmd_txd_i : in std_ulogic_vector(7 downto 0);
--
-- Read data port. Data valid when d_ack=1, no ready
-- signal, receiver must be ready
--
d_rxd_o : out std_ulogic_vector(7 downto 0);
d_ack_o : out std_ulogic := '0';
-- Set when all commands are done. Needed for callers to know when
-- to release CS#
bus_idle_o : out std_ulogic;
--
-- SPI port. These might need to go into special IOBUFs or STARTUPE2 on
-- Xilinx.
--
-- Data lines are organized as follow:
--
-- DATA_LINES = 1
--
-- sdat_o(0) is MOSI (master output slave input)
-- sdat_i(0) is MISO (master input slave output)
--
-- DATA_LINES > 1
--
-- sdat_o(0..n) are DQ(0..n)
-- sdat_i(0..n) are DQ(0..n)
--
-- as such, beware that:
--
-- sdat_o(0) is MOSI (master output slave input)
-- sdat_i(1) is MISO (master input slave output)
--
-- In order to leave dealing with the details of how to wire the tristate
-- and bidirectional pins to the system specific toplevel, we separate
-- the input and output signals, and provide a "sdat_oe" signal which
-- is the "output enable" of each line.
--
sck : out std_ulogic;
sdat_o : out std_ulogic_vector(DATA_LINES-1 downto 0);
sdat_oe : out std_ulogic_vector(DATA_LINES-1 downto 0);
sdat_i : in std_ulogic_vector(DATA_LINES-1 downto 0)
);
end entity spi_rxtx;
architecture rtl of spi_rxtx is
-- Internal clock signal. Output is gated by sck_en_int
signal sck_0 : std_ulogic;
signal sck_1 : std_ulogic;
-- Clock divider latch
signal clk_div : natural range 0 to 255;
-- 1 clk pulses indicating when to send and when to latch
--
-- Typically for CPOL=CPHA
-- sck_send is sck falling edge
-- sck_recv is sck rising edge
--
-- Those pulses are generated "ahead" of the corresponding
-- edge so then are "seen" at the rising sysclk edge matching
-- the corresponding sck edgeg.
signal sck_send : std_ulogic;
signal sck_recv : std_ulogic;
-- Command mode latch
signal cmd_mode : std_ulogic_vector(2 downto 0);
-- Output shift register (use fifo ?)
signal oreg : std_ulogic_vector(7 downto 0);
-- Input latch
signal dat_i_l : std_ulogic_vector(DATA_LINES-1 downto 0);
-- Data ack latch
signal dat_ack_l : std_ulogic;
-- Delayed recv signal for the read machine
signal sck_recv_d : std_ulogic;
-- Input shift register (use fifo ?)
signal ireg : std_ulogic_vector(7 downto 0);
-- Bit counter
signal bit_count : std_ulogic_vector(2 downto 0);
-- Next/start/stop command signals. Set when counter goes negative
signal next_cmd : std_ulogic;
signal start_cmd : std_ulogic;
signal end_cmd : std_ulogic;
function data_single(mode : std_ulogic_vector(2 downto 0)) return boolean is
begin
return mode(2) = '0';
end;
function data_dual(mode : std_ulogic_vector(2 downto 0)) return boolean is
begin
return mode(2 downto 1) = "10";
end;
function data_quad(mode : std_ulogic_vector(2 downto 0)) return boolean is
begin
return mode(2 downto 1) = "11";
end;
function data_write(mode : std_ulogic_vector(2 downto 0)) return boolean is
begin
return mode(0) = '1';
end;
type state_t is (STANDBY, DATA);
signal state : state_t;
begin
-- We don't support multiple data lines at this point
assert DATA_LINES = 1 or DATA_LINES = 2 or DATA_LINES = 4
report "Unsupported DATA_LINES configuration !" severity failure;
-- Clock generation
--
-- XX HARD WIRE CPOL=1 CPHA=1 for now
sck_gen: process(clk)
variable counter : integer range 0 to 255;
begin
if rising_edge(clk) then
if rst = '1' then
sck_0 <= '1';
sck_1 <= '1';
sck_send <= '0';
sck_recv <= '0';
clk_div <= 0;
counter := 0;
elsif counter = clk_div then
counter := 0;
-- Latch new divider
clk_div <= clk_div_i;
-- Internal version of the clock
sck_0 <= not sck_0;
-- Generate send/receive pulses to run out state machine
sck_recv <= not sck_0;
sck_send <= sck_0;
else
counter := counter + 1;
sck_recv <= '0';
sck_send <= '0';
end if;
-- Delayed version of the clock to line up with
-- the up/down signals
--
-- XXX Figure out a better way
if (state = DATA and end_cmd = '0') or (next_cmd = '1' and cmd_valid_i = '1') then
sck_1 <= sck_0;
else
sck_1 <= '1';
end if;
end if;
end process;
-- SPI clock
sck <= sck_1;
-- Ready to start the next command. This is set on the clock down
-- after the counter goes negative.
-- Note: in addition to latching a new command, this will cause
-- the counter to be reloaded.
next_cmd <= '1' when sck_send = '1' and bit_count = "111" else '0';
-- We start a command when we have a valid request at that time.
start_cmd <= next_cmd and cmd_valid_i;
-- We end commands if we get start_cmd and there's nothing to
-- start. This sends up to standby holding CLK high
end_cmd <= next_cmd and not cmd_valid_i;
-- Generate cmd_ready. It will go up and down with sck, we could
-- gate it with cmd_valid to make it look cleaner but that would
-- add yet another combinational loop on the wishbone that I'm
-- to avoid.
cmd_ready_o <= next_cmd;
-- Generate bus_idle_o
bus_idle_o <= '1' when state = STANDBY else '0';
-- Main state machine. Also generates cmd and data ACKs
machine: process(clk)
begin
if rising_edge(clk) then
if rst = '1' then
state <= STANDBY;
cmd_mode <= "000";
else
-- First clk down of a new cycle. Latch a request if any
-- or get out.
if start_cmd = '1' then
state <= DATA;
cmd_mode <= cmd_mode_i;
elsif end_cmd = '1' then
state <= STANDBY;
end if;
end if;
end if;
end process;
-- Run the bit counter in DATA state. It will update on rising
-- SCK edges. It starts at d_clks on command latch
count_bit: process(clk)
begin
if rising_edge(clk) then
if rst = '1' then
bit_count <= (others => '0');
else
if start_cmd = '1' then
bit_count <= cmd_clks_i;
elsif state /= DATA then
bit_count <= (others => '1');
elsif sck_recv = '1' then
bit_count <= std_ulogic_vector(unsigned(bit_count) - 1);
end if;
end if;
end if;
end process;
-- Shift output data
shift_out: process(clk)
begin
if rising_edge(clk) then
-- Starting a command
if start_cmd = '1' then
oreg <= cmd_txd_i(7 downto 0);
elsif sck_send = '1' then
-- Get shift amount
if data_single(cmd_mode) then
oreg <= oreg(6 downto 0) & '0';
elsif data_dual(cmd_mode) then
oreg <= oreg(5 downto 0) & "00";
else
oreg <= oreg(3 downto 0) & "0000";
end if;
end if;
end if;
end process;
-- Data out
sdat_o(0) <= oreg(7);
dl2: if DATA_LINES > 1 generate
sdat_o(1) <= oreg(6);
end generate;
dl4: if DATA_LINES > 2 generate
sdat_o(2) <= oreg(5);
sdat_o(3) <= oreg(4);
end generate;
-- Data lines direction
dlines: process(all)
begin
for i in DATA_LINES-1 downto 0 loop
sdat_oe(i) <= '0';
if state = DATA then
-- In single mode, we always enable MOSI, otherwise
-- we control the output enable based on the direction
-- of transfer.
--
if i = 0 and (data_single(cmd_mode) or data_write(cmd_mode)) then
sdat_oe(i) <= '1';
end if;
if i = 1 and data_dual(cmd_mode) and data_write(cmd_mode) then
sdat_oe(i) <= '1';
end if;
if i > 0 and data_quad(cmd_mode) and data_write(cmd_mode) then
sdat_oe(i) <= '1';
end if;
end if;
end loop;
end process;
-- Latch input data no delay
input_delay_0: if INPUT_DELAY = 0 generate
process(clk)
begin
if rising_edge(clk) then
dat_i_l <= sdat_i;
end if;
end process;
end generate;
-- Latch input data half clock delay
input_delay_1: if INPUT_DELAY = 1 generate
process(clk)
begin
if falling_edge(clk) then
dat_i_l <= sdat_i;
end if;
end process;
end generate;
-- Shift input data
shift_in: process(clk)
begin
if rising_edge(clk) then
if rst = '1' then
ireg <= (others => '0');
end if;
-- Delay the receive signal to match the input latch
if state = DATA then
sck_recv_d <= sck_recv;
else
sck_recv_d <= '0';
end if;
-- Generate read data acks
if bit_count = "000" and sck_recv = '1' then
dat_ack_l <= not cmd_mode(0);
else
dat_ack_l <= '0';
end if;
-- And delay them as well
d_ack_o <= dat_ack_l;
-- Shift register on delayed data & receive signal
if sck_recv_d = '1' then
if DATA_LINES = 1 then
ireg <= ireg(6 downto 0) & dat_i_l(0);
else
if data_dual(cmd_mode) then
ireg <= ireg(5 downto 0) & dat_i_l(1) & dat_i_l(0);
elsif data_quad(cmd_mode) then
ireg <= ireg(3 downto 0) & dat_i_l(3) & dat_i_l(2) & dat_i_l(1) & dat_i_l(0);
else
assert(data_single(cmd_mode));
ireg <= ireg(6 downto 0) & dat_i_l(1);
end if;
end if;
end if;
end if;
end process;
-- Data recieve register
d_rxd_o <= ireg;
end architecture;