SPI传输位数可参数化配置。
SPI_MASTER:
`timescale 1ns / 1ps
module SPI_Master #(
parameter CLK_FREQ = 50,
parameter SPI_CLK = 1000,
parameter CPOL = 0,
parameter CPHA = 0
)(
input clk,
input rst_n,
input WrRdReq, //读/写数据请求
output WrRdReqAck,
output WrRdFinish,
input [6:0] WrRdDataBits, //输入数据位宽
input [31:0] WrData, //要写入的数据
output [31:0] RdData, //读取到的数据
//SPI接口
output SCK,
output MOSI,
input MISO,
output CS
);
//采样信号
wire datain_en;
wire dataout_en;
SPI_Master_Clock#
(
.CLK_FREQ(CLK_FREQ),
.CPOL(CPOL),
.CPHA(CPHA),
.SPI_CLK_FREQ(SPI_CLK)
)
u_SPI_Master_Clock
(
.clk(clk),
.rst_n(rst_n),
.datain_en(datain_en),
.dataout_en(dataout_en),
.SCK(SCK)
);
SPI_Master_ctrl u_SPI_Master_ctrl
(
.clk(clk),
.rst_n(rst_n),
.WrRdReq(WrRdReq),
.WrRdReqAck(WrRdReqAck),
.WrRdFinish(WrRdFinish),
.WrRdDataBits(WrRdDataBits),
.WrData(WrData),
.RdData(RdData),
.datain_en(datain_en),
.dataout_en(dataout_en),
.MOSI(MOSI),
.MISO(MISO),
.CS(CS)
);
endmodule
SPI_MATSER_CLOCK
module SPI_Master_Clock#(
parameter CLK_FREQ = 50, //MHZ
parameter CPOL = 1'b0,
parameter CPHA = 1'b0,
parameter SPI_CLK_FREQ = 1000 //Khz
)(
input clk,
input rst_n,
output datain_en, //数据采样控制信号
output dataout_en, //数据输出控制信号
output SCK
);
localparam sck_idle = CPOL;
localparam CLK_DIV_CNT = (CLK_FREQ *1000)/SPI_CLK_FREQ; //分频计数器,此处分频时钟一个周期,分频时钟计数器要计数50,50个主时钟周期
//位宽计算
function integer clogb2(input integer depth);
begin
for(clogb2 = 0;depth > 0;clogb2= clogb2 + 1)
depth = depth >> 1;
end
endfunction
reg[clogb2(CLK_DIV_CNT)-1:0] clkdiv_cnt; //[(6-1):0]
reg SPI_SCK;
reg datain_en_o = 1'b0,dataout_en_o = 1'b0;
//分频时钟计数器,计数到最大值表明经历了一个SCK周期
always@(posedge clk or negedge rst_n)begin
if(rst_n == 0)begin
clkdiv_cnt <= 1'b0;
end else if(clkdiv_cnt == CLK_DIV_CNT - 1'b1)begin
clkdiv_cnt <= 1'b0;
end else begin
clkdiv_cnt <= clkdiv_cnt + 1'b1;
end
end
//sck
always@(posedge clk or negedge rst_n)begin
if(rst_n == 0)begin
SPI_SCK <= sck_idle;
end else if((clkdiv_cnt == (CLK_DIV_CNT - 1'b1)) || (clkdiv_cnt == (CLK_DIV_CNT >> 1) -1'b1))begin
SPI_SCK <= ~SPI_SCK;
end else begin
SPI_SCK <= SPI_SCK;
end
end
//输入输出使能
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
datain_en_o <= 1'b0;
dataout_en_o <= 1'b0;
end else begin
dataout_en_o <= (clkdiv_cnt == (CLK_DIV_CNT - 'd3))?1'b1:1'b0;
datain_en_o <= (clkdiv_cnt == (CLK_DIV_CNT >> 1) - 'd1)?1'b1:1'b0;
end
end
assign datain_en = datain_en_o;
assign dataout_en = dataout_en_o;
assign SCK = SPI_SCK;
endmodule
SPI_MATSER_CTRL:
module SPI_Master_ctrl
(
input clk,
input rst_n,
input WrRdReq, //读/写数据请求
output WrRdReqAck,
output WrRdFinish,
input [6:0] WrRdDataBits, //输入数据位宽
input [31:0] WrData, //要写入的数据
output [31:0] RdData, //读取到的数据
//SPI写读控制信号
input datain_en,
input dataout_en,
//SPI接口
output MOSI,
input MISO,
output CS
);
localparam [7:0] S_IDLE =8'd0;
localparam [7:0] S_ACK =8'd1;
localparam [7:0] S_RUN =8'd2;
localparam [7:0] S_END =8'd3;
localparam [7:0] S_RST =8'd4;
reg [7:0] current_state = S_IDLE;
reg [7:0] next_state = S_IDLE;
reg CS_O = 1'b1;
reg [7:0] WrRdBitsCnt = 8'd0,WrRdBitsLatch = 8'd0; //用于计数已经发送或接收的位数 //用于存储发送或接收的数据位数
reg [31:0] WrDataLatch = 32'd0,RdDataLatch = 32'd0; //用于存储要发送的数据 //用于存储从 SPI 设备接收到的数据
//reg MOSI_o;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
current_state <= S_RST;
end else begin
current_state <= next_state;
end
end
always@(*)begin
next_state = S_RST;
case(current_state)
S_RST:next_state = S_IDLE;
S_IDLE:next_state = (WrRdReq)?S_ACK:S_IDLE;
S_ACK:next_state = (WrRdReq)?S_ACK:S_RUN;
S_RUN:next_state = (WrRdBitsCnt == WrRdBitsLatch)? S_END : S_RUN;
S_END:next_state = S_IDLE;
default:next_state = S_RST;
endcase
end
//发送数据模块
always@(posedge clk)begin
case(current_state)
S_RST,S_IDLE: WrDataLatch <= 32'd0;
S_ACK: WrDataLatch <= WrData;
S_RUN:begin
if(dataout_en)begin
WrDataLatch <= {WrDataLatch[30:0],1'b0}; //先输出高位数据出来
// MOSI_o <= WrDataLatch[31];
end else begin
WrDataLatch <= WrDataLatch;
end
end
default:WrDataLatch <= 32'd0;
endcase
end
//接收数据模块
always@(posedge clk)begin
case(current_state)
S_RST,S_ACK:RdDataLatch <=32'd0;
S_RUN:begin
if(datain_en)begin
RdDataLatch <= {RdDataLatch[30:0],MISO}; //先把高位数据输入进去
end else begin
RdDataLatch <= RdDataLatch;
end
end
default:RdDataLatch <= RdDataLatch;
endcase
end
//时钟边沿计数 已处理的位数计数器
always@(posedge clk)begin
case(current_state)
S_RST:WrRdBitsCnt <= 8'd0;
S_RUN:begin
if(datain_en || dataout_en)begin
WrRdBitsCnt <= WrRdBitsCnt + 1'b1;
end else begin
WrRdBitsCnt <= WrRdBitsCnt;
end
end
default:WrRdBitsCnt <= 8'd0;
endcase
end
//片选信号
always@(posedge clk)begin
case(current_state) //其余状态保持默认值1
S_RUN:CS_O <= 1'b0;
default:CS_O <= 1'b1;
endcase
end
//锁存要处理的数据位数
always@(posedge clk)begin
case(current_state)
S_RST:WrRdBitsLatch <= 8'd0;
S_ACK:WrRdBitsLatch <= {WrRdDataBits,1'b0};
default:WrRdBitsLatch <= WrRdBitsLatch;
endcase
end
assign WrRdFinish = (current_state == S_END);
assign RdData = RdDataLatch;
//请求ack信号
assign WrRdReqAck = (current_state == S_ACK);
assign MOSI = WrDataLatch[31];
assign CS =CS_O;
endmodule
TB:
`timescale 1ns / 1ps
module tb_SPI_Master;
// 参数定义
parameter CLK_FREQ = 50; // 时钟频率 50 MHz
parameter SPI_CLK = 1000; // SPI 时钟频率 1 MHz
parameter CPOL = 0; // 时钟极性
parameter CPHA = 0; // 时钟相位
// 信号定义
reg clk;
reg rst_n;
reg WrRdReq;
wire WrRdReqAck;
wire WrRdFinish;
reg [6:0] WrRdDataBits;
reg [31:0] SPIMasterWrData; // 主机发送的数据
wire [31:0] SPIMasterRdData; // 主机接收的数据
wire SCK;
wire MOSI;
reg MISO;
wire CS;
// 从机接收数据寄存器
reg [31:0] SPISlaveRdData = 0; // 从机接收的数据
// 从机发送数据寄存器
reg [31:0] SPISlaveWrData = 0; // 从机发送的数据
// 实例化 SPI_Master 模块
SPI_Master #(
.CLK_FREQ(CLK_FREQ),
.SPI_CLK(SPI_CLK),
.CPOL(CPOL),
.CPHA(CPHA)
) uut (
.clk(clk),
.rst_n(rst_n),
.WrRdReq(WrRdReq),
.WrRdReqAck(WrRdReqAck),
.WrRdFinish(WrRdFinish),
.WrRdDataBits(WrRdDataBits),
.WrData(SPIMasterWrData),
.RdData(SPIMasterRdData),
.SCK(SCK),
.MOSI(MOSI),
.MISO(MISO),
.CS(CS)
);
// 时钟生成
always #10 clk = ~clk;
// 模拟从机行为:接收数据
always @(posedge SCK) begin
if (CS == 0) begin
SPISlaveRdData <= {SPISlaveRdData[30:0], MOSI}; // 从机接收数据
end
end
// reg [31:0] SPISlaveWrData_o;
// 模拟从机行为:发送数据
always @(posedge SCK) begin
if (CS == 0) begin
MISO <= SPISlaveWrData[31]; // 从机发送最高位
SPISlaveWrData <= SPISlaveWrData << 1; // 数据左移
end
end
// 测试过程
initial begin
// 初始化信号
clk = 0;
rst_n = 0;
WrRdReq = 0;
WrRdDataBits = 32; // 发送 32 位数据
SPIMasterWrData = 32'h82345678; // 主机发送的数据
SPISlaveWrData = 32'h12312345;
MISO = 0;
// 复位系统
#20 rst_n = 0;
#20 rst_n = 1;
// 发送数据请求
#100 WrRdReq = 1; // 模拟发送请求
#100 WrRdReq = 0; // 结束发送请求
// 等待发送完成
wait(WrRdFinish == 1);
// 检查从机接收到的数据
if (SPISlaveRdData == 32'h82345678) begin
$display("Test Passed: SPISlaveRdData = %h", SPISlaveRdData);
end else begin
$display("Test Failed: SPISlaveRdData = %h, Expected = %h", SPISlaveRdData, 32'h82345678);
end
// 等待主机接收完成
#100;
// 检查主机接收到的数据
if (SPIMasterRdData == 32'h12312345) begin
$display("Test Passed: SPIMasterRdData = %h", SPIMasterRdData);
end else begin
$display("Test Failed: SPIMasterRdData = %h, Expected = %h", SPIMasterRdData, 32'h12312345);
end
// 结束仿真
#100 $stop;
end
endmodule
仿真图略