-
Notifications
You must be signed in to change notification settings - Fork 20
/
Copy pathpsi_common_spi_master.vhd
256 lines (232 loc) · 10.3 KB
/
psi_common_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
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
------------------------------------------------------------------------------
-- Copyright (c) 2018 by Paul Scherrer Institute, Switzerland
-- All rights reserved.
-- Authors: Oliver Bruendler
------------------------------------------------------------------------------
------------------------------------------------------------------------------
-- Description
------------------------------------------------------------------------------
-- This entity implements a simple SPI-master.
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;
use work.psi_common_math_pkg.all;
use work.psi_common_logic_pkg.all;
-- @formatter:off
entity psi_common_spi_master is
generic(clk_div_g : natural range 4 to 1_000_000; -- Must be a multiple of two
trans_width_g : positive; -- SPI Transaction width
cs_high_cycles_g : positive; -- Minimal number of CS high cycle between 2 trans
spi_cpol_g : natural range 0 to 1; -- SPI clock polarity
spi_cpha_g : natural range 0 to 1; -- SPI sampling edge config
slave_cnt_g : positive := 1; -- Number if slaves to support
lsb_first_g : boolean := false; -- False = MSB first trasnmission and True LSB
mosi_idle_state_g : std_logic := '0'; -- Idle state of the MOSI line
rst_pol_g : std_logic:= '1'; -- Reset polarity
read_bit_pol_g : std_logic := '1'; -- Polarity of Read operation in RW bit in MOSI word (needed in 3-Wires SPI)
tri_state_pol_g : std_logic := '1'; -- Polarity of tristate signal in case of a 3-Wires SPI
spi_data_pos_g : positive := 3); -- Starting bit position of Data in MOSI word (needed in 3-Wires SPI)
port( -- Control Signals
clk_i : in std_logic; -- system clock
rst_i : in std_logic; -- system reset (sync)
-- Parallel Interface
start_i : in std_logic; -- a high pulse on this line starts the trasnfer, Note that starting a transaction is only possible when *Busy* is low.
slave_i : in std_logic_vector(log2ceil(slave_cnt_g) - 1 downto 0); -- Slave number to access
busy_o : out std_logic; -- High during a transaction
done_o : out std_logic; -- Pulse that goes high for exactly one clock cycle after a transaction is done and *RdData* is valid
dat_i : in std_logic_vector(trans_width_g - 1 downto 0); -- Data to send to slave. Sampled during *Start = '1'*
dat_o : out std_logic_vector(trans_width_g - 1 downto 0); -- Data received from slave. Must be sampled during *Done = '1'* or *Busy = '0'*.
-- SPI
spi_sck_o : out std_logic; -- SPI clock
spi_mosi_o : out std_logic; -- SPI master to slave data signal
spi_miso_i : in std_logic; -- SPI slave to master data signal
spi_tri_o : out std_logic; -- SPI tri-state buffer select if 3-wires SPI is used
spi_cs_n_o : out std_logic_vector(slave_cnt_g - 1 downto 0); -- SPI slave select signal (low active)
spi_le_o : out std_logic_vector(slave_cnt_g - 1 downto 0)); -- SPI slave latch enable (high active)
end entity;
-- @formatter:on
architecture rtl of psi_common_spi_master is
-- *** Types ***
type State_t is (Idle_s, SftComp_s, ClkInact_s, ClkAct_s, CsHigh_s);
-- *** Constants ***
constant ClkDivThres_c : natural := clk_div_g / 2 - 1;
constant BitCntDataPos_c : natural := trans_width_g - spi_data_pos_g;
-- *** Two Process Method ***
type two_process_r is record
State : State_t;
StateLast : State_t;
IsRead : std_logic;
ShiftReg : std_logic_vector(trans_width_g - 1 downto 0);
dat_o : std_logic_vector(trans_width_g - 1 downto 0);
spi_cs_n_o : std_logic_vector(slave_cnt_g - 1 downto 0);
spi_le_o : std_logic_vector(slave_cnt_g - 1 downto 0);
spi_sck_o : std_logic;
spi_mosi_o : std_logic;
spi_tri_o : std_logic;
ClkDivCnt : integer range 0 to ClkDivThres_c;
BitCnt : integer range 0 to trans_width_g;
CsHighCnt : integer range 0 to cs_high_cycles_g - 1;
busy_o : std_logic;
done_o : std_logic;
MosiNext : std_logic;
end record;
signal r, r_next : two_process_r;
-- *** Functions and procedures ***
function GetClockLevel(ClkActive : boolean) return std_logic is
begin
if spi_cpol_g = 0 then
if ClkActive then
return '1';
else
return '0';
end if;
else
if ClkActive then
return '0';
else
return '1';
end if;
end if;
end function;
procedure ShiftReg(signal BeforeShift : in std_logic_vector(trans_width_g-1 downto 0);
variable AfterShift : out std_logic_vector(trans_width_g-1 downto 0);
signal InputBit : in std_logic;
variable OutputBit : out std_logic) is
begin
if lsb_first_g then
OutputBit := BeforeShift(0);
AfterShift := InputBit & BeforeShift(BeforeShift'high downto 1);
else
OutputBit := BeforeShift(BeforeShift'high);
AfterShift := BeforeShift(BeforeShift'high - 1 downto 0) & InputBit;
end if;
end procedure;
begin
--------------------------------------------------------------------------
-- Assertions
--------------------------------------------------------------------------
assert floor(real(clk_div_g) / 2.0) = ceil(real(clk_div_g) / 2.0) report "###ERROR###: psi_common_spi_master - Ratio clk_div_g must be a multiple of two" severity error;
p_comb : process(r, start_i, dat_i, spi_miso_i, slave_i)
variable v : two_process_r;
begin
-- *** hold variables stable ***
v := r;
-- *** Default Values ***
v.done_o := '0';
-- *** State Machine ***
case r.State is
when Idle_s =>
-- Start of Transfer
if start_i = '1' then
v.ShiftReg := dat_i;
v.IsRead := dat_i (trans_width_g - 1);
v.spi_cs_n_o(to_integer(unsigned(slave_i))) := '0';
v.State := SftComp_s;
v.busy_o := '1';
end if;
v.CsHighCnt := 0;
v.ClkDivCnt := 0;
v.BitCnt := 0;
v.spi_le_o := (others => '0');
when SftComp_s =>
v.State := ClkInact_s;
-- Compensate shift for CPHA 0
if spi_cpha_g = 0 then
ShiftReg(r.ShiftReg, v.ShiftReg, spi_miso_i, v.MosiNext);
end if;
when ClkInact_s =>
v.spi_sck_o := GetClockLevel(false);
-- Apply/Latch data if required
if r.ClkDivCnt = 0 then
if spi_cpha_g = 0 then
v.spi_mosi_o := r.MosiNext;
-- Only for 3-Wires SPI
if r.BitCnt = BitCntDataPos_c and r.IsRead = read_bit_pol_g then
v.spi_tri_o := tri_state_pol_g;
end if;
else
ShiftReg(r.ShiftReg, v.ShiftReg, spi_miso_i, v.MosiNext);
end if;
end if;
-- Clock period handling
if r.ClkDivCnt = ClkDivThres_c then
-- All bits done
if r.BitCnt = trans_width_g then
v.spi_mosi_o := mosi_idle_state_g;
v.State := CsHigh_s;
v.spi_le_o := not r.spi_cs_n_o;
v.spi_tri_o := not tri_state_pol_g;
-- Otherwise contintue
else
v.State := ClkAct_s;
end if;
v.ClkDivCnt := 0;
else
v.ClkDivCnt := r.ClkDivCnt + 1;
end if;
when ClkAct_s =>
v.spi_sck_o := GetClockLevel(true);
-- Apply data if required
if r.ClkDivCnt = 0 then
if spi_cpha_g = 1 then
v.spi_mosi_o := r.MosiNext;
-- Only for 3-Wires SPI
if r.BitCnt = BitCntDataPos_c and r.IsRead = read_bit_pol_g then
v.spi_tri_o := tri_state_pol_g;
end if;
else
ShiftReg(r.ShiftReg, v.ShiftReg, spi_miso_i, v.MosiNext);
end if;
end if;
-- Clock period handling
if r.ClkDivCnt = ClkDivThres_c then
v.State := ClkInact_s;
v.ClkDivCnt := 0;
v.BitCnt := r.BitCnt + 1;
else
v.ClkDivCnt := r.ClkDivCnt + 1;
end if;
when CsHigh_s =>
v.spi_mosi_o := '0';
v.spi_cs_n_o := (others => '1');
v.spi_tri_o := not tri_state_pol_g;
if r.CsHighCnt = cs_high_cycles_g - 1 then
v.State := Idle_s;
v.busy_o := '0';
v.done_o := '1';
v.dat_o := r.ShiftReg;
else
v.CsHighCnt := r.CsHighCnt + 1;
end if;
when others => null;
end case;
-- *** assign signal ***
r_next <= v;
end process;
-- Outputs
busy_o <= r.busy_o;
done_o <= r.done_o;
dat_o <= r.dat_o;
spi_sck_o <= r.spi_sck_o;
spi_cs_n_o <= r.spi_cs_n_o;
spi_mosi_o <= r.spi_mosi_o;
spi_tri_o <= r.spi_tri_o;
spi_le_o <= r.spi_le_o;
p_seq : process(clk_i)
begin
if rising_edge(clk_i) then
r <= r_next;
if rst_i = rst_pol_g then
r.State <= Idle_s;
r.spi_cs_n_o <= (others => '1');
r.spi_le_o <= (others => '0');
r.spi_sck_o <= GetClockLevel(false);
r.busy_o <= '0';
r.done_o <= '0';
r.spi_mosi_o <= mosi_idle_state_g;
r.spi_tri_o <= '0';
end if;
end if;
end process;
end architecture;