0


SPI协议的verilog实现(spi master slave联合实现)

SPI协议介绍

spi是serial peripheral interface的缩写,即串行扩展总线。SPI是单主设备通信,总线中只有一个主设备发起通信,能发起通信的设备称为主设备。当SPI主设备想读写从设备时,首先拉低对应从设备的ss线(低电平有效)。然后发送工作麦种到时钟线上,在相应的脉冲时间上,主设备把信号发送到MOSI实现读写,同时又可以对MISO采样实现读。一般SPI通信涉及到一下术语:
SCLKserial clock (来自主设备)MOSIMaster Output Slave Input(来自主设备)MISOMaster Input Slave Output(来自从设备)SSSlave Select(低电平有效,来自主设备)

主设备和从设备的两种链接方式

一主一从

一主一从模式表明有一个主机和一个从机,如下所示:(其中还有SCK信号,由主机到从机)
在这里插入图片描述

一主多从

一主多从表示有一个主机和一个从机,唯一不同的点是要为每个从机配备一个选择信号。
在这里插入图片描述

SPI协议的工作模式

SPI有四种工作模式,主要由时钟极性CPOL(Clock Polarity),时钟相位CPHA(Clock Phase)的组合决定。

  • CPOL为0,表示SCK在空闲状态为0,为1,则表示SCK在空闲状态为1
  • CPHA为0,表示在SCK的第一个边沿时输出输出数据有效,CPHA为1时,表示在SCK的第二个边沿输入输出数据有效

CPOL = 0, CPHA = 0

在这里插入图片描述

CPOL = 0, CPHA = 1

在这里插入图片描述

CPOL = 1. CPHA = 0

在这里插入图片描述

CPOL = 1, CPHA = 1

在这里插入图片描述

SPI MASTER 的verilog设计思路

设计的引脚说明:
信号名方向 +解释clk输入,时钟信号rst_n输入,复位信号miso输入,从机输入到主机data_i输入,主机发送从机的数据(一定比特位宽)start输入,开始的使能信号mosi输出,主机到从机sclk输出,时钟信号ss_n输出,从机的选择信号finish一次传输完成信号
首先将需要的宏定义收录在一个文件defines.v中,其中的代码如下所示:

`define CPOL 0//clock polarity
`define CPHA 0//clock phase
`define CLK_FREQ 50_000_000  // input clk frequency
`define SCLK_FREQ  5_000_000  // sclk frequency
`define DATA_WIDTH 8// a word width
`define CLK_CYCLE 20

其次时SPI MASTER的设计思路,总体思路时采用一个状态机,首先状态机是在IDLE状态,然后接收开始信号后,会将寄存器data_I中数据一个一个的发送出去,当指定宽距的比特位发送完成后。此时有两种选择,一种是跳转到FINISH状态,另一种是跳转到EXTRA状态,在跳转到FINISH状态。主要是由于当数据发送完成之后要判断此时的SCLK状态是不是空闲下的默认状态,如果不是,则需要跳转到EXTRA状态。结束状态下一个状态回到IDLE状态,得到start命令。
IDLE空闲状态DATA发送数据状态EXTRA额外状态FINISH结束状态
verilog的代码如下:

