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