2
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?

AXI4-Streamを理解する

Last updated at Posted at 2024-08-07

前回の続き

AXIの基礎を理解する

目的

  • AXI4-Streamを理解する

対象

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

目次

AXI4-Streamの基本概念

AXI4-Streamは、主に以下の4つの基本概念に基づいています。

  1. チャネル: データの流れを表す論理的なリンク。
  2. トランザクション: 1つのデータ転送イベント。
  3. ハンドシェイク: データの送信者と受信者が同期するための信号。
  4. データストリーム: 連続的に送信されるデータの流れ。

AXI4-Streamでは、データ送信者をマスター、データ受信者をスレーブと呼びます。マスターがデータを送信し、スレーブがそのデータを受信します。

AXI4-Streamの信号一覧

信号名 信号ソース 使用条件 信号の役割
ACLK グローバル 常に クロック信号
ARESETn グローバル リセットが必要な場合 リセット信号
TVALID マスター マスターがデータを送信するとき データが有効であることを示す
TREADY スレーブ スレーブがデータを受信するとき データを受信できることを示す
TDATA マスター データ転送中 転送される実際のデータ
TKEEP マスター 有効なバイトを示す場合 有効なバイトを示す
TSTRB マスター 各バイトの有効性を示す場合 各バイトが有効かを示す
TLAST マスター パケットの終わりを示す場合 パケットの終わりを示す
TID マスター パケットの識別が必要な場合 パケットの識別子を示す
TDEST マスター 送信先が複数ある場合 データの送信先を示す
TUSER マスター 特定のアプリケーション要件に応じて ユーザー定義の役割を持つ

AXI4-Streamの信号

AXI4-Streamの信号は、データ転送のための基本的なハンドシェイク信号と、データフローを制御するための補助的な信号に分かれます。

基本信号

  • TVALID: データが有効であることを示すマスターからの信号。TVALIDがアサートされているとき、マスターは有効なデータを送信しています。
  • TREADY: スレーブがデータを受信できることを示す信号。TREADYがアサートされているとき、スレーブはデータを受信できます。
  • TDATA: 転送される実際のデータ。データの幅はアプリケーションによって異なります。

補助信号

  • TKEEP: データビート内で有効なバイトを示す信号。パケットの一部のみが有効である場合に使用します。
  • TSTRB: 各バイトが有効かどうかを示す信号。データのストローブ(strobe)として機能します。
  • TLAST: パケットの終わりを示す信号。TLASTがアサートされると、現在のパケットが終了したことを示します。
  • TID: パケットの識別子を示す信号。複数のデータストリームを扱う場合に使用します。
  • TDEST: データの送信先を示す信号。複数の受信者が存在する場合に使用します。
  • TUSER: ユーザー定義の信号。特定のアプリケーション要件に応じて使用されます。

これらの信号を組み合わせることで、AXI4-Streamは柔軟で効率的なデータ転送を実現します。FPGA設計において、これらの信号の使い方を理解し、適切に配置することが重要です。

AXI4-Streamのプロトコル

AXI4-Streamプロトコルには、データ転送のタイミングに基づいて3つのパターンがあります。それぞれのパターンについて説明します。

1. Valid先行型

説明

このパターンでは、マスターが先にTVALID信号をアサートし、その後スレーブがTREADY信号をアサートすることでデータ転送が行われます。

image.png

特徴

  • マスターがデータを準備できたことを示すために、TVALIDを先にアサートします。
  • スレーブがデータを受け取る準備ができると、TREADYをアサートします。
  • 両方の信号がアサートされた時点でデータ転送が行われます。

2. Ready先行型

説明

このパターンでは、スレーブが先にTREADY信号をアサートし、その後マスターがTVALID信号をアサートすることでデータ転送が行われます。

image.png

特徴

  • スレーブがデータを受け取る準備ができたことを示すために、TREADYを先にアサートします。
  • マスターがデータを準備できると、TVALIDをアサートします。
  • 両方の信号がアサートされた時点でデータ転送が行われます。

3. Valid/Ready同時型

説明

このパターンでは、マスターとスレーブが同時にTVALIDおよびTREADY信号をアサートし、データ転送が行われます。

image.png

特徴

  • マスターとスレーブが同時にデータの送受信の準備ができている場合に用いられます。
  • 両方の信号が同時にアサートされることで、効率的なデータ転送が可能となります。

AXI-StreamのSlave側を理解する

今回Slave側の理解を深めるために、簡単な設計を行います。
設計の内容はAXI4-Streamバスからデータを受け取り、データを内部メモリに格納するものです。

image.png

AXI4-StreamのSlave側のVerilog HDLコード

今回は、AXIストリームスレーブメモリモジュールのVerilogコードを初心者向けにコメントを追加して解説します。このモジュールは、データをメモリに書き込むシンプルな例です。
テストベンチも用意しているので、波形図も見てみましょう。

