FPGA 的spi功能模块 主从回环测试
fpga的spi功能模块实现。主spi模块能够实现spi基本通讯时序,从spi模块能够配合主模块实现spi的回环测试,文章包含源代码,源代码仿真以及fpga工程
FPGA 的spi 模块主从回环测试
包含源文件、仿真文件、工程。供大家参考和使用,该文章重在代码分享,关于spi的时序便不做详细介绍,大家可以参考这篇博客了解spi时序:FPGA实现SPI接口(1)–什么是SPI接口?
一、主spi模块
1.1、 功能介绍
主spi模块能够实现spi基本通讯时序,通过参数设置能够改变发送数据的位数、时钟分频数、回读数据的位数、spi时钟sclk的极性以及spi数据端mosi的极性。能够满足大部分器件的spi驱动控制功能。
1.2、 使用说明
-
模块图

-
自定义参数介绍
- CLK_DIV : 设置输入时钟分频数,范围 (min >= 4 且 max <= 65536)
- WDATA_WITH : 设置发送数据的位宽,范围 (min >= 8 且 max <= 128)
- RDATA_WITH : 设置接收数据的位宽,范围 (min >= 8 且 max <= 128)
- SCLK_POL : 设置sclk的极性,0为低电平;1为高电平
- SCLK_POL : 设置mosi的极性,0为低电平;1为高电平
- 引脚介绍
| Port name | Direction | Description |
|---|---|---|
| clk_sys | input | 模块工作时钟 |
| rst | input | 模块复位,高电平有效 |
| start | input | 发送数据控制端,模块检测该信号上升沿时便串行发送wdata数据 |
| wdata | input | 要发送的数据,位宽通过参数WDATA_WITH自定义,默认24位 |
| readBackData | output | 接收的数据,位宽通过参数RDATA_WITH自定义,默认8位 |
| busy | output | 忙标志位,若spi正在串行发送数据,则为高电平;否则为低电平 |
| scs | output | spi的cs |
| mosi | output | spi的mosi |
| miso | input | spi的miso |
| sclk | output | spi的sclk |
- 使用举例
通过spi串行发送24位数据24’h0000aa,并回读8位数据。先确保模块设置WDATA_WITH = 24,RDATA_WITH = 8,然后将要发送的数据24’h0000aa赋给wdata(wdata = 24’h0000aa), 通过拉低start信号(start = 0),持续至少一个输入时钟周期,再拉高start信号(start = 1),触发wdata[23:0]的串行发送。发送过程中busy信号一直是高电平,当其为低电平的时候则标志数据串行发送完毕。
1.3、 源代码
spi_ctrl.v
module spi_ctrl
#(
parameter CLK_DIV = 50 , // divider cnt , min > 4; max < 65536
parameter WDATA_WITH = 24 , // send data width ,number < 128
parameter RDATA_WITH = 16 , // receive data width
parameter SCLK_POL = 1'b0, // sclk polarity
parameter MOSI_POL = 1'b0 // mosi polarity
)
(
input clk_sys,
input rst,
// spi ctrl
input start, // dsp control staring to transfer the data , posedge is vaild
input [WDATA_WITH-1:0] wdata, // parallel data input
output reg [RDATA_WITH-1:0] readBackData, // parallel data output
output reg busy, // stand for spi busy
// spi port
output reg scs ,
output reg mosi,
input miso,
output reg sclk
);
reg sck_reg ;
reg [15:0] clk_cnt ;
reg [7:0] cycle_cnt ;
reg [WDATA_WITH-1:0] wdata_reg ;
reg [WDATA_WITH-1:0] wdata_dly0 ;
reg [WDATA_WITH-1:0] readBackData_reg;
reg start_dly0, start_dly1 ;
wire start_posedge;
assign start_posedge = start_dly0 && ~start_dly1; // signal to send data
//
always @(posedge clk_sys or posedge rst) begin
if(rst == 1'b1) begin
start_dly0 <= 1'b0;
start_dly1 <= 1'b0;
wdata_dly0 <= 0;
end
else begin
start_dly0 <= start;
start_dly1 <= start_dly0;
wdata_dly0 <= wdata;
end
end
// clock divider cnt
always @(posedge clk_sys or posedge rst) begin //clk_cnt from 0 to 49 , divider
if(rst == 1'b1) begin
clk_cnt <= 0 ;
end
else if(scs == 0) begin
if(clk_cnt < CLK_DIV - 1) begin
clk_cnt <= clk_cnt + 1'b1 ;
end
else begin
clk_cnt <= 8'd0 ;
end
end
else begin
clk_cnt <= 0;
end
end
// cycle cnt
always @ (posedge clk_sys or posedge rst) begin //cycle_cnt from 0 to WDATA_WITH,stay 0 when cs is high,update when sck_posedge, chage by negedge sclk
if(rst == 1'b1) begin
cycle_cnt <= 0;
end
else if(scs == 0) begin
if(clk_cnt == CLK_DIV - 1) begin
if(cycle_cnt < WDATA_WITH) begin
cycle_cnt <= cycle_cnt + 1;
end
else begin
cycle_cnt <= 0;
end
end
else begin
cycle_cnt <= cycle_cnt;
end
end
else begin
cycle_cnt <= 0;
end
end
// scs update
always @(posedge clk_sys or posedge rst) begin //when start_posedge come, cs & SFL stay low, recover high when cycle_cnt is 24
if(rst == 1'b1) begin
scs <= 1'b1;
wdata_reg <= 1'b0;
busy <= 1'b0;
end
else begin
// CS LOW
if(start_posedge) begin
scs <= 1'b0;
wdata_reg <= wdata_dly0;
busy <= 1'b1;
end else ;
// CS HIGH
if(scs == 1'b0) begin
if((cycle_cnt == WDATA_WITH) && (clk_cnt == CLK_DIV/3 - 1)) begin // scs delay CLK_DIV time
scs <= 1'b1;
busy <= 1'b0;
end
end else ;
end
end
// sck update
always @(posedge clk_sys or posedge rst) begin
if(rst == 1'b1) begin
sclk <= SCLK_POL;
end
else if(scs == 1'b0) begin
if((clk_cnt < CLK_DIV/2 - 1)||(clk_cnt == CLK_DIV - 1)) begin
sclk <= 1'b0;
end
else if(clk_cnt >= CLK_DIV/2 - 1) begin
sclk <= 1'b1;
end
else begin
sclk <= sclk;
end
end
else begin
sclk <= SCLK_POL;
end
end
// mosi update
always @ (posedge clk_sys or posedge rst) begin
if(rst == 1'b1) begin
mosi <= MOSI_POL;
end
else if(start_posedge) begin // preset mosi
mosi <= wdata_dly0[WDATA_WITH-1];
end
else if(scs == 1'b0) begin
if(cycle_cnt < WDATA_WITH-1) begin
if(clk_cnt == CLK_DIV - 1) begin // sclk posedge
mosi <= wdata_reg[WDATA_WITH-1-1 - cycle_cnt]; // sclk negedge , mosi chage by cycle
end else;
end
else;
end
else begin
mosi <= MOSI_POL;
end
end
// miso update
always @ (posedge clk_sys or posedge rst) begin
if(rst == 1'b1) begin
readBackData_reg <= 0;
readBackData <= 0;
end
else if(scs == 1'b0) begin
if(cycle_cnt < WDATA_WITH) begin
if(clk_cnt == CLK_DIV/2 - 1) begin // sclk posedge
readBackData_reg[WDATA_WITH-1 - cycle_cnt] <= miso;
end else;
end
else;
end
else begin
readBackData <= readBackData_reg[RDATA_WITH-1 : 0]; // read data
end
end
endmodule
1.4、 仿真代码
spi_ctrl_sim.v
module spi_ctrl_sim( );
parameter WDATA_WITH = 24;
parameter RDATA_WITH = 8;
reg clk_sys ; // 50MHz
reg rst ;
reg start ;
reg [WDATA_WITH-1:0] wdata ;
wire [RDATA_WITH-1:0] readBackData ;
wire busy ;
wire scs ;
wire mosi ;
reg miso ;
wire sclk ;
// 模拟发送数据
initial begin
clk_sys = 0;
rst = 1;
start = 0;
wdata = 0;
#100
rst = 0;
#1000
// 发送第一组数据
wdata = 24'h8000_11;
start = 0;
#100
start = 1; // 上升沿开始发送数据
#100000
// 发送第二组数据
wdata = 24'h8003_54;
start = 0;
#100
start = 1; // 上升沿开始发送数据
#100000
// 发送第三组数据
wdata = 24'h0003_54;
start = 0;
#100
start = 1; // 上升沿开始发送数据
end
// 模拟读取数据
initial begin
miso = 0;
#217430 // 这个是根据仿真波形测出来的,在第三组数据中,数到第16个时钟SCLK的下降沿,便开始让miso放置数据
miso = 0;
#1000 // 根据sclk为系统时钟clk_sys的50分频,所以周期为1us
miso = 1;
#1000
miso = 0;
#1000
miso = 1;
#1000
miso = 0;
#1000
miso = 1;
#1000
miso = 1;
#1000
miso = 0;
end
always #10 clk_sys = ~clk_sys; // 50MHz
spi_ctrl
#(
.CLK_DIV (50 ),
.WDATA_WITH (WDATA_WITH ),
.RDATA_WITH (RDATA_WITH ),
.SCLK_POL (0 ),
.MOSI_POL (0 )
)
spi_ctrl_inst(
.clk_sys (clk_sys ),
.rst (rst ),
.start (start ),
.wdata (wdata ),
.readBackData (readBackData ),
.busy (busy ),
.scs (scs ),
.mosi (mosi ),
.miso (miso ),
.sclk (sclk )
);
endmodule
1.5、 仿真图
总仿真
a) WDATA_WITH = 24; RDATA_WITH = 8; SCLK_POL = 0; MOSI_POL = 0;
b) WDATA_WITH = 24; RDATA_WITH = 8; SCLK_POL = 1; MOSI_POL = 0;
c) WDATA_WITH = 24; RDATA_WITH = 8; SCLK_POL = 0; MOSI_POL = 1;
d) WDATA_WITH = 24; RDATA_WITH = 8; SCLK_POL = 1; MOSI_POL = 1;
e) 验证回读数据 (RDATA_WITH = 8)
f) 验证写数据宽度 (WDATA_WITH = 32)
二、从spi模块
2.1、 功能介绍
从spi模块功能是模仿器件的spi通讯功能,配合主模块进行spi的回环功能测试。地址从16’h00到16’h08共9个地址,地址16’h00到16’h04为可读和可写寄存器,地址16’h05到16’h08仅是可读寄存器,其中地址16’h00的寄存器,每往里面写入一个数,该数便加1再存如寄存器中。仅记模块默认地址的最高位为读写控制位,实际地址少一位。
2.2、 使用说明
-
模块图

-
自定义参数介绍
- ADDR_WITH : 设置地址位宽,该位宽注意和主spi模块对应,默认16
- DATA_WITH : 设置发送数据位宽,该位宽注意和主spi模块对应,默认8
2.3、 源代码
spi_slave.v
module spi_slave
#(
parameter ADDR_WITH = 16,
parameter DATA_WITH = 8
)
(
input clk_sys,
input rst,
// spi port
input scs ,
input mosi,
output reg miso,
input sclk
);
localparam TOTAL_WITH = DATA_WITH+ADDR_WITH;
reg [DATA_WITH-1:0] wdata ;
reg [DATA_WITH-1:0] rdata ;
reg [ADDR_WITH-1:0] addr;
reg [9:0] cnt; // bit cnt counter
reg wrReg_flag, rdReg_flag; // write or read register flag
wire rdwr_en; // read or write flag
reg sclk_reg0, sclk_reg1;
wire sclk_posedge, sclk_negedge;
assign sclk_posedge = sclk_reg0 & (~sclk_reg1); // detect sclk posedge
assign sclk_negedge = (~sclk_reg0) & sclk_reg1; // detect sclk negedge
assign rdwr_en = addr[ADDR_WITH-1]; // the highest addr bit stand for read/write flag
reg [DATA_WITH-1:0] dataReg0;
reg [DATA_WITH-1:0] dataReg1;
reg [DATA_WITH-1:0] dataReg2;
reg [DATA_WITH-1:0] dataReg3;
reg [DATA_WITH-1:0] dataReg4;
//sclk delay
always @(posedge clk_sys or posedge rst) begin
if(rst) begin
sclk_reg0 <= 0;
sclk_reg1 <= 0;
end
else begin
if(scs) begin
sclk_reg0 <= 0;
sclk_reg1 <= 0;
end
else begin
sclk_reg0 <= sclk;
sclk_reg1 <= sclk_reg0;
end
end
end
//bit cnt
always @(posedge clk_sys or posedge rst) begin
if(rst) begin
cnt <= 0;
end
else if(~scs) begin
if(sclk_posedge) begin // sclk posedge counter
cnt <= cnt + 1'b1;
end
else begin
cnt <= cnt;
end
end
else begin
cnt <= 0;
end
end
// master write addr
always @(posedge clk_sys or posedge rst) begin
if(rst) begin
addr <= 0;
end
else if(~scs) begin
if(sclk_posedge && (cnt < ADDR_WITH)) begin // record addr
addr[ADDR_WITH-1 - cnt] <= mosi;
end else;
end
else begin
addr <= addr;
end
end
// master write data
always @(posedge clk_sys or posedge rst) begin
if(rst) begin
wdata <= 0;
end
else if(~scs && (~rdwr_en)) begin
if(sclk_posedge && (cnt >= ADDR_WITH) && (cnt < TOTAL_WITH)) begin // record wdata
wdata[TOTAL_WITH-1 - cnt] <= mosi;
end else;
end
else begin
wdata <= wdata;
end
end
// master read data
always @(posedge clk_sys or posedge rst) begin
if(rst) begin
miso <= 0;
end
else if(~scs && rdwr_en) begin
if(sclk_negedge && (cnt >= ADDR_WITH) && (cnt < TOTAL_WITH)) begin
miso <= rdata[TOTAL_WITH-1 - cnt];
end else;
end
else begin
miso <= 0;
end
end
// produce write and read register signal
always @(posedge clk_sys or posedge rst) begin
if(rst) begin
wrReg_flag <= 1'b0;
rdReg_flag <= 1'b0;
end
else if(~scs && sclk_posedge && (cnt == ADDR_WITH-1) && rdwr_en) begin // when bit cnt finish addr latch
rdReg_flag <= 1'b1;
end
else if(~scs && sclk_posedge && (cnt == TOTAL_WITH-1) && (~rdwr_en)) begin // when bit cnt finish final data bit latch
wrReg_flag <= 1'b1;
end
else begin
wrReg_flag <= 1'b0;
rdReg_flag <= 1'b0;
end
end
// register operation , the addr[15] stand for write/read enable
always @(posedge clk_sys or posedge rst) begin
if(rst) begin
dataReg0 <= 0;
dataReg1 <= 0;
dataReg2 <= 0;
dataReg3 <= 0;
dataReg4 <= 0;
rdata <= 0;
end
else begin
if(wrReg_flag) begin // wirte operation
case(addr[14:0])
16'h00: dataReg0 <= wdata + 1'b1;
16'h01: dataReg1 <= wdata;
16'h02: dataReg2 <= wdata;
16'h03: dataReg3 <= wdata;
16'h04: dataReg4 <= wdata;
default : begin
dataReg0 <= dataReg0;
dataReg1 <= dataReg1;
dataReg2 <= dataReg2;
dataReg3 <= dataReg3;
dataReg4 <= dataReg4;
end
endcase
end
else if(rdReg_flag) begin // read operation
case(addr[14:0])
16'h00: rdata <= dataReg0 ;
16'h01: rdata <= dataReg1 ;
16'h02: rdata <= dataReg2 ;
16'h03: rdata <= dataReg3 ;
16'h04: rdata <= dataReg4 ;
16'h05: rdata <= 8'haa;
16'h06: rdata <= 8'h55;
16'h07: rdata <= 8'h11;
16'h08: rdata <= 8'he6;
default : begin
rdata <= rdata;
end
endcase
end
else begin
dataReg0 <= dataReg0;
dataReg1 <= dataReg1;
dataReg2 <= dataReg2;
dataReg3 <= dataReg3;
dataReg4 <= dataReg4;
rdata <= rdata;
end
end
end
endmodule
三、 spi主从回环测试仿真
3.1、 测试仿真说明
仿真有三步:
- 往寄存器16’h00写入数据,再回读寄存器16’h00的数据。理论上回读到的数据为原数据上加1。
- 往寄存器16’h02写入数据,再回读寄存器16’h02的数据。理论上回读到的数据为原数据。
- 回读寄存器16’h08的数据。理论上回读到的数据为固定数据。
3.2、 测试仿真代码
module spi_sim( );
parameter WDATA_WITH = 24;
parameter RDATA_WITH = 8;
reg clk_sys ; // 50MHz
reg rst ;
reg start ;
reg [WDATA_WITH-1:0] wdata ;
wire [RDATA_WITH-1:0] readBackData ;
wire busy ;
wire scs ;
wire mosi ;
wire miso ;
wire sclk ;
// 模拟主spi操作从spi数据 这里在从spi设定最高位为读写标志位
initial begin
clk_sys = 0;
rst = 1;
start = 0;
wdata = 0;
#100
rst = 0;
#1000
// 发送第一组数据 往地址16'h0000 写入数据8'h11
wdata = 24'h0000_11;
start = 0;
#100
start = 1; // 上升沿开始发送数据
#100000
// 发送第二组数据 往地址16'h0000 读出数据
wdata = 24'h8000_00;
start = 0;
#100
start = 1;
#100000
// 发送第三组数据 往地址16'h0002 写入数据8'h33
wdata = 24'h0002_54;
start = 0;
#100
start = 1;
#100000
// 发送第四组数据 往地址16'h0002 读取数据
wdata = 24'h8002_00;
start = 0;
#100
start = 1;
#100000
// 发送第五组数据 往地址16'h0008 读取数据
wdata = 24'h8008_00;
start = 0;
#100
start = 1;
end
// product system clock
always #10 clk_sys = ~clk_sys; // 50MHz
// master spi
spi_ctrl #(
.CLK_DIV (50 ),
.WDATA_WITH (WDATA_WITH ),
.RDATA_WITH (RDATA_WITH ),
.SCLK_POL (0 ),
.MOSI_POL (0 )
)
spi_ctrl_inst(
.clk_sys (clk_sys ),
.rst (rst ),
.start (start ),
.wdata (wdata ),
.readBackData (readBackData ),
.busy (busy ),
.scs (scs ),
.mosi (mosi ),
.miso (miso ),
.sclk (sclk )
);
// slave spi
spi_slave #(
.ADDR_WITH(16),
.DATA_WITH(8)
)
spi_slave_inst(
.clk_sys (clk_sys),
.rst (rst ),
.scs (scs ),
.mosi (mosi ),
.miso (miso ),
.sclk (sclk )
);
endmodule
3.2、 仿真结果
-
仿真图

-
仿真图波形说明
- wdata: 主spi的写入数据;
- start: 主spi的写入数据控制端,上升沿触发;
- readBackData:主spi的回读到的数据;
- dataReg0~4:从spi的寄存器的值;
fpga工程分享链接
- 百度网盘链接:
链接:https://pan.baidu.com/s/1QNF-4rUJe3wxSbYDcNHaVA
提取码:3840
至此结束,欢迎大家学习交流,如果能帮到你,可以点个赞,感谢大家~
更多推荐



所有评论(0)