`include "defines.v"

module SPI_MASTER(
    input   wire                        clk     ,
    input   wire                        rst_n   ,
    input   wire                        miso    ,
    input   wire    [`DATA_WIDTH-1:0]   data_i  ,
    input   wire                        start   ,

    output  wire                        mosi    ,
    output  reg                         sclk    ,
    output  reg                         ss_n    ,
    output  wire                        finish    
);
    parameter       IDLE    =5'b00001  ,//CHOOSE  =   5'b00010  ,
                    DATA    =5'b00100  ,
                    EXTRA   =5'b01000  ,
                    FINISH  =5'b10000  ;
    
    parameter       CNT_MAX =   `CLK_FREQ / `SCLK_FREQ -1;
    
    reg     [31:0]      cnt                     ;//sclk的时钟周期的计数器
    reg     [4:0]       state                   ;   
    reg     [4:0]       nx_state                ;
    wire    [3:0]       cnt_data                ;//输出的数据计数器
    reg                 sclk_dly                ;//sclk的打一拍信号
    reg     [3:0]       cnt_sclk_pos            ;//sclk的上升沿计数器信号
    reg     [3:0]       cnt_sclk_neg            ;//sclk的下降沿计数器信号
    reg                 start_dly               ;//start的打一拍信号
    reg     [3:0]       cnt_data_dly            ;

    wire                cnt_max_flag            ;//计数器cnt达到最大值的信号
    wire                dec_pos_or_neg_sample   ;//1 posedge sample, 0 negedge sample
    wire                sclk_posedge            ;//sclk的上升沿
    wire                sclk_negedge            ;//sclk的下降沿

    assign  dec_pos_or_neg_sample =(`CPOL == `CPHA)?1'b1 : 1'b0;

    assign cnt_max_flag =(cnt == CNT_MAX )?1'b1 : 1'b0;
    assign sclk_posedge =((sclk ==1'b1) && (sclk_dly == 1'b0))?1'b1 : 1'b0;
    assign sclk_negedge =((sclk ==1'b0) && (sclk_dly == 1'b1))?1'b1 : 1'b0;
    assign cnt_data = dec_pos_or_neg_sample ? cnt_sclk_pos : cnt_sclk_neg;

    always @(posedge clk or negedge rst_n) begin
        sclk_dly <= sclk;
        start_dly <= start;
    end

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            cnt_sclk_pos <=4'd0;
        end
        elseif(state == FINISH) begin
            cnt_sclk_pos <=4'd0;
        end
        //else if((sclk_posedge) && (cnt_sclk_pos == `DATA_WIDTH - 1)) begin//    cnt_sclk_pos <= `DATA_WIDTH - 1;//endelseif(sclk_posedge) begin
            cnt_sclk_pos <= cnt_sclk_pos +1'b1;
        end
        else begin
            cnt_sclk_pos <= cnt_sclk_pos;
        end
    end

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            cnt_sclk_neg <=4'd0;
        end
        elseif(state == FINISH) begin
            cnt_sclk_neg <=4'd0;
        end
        //else if((sclk_negedge) && (cnt_sclk_neg == `DATA_WIDTH - 1)) begin//    cnt_sclk_neg <= `DATA_WIDTH - 1;//endelseif(sclk_negedge) begin
            cnt_sclk_neg <= cnt_sclk_neg +1'b1;
        end
        else begin
            cnt_sclk_neg <= cnt_sclk_neg;
        end
    end
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            state <= IDLE;
        end
        else begin
            state <= nx_state;
        end
    end

    always @(*) begin
        nx_state <= IDLE;case(state)
            IDLE:   nx_state <= start_dly ? DATA : IDLE;
            DATA:   nx_state <=(cnt_data == `DATA_WIDTH)?(`CPHA ==0)? EXTRA : FINISH : DATA;
            EXTRA:  nx_state <= cnt_max_flag ? FINISH : EXTRA ;
            FINISH: nx_state <= IDLE;default:nx_state <= IDLE;
        endcase
    end

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            cnt <= 'd0;
        end
        elseif((state == DATA)&&(nx_state == FINISH)&&(cnt == CNT_MAX)) begin
            cnt <= 'd0;
        end
        elseif((state == DATA)&&(nx_state == EXTRA)&&(cnt == CNT_MAX)) begin
            cnt <= 'd0;
        end
        elseif((state == DATA)&&(cnt == CNT_MAX)) begin
            cnt <= 'd0;
        end
        elseif((state == EXTRA)&&(cnt == CNT_MAX)) begin
            cnt <= 'd0;
        end
        elseif(state == DATA) begin
            cnt <= cnt +1'b1;
        end
        elseif(state == EXTRA) begin
            cnt <= cnt +1'b1;
        end
        else begin
            cnt <= 'd0;
        end
    end 

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            sclk <=(`CPOL)?1'b1 : 1'b0;
        end
        elseif(start_dly) begin
            sclk <=~sclk;
        end
        elseif((state == DATA)&&(cnt_max_flag)&&(cnt_data < `DATA_WIDTH)) begin
            sclk <=~sclk;
        end
        elseif((state == DATA)&&(cnt_max_flag)&&(cnt_data == `DATA_WIDTH)&&(nx_state == EXTRA)) begin
            sclk <=~sclk;
        end
        elseif((state == EXTRA)&&(cnt_max_flag)&&(nx_state == FINISH)) begin
            sclk <=~sclk;
        end
        else begin
            sclk <= sclk;
        end
    end

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            ss_n <=1'b1;
        end
        elseif(start) begin
            ss_n <=1'b0;
        end
        elseif(state == FINISH) begin
            ss_n <=1'b1;
        end
        else begin
            ss_n <= ss_n;
        end
    end

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            cnt_data_dly <= 'd0;
        end
        else begin
            cnt_data_dly <= cnt_data;
        end
    end

    assign finish =(state == FINISH)?1'b1 : 1'b0;

    assign mosi  =(state == DATA)?((cnt_data_dly < `DATA_WIDTH)? data_i[cnt_data_dly]: data_i[`DATA_WIDTH-1]): data_i[0]; 
endmodule

仿真的testbench如下:

`timescale  1ns/1ns
`include "defines.v"

module tb_master_slave();

    reg                         clk         ;
    reg                         rst_n       ;//reg                         mosi        ;
    reg     [`DATA_WIDTH-1:0]   data_i      ;
    reg                         start       ;
    reg                         miso        ;

    wire                        mosi        ;
    wire                        sclk        ;
    wire                        finish      ;
    wire                        ss_n        ;
    wire    [`DATA_WIDTH-1:0]   data_o      ;
    wire                        r_finish    ;

    SPI_MASTER u_spi_master(.clk(clk),.rst_n(rst_n),.miso(miso),.data_i(data_i),.start(start),.mosi(mosi),.sclk(sclk),.finish(finish),.ss_n(ss_n));

    SPI_SLAVE u_spi_slave(.clk(clk),.rst_n(rst_n),.mosi(mosi),.sclk(sclk),.tx_finish(finish),.start(start),.ss_n(ss_n),.data_o(data_o),.r_finish(r_finish));

    initial begin
         clk =1'b0;
         rst_n =1'b0;
         start =1'b0;
         data_i =8'h35;
         miso =1'b0;
         #30
         rst_n =1'b1;
         #10;
         @(posedge clk);
         start <=1'b1;
         @(posedge clk);
         start <=1'b0;
         @(negedge finish);
         data_i =8'h44;repeat(2) @(posedge clk);
         start =1'b1;
         @(posedge clk);
         start =1'b0;
    end

    always #(`CLK_CYCLE /2) clk =~clk;

endmodule

仿真波形如下,可以通过mosi成功发出了数据。
在这里插入图片描述

SPI SLAVE设计思路

SPI SPLAVE的设计思路大体如master类似,端口说明如下:
信号名方向 +解释clk输入,时钟信号rst_n输入,复位信号miso输入,从机输入到主机data_i输入,主机发送从机的数据(一定比特位宽)start输入,开始的使能信号mosi输出,主机到从机sclk输入,时钟信号ss_n输入,从机的选择信号r_finish一次传输完成信号data_o输出,收集到的数据
依然采用的是状态机思路,首先在IDLE状态,当开始信号使能之后,会跳转到RV_DATA接收到数据状态。RV_DATA数据接收完成,之后回跳转到FINISH状态,表明此次读取完成。
IDLE空闲状态RV_ DATA接收数据状态FINISH结束状态spi slave的代码如下所示:

`include "defines.v"
module SPI_SLAVE(
    input   wire                        clk         ,
    input   wire                        rst_n       ,
    input   wire                        mosi        ,
    input   wire                        sclk        ,
    input   wire                        tx_finish   ,
    input   wire                        start       ,
    input   wire                        ss_n        ,

    output  wire    [`DATA_WIDTH-1:0]   data_o      ,//output  wire                        miso        ,
    output  wire                        r_finish
);
    parameter       IDLE    =4'b0001     ,
                    RV_DATA =4'b0010     ,
                    FINISH  =4'b0100     ;

    wire                        sclk_posedge        ;
    wire                        sclk_negedge        ;
    wire                        dec_pos_or_neg_sample;//wire                        sclk_posedge        ;//wire                        sclk_negedge        ;

    reg                         sclk_dly            ;
    reg     [`DATA_WIDTH-1:0]   data_shift_pos      ;
    reg     [`DATA_WIDTH-1:0]   data_shift_neg      ;
    reg     [3:0]               state               ;
    reg     [3:0]               nx_state            ;
    reg     [3:0]               cnt_sclk_pos        ;
    reg     [3:0]               cnt_sclk_neg        ;
    wire    [3:0]               num_sample_data     ;

    assign  sclk_posedge =((sclk ==1'b1) && (sclk_dly == 1'b0))?1'b1 : 1'b0;
    assign  sclk_negedge =((sclk ==1'b0) && (sclk_dly == 1'b1))?1'b1 : 1'b0;
    assign  dec_pos_or_neg_sample =(`CPOL == `CPHA)?1'b1 : 1'b0;//assign  sclk_posedge = ((sclk == 1'b1) && (sclk_dly == 1'b0)) ? 1'b1 : 1'b0;//assign  sclk_negedge = ((sclk == 1'b0) && (sclk_dly == 1'b1)) ? 1'b1 : 1'b0;
    assign  num_sample_data =(dec_pos_or_neg_sample)? cnt_sclk_pos : cnt_sclk_neg;
    
    always @(posedge clk or negedge rst_n) begin
        sclk_dly <= sclk;
    end

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            state <= IDLE;
        end
        else begin
            state <= nx_state;
        end
    end

    always @(*) begin
        nx_state <= IDLE;case(state)
            IDLE: nx_state <= start ? RV_DATA :IDLE;
            RV_DATA: begin
                if((num_sample_data ==7)&&(dec_pos_or_neg_sample)&&(sclk_posedge)&&(!ss_n)) begin
                    nx_state <= FINISH;
                end
                elseif((num_sample_data ==7)&&(~dec_pos_or_neg_sample)&&(sclk_negedge)&&(!ss_n)) begin
                    nx_state <= FINISH;
                end
                else begin
                    nx_state <= RV_DATA;
                end
            end
            FINISH: nx_state <= IDLE;
        endcase
    end

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            cnt_sclk_pos <=4'd0;
        end
        elseif((state == FINISH)) begin
            cnt_sclk_pos <=4'd0;
        end
        elseif(sclk_posedge) begin
            cnt_sclk_pos <= cnt_sclk_pos +1'b1;
        end
        else begin
            cnt_sclk_pos <= cnt_sclk_pos;
        end
    end

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            cnt_sclk_neg <=4'd0;
        end
        elseif(state == FINISH) begin
            cnt_sclk_neg <=4'd0;
        end
        elseif(sclk_negedge) begin
            cnt_sclk_neg <= cnt_sclk_neg +1'b1;
        end
        else begin
            cnt_sclk_neg <= cnt_sclk_neg;
        end
    end

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            data_shift_pos <={`DATA_WIDTH{1'b0}};
        end
        elseif((state == RV_DATA)&&(sclk_posedge)) begin
            data_shift_pos <={mosi, data_shift_pos[`DATA_WIDTH-1:1]};
        end
        elseif(state == FINISH) begin
            data_shift_pos <={`DATA_WIDTH{1'b0}};
        end
        else begin
            data_shift_pos <= data_shift_pos;
        end
    end

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            data_shift_neg <={`DATA_WIDTH{1'b0}};
        end
        elseif((state == RV_DATA)&&(sclk_negedge)) begin
            data_shift_neg <={mosi, data_shift_neg[`DATA_WIDTH-1:1]};
        end
        elseif(state == FINISH) begin
            data_shift_neg <={`DATA_WIDTH{1'b0}};
        end
        else begin
            data_shift_neg <= data_shift_neg;
        end
    end

    //assign data_o = dec_pos_or_neg_sample ? data_shift_pos : data_shift_neg;

    assign  data_o =(state == FINISH)?(dec_pos_or_neg_sample ? data_shift_pos : data_shift_neg):{`DATA_WIDTH{1'b0}};
    assign  r_finish =(state == FINISH); 

endmodule

SPI MASTRT 和 SLAVE联合仿真

testbench如下所示:

`timescale  1ns/1ns
`include "defines.v"

module tb_master_slave();

    reg                         clk         ;
    reg                         rst_n       ;//reg                         mosi        ;
    reg     [`DATA_WIDTH-1:0]   data_i      ;
    reg                         start       ;
    reg                         miso        ;

    wire                        mosi        ;
    wire                        sclk        ;
    wire                        finish      ;
    wire                        ss_n        ;
    wire    [`DATA_WIDTH-1:0]   data_o      ;
    wire                        r_finish    ;

    SPI_MASTER u_spi_master(.clk(clk),.rst_n(rst_n),.miso(miso),.data_i(data_i),.start(start),.mosi(mosi),.sclk(sclk),.finish(finish),.ss_n(ss_n));

    SPI_SLAVE u_spi_slave(.clk(clk),.rst_n(rst_n),.mosi(mosi),.sclk(sclk),.tx_finish(finish),.start(start),.ss_n(ss_n),.data_o(data_o),.r_finish(r_finish));

    initial begin
         clk =1'b0;
         rst_n =1'b0;
         start =1'b0;
         data_i =8'h35;
         miso =1'b0;
         #30
         rst_n =1'b1;
         #10;
         @(posedge clk);
         start <=1'b1;
         @(posedge clk);
         start <=1'b0;
         @(negedge finish);
         data_i =8'h44;repeat(2) @(posedge clk);
         start =1'b1;
         @(posedge clk);
         start =1'b0;
    end

    always #(`CLK_CYCLE /2) clk =~clk;

endmodule

仿真的波形如下,可知成功的读取到了8’h35和8’h44。验证成功。
在这里插入图片描述
在这里插入图片描述

总结

通过这次spi的协议的编写,感觉自己对于状态机的掌握,以及脑子里可以浮现波形。加油加油!!!


本文转载自: https://blog.csdn.net/weixin_45614076/article/details/126887159
版权归原作者 IC2ICU 所有, 如有侵权,请联系我们删除。

“SPI协议的verilog实现(spi master slave联合实现)”的评论:

还没有评论