`timescale 1ns / 1ps

// AXIストリームスレーブメモリモジュール
module axis_slave_mem
(
    input  wire         s_axis_aclk,    // AXIストリームのクロック信号
    input  wire         s_axis_aresetn, // 非同期リセット(アクティブロー)
    input  wire [31: 0] s_axis_tdata,   // データバス(32ビット)
    input  wire [3 : 0] s_axis_tstrb,   // ストローブ信号(未使用)
    input  wire [3 : 0] s_axis_tkeep,   // キープ信号(未使用)
    input  wire         s_axis_tvalid,  // 有効データ信号
    output wire         s_axis_tready,  // レディ信号(データ受け取り準備完了)
    input  wire         s_axis_tlast    // 最後のデータ信号
);

// シミュレーションフロー制御用のパラメータ
parameter FLOW_SIM = 1;

// メモリアドレス用レジスタ(7ビット)
reg [6:0]  memory_address;

// データメモリ(128x32ビット)
reg [31:0] data_memory [0:127];

// リセット信号のレジスタ
reg s_axis_aresetn_reg;

// 疑似乱数生成用のレジスタ(6ビット)
reg [5:0] lfsr;

// クロックの立ち上がりエッジで動作するalwaysブロック
always @(posedge s_axis_aclk)
begin
  // リセット信号をレジスタに保存
  s_axis_aresetn_reg <= s_axis_aresetn;

  // リセット時、LFSRを初期値に設定
  if (s_axis_aresetn == 0)
    lfsr <= 6'b000101;
  else
    // データが有効な時、またはLFSRの最上位ビットが0の時にLFSRを更新
    if ((s_axis_tvalid == 1) || ((s_axis_tvalid == 0) && (lfsr[5] == 0))) begin
      lfsr[0] <= lfsr[5] ^ lfsr[4] ^ 1'b1; // LFSRの最下位ビットを更新
      lfsr[5:1] <= lfsr[4:0];              // LFSRのシフト操作
    end
end

// s_axis_treadyの割り当て
assign s_axis_tready = ((s_axis_aresetn == 0) || (s_axis_aresetn_reg == 0)) ? 1'b0 : FLOW_SIM ? lfsr[5] : 1'b1;

// メモリアドレスの更新
always @(posedge s_axis_aclk)
begin
  if (s_axis_aresetn == 0)
    memory_address <= 7'h00; // リセット時、アドレスを0に設定
  else begin
    // 最後のデータ時、アドレスを0にリセット
    if ((s_axis_tlast == 1) && (s_axis_tvalid == 1) && (s_axis_tready == 1))
        memory_address <= 0;
    // データ受信時、アドレスをインクリメント
    else if ((s_axis_tvalid == 1) && (s_axis_tready == 1))
      memory_address <= memory_address + 1;
  end
end

// データメモリへのデータ書き込み
always @(posedge s_axis_aclk)
begin
  if ((s_axis_tvalid == 1) && (s_axis_tready == 1))
    data_memory[memory_address] <= s_axis_tdata; // データ書き込み
end

endmodule

テストベンチ

`timescale 1ns / 1ps

// AXIストリームスレーブメモリモジュールのテストベンチ
module t_axis_slave_mem;

reg s_axis_aclk;          // クロック信号
reg s_axis_aresetn;       // リセット信号(アクティブロー)
reg [31:0] s_axis_tdata;  // データバス(32ビット)
reg [3 : 0] s_axis_tstrb; // ストローブ信号(未使用)
reg [3 : 0] s_axis_tkeep; // キープ信号(未使用)
wire s_axis_tvalid;       // 有効データ信号
wire s_axis_tready;       // レディ信号
wire s_axis_tlast;        // 最後のデータ信号
reg [5 : 0] lfsr = 6'b000011; // 疑似乱数生成用のレジスタ
reg tlast;               // 最後のデータ信号

// クロックの初期化
initial
begin
  s_axis_aclk = 1'b0;
end

// 50nsごとにクロックを反転させる
always
  #50 s_axis_aclk = ~s_axis_aclk;


// テスト対象のAXIストリームスレーブメモリモジュールをインスタンス化
axis_slave_mem uut (
 .s_axis_aclk(s_axis_aclk),
 .s_axis_aresetn(s_axis_aresetn),
 .s_axis_tdata(s_axis_tdata),
 .s_axis_tstrb(s_axis_tstrb),
 .s_axis_tkeep(s_axis_tkeep),
 .s_axis_tvalid(s_axis_tvalid),
 .s_axis_tready(s_axis_tready),
 .s_axis_tlast(s_axis_tlast)
);

// クロックの立ち上がりエッジでLFSRを更新
always @(posedge s_axis_aclk) begin
  if ((s_axis_tready == 1) || ((s_axis_tready == 0) && (s_axis_tvalid == 0))) begin
    lfsr[0] <= lfsr[5] ^ lfsr[4] ^ 1'b1; // LFSRの最下位ビットを更新
    lfsr[5:1] <= lfsr[4:0];              // LFSRのシフト操作
  end
end

// s_axis_tvalid信号の割り当て
assign s_axis_tvalid = lfsr[5];

// s_axis_tlast信号の割り当て
assign s_axis_tlast = (lfsr[5] & (s_axis_tdata == 127));

// 初期化とリセット解除
initial
begin
  s_axis_aresetn = 1'b0;    // リセット信号をアサート
  s_axis_tstrb	= 4'b1111;  // ストローブ信号を設定(未使用)
  s_axis_tkeep	= 4'b1111;  // キープ信号を設定(未使用)
  s_axis_tdata   = 32'd0;   // データを0に初期化
  @(posedge s_axis_aclk);   // クロックの立ち上がりエッジを待つ
  @(posedge s_axis_aclk);
  @(posedge s_axis_aclk);
  s_axis_aresetn = 1'b1;    // リセット信号をデアサート
end

// データバスの値を更新
always @(posedge s_axis_aclk) begin
  if ((s_axis_tvalid == 1) && (s_axis_tready == 1)) begin
    if (s_axis_tdata == 127) 
      s_axis_tdata <= 0; // データが127に達したら0にリセット
    else
      s_axis_tdata <= s_axis_tdata + 1; // それ以外の場合、データをインクリメント
  end
end

endmodule

波形図

image.png

AXI-StreamのMaster側を理解する

初心者の方にとっては一見難しそうに見えるかと思いますが、一つ一つの機能を理解すれば大したことはありません。今回、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の基礎を理解する

2
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
2
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?