SPI主机 双工

发布时间 2023-09-26 19:47:47作者: xgj_0817
//主程序
//功能:完成32位以内SPI接口的数据双向通信
module  lcd_spi_m
#(
    parameter [5:0]spi_in_width  =6'd9,//spi  输入位数
    parameter [5:0]spi_out_width =6'd9,//SPI  输出位数
    parameter [0:0]spi_cpol=1'b0,//空闲状态SCL电平 0:SCL=0  1:SCL=1
    parameter [0:0]spi_cpha=1'b1 //SPI数据在哪个SCL边沿有效 0:数据在SCL第一个边沿有效,1:数据在SCL第二个边沿有效,


)
(
     input  wire  [0:0]  rst_n_i, //复位输入,低电平复位 
     input  wire  [0:0]  spi_x2clk_i,//SPI系统时钟 为SCL输出时钟的两倍
     input  wire  [31:0] spi_data_i,//输入32位要从MOSI发送出去的数据
     input  wire  [0:0]  spi_start, //单次发送开始,把数据送到spi_data_i 并把spi_start维技一个周期的高电平   
     input  wire  [0:0]  spi_miso_i,//主机接收从机输出引脚
     output reg   [0:0]  spi_done,//SPI完成一次传输并从spi_data_o输出读到的数据
     output reg   [0:0]  spi_busy,//SPI忙信号输出,在忙状态时不接收外部数据,高表示忙
     output reg   [0:0]  spi_cs_o,//SPI片选信号输出低有效
     output reg   [0:0]  spi_scl_o,//SPI 时钟信号输出,请结合CPOL CPHA分析有效性
     output reg   [0:0]  spi_mosi_o,//SPI主机输出从机输入接口
     output reg   [31:0] spi_data_o//从从机读到的数据在SPI_DONE为高时为有效数据
     
             
);

localparam [7:0] CS_F_DELAY=8'D1; //CS前面延时
localparam [7:0] CS_B_DELAY=8'D1; //CS后面延时
localparam [7:0] CS_F_CNT=CS_F_DELAY+CS_F_DELAY; //CS前面延时
localparam [7:0] CS_B_CNT=CS_B_DELAY+CS_B_DELAY; //CS后面延时
localparam [7:0] WIDTH_MAX=(spi_in_width>spi_in_width)? (spi_in_width*2'D2+CS_F_CNT+CS_B_CNT) :(spi_out_width*2'D2+CS_F_CNT+CS_B_CNT);

