Abstract: Verilog SPI 模块设计

设计思路

使用状态机实现

在不同的 SPI 时钟边沿操作移位寄存器输出或者读入数据线上的数据

模块文件

主机

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
// SPI 主机 片选信号需要单独生成
module SPI_master #(
parameter CLK_FREQ_MHZ = 27, // 系统时钟频率
parameter BAUD_RATE_KHZ = 40, // SPI 时钟频率
parameter DATA_WIDTH = 8, // 数据位宽
parameter CPOL = 0, // 时钟极性
parameter CPHA = 0, // 时钟相位
parameter FIRST_BIT = 0 // 高位或低位优先 1为高位优先
) (
input sys_clk, // 系统时钟
input rst_n, // 低电平异步复位
input miso, // MISO
input frame_start, // 一个数据帧开始
input [DATA_WIDTH-1:0] send_data, // 待发送数据

output spi_clk, // SPI 时钟
output mosi, // MOSI
output frame_end, // 一个数据帧结束
output [DATA_WIDTH-1:0] rev_data // 接收到的数据
);

localparam IDLE_S=0, LOAD_S=1, SHIFT_S=2, DONE_S=3;
localparam BAUD_CNT_THRESHOLD = CLK_FREQ_MHZ * 1000 / BAUD_RATE_KHZ; // 波特率计数最大值

reg start_r0, start_r1;
reg spi_clk_r0, spi_clk_r1;
reg [1:0] cur_state, next_state; // 状态
reg [$clog2(DATA_WIDTH):0] data_cnt; // 数据计数
reg finish_pulse_r; // 结束信号
reg spi_clk_en; // SPI 时钟使能信号
reg [$clog2(BAUD_CNT_THRESHOLD):0] baud_rate_cnt; // 波特率计数器
reg spi_clk_r; // SPI 时钟
reg [DATA_WIDTH-1:0] shift_in_register; // 输入移位寄存器
reg [DATA_WIDTH-1:0] shift_out_register; // 输出移位寄存器

wire start_posedge, spi_clk_posegde, spi_clk_negedge; // 边沿检测
wire sample_en, shift_en; // 采样使能信号 移位使能信号

//****边沿检测****//
always @(posedge sys_clk or negedge rst_n) begin
if (~rst_n) begin
start_r0 <= 1'b0;
start_r1 <= 1'b0;
end
else begin
start_r0 <= frame_start;
start_r1 <= start_r0;
end
end

always @(posedge sys_clk or negedge rst_n) begin
if (~rst_n) begin
spi_clk_r0 <= CPOL;
spi_clk_r1 <= CPOL;
end
else begin
spi_clk_r0 <= spi_clk_r;
spi_clk_r1 <= spi_clk_r0;
end
end

assign start_posedge = start_r0 && ~start_r1; // 开始信号上升沿
assign spi_clk_posegde = spi_clk_r0 && ~spi_clk_r1; // SPI 时钟上升沿
assign spi_clk_negedge = ~spi_clk_r0 && spi_clk_r1; // SPI 时钟下降沿
//********//

//****使能信号逻辑****//
generate
case (CPOL)
0 : assign sample_en = (CPHA==1)? spi_clk_negedge&&(cur_state!=IDLE_S) : spi_clk_posegde&(cur_state!=IDLE_S);
1 : assign sample_en = (CPHA==0)? spi_clk_negedge&&(cur_state!=IDLE_S) : spi_clk_posegde&&(cur_state!=IDLE_S);
default : assign sample_en = spi_clk_posegde;
endcase
endgenerate

generate
case (CPOL)
0 : assign shift_en = (CPHA==1)? spi_clk_posegde&&(data_cnt!='d0) : spi_clk_negedge&&(data_cnt!='d0);
1 : assign shift_en = (CPHA==0)? spi_clk_posegde&&(data_cnt!='d0) : spi_clk_negedge&&(data_cnt!='d0);
default : assign shift_en = spi_clk_negedge;
endcase
endgenerate
//********//

//****状态机 状态转换****//
always @(posedge sys_clk or negedge rst_n) begin
if (~rst_n) begin
cur_state <= IDLE_S;
end
else begin
cur_state <= next_state;
end
end
//********//

