SPI主机 双工
//主程序 //功能:完成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_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_start为1时捕获数据 reg [31:0] temp_data_i; //输出SPI_MOSI信号 always @(posedge spi_x2clk_i or negedge rst_n_i) begin if(rst_n_i==1'b0) begin spi_mosi_o<=1'b0; spi_busy<=1'b0; temp_data_i<=32'b0; end else if(spi_start==1'b1 && spi_busy==1'b0) //在spi_start为1时捕获数据 begin temp_data_i<=spi_data_i; spi_busy<=1'b1; end else if(spi_done==1'b1) spi_busy<=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)) && (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; @(posedge spi_done) #200; @(posedge spi_x2clk_i) spi_data_i=9'h1AA; spi_start=1; @(posedge spi_x2clk_i) spi_start=0; @(posedge spi_done) $stop; end always @(negedge 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'b0) ) 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