-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathspi_master.vhd
151 lines (134 loc) · 7.08 KB
/
spi_master.vhd
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
LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.std_logic_arith.all;
USE ieee.std_logic_unsigned.all;
ENTITY spi_master IS
GENERIC(
slaves : INTEGER := 1; --number of spi slaves
d_width : INTEGER := 16); --data bus width
PORT(
clock : IN STD_LOGIC; --system clock
reset_n : IN STD_LOGIC; --asynchronous reset
enable : IN STD_LOGIC; --initiate transaction
cpol : IN STD_LOGIC; --spi clock polarity
cpha : IN STD_LOGIC; --spi clock phase
cont : IN STD_LOGIC; --continuous mode command
clk_div : IN INTEGER; --system clock cycles per 1/2 period of sclk
addr : IN INTEGER; --address of slave
tx_data : IN STD_LOGIC_VECTOR(d_width-1 DOWNTO 0); --data to transmit
miso : IN STD_LOGIC; --master in, slave out
sclk : BUFFER STD_LOGIC; --spi clock
ss_n : BUFFER STD_LOGIC_VECTOR(slaves-1 DOWNTO 0); --slave select
mosi : OUT STD_LOGIC; --master out, slave in
busy : OUT STD_LOGIC; --busy / data ready signal
rx_data : OUT STD_LOGIC_VECTOR(d_width-1 DOWNTO 0)); --data received
END spi_master;
ARCHITECTURE logic OF spi_master IS
TYPE machine IS(ready, execute); --state machine data type
SIGNAL state : machine; --current state
SIGNAL slave : INTEGER; --slave selected for current transaction
SIGNAL clk_ratio : INTEGER; --current clk_div
SIGNAL count : INTEGER; --counter to trigger sclk from system clock
SIGNAL clk_toggles : INTEGER RANGE 0 TO d_width*2 + 1; --count spi clock toggles
SIGNAL assert_data : STD_LOGIC; --'1' is tx sclk toggle, '0' is rx sclk toggle
SIGNAL continue : STD_LOGIC; --flag to continue transaction
SIGNAL rx_buffer : STD_LOGIC_VECTOR(d_width-1 DOWNTO 0); --receive data buffer
SIGNAL tx_buffer : STD_LOGIC_VECTOR(d_width-1 DOWNTO 0); --transmit data buffer
SIGNAL last_bit_rx : INTEGER RANGE 0 TO d_width*2; --last rx data bit location
BEGIN
PROCESS(clock, reset_n)
BEGIN
IF(clock'EVENT AND clock = '1') THEN
IF(reset_n = '0') THEN --reset system
busy <= '1'; --set busy signal
ss_n <= (OTHERS => '1'); --deassert all slave select lines
mosi <= 'Z'; --seria como poner en 'desconectado', que el master no envia nada
rx_data <= (OTHERS => '0'); --clear receive data port
state <= ready; --go to ready state when reset is exited
ELSE
CASE state IS --state machine
WHEN ready =>
busy <= '0'; --clock out not busy signal
ss_n <= (OTHERS => '1'); --set all slave select outputs high
mosi <= 'Z'; --set mosi output high impedance
continue <= '0'; --clear continue flag
--user input to initiate transaction
IF(enable = '1') THEN
busy <= '1'; --set busy signal
IF(addr < slaves) THEN --check for valid slave address
slave <= addr; --clock in current slave selection if valid
ELSE
slave <= 0; --set to first slave if not valid
END IF;
IF(clk_div = 0) THEN --check for valid spi speed
clk_ratio <= 1; --set to maximum speed if zero
count <= 1; --initiate system-to-spi clock counter
ELSE
clk_ratio <= clk_div; --set to input selection if valid
count <= clk_div; --initiate system-to-spi clock counter
END IF;
sclk <= NOT cpol; --set spi clock polarity
assert_data <= NOT cpha; --set spi clock phase
tx_buffer <= tx_data; --clock in data for transmit into buffer
clk_toggles <= 0; --initiate clock toggle counter
last_bit_rx <= d_width*2 + conv_integer(cpha) - 1; --set last rx data bit
state <= execute; --proceed to execute state
ELSE
state <= ready; --remain in ready state
END IF;
WHEN execute =>
busy <= '1'; --set busy signal
ss_n(slave) <= '0'; --set proper slave select output
--system clock to sclk ratio is met
IF(count = clk_ratio) THEN
count <= 1; --reset system-to-spi clock counter
assert_data <= NOT assert_data; --switch transmit/receive indicator
IF(clk_toggles = d_width*2 + 1) THEN
clk_toggles <= 0; --reset spi clock toggles counter
ELSE
clk_toggles <= clk_toggles + 1; --increment spi clock toggles counter
END IF;
--spi clock toggle needed
IF(clk_toggles <= d_width*2 AND ss_n(slave) = '0') THEN
sclk <= NOT sclk; --toggle spi clock
END IF;
--receive spi clock toggle
IF(assert_data = '0' AND clk_toggles < last_bit_rx + 1 AND ss_n(slave) = '0') THEN
rx_buffer <= rx_buffer(d_width-2 DOWNTO 0) & miso; --shift in received bit
END IF;
--transmit spi clock toggle
IF(assert_data = '1' AND clk_toggles < last_bit_rx) THEN
mosi <= tx_buffer(d_width-1); --clock out data bit
tx_buffer <= tx_buffer(d_width-2 DOWNTO 0) & '0'; --shift data transmit buffer
END IF;
--last data receive, but continue
IF(clk_toggles = last_bit_rx AND cont = '1') THEN
tx_buffer <= tx_data; --reload transmit buffer
clk_toggles <= last_bit_rx - d_width*2 + 1; --reset spi clock toggle counter
continue <= '1'; --set continue flag
END IF;
--normal end of transaction, but continue
IF(continue = '1') THEN
continue <= '0'; --clear continue flag
busy <= '0'; --clock out signal that first receive data is ready
rx_data <= rx_buffer; --clock out received data to output port
END IF;
--end of transaction
IF((clk_toggles = d_width*2 + 1) AND cont = '0') THEN
busy <= '0'; --clock out not busy signal
ss_n <= (OTHERS => '1'); --set all slave selects high
mosi <= 'Z'; --set mosi output high impedance
rx_data <= rx_buffer; --clock out received data to output port
state <= ready; --return to ready state
ELSE --not end of transaction
state <= execute; --remain in execute state
END IF;
ELSE --system clock to sclk ratio not met
count <= count + 1; --increment counter
state <= execute; --remain in execute state
END IF;
END CASE;
END IF;
END IF;
END PROCESS;
END logic;