0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AXI-Streamを理解する(Master側)

Last updated at Posted at 2024-08-08

前回の続き

AXIの基礎を理解する

目的

  • AXI4-Streamを理解する

対象

  • FPGAを勉強し始めた初心者の方

AXI-Streamで設計してみる

初心者の方にとっては一見難しそうに見えるかと思いますが、一つ一つの機能を理解すれば大したことはありません。今回、AXI-Streamの理解のためにStateMachine、FIFOを導入していますので、それぞれの理解を重点的に行います。

ブロック図

image.png

設計のポイント

AXI4-Streamの設計概要

  • A/Dコンバータ(ADC)からデータを収集し、各ADC Clock のedgeでサンプリングします。
  • サンプリングされたデータを非同期FIFOにロードします。
  • AXI4-Stream Masterは、この非同期FIFOからデータを取得し、AXI4ストリームバスを介してスレーブに送信します。

フロー制御のためのステートマシン

  • ステートマシンを使用してフロー制御を行います。

  • FIFOが空でないことを示すフラグを使用して、データが送信準備ができていることを示します。

  • スレーブのTREADY信号を使用して、スレーブがデータを受信する準備ができていることを示します。

AXI-StreamにおけるState Machineを理解する

状態遷移図

  • INIT: 初期状態。リセット信号がアクティブであれば、この状態になります。
  • VALID_DATA: 有効データ状態。FIFOが空でなく、AXIストリームが準備完了である場合、この状態になります。
  • STALL_DATA: データ停滞状態。FIFOが空で、AXIストリームが準備完了である場合、この状態になります。
  • SLAVE_STALL: スレーブ停滞状態。FIFOが空でなく、AXIストリームが準備完了でない場合、この状態になります。
    image.png

Verilog HDL のコード

State Machine

FIFOのデータをステートマシンで処理をします。