//****状态机 次态确定****//
always @(*) begin
case (cur_state)
IDLE_S : begin // 空闲
next_state = (start_posedge)? LOAD_S : IDLE_S;
end
LOAD_S : begin // 数据装载
next_state = SHIFT_S;
end
SHIFT_S : begin // 交换数据
next_state = (data_cnt==DATA_WIDTH)? DONE_S : SHIFT_S;
end
DONE_S : begin // 结束
next_state =IDLE_S;
end
default : begin
next_state = IDLE_S;
end
endcase
end
//********//

//****状态机 行为逻辑****//
always @(posedge sys_clk or negedge rst_n) begin
if (~rst_n) begin
spi_clk_en <= 1'b0;
shift_in_register <= 'd0;
shift_out_register <= 'd0;
data_cnt <= 'd0;
finish_pulse_r <= 1'b0;
end
else begin
case (cur_state)
IDLE_S : begin
spi_clk_en <= 1'b0;
shift_in_register <= 'd0;
shift_out_register <= 'd0;
data_cnt <= 'd0;
finish_pulse_r <= 1'b0;
end
LOAD_S : begin
spi_clk_en <= 1'b0;
shift_in_register <= 'd0;
shift_out_register <= send_data;
data_cnt <= 'd0;
finish_pulse_r <= 1'b0;
end
SHIFT_S : begin
spi_clk_en <= 1'b1;
if (shift_en) begin
shift_out_register <= (FIRST_BIT==1)? {shift_out_register[DATA_WIDTH-2:0], 1'd0} : {1'd0, shift_out_register[DATA_WIDTH-1:1]};
end
else begin
shift_out_register <= shift_out_register;
end

if (sample_en) begin
shift_in_register <= (FIRST_BIT==1)? {shift_in_register[DATA_WIDTH-2:0], miso} : {miso, shift_in_register[DATA_WIDTH-1:1]};
data_cnt <= data_cnt+'d1;

end
else begin
shift_in_register <= shift_in_register;
data_cnt <= data_cnt;
end

finish_pulse_r <= 1'b0;
end
DONE_S : begin
spi_clk_en <= 1'b0;
shift_in_register <= shift_in_register;
shift_out_register <= shift_out_register;
data_cnt <= 'd0;
finish_pulse_r <= 1'b1;
end
default : begin
spi_clk_en <= 1'b0;
shift_in_register <= 'd0;
shift_out_register <= 'd0;
data_cnt <= 'd0;
finish_pulse_r <= 1'b0;
end
endcase
end
end

assign mosi = (FIRST_BIT==1)? shift_out_register[DATA_WIDTH-1] : shift_out_register[0];
assign frame_end = finish_pulse_r;
assign rev_data = (finish_pulse_r)? shift_in_register : 'd0;
//********//

//****SPI 时钟生成****//
always @(posedge sys_clk or negedge rst_n) begin
if (~rst_n) begin
baud_rate_cnt <= 'd0;
end
else begin
if (spi_clk_en) begin
if (baud_rate_cnt==BAUD_CNT_THRESHOLD) begin
baud_rate_cnt <= 'd0;
end
else begin
baud_rate_cnt <= baud_rate_cnt + 'd1;
end
end
else begin
baud_rate_cnt <= 'd0;
end
end
end

always @(posedge sys_clk or negedge rst_n) begin
if (~rst_n) begin
spi_clk_r <= CPOL;
end
else begin
if (baud_rate_cnt==BAUD_CNT_THRESHOLD) begin
spi_clk_r <= ~spi_clk_r;
end
else begin
spi_clk_r <= spi_clk_r;
end
end
end

assign spi_clk = spi_clk_r;
//**********//

endmodule

从机

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
// SPI 从机
module SPI_slave # (
parameter CLK_FREQ_MHZ = 27, // 系统时钟频率
parameter BAUD_RATE_KHZ = 40, // SPI 时钟频率
parameter DATA_WIDTH = 8, // 数据位宽
parameter CPOL = 0, // 时钟极性
parameter CPHA = 0, // 时钟相位
parameter FIRST_BIT = 0 // 高位或低位优先 1为高位优先
) (
input sys_clk, // 系统时钟
input rst_n, // 低电平异步复位
input spi_clk, // SPI 时钟
input mosi, // MOSI
input cs_n, // 片选 低电平有效
input [DATA_WIDTH-1:0] send_data, // 待发送数据

output miso, // MISO
output frame_end, // 一个数据帧结束
output [DATA_WIDTH-1:0] rev_data // 接收到的数据
);

