前回の続き
目的
- AXI4-Streamを理解する
対象
- FPGAを勉強し始めた初心者の方
目次
AXI4-Streamの基本概念
AXI4-Streamは、主に以下の4つの基本概念に基づいています。
- チャネル: データの流れを表す論理的なリンク。
- トランザクション: 1つのデータ転送イベント。
- ハンドシェイク: データの送信者と受信者が同期するための信号。
- データストリーム: 連続的に送信されるデータの流れ。
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
信号をアサートすることでデータ転送が行われます。
特徴
- マスターがデータを準備できたことを示すために、
TVALID
を先にアサートします。 - スレーブがデータを受け取る準備ができると、
TREADY
をアサートします。 - 両方の信号がアサートされた時点でデータ転送が行われます。
2. Ready先行型
説明
このパターンでは、スレーブが先にTREADY
信号をアサートし、その後マスターがTVALID
信号をアサートすることでデータ転送が行われます。
特徴
- スレーブがデータを受け取る準備ができたことを示すために、
TREADY
を先にアサートします。 - マスターがデータを準備できると、
TVALID
をアサートします。 - 両方の信号がアサートされた時点でデータ転送が行われます。
3. Valid/Ready同時型
説明
このパターンでは、マスターとスレーブが同時にTVALID
およびTREADY
信号をアサートし、データ転送が行われます。
特徴
- マスターとスレーブが同時にデータの送受信の準備ができている場合に用いられます。
- 両方の信号が同時にアサートされることで、効率的なデータ転送が可能となります。
AXI-StreamのSlave側を理解する
今回Slave側の理解を深めるために、簡単な設計を行います。
設計の内容はAXI4-Streamバスからデータを受け取り、データを内部メモリに格納するものです。
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
波形図
AXI-StreamのMaster側を理解する
初心者の方にとっては一見難しそうに見えるかと思いますが、一つ一つの機能を理解すれば大したことはありません。今回、AXI-Streamの理解のためにStateMachine、FIFOを導入していますので、それぞれの理解を重点的に行います。
ブロック図
設計のポイント
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ストリームが準備完了でない場合、この状態になります。
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の特徴
- 非同期: データの書き込みと読み出しが異なるクロックで行われます。
- クロックドメインの分離: 異なるクロックドメイン間でデータ転送を行うための工夫が必要です。
ブロック図
この非同期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つのモジュールを用いてシミュレーションを行います。
無事に波形が観測できました。