`timescale 1ns / 1ps
module axis_master_adc
(
  input  wire         adc_clk,        // ADCクロック入力
  input  wire [13: 0] adc_data,       // ADCデータ入力
  input  wire         m_axis_aclk,    // AXIストリームクロック入力
  input  wire         m_axis_aresetn, // AXIストリームリセット入力(アクティブロー)
  output wire [15: 0] m_axis_tdata,   // AXIストリームデータ出力
  output wire [1 : 0] m_axis_tstrb,   // AXIストリームストローブ出力
  output wire [1 : 0] m_axis_tkeep,   // AXIストリームキープ出力
  output reg          m_axis_tvalid,  // AXIストリームデータ有効信号出力
  input  wire         m_axis_tready,  // AXIストリーム準備完了信号入力
  output reg          m_axis_tlast    // AXIストリーム最後のデータ信号出力
);

// FIFO関連の信号宣言
wire fifo_empty;      // FIFO空信号
wire fifo_afull;      // FIFOほぼ満杯信号
reg rd_en;            // FIFO読み出しイネーブル信号
reg wr_en;            // FIFO書き込みイネーブル信号
reg [5:0] data_count; // データカウンタ
wire [13:0] rd_data;  // FIFOから読み出されたデータ
reg wr_en_sync;       // 書き込みイネーブル同期信号
reg m_axis_aresetn_reg; // リセット信号のレジスタ

// ステートマシンの状態宣言
localparam INIT = 0,
           VALID_DATA = 1,
           STALL_DATA = 2,
           SLAVE_STALL = 3;
reg [1:0] state, next_state = 2'b00;

// 非同期FIFOのインスタンス
async_fifo fifo_inst
(
  .wr_clk(adc_clk),    
  .wr_en(wr_en),
  .wr_data(adc_data),
  .rd_clk(m_axis_aclk),
  .rd_en(rd_en),
  .rd_data(rd_data),
  .fifo_empty(fifo_empty),
  .fifo_afull(fifo_afull)
);

// AXIストリーム出力信号の割り当て
assign m_axis_tkeep = 2'b11;
assign m_axis_tstrb = 2'b11;
assign m_axis_tdata[13:0]  = rd_data;
assign m_axis_tdata[15:14] = 2'b00;

// ADCクロックの立ち上がりエッジで書き込みイネーブル信号を同期化
always @(posedge adc_clk)
begin
  if (state == INIT)
    wr_en_sync <= 1'b0;
  else
    wr_en_sync <= 1'b1;

  wr_en <= wr_en_sync;
end

// ステートマシンの次状態遷移ロジック
always @(state, fifo_empty, m_axis_tready, data_count) begin
  next_state = state;
  case (state)
    INIT: begin
      if ((m_axis_aresetn == 0) || (m_axis_aresetn_reg == 0))
        next_state = INIT;
      else if (fifo_empty == 1)
        next_state = STALL_DATA;
      else if (m_axis_tready == 0)
        next_state = SLAVE_STALL;
      else if (fifo_empty == 0)
        next_state = VALID_DATA;
    end
    VALID_DATA: begin
      if (m_axis_tready == 0)
        next_state = SLAVE_STALL;
      else if (fifo_empty == 1)
        next_state = STALL_DATA;
    end
    STALL_DATA: begin
      if ((m_axis_tready == 0)  && (fifo_empty == 0))
        next_state = SLAVE_STALL;
      else if (fifo_empty == 0)
        next_state = VALID_DATA;
    end
    SLAVE_STALL: begin
      if ((m_axis_tready == 1) && (fifo_empty == 0))
        next_state = VALID_DATA;
      else if (m_axis_tready == 1)
        next_state = STALL_DATA;
    end
    default: next_state = INIT;
  endcase
end

// AXIストリームクロックの立ち上がりエッジで状態を更新
always @(posedge m_axis_aclk)
begin
  m_axis_aresetn_reg <= m_axis_aresetn;
  if (m_axis_aresetn == 0)
    state <= INIT;
  else
    state <= next_state;
end

// データカウンタと最後のデータ信号の制御
always @(posedge m_axis_aclk)
begin
  if (next_state == INIT) begin 
    data_count <= 0;
    m_axis_tlast <= 0;
  end
  else if ((next_state == VALID_DATA) && (data_count == 63)) begin
    data_count <= 0;
    m_axis_tlast <= 1;
  end
  else if (next_state == VALID_DATA) begin
    data_count <= data_count + 1;
    m_axis_tlast <= 0;
  end 
end

// ステートマシンの出力ロジック
always @(next_state) begin
  case (next_state)
    INIT: begin
      m_axis_tvalid = 0;
      rd_en = 0;
    end
    VALID_DATA: begin
      m_axis_tvalid = 1;
      rd_en = 1;
    end
    STALL_DATA: begin
      m_axis_tvalid = 0;
      rd_en = 0;
    end
    SLAVE_STALL: begin
      m_axis_tvalid = 1;
      rd_en = 0;
    end
  endcase
end

endmodule

非同期FIFOを理解する

非同期FIFO (First In, First Out) は、異なるクロックドメイン間でデータを転送するために使用されるデータバッファリングの一種です。

非同期FIFOの基本概念

FIFOとは

FIFOは「先入れ先出し」の略で、最初に入力されたデータが最初に出力されるデータ構造です。

非同期FIFOの特徴

  • 非同期: データの書き込みと読み出しが異なるクロックで行われます。
  • クロックドメインの分離: 異なるクロックドメイン間でデータ転送を行うための工夫が必要です。

ブロック図

image.png
この非同期FIFOモジュールは、異なるクロックドメイン間でデータをバッファリングするためのものです。以下に、モジュールの各部分について説明します。

書き込み操作

  • 書き込みクロックの立ち上がりエッジで、FIFO Fullが0でWirte Enableが1の場合に書き込みアドレスがインクリメントされ、データがFIFOメモリに書き込まれます。
  • 同時に、読み出しアドレスが同期化されます。

読み出し操作

  • 読み出しクロックの立ち上がりエッジで、FIFO Emptyが0でRead Enableが1の場合に読み出しアドレスがインクリメントされます。
  • 同時に、書き込みアドレスが同期化されます。

アドレス変換

  • 書き込みと読み出しのアドレスはグレイコードに変換され、同期用レジスタを介して同期されます。
  • 同期されたアドレスはバイナリに戻されます。

空きスペースと状態の計算

  • FIFOの空きスペースは書き込みアドレスと同期された読み出しアドレスの差で計算されます。
  • FIFOが満杯かほぼ満杯、または空であるかを示す信号が生成されます。

Verilog HDL のコード

FIFO

ブロック図を基に非同期FIFO(First In, First Out)を設計します。

`timescale 1ns / 1ps

