SPI プロトコルの Verilog 実装 (spi マスター スレーブ ジョイント実装)

SPIプロトコルの紹介

SPIとはSerial Peripheral Interfaceの略で、シリアル拡張バスのことです。SPI はシングルマスターデバイス通信であり、バス上に通信を開始するマスターデバイスは 1 台だけあり、通信を開始できるデバイスをマスターデバイスと呼びます。SPI マスター デバイスがスレーブ デバイスからの読み取りおよび書き込みを行う場合、まず対応するスレーブ デバイスの ss ラインをプルダウンします (ロー レベルがアクティブ)。次に、動作する小麦の種子をクロックラインに送信し、対応するパルス時間でマスターデバイスがMOSIに信号を送信して読み取りと書き込みを実現し、同時にMISOをサンプリングして読み取りを実現します。一般的な SPI 通信には次の用語が含まれます。

SCLK シリアルクロック (マスターデバイスから)
モシ マスター出力 スレーブ入力 (マスターデバイスから)
味噌 マスター入力 スレーブ出力 (スレーブデバイスから)
SS スレーブ選択 (アクティブロー、マスターデバイスから)

マスター機器とスレーブ機器の2つのリンク方式

1 つのマスターと 1 つのスレーブ

1 つのマスターと 1 つのスレーブ モードは、以下に示すように、マスターとスレーブが存在することを示します (マスターからスレーブへの SCK 信号もあります)。
ここに画像の説明を挿入します

1 人のマスター、多数のスレーブ

1 つのマスターと複数のスレーブは、1 つのマスターと 1 つのスレーブが存在することを意味しますが、唯一の違いは、各スレーブに選択信号が装備されている必要があることです。
ここに画像の説明を挿入します

SPIプロトコルの動作モード

SPI には 4 つの動作モードがあり、これらは主にクロック極性 CPOL (Clock Polarity) とクロック位相 CPHA (Clock Phase) の組み合わせによって決まります。

  • CPOL が 0 の場合はアイドル状態で SCK が 0 であることを意味し、1 の場合はアイドル状態で SCK が 1 であることを意味します。
  • CPHA が 0 の場合は SCK の最初のエッジで出力データが有効であることを意味し、CPHA が 1 の場合は SCK の 2 番目のエッジで入出力データが有効であることを意味します。

CPOL = 0、CPHA = 0

ここに画像の説明を挿入します

CPOL = 0、CPHA = 1

ここに画像の説明を挿入します

CPOL = 1.CPHA = 0

ここに画像の説明を挿入します

CPOL = 1、CPHA = 1

ここに画像の説明を挿入します

SPI MASTERのVerilog設計アイデア

設計ピンの説明:

信号名 行き方と説明
カチカチ 入力、クロック信号
最初_n 入力、リセット信号
味噌 入力、マスターへのスレーブ入力
データi 入力、マスターがスレーブからデータを送信します(特定のビット幅)
始める 入力、開始のためのイネーブル信号
モシ 出力、マスターからスレーブへ
スクク 出力、クロック信号
ss_n 出力、スレーブ選択信号
仕上げる 転送完了信号

まず、必要なマクロ定義をファイル 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

2 つ目は SPI MASTER の設計アイデアです。一般的なアイデアはステート マシンを使用することです。最初にステート マシンは IDLE 状態にあります。その後、スタート信号を受信した後、レジスタ data_I 内のデータが送信されます。指定されたワイドスペースビットが終了後に送信されるとき。このときの選択肢は 2 つあり、1 つは FINISH ステートにジャンプする方法、もう 1 つは EXTRA ステートにジャンプしてから FINISH ステートにジャンプする方法です。主な理由は、データ送信が完了した後、その時の SCLK 状態がアイドル モードのデフォルト状態であるかどうかを判断する必要があり、そうでない場合は EXTRA ステートにジャンプする必要があるためです。終了ステートの後、次のステートは IDLE ステートに戻り、開始コマンドが取得されます。

アイドル アイドル状態
データ 送信データステータス
余分な 追加ステータス
仕上げる 終了状態

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
        else if(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;
        //end
        else if(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
        else if(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;
        //end
        else if(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
        else if((state == DATA) && (nx_state == FINISH) && (cnt == CNT_MAX)) begin
            cnt <= 'd0;
        end
        else if((state == DATA) && (nx_state == EXTRA) && (cnt == CNT_MAX)) begin
            cnt <= 'd0;
        end
        else if((state == DATA) && (cnt == CNT_MAX)) begin
            cnt <= 'd0;
        end
        else if((state == EXTRA) && (cnt == CNT_MAX)) begin
            cnt <= 'd0;
        end
        else if(state == DATA) begin
            cnt <= cnt + 1'b1;
        end
        else if(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
        else if(start_dly) begin
            sclk <= ~sclk;
        end
        else if((state == DATA) && (cnt_max_flag) && (cnt_data < `DATA_WIDTH) ) begin
            sclk <= ~sclk;
        end
        else if((state == DATA) && (cnt_max_flag) && (cnt_data == `DATA_WIDTH) && (nx_state == EXTRA)) begin
            sclk <= ~sclk;
        end
        else if((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
        else if(start) begin
            ss_n <= 1'b0;
        end
        else if(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


シミュレートされたテストベンチは次のとおりです。

`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 の設計思想はマスターの設計思想とほぼ同様であり、ポートの説明は次のとおりです。

信号名 行き方と説明
カチカチ 入力、クロック信号
最初_n 入力、リセット信号
味噌 入力、マスターへのスレーブ入力
データi 入力、マスターがスレーブからデータを送信します(特定のビット幅)
始める 入力、開始のためのイネーブル信号
モシ 出力、マスターからスレーブへ
スクク 入力、クロック信号
ss_n 入力、スレーブのセレクト信号
r_finish 転送完了信号
日付_o 出力、収集されたデータ

引き続きステート マシンのアイデアを使用し、最初は IDLE 状態で、開始信号が有効になると、RV_DATA 受信データ状態にジャンプします。RV_DATA データ受信が完了すると、読み取りが完了したことを示す FINISH ステートにジャンプします。

アイドル アイドル状態
RV_データ 受信データステータス
仕上げる 終了状態
spiスレーブのコードは次のとおりです。
`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
                else if((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
        else if((state == FINISH)) begin
            cnt_sclk_pos <= 4'd0;
        end
        else if(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
        else if (state == FINISH) begin
            cnt_sclk_neg <= 4'd0;
        end
        else if (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
        else if((state == RV_DATA) && (sclk_posedge)) begin
            data_shift_pos <= {
    
    mosi, data_shift_pos[`DATA_WIDTH-1:1]};
        end
        else if (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
        else if((state == RV_DATA) && (sclk_negedge)) begin
            data_shift_neg <= {
    
    mosi, data_shift_neg[`DATA_WIDTH-1:1]};
        end
        else if(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 の共同シミュレーション

テストベンチは次のようになります。

`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プロトコルを書くことで、ステートマシンをよく理解でき、波形を頭の中に持つことができるようになったと感じます。さあさあ!

おすすめ

転載: blog.csdn.net/weixin_45614076/article/details/126887159