FPGA 的spi 模块主从回环测试

包含源文件、仿真文件、工程。供大家参考和使用,该文章重在代码分享,关于spi的时序便不做详细介绍,大家可以参考这篇博客了解spi时序:FPGA实现SPI接口(1)–什么是SPI接口?

一、主spi模块

1.1、 功能介绍

主spi模块能够实现spi基本通讯时序,通过参数设置能够改变发送数据的位数、时钟分频数、回读数据的位数、spi时钟sclk的极性以及spi数据端mosi的极性。能够满足大部分器件的spi驱动控制功能。

1.2、 使用说明
  • 模块图
    在这里插入图片描述

  • 自定义参数介绍

  1. CLK_DIV : 设置输入时钟分频数,范围 (min >= 4 且 max <= 65536)
  2. WDATA_WITH : 设置发送数据的位宽,范围 (min >= 8 且 max <= 128)
  3. RDATA_WITH : 设置接收数据的位宽,范围 (min >= 8 且 max <= 128)
  4. SCLK_POL : 设置sclk的极性,0为低电平;1为高电平
  5. 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、 使用说明
  • 模块图
    在这里插入图片描述

  • 自定义参数介绍

  1. ADDR_WITH : 设置地址位宽,该位宽注意和主spi模块对应,默认16
  2. 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、 测试仿真说明

仿真有三步:

  1. 往寄存器16’h00写入数据,再回读寄存器16’h00的数据。理论上回读到的数据为原数据上加1。
  2. 往寄存器16’h02写入数据,再回读寄存器16’h02的数据。理论上回读到的数据为原数据。
  3. 回读寄存器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、 仿真结果
  • 仿真图
    在这里插入图片描述

  • 仿真图波形说明

  1. wdata: 主spi的写入数据;
  2. start: 主spi的写入数据控制端,上升沿触发;
  3. readBackData:主spi的回读到的数据;
  4. dataReg0~4:从spi的寄存器的值;

fpga工程分享链接

  • 百度网盘链接:
    链接:https://pan.baidu.com/s/1QNF-4rUJe3wxSbYDcNHaVA
    提取码:3840

至此结束,欢迎大家学习交流,如果能帮到你,可以点个赞,感谢大家~

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