FPGA实现SPI功能、Verilog编程(代码篇)

一、前言

关于SPI的基础知识和我的实现方法在我的另一篇文章中已经进行了阐述,这里就不作介绍了。详细信息可以查看
链接:FPGA实现SPI协议、verilog编程、SPI知识梳理(讲解篇)

二、代码

主功能代码

说明:根据需求取消对应的宏定义以实现相应功能
例如想要三线制SPI功能,则宏定义部分如下:

//==========================================
//SPI线制定义 |三线制 或者 四线制
//==========================================
   `define four_wire  1 //四线制SPI申明
   // `define three_wire 1 //三线制SPI申明
   `ifdef four_wire
         //==========================================
         //四线SPI类型定义 |全双工类型 或者 半双工类型
         //==========================================
            // `define half_duplex 1 //半双工类型
            // `define full_duplex 1 //全双工类型
    `endif
//==========================================

例如想要四线制半双工SPI功能,则宏定义部分如下:

//==========================================
//SPI线制定义 |三线制 或者 四线制
//==========================================
   // `define four_wire  1 //四线制SPI申明
   `define three_wire 1 //三线制SPI申明
   `ifdef four_wire
         //==========================================
         //四线SPI类型定义 |全双工类型 或者 半双工类型
         //==========================================
            `define half_duplex 1 //半双工类型
            // `define full_duplex 1 //全双工类型
    `endif
//==========================================

// Engineer       : Zheng LiGuo
// File name      : spi_modu
// Create time    : 2024/11/01
// Last modified  : 2024/11/06
// Last version   : V1.0
// Desoriptions   : SPI主机驱动模块

//==========================================
//SPI线制定义 |三线制 或者 四线制
//==========================================
   // `define four_wire  1 //四线制SPI申明
   // `define three_wire 1 //三线制SPI申明
   `ifdef four_wire
         //==========================================
         //四线SPI类型定义 |全双工类型 或者 半双工类型
         //==========================================
            // `define half_duplex 1 //半双工类型
            // `define full_duplex 1 //全双工类型
    `endif
//==========================================
`resetall 
`timescale 1ns/1ps 
module spi_modu #(
   parameter   CLK_FREQ  = 'd50  ,  // 模块时钟频率 |单位:Mhz
   parameter   SPI_FREQ  = 'd100 ,  // SPI通信频率 |单位:khz
   parameter   DATA_SIDE = "MSB" ,  // MSB:大端模式 LSB:小端模式
   parameter   BIT_NUM   = 'd8   ,  // 数据长度 |单位bit
   parameter   CPOL      = 'd0   ,  // 时钟极性
   parameter   CPHA      = 'd0   ,  // 时钟相位
   localparam  W1 = BIT_NUM-1    
)(
   input  wire          clk         ,  // input  wire [0        :0] |模块时钟输入
   input  wire          reset       ,  // input  wire [0        :0] |模块复位输入 |0:无效 1:复位
`ifdef four_wire
 `ifdef full_duplex
   input  wire          swop_en     ,  // input  wire [0        :0] |开始交换使能
   input  wire [W1:0]   send_data   ,  // input  wire [BIT_NUM-1:0] |发送数据输入
   output reg           recv_vld    ,  // output reg  [0        :0] |接收数据有效标志
   output reg  [W1:0]   recv_data   ,  // output reg  [BIT_NUM-1:0] |接收数据输出
   output reg           swop_done   ,  // output reg  [0        :0] |交换结束标志
   output reg           comm_done   ,  // output reg  [0        :0] |通信结束标志

   output reg           spi_cs_n    ,  // output reg  [0        :0] |设备片选
   output reg           spi_sclk    ,  // output reg  [0        :0] |通信时钟
   output reg           spi_mosi    ,  // output reg  [0        :0] |数据输出
   input  wire          spi_miso       // input  wire [0        :0] |数据输入
 `elsif half_duplex
   input  wire          send_en     ,  // input  wire [0        :0] |发送数据使能
   input  wire [W1:0]   send_data   ,  // input  wire [BIT_NUM-1:0] |发送数据输入
   output reg           send_done   ,  // output reg  [0        :0] |发送数据结束标志
   input  wire          recv_en     ,  // input  wire [0        :0] |接收数据使能
   output reg           recv_vld    ,  // output reg  [0        :0] |接收数据有效标志
   output reg  [W1:0]   recv_data   ,  // output reg  [BIT_NUM-1:0] |接收数据输出
   output reg           recv_done   ,  // output reg  [0        :0] |接收数据结束标志
   output reg           comm_done   ,  // output reg  [0        :0] |通信结束标志

   output reg           spi_cs_n    ,  // output reg  [0        :0] |设备片选
   output reg           spi_sclk    ,  // output reg  [0        :0] |通信时钟
   output reg           spi_mosi    ,  // output reg  [0        :0] |数据输出
   input  wire          spi_miso       // input  wire [0        :0] |数据输入
 `endif
`elsif three_wire
   input  wire          send_en     ,  // input  wire [0        :0] |发送数据使能
   input  wire [W1:0]   send_data   ,  // input  wire [BIT_NUM-1:0] |发送数据输入
   output reg           send_done   ,  // output reg  [0        :0] |发送数据结束标志
   input  wire          recv_en     ,  // input  wire [0        :0] |接收数据使能
   output reg           recv_vld    ,  // output reg  [0        :0] |接收数据有效标志
   output reg  [W1:0]   recv_data   ,  // output reg  [BIT_NUM-1:0] |接收数据输出
   output reg           recv_done   ,  // output reg  [0        :0] |接收数据结束标志
   output reg           comm_done   ,  // output reg  [0        :0] |通信结束标志

   output reg           spi_cs_n    ,  // output reg  [0        :0] |设备片选
   output reg           spi_sclk    ,  // output reg  [0        :0] |通信时钟
   inout  wire          spi_sdio       // inout  wire [0        :0] |数据输入输出
