Abstract: Verilog SPI 模块设计
目录:
设计思路
使用状态机实现
在不同的 SPI 时钟边沿操作移位寄存器输出或者读入数据线上的数据
模块文件
主机
// 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
从机
// 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 测试文件
// 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