localparam IDLE_S=0, LOAD_S=1, SHIFT_S=2, DONE_S=3;

reg spi_clk_r0, spi_clk_r1;
reg cs_n_r0, cs_n_r1;
reg [1:0] cur_state, next_state; // 状态
reg [$clog2(DATA_WIDTH):0] data_cnt; // 数据计数
reg [DATA_WIDTH-1:0] shift_in_register; // 输入移位寄存器
reg [DATA_WIDTH-1:0] shift_out_register; // 输出移位寄存器
reg finish_pulse_r; // 结束脉冲信号

wire spi_clk_posegde, spi_clk_negedge; // SPI 时钟边沿检测
wire cs_n_negedge; // 片选信号边沿检测
wire sample_en, shift_en; // 采样使能信号 移位使能信号

//****边沿检测****//
always @(posedge sys_clk or negedge rst_n) begin
if (~rst_n) begin
spi_clk_r0 <= CPOL;
spi_clk_r1 <= CPOL;
end
else begin
spi_clk_r0 <= spi_clk;
spi_clk_r1 <= spi_clk_r0;
end
end

always @(posedge sys_clk or negedge rst_n) begin
if (~rst_n) begin
cs_n_r0 <= 1'b1;
cs_n_r1 <= 1'b1;
end
else begin
cs_n_r0 <= cs_n;
cs_n_r1 <= cs_n_r0;
end
end

assign spi_clk_posegde = spi_clk_r0 && ~spi_clk_r1; // SPI 时钟上升沿
assign spi_clk_negedge = ~spi_clk_r0 && spi_clk_r1; // SPI 时钟下降沿
assign cs_n_negedge = ~cs_n_r0 && cs_n_r1; // 片选信号下降沿
//********//

//****使能信号逻辑****//
generate
case (CPOL)
0 : assign sample_en = (CPHA==1)? spi_clk_negedge&&(cur_state!=IDLE_S) : spi_clk_posegde&&(cur_state!=IDLE_S);
1 : assign sample_en = (CPHA==0)? spi_clk_negedge&&(cur_state!=IDLE_S) : spi_clk_posegde&&(cur_state!=IDLE_S);
default : assign sample_en = spi_clk_posegde;
endcase
endgenerate

generate
case (CPOL)
0 : assign shift_en = (CPHA==1)? spi_clk_posegde&&(data_cnt!='d0) : spi_clk_negedge&&(data_cnt!='d0);
1 : assign shift_en = (CPHA==0)? spi_clk_posegde&&(data_cnt!='d0) : spi_clk_negedge&&(data_cnt!='d0);
default : assign shift_en = spi_clk_negedge;
endcase
endgenerate
//********//

//****状态机 状态转换****//
always @(posedge sys_clk or negedge rst_n) begin
if (~rst_n) begin
cur_state <= IDLE_S;
end
else begin
cur_state <= next_state;
end
end
//********//

//****状态机 次态确定****//
always @(*) begin
case (cur_state)
IDLE_S : begin // 空闲
next_state = (cs_n_negedge)? LOAD_S : IDLE_S;
end
LOAD_S : begin // 数据装载
next_state = SHIFT_S;
end
SHIFT_S : begin // 交换数据
next_state = (data_cnt==DATA_WIDTH)? DONE_S : SHIFT_S;
end
DONE_S : begin // 结束
next_state =IDLE_S;
end
default : begin
next_state = IDLE_S;
end
endcase
end
//********//

//****状态机 行为逻辑****//
always @(posedge sys_clk or negedge rst_n) begin
if (~rst_n) begin
shift_in_register <= 'd0;
shift_out_register <= 'd0;
data_cnt <= 'd0;
finish_pulse_r <= 1'b0;
end
else begin
case (cur_state)
IDLE_S : begin
shift_in_register <= 'd0;
shift_out_register <= 'd0;
data_cnt <= 'd0;
finish_pulse_r <= 1'b0;
end
LOAD_S : begin
shift_in_register <= 'd0;
shift_out_register <= send_data;
data_cnt <= 'd0;
finish_pulse_r <= 1'b0;
end
SHIFT_S : begin
if (shift_en) begin
shift_out_register <= (FIRST_BIT==1)? {shift_out_register[DATA_WIDTH-2:0], 1'd0} : {1'd0, shift_out_register[DATA_WIDTH-1:1]};
end
else begin
shift_out_register <= shift_out_register;
end