// 非同期FIFO(First In, First Out)モジュール
module async_fifo
(
    input  wire         wr_clk,     // 書き込みクロック
    input  wire         wr_en,      // 書き込みイネーブル信号
    input  wire [13: 0] wr_data,    // 書き込みデータ
    input  wire         rd_clk,     // 読み出しクロック
    input  wire         rd_en,      // 読み出しイネーブル信号
    output wire [13: 0] rd_data,    // 読み出しデータ
    output wire         fifo_empty, // FIFO空信号
    output wire         fifo_afull  // FIFOほぼ満杯信号
);

// 書き込みと読み出しのアドレス
reg [3:0] wr_addr = 4'b0000;
reg [3:0] rd_addr = 4'b0000;

// グレイコード変換されたアドレスとその同期用レジスタ
wire [3:0] wr_addr_grey;
reg [3:0] wr_addr_grey_sync;
reg [3:0] wr_addr_grey_sync2;
wire [3:0] wr_addr_sync;

wire [3:0] rd_addr_grey;
reg [3:0] rd_addr_grey_sync;
reg [3:0] rd_addr_grey_sync2;
wire [3:0] rd_addr_sync;

// FIFOメモリ空間
reg [13:0] fifo_memory [0:15];

// FIFOの空きスペース、満杯、空の状態
wire [3:0] fifo_space;
wire full;
wire empty;

// 書き込みアドレスをグレイコードに変換
assign wr_addr_grey = {wr_addr[3],wr_addr[3] ^ wr_addr[2],wr_addr[2] ^ wr_addr[1],wr_addr[1] ^ wr_addr[0]};

// 同期化された書き込みアドレスをバイナリに戻す
assign wr_addr_sync = {wr_addr_grey_sync2[3],wr_addr_grey_sync2[3] ^ wr_addr_grey_sync2[2],
                  wr_addr_grey_sync2[3] ^ wr_addr_grey_sync2[2] ^ wr_addr_grey_sync2[1],
                  wr_addr_grey_sync2[3] ^ wr_addr_grey_sync2[2] ^ wr_addr_grey_sync2[1] ^ wr_addr_grey_sync2[0]};

// 読み出しアドレスをグレイコードに変換
assign rd_addr_grey = {rd_addr[3],rd_addr[3] ^ rd_addr[2],rd_addr[2] ^ rd_addr[1],rd_addr[1] ^ rd_addr[0]};

// 同期化された読み出しアドレスをバイナリに戻す
assign rd_addr_sync = {rd_addr_grey_sync2[3],rd_addr_grey_sync2[3] ^ rd_addr_grey_sync2[2],
                 rd_addr_grey_sync2[3] ^ rd_addr_grey_sync2[2] ^ rd_addr_grey_sync2[1],
                 rd_addr_grey_sync2[3] ^ rd_addr_grey_sync2[2] ^ rd_addr_grey_sync2[1] ^ rd_addr_grey_sync2[0]};

// FIFOの空きスペースを計算
assign fifo_space =  wr_addr - rd_addr_sync;
assign full       =  (fifo_space > 14) ? 1:0;
assign fifo_afull =  (fifo_space > 13) ? 1:0;
assign empty      =  ((rd_addr == wr_addr_sync) ? 1:0);
assign fifo_empty = empty;

// 書き込みクロックの立ち上がりエッジで書き込みアドレスをインクリメント
always @(posedge wr_clk)
begin
  if ((full == 0) && (wr_en == 1))
    wr_addr <= wr_addr + 1;
end

// 書き込みクロックの立ち上がりエッジで読み出しアドレスを同期化
always @(posedge wr_clk)
begin
    rd_addr_grey_sync  <= rd_addr_grey;
    rd_addr_grey_sync2 <= rd_addr_grey_sync;
end

// 書き込みクロックの立ち上がりエッジでデータをFIFOメモリに書き込む
always @(posedge wr_clk)
begin
    if ((full == 0) && (wr_en == 1))
      fifo_memory[wr_addr] <= wr_data;
end

// 読み出しデータの割り当て
assign rd_data = fifo_memory[rd_addr];

// 読み出しクロックの立ち上がりエッジで書き込みアドレスを同期化
always @(posedge rd_clk)
begin
    wr_addr_grey_sync  <= wr_addr_grey;
    wr_addr_grey_sync2 <= wr_addr_grey_sync;
end

// 読み出しクロックの立ち上がりエッジで読み出しアドレスをインクリメント
always @(posedge rd_clk)
begin
  if ((rd_en == 1) && (empty == 0))
    rd_addr <= rd_addr + 1;
end

endmodule

Verilog HDLを用いたサンプルコード

ADC

簡易的なADCのモデルを作成します。

