-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathspi_master.v
248 lines (226 loc) · 7.59 KB
/
spi_master.v
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
/**
* Copyright (C) 2009 Ubixum, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
**/
// Author: Lane Brooks
// Date: 10/31/2009
// Desc: Implements a low level SPI master interface. Set the
// DATA_WIDTH parameter at instatiation. Put the data you want
// to send on the 'datai' port and strobe the 'go' signal. The
// bits of 'datai' will get serially shifted out to the device
// and the bits coming back from the device will get serially
// shifted into the 'datao' register. Hook up the 'csb',
// 'sclk', 'din', and 'dout' wires to the device. 'busy' is
// high while the shift is running and goes low when the shift
// is complete.
//
// The NUM_PORTS parameter can be used when the 'csb' and 'sclk'
// lines are shared with multiple devices and the 'din' and 'dout'
// lines are unique. For example, if you have two devices, the
// specify NUM_PORTS=2 and 'din' and 'dout' become width 2 ports
// and 'datai' and 'datao' become DATA_WIDTH*NUM_PORTS wide.
//
// Set the CLK_DIVIDER_WIDTH at instantiation. The rate of
// 'sclk' to the device is then set by the input 'clk_divider'.
// 'clk_divider' must be at least 2.
//
// The clock polarity and phasing of this master is set via the
// CPOL and CPHA inputs. See
// http://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus
// a description of these conventions.
//
//
// Modifications:
// Author: Kurt Snieckus, Eventide Inc.
// Date: 07/01/2014
// Desc: Hacked-in control signal to limit number of clock cycles to half
// DATA_WIDTH for a system which required 16 bit reads and 8 bit reads
`define SYNC_RESET
module spi_master
#(parameter DATA_WIDTH=16,
NUM_PORTS=1,
CLK_DIVIDER_WIDTH=8
)
(input clk,
input resetb,
input CPOL,
input CPHA,
input half_cycle_n,
input [CLK_DIVIDER_WIDTH-1:0] clk_divider,
input go,
input [(NUM_PORTS*DATA_WIDTH)-1:0] datai,
output [(NUM_PORTS*DATA_WIDTH)-1:0] datao,
output reg busy,
output reg done,
input [NUM_PORTS-1:0] dout,
output [NUM_PORTS-1:0] din,
output reg csb,
output reg sclk
);
reg [NUM_PORTS-1:0] dout_s;
reg [CLK_DIVIDER_WIDTH-1:0] clk_count;
wire [CLK_DIVIDER_WIDTH-1:0] next_clk_count = clk_count + 1;
wire pulse = next_clk_count == (clk_divider >> 1);
reg state;
`ifdef verilator
localparam LOG2_DATA_WIDTH = $clog2(DATA_WIDTH+1);
`else
function integer log2;
input integer value;
integer count;
begin
value = value-1;
for (count=0; value>0; count=count+1)
value = value>>1;
log2=count;
end
endfunction
localparam LOG2_DATA_WIDTH = log2(DATA_WIDTH+1);
`endif
reg [LOG2_DATA_WIDTH:0] shift_count;
wire start = shift_count == 0;
/* verilator lint_off WIDTH */
wire [31:0] stop_detect = ((half_cycle_n)+1)*DATA_WIDTH-1;
wire stop = shift_count >= stop_detect;
/* verilator lint_on WIDTH */
localparam IDLE_STATE = 0,
RUN_STATE = 1;
sro #(.DATA_WIDTH(DATA_WIDTH)) sro[NUM_PORTS-1:0]
(.clk(clk),
.resetb(resetb),
.shift(pulse && !csb && (shift_count[0] == 0)),
.dout(dout),
.datao(datao));
sri #(.DATA_WIDTH(DATA_WIDTH)) sri[NUM_PORTS-1:0]
(.clk(clk),
.resetb(resetb),
.datai(half_cycle_n ? datai : {datai[DATA_WIDTH/2-1:0], {DATA_WIDTH/2{1'b0}}}),
.sample(go && (state == IDLE_STATE)), // we condition on state so that if the user holds 'go' high, this will sample only at the start of the transfer
.shift(pulse && !csb && (shift_count[0] == 1)),
.din(din));
`ifdef SYNC_RESET
always @(posedge clk) begin
`else
always @(posedge clk or negedge resetb) begin
`endif
if(!resetb) begin
clk_count <= 0;
shift_count <= 0;
sclk <= 1;
csb <= 1;
state <= IDLE_STATE;
busy <= 0;
done <= 0;
end else begin
// generate the pulse train
if(pulse) begin
clk_count <= 0;
end else begin
clk_count <= next_clk_count;
end
// generate csb
if(state == IDLE_STATE) begin
csb <= 1;
shift_count <= 0;
done <= 0;
if(go && !busy) begin // the !busy condition here allows the user to hold go high and this will then run transactions back-to-back at maximum speed where busy drops at for at least one clock cycle but we stay in this idle state for two clock cycles. Staying in idle state for two cycles probably isn't a big deal since the serial clock is running slower anyway.
state <= RUN_STATE;
busy <= 1;
end else begin
busy <= 0;
end
end else begin
if(pulse) begin
if(stop) begin
csb <= 1;
state <= IDLE_STATE;
done <= 1;
end else begin
csb <= 0;
if(!csb) begin
shift_count <= shift_count + 1;
end
end
end
end
// generate sclk
if(pulse) begin
if((CPHA==1 && state==RUN_STATE && !stop) ||
(CPHA==0 && !csb)) begin
sclk <= !sclk;
end else begin
sclk <= CPOL;
end
end
end
end
endmodule // spi_master
module sri
// This is a shift register that sends data out to the di lines of
// spi slaves.
#(parameter DATA_WIDTH=16)
(input clk,
input resetb,
input [DATA_WIDTH-1:0] datai,
input sample,
input shift,
output din
);
reg [DATA_WIDTH-1:0] sr_reg;
assign din = sr_reg[DATA_WIDTH-1];
`ifdef SYNC_RESET
always @(posedge clk) begin
`else
always @(posedge clk or negedge resetb) begin
`endif
if(!resetb) begin
sr_reg <= 0;
end else begin
if(sample) begin
sr_reg <= datai;
end else if(shift) begin
sr_reg <= sr_reg << 1;
end
end
end
endmodule
module sro
// This is a shift register that receives data on the dout lines
// from spi slaves.
#(parameter DATA_WIDTH=16)
(input clk,
input resetb,
input shift,
input dout,
output reg [DATA_WIDTH-1:0] datao
);
reg dout_s;
`ifdef SYNC_RESET
always @(posedge clk) begin
`else
always @(posedge clk or negedge resetb) begin
`endif
if(!resetb) begin
dout_s <= 0;
datao <= 0;
end else begin
dout_s <= dout;
if(shift) begin
datao <= { datao[DATA_WIDTH-2:0], dout_s };
end
end
end
endmodule