if (sample_en) begin
shift_in_register <= (FIRST_BIT==1)? {shift_in_register[DATA_WIDTH-2:0], mosi} : {mosi, shift_in_register[DATA_WIDTH-1:1]};
data_cnt <= data_cnt+'d1;
end
else begin
shift_in_register <= shift_in_register;
data_cnt <= data_cnt;
end

finish_pulse_r <= 1'b0;
end
DONE_S : begin
shift_in_register <= shift_in_register;
shift_out_register <= shift_out_register;
data_cnt <= 'd0;
finish_pulse_r <= 1'b1;
end
default : begin
shift_in_register <= 'd0;
shift_out_register <= 'd0;
data_cnt <= 'd0;
finish_pulse_r <= 1'b0;
end
endcase
end
end

assign miso = (FIRST_BIT==1)? shift_out_register[DATA_WIDTH-1] : shift_out_register[0];
assign frame_end = finish_pulse_r;
assign rev_data = (finish_pulse_r)? shift_in_register : 'd0;
//********//

endmodule

Module-sim 测试文件

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
// SPI 模块测试
`timescale 1ns/1ns

module SPI_module_tb;

localparam CLK_FREQ_MHZ = 27; // 系统时钟频率
localparam BAUD_RATE_KHZ = 40; // SPI 时钟频率
localparam DATA_WIDTH = 8; // 数据位宽
localparam CPOL = 0; // 时钟极性
localparam CPHA = 0; // 时钟相位
localparam FIRST_BIT = 0; // 高位或低位优先 1为高位优先

reg sys_clk;
reg rst_n;
reg cs_n;
reg frame_start; // 帧开始信号 控制主机
reg [DATA_WIDTH-1:0] master_send_data; // 主机待发送数据
reg [DATA_WIDTH-1:0] slave_send_data; // 从机待发送数据

wire [DATA_WIDTH-1:0] master_rev_data; // 主机接收到数据
wire [DATA_WIDTH-1:0] slave_rev_data; // 从机接收到数据
wire master_frame_end; // 主机帧结束信号
wire slave_frame_end; // 从机帧结束信号
wire miso, mosi; // 串行数据线
wire spi_clk; // SPI 时钟

initial begin
sys_clk = 1'b0;
rst_n = 1'b0;
# 5;
rst_n = 1'b1;

forever begin // 50 MHz
sys_clk = 1'b0;
# 1;
sys_clk = 1'b1;
# 1;
end
end

initial begin
master_send_data = 'h12;
slave_send_data = 'h21;
end

initial begin
cs_n = 1'b1;
frame_start = 1'b0;

# 20;
cs_n = 1'b0;
# 5;
frame_start = 1'b1;
end

SPI_master #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ),
.BAUD_RATE_KHZ(BAUD_RATE_KHZ),
.DATA_WIDTH(DATA_WIDTH),
.CPOL(CPOL),
.CPHA(CPHA),
.FIRST_BIT(FIRST_BIT)
) spi_master_tb (
.sys_clk(sys_clk), // 系统时钟
.rst_n(rst_n), // 低电平异步复位
.miso(miso), // MISO
.frame_start(frame_start), // 一个数据帧开始
.send_data(master_send_data), // 待发送数据

.spi_clk(spi_clk), // SPI 时钟
.mosi(mosi), // MOSI
.frame_end(master_frame_end), // 一个数据帧结束
.rev_data(master_rev_data) // 接收到的数据
);

SPI_slave #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ),
.BAUD_RATE_KHZ(BAUD_RATE_KHZ),
.DATA_WIDTH(DATA_WIDTH),
.CPOL(CPOL),
.CPHA(CPHA),
.FIRST_BIT(FIRST_BIT)
) spi_slave_tb (
.sys_clk(sys_clk), // 系统时钟
.rst_n(rst_n), // 低电平异步复位
.spi_clk(spi_clk), // SPI 时钟
.mosi(mosi), // MOSI
.cs_n(cs_n), // 片选 低电平有效
.send_data(slave_send_data), // 待发送数据

.miso(miso), // MISO
.frame_end(slave_frame_end), // 一个数据帧结束
.rev_data(slave_rev_data) // 接收到的数据
);


endmodule