//在spi_start为1时捕获数据
reg [31:0] temp_data_i;
always @(posedge spi_x2clk_i  or negedge rst_n_i)
     begin
         if(rst_n_i==1'b0)
             begin
                 temp_data_i<=32'b0;
                 spi_busy<=1'b0;
             end
         else if(spi_start==1'b1 && spi_busy==1'b0)  
                 begin
                     temp_data_i<=spi_data_i;
                     spi_busy<=1'b1;
                 end               
                 
         else if(spi_done==1'b1)
                 spi_busy<=1'b0;         
         else
                  begin
                      temp_data_i<=temp_data_i;
                      spi_busy<=spi_busy;
                  end     
     end

      
//产生spi_x2clk_i时钟计数 
reg [6:0] clk_cnt;
always @(posedge spi_x2clk_i  or negedge rst_n_i)
     begin
         if(rst_n_i==1'b0)
             clk_cnt<=6'd0;
         else if(spi_busy==1'b1 )  
                 if(clk_cnt<WIDTH_MAX)                             
                       clk_cnt<=clk_cnt+1'd1;    
                   else 
                    clk_cnt<=7'd0;                
        else
                clk_cnt<=7'd0;   
     end       
     
//输出SPI_CS信号 
always @(posedge spi_x2clk_i  or negedge rst_n_i)
     begin
         if(rst_n_i==1'b0)
             spi_cs_o<=1'b1;       
         else if(spi_start==1'b1 && spi_busy==1'b0)                                 
             spi_cs_o<=1'b0;                         
         else if(clk_cnt<(WIDTH_MAX-1'd1))
             spi_cs_o<=spi_cs_o; 
         else
            spi_cs_o<=1'b1;   
     end  

    

     
//输出spi_scl信号
always @(posedge spi_x2clk_i  or negedge rst_n_i)
     begin
         if(rst_n_i==1'b0)
             begin
                 if(spi_cpol==1'b0)
                     spi_scl_o<=1'b0;
                 else 
                    spi_scl_o<=1'b1;
                spi_done<=1'b0;            
             end             
         else if(clk_cnt>CS_F_CNT-1'd1 && clk_cnt<(WIDTH_MAX-CS_B_CNT-1'd1))  
                 begin
                    spi_scl_o<=~spi_scl_o;
                end
        else if(clk_cnt==WIDTH_MAX-1'd1)
                begin    
                     spi_done<=1'b1;

                 end
         else 
            begin
                 if(spi_cpol==1'b0)
                     spi_scl_o<=1'b0;
                 else 
                    spi_scl_o<=1'b1;
                spi_done<=1'b0;
    
             end  
     end   
                
//输出SPI_MOSI信号
always @(posedge spi_x2clk_i  or negedge rst_n_i)
     begin
         if(rst_n_i==1'b0)
             spi_mosi_o<=1'b0;
         else if(spi_cpha==1'b0) 
                 begin
                     if((clk_cnt>=(CS_F_CNT-8'd1)) && (clk_cnt<(WIDTH_MAX-CS_B_CNT-CS_F_CNT-8'd2)) && (clk_cnt%2==1)  )
                         begin
                             spi_mosi_o<=temp_data_i[spi_out_width-1'd1];
                             temp_data_i<={temp_data_i[(spi_out_width-2'd2):0],temp_data_i[spi_out_width-1'd1]};                             
                         end
                     else 
                         begin                            
                             spi_mosi_o<=spi_mosi_o;                         
                         end

                 end    
        else  if(spi_cpha==1'b1) 
                 begin
                     if((clk_cnt>=CS_F_CNT) && clk_cnt<(WIDTH_MAX-CS_B_CNT-CS_F_CNT) && (clk_cnt%2==0) )
                         begin
                             spi_mosi_o<=temp_data_i[spi_out_width-1'd1];
                             temp_data_i<={temp_data_i[spi_out_width-2'd2:0],temp_data_i[spi_out_width-1'd0]};                             
                         end
                     else 
                         begin                            
                             spi_mosi_o<=spi_mosi_o;                         
                         end
                 end    
         else 
             begin 
                             spi_mosi_o<=1'b1;  
             end
                  
     end 
     
//接收SPI_MISO信号
always @(posedge spi_x2clk_i or negedge rst_n_i)
     begin
         if(rst_n_i==1'b0)
             spi_data_o<=32'b0;
         else if(spi_cpha==1'b0) 
                 begin
                     if((clk_cnt>=CS_F_CNT-8'D1) && (clk_cnt<(WIDTH_MAX-CS_B_CNT)) && (clk_cnt%2==1)  )
                         begin         
                             spi_data_o<={spi_data_o[30:0],spi_miso_i};                             
                         end
                     else 
                         begin
                             spi_data_o<=spi_data_o;                                                   
                         end
                 end    
        else  if(spi_cpha==1'b1) 
                 begin
                     if((clk_cnt>=CS_F_CNT) && (clk_cnt<(WIDTH_MAX-CS_B_CNT)) && (clk_cnt%2==1) )
                         begin
                             spi_data_o<={spi_data_o[30:0],spi_miso_i};                             
                         end
                     else 
                         begin
                             spi_data_o<=spi_data_o;                                                   
                         end
                 end    
         else 
             begin
                    spi_data_o<=spi_data_o;     
             end
                  
     end 

endmodule


//testbench
`timescale 1ns/1ns
module  lcd_spi_m_tb();
 reg rst_n_i;    
 reg spi_x2clk_i; 
 reg [31:0] spi_data_i;
 reg spi_start;     
 reg spi_miso_i;
 wire [0:0]  spi_done;
 wire [0:0]  spi_busy;
 wire [0:0]  spi_cs_o;
 wire [0:0]  spi_scl_o;
 wire [0:0]  spi_mosi_o;
 wire [31:0] spi_data_o;



always #50 spi_x2clk_i<=~spi_x2clk_i;
initial begin
         rst_n_i=0;
         spi_x2clk_i=0;
         #200; 
         spi_start=0;        
         rst_n_i=1;
         @(posedge spi_x2clk_i)
         spi_data_i=9'h1B0;
         spi_start=1;
         @(posedge spi_x2clk_i)
         spi_start=0;
         @(negedge spi_done)
         #200; 
         $stop;
        end
        
always @(posedge spi_scl_o or negedge rst_n_i )//要根据CPOL CPHA配置 spi_scl_o采样极性
     begin
         if(rst_n_i==1'b0)
             spi_miso_i<=1'b0;
         else if (spi_cs_o==1'b0 )
             spi_miso_i<=~spi_miso_i;                         
         else
             spi_miso_i<=1'b0;    
     end        

lcd_spi_m
#(
    .spi_in_width(6'd9),//spi  输入位数
    .spi_out_width (6'd9),//SPI  输出位数
    .spi_cpol(1'b0),
    .spi_cpha(1'b1)


)
lcd_spi_m_inst
(
     .rst_n_i            (rst_n_i     )       ,    
     .spi_x2clk_i        (spi_x2clk_i )       ,
     .spi_data_i         (spi_data_i  )       ,
     .spi_start          (spi_start   )       ,     
     .spi_miso_i         (spi_miso_i  )       ,
     .spi_done           (spi_done    )       ,
     .spi_busy           (spi_busy    )       ,
     .spi_cs_o           (spi_cs_o    )       ,
     .spi_scl_o          (spi_scl_o   )       ,
     .spi_mosi_o         (spi_mosi_o  )       ,
     .spi_data_o         (spi_data_o  )    
             
);

endmodule