`endif
);
//================================================
// 
//================================================
localparam MAX1 = ((CLK_FREQ*250)/(SPI_FREQ*2))-1;
localparam W2   = $clog2(MAX1+1)-1;    //时钟计数器最大值 |两倍SPI时钟
localparam W3   = $clog2(BIT_NUM)-1;   //数据位宽
localparam W4   = W1+1;
reg  [W2:0] cnt_a = 'd0; //时钟计数器(八倍SPI时钟)
reg  [1 :0] cnt_b = 'd0; //分段计数器(将半个SPI时钟周期分为四段)
reg  [W3:0] cnt_c = 'd0; //分段计数器(将一个SPI时钟周期分为八段)
reg  [3 :0] cnt_d = 'd0; //bit计数器
reg  [2 :0] add_r = 'd0; //输入数据累加寄存器
reg  [W4:0] t_data_r = 'd0; //输出数据
reg  [W4:0] t_data   = 'd0; //待输出数据
reg  [W1:0] r_data_r = 'd0; //接收数据
reg         again    = 'd0; //再一次标志寄存器
reg  [2 :0] NOW_S0   = 'd0;

`ifdef four_wire
 `ifdef full_duplex
   reg         en_r = 'd0;
   wire        en_up;
   always @(posedge clk) en_r<=swop_en;
   assign   en_up = {en_r,swop_en}==2'b01 ? 1'b1 : 1'b0;
   always @(posedge clk or posedge reset) begin 
      if(reset) begin 
         recv_vld  <= 'd0; // output reg  [0        :0] |接收数据有效标志
         recv_data <= 'd0; // output reg  [BIT_NUM-1:0] |接收数据输出
         swop_done <= 'd0; // output reg  [0        :0] |交换结束标志
         comm_done <= 'd0; // output reg  [0        :0] |通信结束标志
         spi_cs_n  <= 'd1; // output reg  [0        :0] |设备片选
         spi_mosi  <= 'd0; // output reg  [0        :0] |数据输出
         if(CPOL) spi_sclk<='d1;
         else     spi_sclk<='d0;

         cnt_a    <= 'd0; 
         cnt_b    <= 'd0; 
         cnt_c    <= 'd0; 
         cnt_d    <= 'd0; 
         add_r    <= 'd0; 
         t_data_r <= 'd0;
         t_data   <= 'd0;
         r_data_r <= 'd0;
         again    <= 'd0;
         NOW_S0   <= 'd0;
      end 
      else begin 
         case(NOW_S0)
            'd0 : begin //空闲状态
               comm_done<='d0;
               spi_cs_n <='d1;
               if(CPOL) spi_sclk<='d1;
               else     spi_sclk<='d0;
               if(en_up) begin t_data_r<=send_data; 
                  if(DATA_SIDE=="MSB") t_data_r<={1'b0,send_data};
                  else                 t_data_r<={send_data,1'b0};
                  NOW_S0<='d1; 
               end 
            end 
            'd1 : begin //收发数据状态
               spi_cs_n<='d0;
               if(DATA_SIDE=="MSB") spi_mosi<=t_data_r[W4];
               else                 spi_mosi<=t_data_r[0];
               if(cnt_a==MAX1) begin 
                  cnt_a<='d0;
                  if(cnt_d=='d8&&cnt_c=='d1) begin 
                     if(again=='d1) begin cnt_b<='d2; cnt_c<='d2; cnt_d<='d1; t_data_r<=t_data; end 
                     else           begin cnt_b<='d0; cnt_c<='d0; cnt_d<='d0; NOW_S0<='d0; comm_done<='d1; end 
                  end else begin 
                     cnt_b<=cnt_b+1;
                     cnt_c<=cnt_c+1;
                     if(&cnt_b)spi_sclk <=~spi_sclk;
                     if(cnt_c=='d1) begin 
                        cnt_d<=cnt_d+1;
                        if(DATA_SIDE=="MSB") t_data_r<=t_data_r<<1;
                        else                 t_data_r<=t_data_r>>1;
                     end 
                  end 
                  if(cnt_d=='d8&&cnt_c=='d7) recv_data<=r_data_r;
                  if(DATA_SIDE=="MSB") begin 
                     if(cnt_c=='d6) r_data_r<={r_data_r[W1-1:0],add_r[2]};
                  end else begin 
                     if(cnt_c=='d6) r_data_r<={add_r[2],r_data_r[W1:1]};
                  end 
               end 
               else cnt_a<=cnt_a+1;
               if(cnt_c=='d6&&cnt_a<='d6) add_r<=add_r+spi_miso;
               else if(cnt_c=='d7)        add_r<='d0;
               if(cnt_a=='d0 &&cnt_d=='d8&&cnt_c=='d0) recv_vld <='d1;
               else                                    recv_vld <='d0;
               if(cnt_a==MAX1&&cnt_d=='d8&&cnt_c=='d0) swop_done<='d1;
               else                                    swop_done<='d0;
               if(cnt_d=='d8&&cnt_c<='d1&&en_up=='d1) begin 
                  if(DATA_SIDE=="MSB") t_data<={send_data,1'b0};
                  else                 t_data<={1'b0,send_data};
               end 
               if(cnt_d=='d8&&cnt_c<='d1&&en_up=='d1) again <= 'd1;
               else if(cnt_d=='d1&&cnt_c<='d1)        again <= 'd0;
            end 
         endcase 
      end 
   end 
 `elsif half_duplex
   reg  [1 :0] tr_reg = 'd3;
   reg         t_en = 'd0, r_en = 'd0;
   wire        t_up, r_up, run;
   always @(posedge clk) t_en<= #20 send_en;
   always @(posedge clk) r_en<= #20 recv_en;
   assign t_up = {t_en,send_en}==2'b01 ? 1'b1 : 1'b0;
   assign r_up = {r_en,recv_en}==2'b01 ? 1'b1 : 1'b0;
   assign run  = (t_up || r_up) ? 1'b1 : 1'b0;
   always @(posedge clk or posedge reset) begin 
      if(reset) begin 
         send_done <= 'd0;// output reg  [0        :0] |发送数据结束标志
         recv_vld  <= 'd0;// output reg  [0        :0] |接收数据有效标志
         recv_data <= 'd0;// output reg  [BIT_NUM-1:0] |接收数据输出
         recv_done <= 'd0;// output reg  [0        :0] |接收数据结束标志
         comm_done <= 'd0;// output reg  [0        :0] |通信结束标志
         spi_cs_n  <= 'd0;// output reg  [0        :0] |设备片选
         spi_mosi  <= 'd0;// output reg  [0        :0] |数据输出
         if(CPOL) spi_sclk<='d1;
         else     spi_sclk<='d0;

         cnt_a    <= 'd0; 
         cnt_b    <= 'd0; 
         cnt_c    <= 'd0; 
         cnt_d    <= 'd0; 
         add_r    <= 'd0; 
         t_data_r <= 'd0;
         t_data   <= 'd0;
         r_data_r <= 'd0;
         again    <= 'd0;
         NOW_S0   <= 'd0;
      end 
      else begin 
         case(NOW_S0)
            'd0 : begin //空闲状态
               comm_done<='d0;
               spi_cs_n <='d1;
               if(run) NOW_S0<='d1;
               if     (t_up) tr_reg[0]<='d0;
               else if(r_up) tr_reg[0]<='d1;
               if(t_up) begin 
                  if(DATA_SIDE=="MSB") t_data_r<={1'b0,send_data};
                  else                 t_data_r<={send_data,1'b0};
               end 
            end 
            'd1 : begin //工作状态
               spi_cs_n <='d0;
               if(tr_reg[0]=='d0) begin 
                  if(DATA_SIDE=="MSB") spi_mosi<=t_data_r[W4];
                  else                 spi_mosi<=t_data_r[0];
               end 
               if(cnt_a==MAX1) begin 
                  cnt_a<='d0;
                  if(cnt_d=='d8&&cnt_c=='d1) begin 
                     if(again) begin cnt_b<='d2; cnt_c<='d2; cnt_d<='d1; t_data_r<=t_data; tr_reg<=tr_reg>>1; end 
                     else      begin cnt_b<='d0; cnt_c<='d0; cnt_d<='d0; NOW_S0<='d0; comm_done<='d1; end 
                  end else begin 
                     cnt_b<=cnt_b+1;
                     cnt_c<=cnt_c+1;
                     if(&cnt_b) spi_sclk<=~spi_sclk;
                     if(cnt_c=='d1) begin 
                        cnt_d<=cnt_d+1;
                        if(~tr_reg[0]) begin 
                           if(DATA_SIDE=="MSB") t_data_r<=t_data_r<<1;
                           else                 t_data_r<=t_data_r>>1;
                        end 
                     end 
                  end 
                  if(tr_reg[0]) begin 
                     if(DATA_SIDE=="MSB") begin 
                        if(cnt_c=='d6) r_data_r<={r_data_r[W1-1:0],add_r[2]};
                     end else begin 
                        if(cnt_c=='d6) r_data_r<={add_r[2],r_data_r[W1:1]};
                     end 
                  end 
                  if(tr_reg[0]&&cnt_d=='d8&&cnt_c=='d7) recv_data<=r_data_r;
               end else cnt_a<=cnt_a+1;

               if(cnt_a==MAX1&&cnt_d=='d8&&cnt_c=='d0) begin 
                  if(tr_reg[0])  recv_done<='d1;
                  else           send_done<='d1;
               end else begin recv_done<='d0; send_done<='d0; end 
               if(cnt_d=='d8&&cnt_c=='d1) begin 
                  if(t_up=='d1) begin 
                     tr_reg[1]<='d0;
                     if(DATA_SIDE=="MSB") t_data<={send_data,1'b0};
                     else                 t_data<={1'b0,send_data};
                  end 
                  if(r_up) tr_reg[1]<='d1;
               end 
               if(cnt_d=='d8&&cnt_c=='d1&&run=='d1) again<='d1;
               else if(cnt_d=='d1)                  again<='d0;
               if(tr_reg[0]) begin 
                  if(cnt_c=='d6&&cnt_a<='d6) add_r<=add_r+spi_miso;
                  else if(cnt_c=='d7)        add_r<='d0;
                  if(cnt_a=='d0 &&cnt_d=='d8&&cnt_c=='d0) recv_vld <='d1;
                  else                                    recv_vld <='d0;
               end 
            end 
         endcase 
      end 
   end 
 `endif 
`elsif three_wire
   reg         T_dio='d1, O_sdi='d0;
   wire        I_sdo;
   reg         t_en = 'd0, r_en = 'd0;
   wire        t_up, r_up, run;
   reg  [1 :0] tr_reg = 'd3;
   always @(posedge clk) t_en<=send_en;
   always @(posedge clk) r_en<=recv_en;
   assign t_up = {t_en,send_en}==2'b01 ? 1'b1 : 1'b0;
   assign r_up = {r_en,recv_en}==2'b01 ? 1'b1 : 1'b0;
   assign run  = {t_en,send_en}==2'b01 || {r_en,recv_en}==2'b01 ? 1'b1 : 1'b0;
   always @(posedge clk or posedge reset) begin 
      if(reset) begin 
         send_done <= 'd0; // output reg  [0        :0] |发送数据结束标志
         recv_vld  <= 'd0; // output reg  [0        :0] |接收数据有效标志
         recv_data <= 'd0; // output reg  [BIT_NUM-1:0] |接收数据输出
         recv_done <= 'd0; // output reg  [0        :0] |接收数据结束标志
         comm_done <= 'd0; // output reg  [0        :0] |通信结束标志
         spi_cs_n  <= 'd1; // output reg  [0        :0] |设备片选
         if(CPOL) spi_sclk<='d1;
         else     spi_sclk<='d0;

         T_dio    <= 'd1;
         O_sdi    <= 'd0;
         tr_reg   <= 'd3;
         cnt_a    <= 'd0;
         cnt_b    <= 'd0;
         cnt_c    <= 'd0;
         cnt_d    <= 'd0;
         add_r    <= 'd0;
         t_data_r <= 'd0;
         t_data   <= 'd0;
         r_data_r <= 'd0;
         again    <= 'd0;
         NOW_S0   <= 'd0;
      end 
      else begin 
         case(NOW_S0)
            'd0 : begin //空闲状态
               comm_done<='d0;
               spi_cs_n<='d1;
               if(run) NOW_S0<='d1;
               if(t_up) begin 
                  if(DATA_SIDE=="MSB") t_data_r<={1'b0,send_data};
                  else                 t_data_r<={send_data,1'b0};
               end 
               if     (t_up) tr_reg[0]<='d0;
               else if(r_up) tr_reg[0]<='d1;
            end 
            'd1 : begin //工作状态
               spi_cs_n<='d0;
               if(tr_reg[0]=='d1)   T_dio<='d1;
               else                 T_dio<='d0;
               if(DATA_SIDE=="MSB") O_sdi<=t_data_r[W4];
               else                 O_sdi<=t_data_r[0];
               if(cnt_a==MAX1) begin 
                  cnt_a<='d0;
                  if(cnt_d=='d8&&cnt_c=='d1) begin 
                     if(again) begin cnt_b<='d2; cnt_c<='d2; cnt_d<='d1; t_data_r<=t_data; tr_reg<=tr_reg>>1; end 
                     else      begin cnt_b<='d0; cnt_c<='d0; cnt_d<='d0; NOW_S0<='d0; T_dio<='d1; comm_done<='d1; end 
                  end else begin 
                     cnt_b<=cnt_b+1;
                     cnt_c<=cnt_c+1;
                     if(&cnt_b) spi_sclk<=~spi_sclk;
                     if(cnt_c=='d1) begin 
                        cnt_d<=cnt_d+1;
                        if(~tr_reg[0]) begin 
                           if(DATA_SIDE=="MSB") t_data_r<=t_data_r<<1;
                           else                 t_data_r<=t_data_r>>1;
                        end 
                     end 
                  end 
                  if(tr_reg[0]) begin 
                     if(DATA_SIDE=="MSB") begin 
                        if(cnt_c=='d6) r_data_r<={r_data_r[W1-1:0],add_r[2]};
                     end else begin 
                        if(cnt_c=='d6) r_data_r<={add_r[2],r_data_r[W1:1]};
                     end 
                  end 
                  if(tr_reg[0]&&cnt_d=='d8&&cnt_c=='d7) recv_data<=r_data_r;
               end else cnt_a<=cnt_a+1;

               if(cnt_a==MAX1&&cnt_d=='d8&&cnt_c=='d0) begin 
                  if(tr_reg[0])  recv_done<='d1;
                  else           send_done<='d1;
               end else begin recv_done<='d0; send_done<='d0; end 
               if(cnt_d=='d8&&cnt_c=='d1) begin 
                  if(t_up=='d1) begin 
                     tr_reg[1]<='d0;
                     if(DATA_SIDE=="MSB") t_data<={send_data,1'b0};
                     else                 t_data<={1'b0,send_data};
                  end 
                  if(r_up) tr_reg[1]<='d1;
               end 
               if(cnt_d=='d8&&cnt_c=='d1&&run=='d1) again<='d1;
               else if(cnt_d=='d1)                  again<='d0;
               if(tr_reg[0]) begin 
                  if(cnt_c=='d6&&cnt_a<='d6) add_r<=add_r+I_sdo;
                  else if(cnt_c=='d7)        add_r<='d0;
                  if(cnt_a=='d0 &&cnt_d=='d8&&cnt_c=='d0) recv_vld <='d1;
                  else                                    recv_vld <='d0;
               end 
            end 
         endcase 
      end 
   end 
   IOBUF #(
      .DRIVE         (12         ), // Specify the output drive strength
      .IBUF_LOW_PWR  ("TRUE"     ), // Low Power - "TRUE", High Performance = "FALSE" 
      .IOSTANDARD    ("DEFAULT"  ), // Specify the I/O standard
      .SLEW          ("SLOW"     )  // Specify the output slew rate
   ) IOBUF_sdio (
      .O (I_sdo   ), // Buffer output
      .IO(spi_sdio), // Buffer inout port (connect directly to top-level port)
      .I (O_sdi   ), // Buffer input
      .T (T_dio   )  // 3-state enable input, high=input, low=output
   );   
`endif 
endmodule 

例化模板

三线制

//==========================================================
// SPI主机驱动模块
//==========================================================
spi_modu #(
   .CLK_FREQ   (CLK_FREQ   ), // 模块时钟频率 |单位:Mhz
   .SPI_FREQ   (SPI_FREQ   ), // SPI通信频率 |单位:khz
   .DATA_SIDE  ("MSB"      ), // MSB:大端模式 LSB:小端模式
   .BIT_NUM    ('d8        ), // 数据长度 |单位bit
   .CPHA       (CPHA       ), // 时钟相位
   .CPOL       (CPOL       )  // 时钟极性
) spi_modu_db (
   .clk        (clk        ), // input  wire [0        :0] |模块时钟输入
   .reset      (reset      ), // input  wire [0        :0] |模块复位输入 |0:无效 1:复位

   .send_en    (send_en    ), // input  wire [0        :0] |发送数据使能
   .send_data  (send_data  ), // input  wire [BIT_NUM-1:0] |发送数据输入
   .send_done  (send_done  ), // output reg  [0        :0] |发送数据结束标志
   .recv_en    (recv_en    ), // input  wire [0        :0] |接收数据使能
   .recv_vld   (recv_vld   ), // output reg  [0        :0] |接收数据有效标志
   .recv_data  (recv_data  ), // output reg  [BIT_NUM-1:0] |接收数据输出
   .recv_done  (recv_done  ), // output reg  [0        :0] |接收数据结束标志
   .comm_done  (comm_done  ), // output reg  [0        :0] |通信结束标志

   .spi_cs_n   (spi_cs_n   ), // output reg  [0        :0] |设备片选
   .spi_sclk   (spi_sclk   ), // output reg  [0        :0] |通信时钟
   .spi_sdio   (spi_sdio   )  // inout  wire [0        :0] |数据输入输出
);

四线制全双工

//==========================================================
// SPI主机驱动模块
//==========================================================
spi_modu #(
   .CLK_FREQ   (CLK_FREQ   ), // 模块时钟频率 |单位:Mhz
   .SPI_FREQ   (SPI_FREQ   ), // SPI通信频率 |单位:khz
   .DATA_SIDE  ("MSB"      ), // MSB:大端模式 LSB:小端模式
   .BIT_NUM    ('d8        ), // 数据长度 |单位bit
   .CPHA       (CPHA       ), // 时钟相位
   .CPOL       (CPOL       )  // 时钟极性
) spi_modu_db (
   .clk        (clk        ), // input  wire [0        :0] |模块时钟输入
   .reset      (reset      ), // input  wire [0        :0] |模块复位输入 |0:无效 1:复位

   .swop_en    (swop_en    ), // input  wire [0        :0] |开始交换使能
   .send_data  (send_data  ), // input  wire [BIT_NUM-1:0] |发送数据输入
   .recv_vld   (recv_vld   ), // output reg  [0        :0] |接收数据有效标志
   .recv_data  (recv_data  ), // output reg  [BIT_NUM-1:0] |接收数据输出
   .swop_done  (swop_done  ), // output reg  [0        :0] |交换结束标志
   .comm_done  (comm_done  ), // output reg  [0        :0] |通信结束标志

   .spi_cs_n   (spi_cs_n   ), // output reg  [0        :0] |设备片选
   .spi_sclk   (spi_sclk   ), // output reg  [0        :0] |通信时钟
   .spi_mosi   (spi_mosi   ), // output reg  [0        :0] |数据输出
   .spi_miso   (spi_miso   )  // input  wire [0        :0] |数据输入
);

四线制半双工

//==========================================================
// SPI主机驱动模块
//==========================================================
spi_modu #(
   .CLK_FREQ   (CLK_FREQ   ), // 模块时钟频率 |单位:Mhz
   .SPI_FREQ   (SPI_FREQ   ), // SPI通信频率 |单位:khz
   .DATA_SIDE  ("MSB"      ), // MSB:大端模式 LSB:小端模式
   .BIT_NUM    ('d8        ), // 数据长度 |单位bit
   .CPHA       (CPHA       ), // 时钟相位
   .CPOL       (CPOL       )  // 时钟极性
) spi_modu_db (
   .clk        (clk        ), // input  wire [0        :0] |模块时钟输入
   .reset      (reset      ), // input  wire [0        :0] |模块复位输入 |0:无效 1:复位

   .send_en    (send_en    ), // input  wire [0        :0] |发送数据使能
   .send_data  (send_data  ), // input  wire [BIT_NUM-1:0] |发送数据输入
   .send_done  (send_done  ), // output reg  [0        :0] |发送数据结束标志
   .recv_en    (recv_en    ), // input  wire [0        :0] |接收数据使能
   .recv_vld   (recv_vld   ), // output reg  [0        :0] |接收数据有效标志
   .recv_data  (recv_data  ), // output reg  [BIT_NUM-1:0] |接收数据输出
   .recv_done  (recv_done  ), // output reg  [0        :0] |接收数据结束标志
   .comm_done  (comm_done  ), // output reg  [0        :0] |通信结束标志

   .spi_cs_n   (spi_cs_n   ), // output reg  [0        :0] |设备片选
   .spi_sclk   (spi_sclk   ), // output reg  [0        :0] |通信时钟
   .spi_mosi   (spi_mosi   ), // output reg  [0        :0] |数据输出
   .spi_miso   (spi_miso   )  // input  wire [0        :0] |数据输入

仿真代码

//
// Engineer       : Zheng LiGuo
// Flie name      : tb_spi_modu
// Create time    : 2024/11/02
// Last modified  : 2024/11/06
// Last version   : V1.0
// Desoriptions   : 仿真激励文件
// Target file    : 目标文件
// Support file   : 支持文件
//
//==========================================
//SPI线制定义 |三线制 或者 四线制
//==========================================
   // `define four_wire  1 //四线制SPI申明
   // `define three_wire 1 //三线制SPI申明

   `ifdef four_wire
      //==========================================
      //SPI类型定义 |全双工类型 或者 半双工类型
      //==========================================
         // `define half_duplex 1 //半双工类型
         // `define full_duplex 1 //全双工类型
   `endif
`timescale 1ns/1ps
module tb_spi_modu ();
localparam CLK_FREQ = 'd100;
localparam FREQ = 1000/CLK_FREQ/2;
//================================================
// 时钟&复位
//================================================
   reg            clk   = 'd1 ;
   reg            reset = 'd1 ;
   always  #FREQ  clk  <= ~clk;
   initial #20    reset = 'd0 ;

//================================================
// 
//================================================
   localparam  SPI_FREQ  = 'd100 ;  // SPI通信频率 |单位:khz
   localparam  DATA_SIDE = "MSB" ;  // MSB:大端模式 LSB:小端模式
   localparam  BIT_NUM   = 'd8   ;  // 数据长度 |单位bit
   localparam  CPHA      = 'd0   ;  // 时钟相位
   localparam  CPOL      = 'd0   ;  // 时钟极性
   localparam  W1 = BIT_NUM-1    ;

`ifdef four_wire
 `ifdef full_duplex
   reg                  swop_en   = 'd0;  // input  wire [0        :0] |开始交换使能
   reg  [BIT_NUM-1:0]   send_data = 'd0;  // input  wire [BIT_NUM-1:0] |发送数据输入
   wire                 recv_vld       ;  // output reg  [0        :0] |接收数据有效标志
   wire [BIT_NUM-1:0]   recv_data      ;  // output reg  [BIT_NUM-1:0] |接收数据输出
   wire                 swop_done      ;  // output reg  [0        :0] |交换结束标志
   wire                 comm_done      ;  // output reg  [0        :0] |通信结束标志
   wire                 spi_cs_n       ;  // output reg  [0        :0] |设备片选
   wire                 spi_sclk       ;  // output reg  [0        :0] |通信时钟
   wire                 spi_mosi       ;  // output reg  [0        :0] |数据输出
   reg                  spi_miso  = 'd0;  // input  wire [0        :0] |数据输入

   localparam byte_max = 'd5;
   reg  [2 :0] cnt_s1 = 'd0, cnt_s2 = 'd0;
   reg         run = 'd0;
   always @(posedge clk or posedge reset) begin 
      if(reset) begin 
         swop_en   <= 'd0;  // input  wire [0        :0] |开始交换使能
         send_data <= 'd0;  // input  wire [BIT_NUM-1:0] |发送数据输入
         cnt_s1 <= 'd0;
         cnt_s2 <= 'd0;
         run    <= 'd0;
      end else begin 
         case(cnt_s1)
            'd0 : begin 
               if(run) cnt_s1<='d1;
            end 
            'd1 : begin 
               swop_en <= 'd1;
               case(cnt_s2)
               'd0 : begin send_data<=8'haa; end 
               'd1 : begin send_data<=8'ha5; end 
               'd2 : begin send_data<=8'h55; end 
               'd3 : begin send_data<=8'h5a; end 
               'd4 : begin send_data<=8'hf0; end 
               'd5 : begin send_data<=8'h0f; end 
               'd6 : begin send_data<=8'hfa; end 
               endcase 
               cnt_s1<='d2;
            end 
            'd2 : begin 
               swop_en <= 'd0;
               if(swop_done) begin 
                  if(cnt_s2==byte_max-1) begin cnt_s1<='d0; cnt_s2<='d0     ; end 
                  else                   begin cnt_s1<='d1; cnt_s2<=cnt_s2+1; end 
               end 
            end 
         endcase 
      end 
   end 
   initial begin
      #200  run <= 'd1;
      #20   run <= 'd0;
   end
 `elsif half_duplex
   reg                  send_en   = 'd0;  // input  wire [0        :0] |发送数据使能
   reg  [BIT_NUM-1:0]   send_data = 'd0;  // input  wire [BIT_NUM-1:0] |发送数据输入
   wire                 send_done      ;  // output reg  [0        :0] |发送数据结束标志
   reg                  recv_en   = 'd0;  // input  wire [0        :0] |接收数据使能
   wire                 recv_vld       ;  // output reg  [0        :0] |接收数据有效标志
   wire [BIT_NUM-1:0]   recv_data      ;  // output reg  [BIT_NUM-1:0] |接收数据输出
   wire                 recv_done      ;  // output reg  [0        :0] |接收数据结束标志
   wire                 comm_done      ;  // output reg  [0        :0] |通信结束标志
   wire                 spi_cs_n       ;  // output reg  [0        :0] |设备片选
   wire                 spi_sclk       ;  // output reg  [0        :0] |通信时钟
   wire                 spi_mosi       ;  // output reg  [0        :0] |数据输出
   reg                  spi_miso  = 'd0;  // input  wire [0        :0] |数据输入

   localparam t_byte_max = 'd5;
   localparam r_byte_max = 'd5;
   reg  [2 :0] cnt_s1 = 'd0, cnt_s2 = 'd0;
   reg         run = 'd0;
   always @(posedge clk or posedge reset) begin 
      if(reset) begin 
         send_en   <= 'd0;  // input  wire [0        :0] |开始交换使能
         send_data <= 'd0;  // input  wire [BIT_NUM-1:0] |发送数据输入
         recv_en   <= 'd0;  // input  wire [0        :0] |接收数据使能
         cnt_s1 <= 'd0;
         cnt_s2 <= 'd0;
         run    <= 'd0;
      end else begin 
         case(cnt_s1)
            'd0 : begin 
               if(run) cnt_s1<='d1;
            end 
            'd1 : begin 
               send_en <= 'd1;
               case(cnt_s2)
               'd0 : begin send_data<=8'haa; end 
               'd1 : begin send_data<=8'ha5; end 
               'd2 : begin send_data<=8'h55; end 
               'd3 : begin send_data<=8'h5a; end 
               'd4 : begin send_data<=8'hf0; end 
               'd5 : begin send_data<=8'h0f; end 
               'd6 : begin send_data<=8'hfa; end 
               endcase 
               cnt_s1<='d2;
            end 
            'd2 : begin 
               send_en <= 'd0;
               if(send_done) begin 
                  if(cnt_s2==t_byte_max-1) begin cnt_s1<='d3; cnt_s2<='d0     ; end 
                  else                     begin cnt_s1<='d1; cnt_s2<=cnt_s2+1; end 
               end 
            end 
            'd3 : begin recv_en<='d1; cnt_s1<='d4;end 
            'd4 : begin 
               recv_en<='d0;
               if(recv_done) begin 
                  if(cnt_s2==r_byte_max-1) begin cnt_s1<='d0; cnt_s2<='d0     ; end 
                  else                     begin cnt_s1<='d3; cnt_s2<=cnt_s2+1; end 
               end 
            end 
         endcase 
      end 
   end 
   initial begin
      #200  run <= 'd1;
      #20   run <= 'd0;
   end
 `endif
   always #5_000 spi_miso <= $random;   
`elsif three_wire
   reg                  send_en   = 'd0;  // input  wire [0        :0] |发送数据使能
   reg  [BIT_NUM-1:0]   send_data = 'd0;  // input  wire [BIT_NUM-1:0] |发送数据输入
   wire                 send_done      ;  // output reg  [0        :0] |发送数据结束标志
   reg                  recv_en   = 'd0;  // input  wire [0        :0] |接收数据使能
   wire                 recv_vld       ;  // output reg  [0        :0] |接收数据有效标志
   wire [BIT_NUM-1:0]   recv_data      ;  // output reg  [BIT_NUM-1:0] |接收数据输出
   wire                 recv_done      ;  // output reg  [0        :0] |接收数据结束标志
   wire                 comm_done      ;  // output reg  [0        :0] |通信结束标志

   wire                 spi_cs_n       ;  // output reg  [0        :0] |设备片选
   wire                 spi_sclk       ;  // output reg  [0        :0] |通信时钟
   wire                 spi_sdio       ;  // inout  wire [0        :0] |数据输入输出

   PULLDOWN PULLDOWN_sdio (
      .O(spi_sdio)     // Pullup output (connect directly to top-level port)
   );
   reg      T_dio='d1, O_sdi='d0;
   always #5_000 O_sdi <= $random;   
   wire     I_sdo;
   IOBUF #(
      .DRIVE         (12         ), // Specify the output drive strength
      .IBUF_LOW_PWR  ("TRUE"     ), // Low Power - "TRUE", High Performance = "FALSE" 
      .IOSTANDARD    ("DEFAULT"  ), // Specify the I/O standard
      .SLEW          ("SLOW"     )  // Specify the output slew rate
   ) IOBUF_sdio (
      .O (I_sdo   ), // Buffer output
      .IO(spi_sdio), // Buffer inout port (connect directly to top-level port)
      .I (O_sdi   ), // Buffer input
      .T (T_dio   )  // 3-state enable input, high=input, low=output
   );   
   localparam t_byte_max = 'd5;
   localparam r_byte_max = 'd5;
   reg  [2 :0] cnt_s1 = 'd0, cnt_s2 = 'd0;
   reg         run = 'd0;
   always @(posedge clk or posedge reset) begin 
      if(reset) begin 
         send_en   <= 'd0;  // input  wire [0        :0] |开始交换使能
         send_data <= 'd0;  // input  wire [BIT_NUM-1:0] |发送数据输入
         recv_en   <= 'd0;  // input  wire [0        :0] |接收数据使能
         cnt_s1 <= 'd0;
         cnt_s2 <= 'd0;
         run    <= 'd0;
      end else begin 
         case(cnt_s1)
            'd0 : begin 
               T_dio<='d1;
               if(run) cnt_s1<='d1;
            end 
            'd1 : begin 
               send_en<='d1; T_dio<='d1;
               case(cnt_s2)
               'd0 : begin send_data<=8'haa; end 
               'd1 : begin send_data<=8'ha5; end 
               'd2 : begin send_data<=8'h55; end 
               'd3 : begin send_data<=8'h5a; end 
               'd4 : begin send_data<=8'hf0; end 
               'd5 : begin send_data<=8'h0f; end 
               'd6 : begin send_data<=8'hfa; end 
               endcase 
               cnt_s1<='d2;
            end 
            'd2 : begin 
               send_en <= 'd0;
               if(send_done) begin 
                  if(cnt_s2==t_byte_max-1) begin cnt_s1<='d3; cnt_s2<='d0     ; end 
                  else                     begin cnt_s1<='d1; cnt_s2<=cnt_s2+1; end 
               end 
            end 
            'd3 : begin recv_en<='d1; cnt_s1<='d4; T_dio<='d0;end 
            'd4 : begin 
               recv_en<='d0;
               if(recv_done) begin 
                  if(cnt_s2==r_byte_max-1) begin cnt_s1<='d0; cnt_s2<='d0     ; end 
                  else                     begin cnt_s1<='d3; cnt_s2<=cnt_s2+1; end 
               end 
            end 
         endcase 
      end 
   end 
   initial begin
      #200  run <= 'd1;
      #20   run <= 'd0;
   end
`endif 

//==========================================================
// SPI主机驱动模块
//==========================================================
spi_modu #(
   .CLK_FREQ   (CLK_FREQ   ), // 模块时钟频率 |单位:Mhz
   .SPI_FREQ   (SPI_FREQ   ), // SPI通信频率 |单位:khz
   .DATA_SIDE  (DATA_SIDE  ), // MSB:大端模式 LSB:小端模式
   .BIT_NUM    (BIT_NUM    ), // 数据长度 |单位bit
   .CPHA       (CPHA       ), // 时钟相位
   .CPOL       (CPOL       )  // 时钟极性
) spi_modu_db (
   .clk        (clk        ), // input  wire [0        :0] |模块时钟输入
   .reset      (reset      ), // input  wire [0        :0] |模块复位输入 |0:无效 1:复位
`ifdef four_wire
 `ifdef full_duplex
   .swop_en    (swop_en    ), // input  wire [0        :0] |开始交换使能
   .send_data  (send_data  ), // input  wire [BIT_NUM-1:0] |发送数据输入
   .recv_vld   (recv_vld   ), // output reg  [0        :0] |接收数据有效标志
   .recv_data  (recv_data  ), // output reg  [BIT_NUM-1:0] |接收数据输出
   .swop_done  (swop_done  ), // output reg  [0        :0] |交换结束标志
   .comm_done  (comm_done  ), // output reg  [0        :0] |通信结束标志

   .spi_cs_n   (spi_cs_n   ), // output reg  [0        :0] |设备片选
   .spi_sclk   (spi_sclk   ), // output reg  [0        :0] |通信时钟
   .spi_mosi   (spi_mosi   ), // output reg  [0        :0] |数据输出
   .spi_miso   (spi_miso   )  // input  wire [0        :0] |数据输入
 `elsif half_duplex
   .send_en    (send_en    ), // input  wire [0        :0] |发送数据使能
   .send_data  (send_data  ), // input  wire [BIT_NUM-1:0] |发送数据输入
   .send_done  (send_done  ), // output reg  [0        :0] |发送数据结束标志
   .recv_en    (recv_en    ), // input  wire [0        :0] |接收数据使能
   .recv_vld   (recv_vld   ), // output reg  [0        :0] |接收数据有效标志
   .recv_data  (recv_data  ), // output reg  [BIT_NUM-1:0] |接收数据输出
   .recv_done  (recv_done  ), // output reg  [0        :0] |接收数据结束标志
   .comm_done  (comm_done  ), // output reg  [0        :0] |通信结束标志

   .spi_cs_n   (spi_cs_n   ), // output reg  [0        :0] |设备片选
   .spi_sclk   (spi_sclk   ), // output reg  [0        :0] |通信时钟
   .spi_mosi   (spi_mosi   ), // output reg  [0        :0] |数据输出
   .spi_miso   (spi_miso   )  // input  wire [0        :0] |数据输入
 `endif
`elsif three_wire
   .send_en    (send_en    ), // input  wire [0        :0] |发送数据使能
   .send_data  (send_data  ), // input  wire [BIT_NUM-1:0] |发送数据输入
   .send_done  (send_done  ), // output reg  [0        :0] |发送数据结束标志
   .recv_en    (recv_en    ), // input  wire [0        :0] |接收数据使能
   .recv_vld   (recv_vld   ), // output reg  [0        :0] |接收数据有效标志
   .recv_data  (recv_data  ), // output reg  [BIT_NUM-1:0] |接收数据输出
   .recv_done  (recv_done  ), // output reg  [0        :0] |接收数据结束标志
   .comm_done  (comm_done  ), // output reg  [0        :0] |通信结束标志

   .spi_cs_n   (spi_cs_n   ), // output reg  [0        :0] |设备片选
   .spi_sclk   (spi_sclk   ), // output reg  [0        :0] |通信时钟
   .spi_sdio   (spi_sdio   )  // inout  wire [0        :0] |数据输入输出
`endif 
);
endmodule 

关于仿真截图

仿真截图就不上了,尝试了一下,要把细节描述清楚实在太难了。
有兴趣的自己仿真一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值