`timescale 1ns / 1ps
module adc_model
(
    input  wire         adc_clk,
    output reg  [13: 0] adc_data
);

initial begin
    adc_data = 14'b0;
end

always@(posedge adc_clk) begin
  adc_data <= adc_data + 1;
end

endmodule

テストベンチ

`timescale 1ns / 1ps

// テストベンチモジュール

module t_axis_master_adc;

reg adc_clk;             // ADCクロック信号
wire [13:0] adc_data;    // ADCデータ信号
reg m_axis_aclk;         // AXIストリームクロック信号
reg m_axis_aresetn;      // AXIストリームリセット信号(アクティブロー)
wire [15 : 0] m_axis_tdata;   // AXIストリームデータ信号
wire [1 : 0]  m_axis_tstrb;   // AXIストリームストローブ信号
wire [1 : 0]  m_axis_tkeep;   // AXIストリームキープ信号
wire m_axis_tvalid;      // AXIストリームデータ有効信号
wire m_axis_tready;      // AXIストリームレディ信号
wire m_axis_tlast;       // AXIストリーム最後のデータ信号

reg [5:0] lfsr;          // 擬似乱数生成器
reg [15:0] data_check;   // データチェック用
reg [15:0] rem_value;    // リマインダー値
reg error;               // エラーフラグ
reg tlast_error;         // 最後のデータエラーフラグ
integer i;               // ループカウンタ

// 初期化
initial
begin
  adc_clk = 1'b0;       // ADCクロック初期化
  m_axis_aclk = 1'b0;   // AXIストリームクロック初期化
  m_axis_aresetn = 1'b0; // リセット初期化
  lfsr = 6'b100000;     // LFSR初期化
  for (i = 0; i < 5; i = i + 1) @(posedge m_axis_tlast); // tlastの立ち上がりエッジを5回待つ
  $finish;              // シミュレーション終了
end

// クロック生成
always #20 m_axis_aclk = ~m_axis_aclk; // 20ns周期でAXIクロックを反転
always #50 adc_clk = ~adc_clk;         // 50ns周期でADCクロックを反転

// DUT(デバイスアンダーテスト)のインスタンス化
axis_master_adc uut (
  .adc_clk(adc_clk),
  .adc_data(adc_data),
  .m_axis_aclk(m_axis_aclk),
  .m_axis_aresetn(m_axis_aresetn),
  .m_axis_tdata(m_axis_tdata),
  .m_axis_tstrb(m_axis_tstrb),
  .m_axis_tkeep(m_axis_tkeep),
  .m_axis_tvalid(m_axis_tvalid),
  .m_axis_tlast(m_axis_tlast),
  .m_axis_tready(m_axis_tready)
);

// ADCモデルのインスタンス化
adc_model adc_model_inst (
  .adc_clk(adc_clk),
  .adc_data(adc_data)
);

// リセットシーケンス
initial
begin
  m_axis_aresetn = 1'b0; // リセットアサート
  #200 m_axis_aresetn = 1'b1; // 200ns後にリセットデアサート
end

// LFSRの生成
always @(posedge m_axis_aclk) begin
  lfsr[0] <= lfsr[5] ^ lfsr[4] ^ 1'b1;
  lfsr[5:1] <= lfsr[4:0];
end
assign m_axis_tready = lfsr[5]; // tready信号をLFSRの最上位ビットに設定

// データチェック
initial begin
  while (!((m_axis_tvalid === 1) && (lfsr[5] == 1))) @(posedge m_axis_aclk); // tvalidとLFSRが1になるのを待つ
  data_check = m_axis_tdata;
  rem_value = m_axis_tdata % 64;
  while (1) begin
    @(posedge m_axis_aclk);
    if ((m_axis_tvalid == 1) && (lfsr[5] == 1))
      data_check = data_check + 1;
    if ((m_axis_tdata != data_check) && (m_axis_tvalid == 1) && (lfsr[5] == 1))
      error = 1;
    else
      error = 0;

    if (((m_axis_tdata % 64) != rem_value) && (m_axis_tvalid == 1) && (m_axis_tlast == 1) && (lfsr[5] == 1))
      tlast_error = 1;
    else
      tlast_error = 0;
  end
end

endmodule

波形図

  • axi_master_adc.v
  • async_fifo.v
  • adc_model.v
  • t_axi_master_adc.v

上記の4つのモジュールを用いてシミュレーションを行います。
無事に波形が観測できました。

image.png

前回の記事

AXIの基礎を理解する

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?