AXI-Fullで多倍長転送
暗号回路では256ビットとかの数値を扱うのでAXIを使うとバースト転送が欲しくなる。(AXI-FULLの規格上は256ビットバスも作れるが、Zynqにつながってるバスが32bitなのでハード的に32bit転送に制限される。)
ネットに転がっている情報はAXI-LiteかAXI-Streamが多くてAXI-Fullを簡単に使うにはどうすればよいかわからなかったのでここに記す。
テンプレート作成
スクラッチからAXIを書いてる人とかもいるけど、VivadoにAXIのテンプレートがついてるのでそれを使う。
Create and Package New IP
->Create a new AXI4 peripheral
でAXI4マスタを作る。
最後Edit IP
にすると編集できる。
ソースコードとしてはラッパーファイルと本体の2種類ができる。
さてこのコードが何をしているかというと、
// Example State machine to initialize counter, initialize write transactions,
// initialize read transactions and comparison of read data with the
// written data words.
parameter [1:0] IDLE = 2'b00, // This state initiates AXI4Lite transaction
// after the state machine changes state to INIT_WRITE
// when there is 0 to 1 transition on INIT_AXI_TXN
INIT_WRITE = 2'b01, // This state initializes write transaction,
// once writes are done, the state machine
// changes state to INIT_READ
INIT_READ = 2'b10, // This state initializes read transaction
// once reads are done, the state machine
// changes state to INIT_COMPARE
INIT_COMPARE = 2'b11; // This state issues the status of comparison
// of the written data with the read data
この辺に書いてあるように、サンプルとして4状態のステートマシンを持っている。
参考
初期状態IDLE
からINIT_AXI_TXN
信号でINIT_WRITE
に遷移しテストデータを書き込む。書き込み終わったらINIT_READ
に遷移して書き込んだ値を読み取りINIT_COMPARE
に遷移、値を比較するという流れ。
利用するときはINIT_COMPARE
はいらないとして、INIT_READ
とINIT_WRITE
を好きなタイミングでトリガすればバーストリード/ライトできるんじゃないか?ちなみにINIT_WRITE
は外部からのINIT_AXI_TXN
信号でトリガされるので、ここに書き込み信号を入れてやればいい。
AXI Verification IPによる検証
一応やってみたが、マスタ検証時はBRAMで十分な気がしたので見る価値ないかも
パッケージしたAXIテンプレートをAXI VIPにつないで検証してみる。VIPの使い方は
参考
を参考。
適当に繋いで、AXI_BURST_LENを8にしている。m00_axi_init_axi_txn
がINIT_WRITE
になるので、ここにRUN
信号を入れることで書き込みを開始する。
上記サイトやサンプルプログラム(ブロックデザインのIPを右クリックしてOpen IP Example Design
)を参考に次のようなテストベンチを用意
`timescale 1ns / 1ps
import axi_vip_pkg::*;
import design_1_axi_vip_0_0_pkg::*;
module tb_axi();
reg rst, clk, run;
parameter int RST_WAIT = 77;
parameter int CLK_WAIT = 10;
task clk_gen();
clk = 0;
forever #(CLK_WAIT/2) clk = ~clk;
endtask
task rst_gen();
rst = 1;
#(RST_WAIT);
rst = 0;
endtask
design_1_wrapper DUT(
.RUN(run),
.CLK(clk),
.RSTN(~rst)
);
design_1_axi_vip_0_0_slv_t agent;
task init_agent();
agent = new("design_1_axi_vip_0_0_slv", DUT.design_1_i.axi_vip_0.inst.IF);
agent.start_slave();
endtask
initial begin
fork
clk_gen();
init_agent();
join_none
run <= 0;
rst_gen();
#100
run <= 1;
#CLK_WAIT
run <= 0;
#(CLK_WAIT * 10);
$finish();
end
endmodule
まずインポートパッケージについて
import axi_vip_pkg::*;
import design_1_axi_vip_0_0_pkg::*;
axi_vip_pkg::*
は固定?design_1_axi_vip_0_0_pkg::*
はデザインごとに異なるので適宜設定。
IP Sourcesの
にソースコードがあるのでそれを指定すればいい。
ちなみにこのパッケージに
///////////////////////////////////////////////////////////////////////////
// How to start the verification component
///////////////////////////////////////////////////////////////////////////
// design_1_axi_vip_0_0_slv_t design_1_axi_vip_0_0_slv;
// initial begin : START_design_1_axi_vip_0_0_SLAVE
// design_1_axi_vip_0_0_slv = new("design_1_axi_vip_0_0_slv", `design_1_axi_vip_0_0_PATH_TO_INTERFACE);
// design_1_axi_vip_0_0_slv.start_slave();
// end
のようにVIPの起動方法が書いてある。
テストベンチはrun
を上げているだけ。
実行結果は
こんな感じで、run
の立ち上げ理から3クロック後にvalid/ready
が全部上がってバースト転送開始している。転送アドレスは0x40000000で、これは単なる初期値なので、指定したいときはほかの入力が必要。転送アドレスは1ワードずつ増加するのではなく、バースト長単位=
書き込むデータは1, 2,...という感じで、これも自動生成なので、実際使うときは与えないといけない。
ここまでやって気づいたが、マスタをテストするときはVIPじゃなくてAXI-BRAMでいいのでは?
AXI BRAMによるテスト
こんな感じ
で単にAXI BRAMと自作AXIをつなげる。コントローラはインタフェースを1にして幅32ビット。
自作AXIはRUN_RとRUN_Wでそれぞれバーストリード/ライトを開始するようにしている。
これは、
//implement master command interface state machine
always @ ( posedge M_AXI_ACLK)
begin
if (M_AXI_ARESETN == 1'b0 )
begin
// reset condition
// All the signals are assigned default values under reset condition
mst_exec_state <= IDLE;
start_single_burst_write <= 1'b0;
start_single_burst_read <= 1'b0;
ERROR <= 1'b0;
end
else
begin
// state transition
case (mst_exec_state)
IDLE:
// This state is responsible to wait for user defined C_M_START_COUNT
// number of clock cycles.
if ( init_txn_pulse == 1'b1)
begin
mst_exec_state <= INIT_WRITE;
end
else if ( init_rxn_pulse == 1'b1)
begin
mst_exec_state <= INIT_READ;
end
else
begin
mst_exec_state <= IDLE;
end
INIT_WRITE:
// This state is responsible to issue start_single_write pulse to
// initiate a write transaction. Write transactions will be
// issued until burst_write_active signal is asserted.
// write controller
if (writes_done)
begin
mst_exec_state <= IDLE;//
end
else
begin
mst_exec_state <= INIT_WRITE;
if (~axi_awvalid && ~start_single_burst_write && ~burst_write_active)
begin
start_single_burst_write <= 1'b1;
end
else
begin
start_single_burst_write <= 1'b0; //Negate to generate a pulse
end
end
INIT_READ:
// This state is responsible to issue start_single_read pulse to
// initiate a read transaction. Read transactions will be
// issued until burst_read_active signal is asserted.
// read controller
if (reads_done)
begin
mst_exec_state <= IDLE;
end
else
begin
mst_exec_state <= INIT_READ;
if (~axi_arvalid && ~burst_read_active && ~start_single_burst_read)
begin
start_single_burst_read <= 1'b1;
end
else
begin
start_single_burst_read <= 1'b0; //Negate to generate a pulse
end
end
default :
begin
mst_exec_state <= IDLE;
end
endcase
end
end //MASTER_EXECUTION_PROC
のように、テンプレートのステートマシン記述を書き換えることで実現している。
あと比較機能とか、不要と思われる機能はテンプレートからなるべく排除している。
テストベンチは
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 05/29/2023 05:47:25 PM
// Design Name:
// Module Name: tb_axi_bram
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module tb_axi_bram();
reg rst, clk, run_r, run_w;
parameter int RST_WAIT = 77;
parameter int CLK_WAIT = 10;
task clk_gen();
clk = 0;
forever #(CLK_WAIT/2) clk = ~clk;
endtask
task rst_gen();
rst = 1;
#(RST_WAIT);
rst = 0;
endtask
design_2_wrapper DUT(
.RUN_R(run_r),
.RUN_W(run_w),
.CLK(clk),
.RSTN(~rst)
);
initial begin
fork
clk_gen();
join_none
run_w <= 0;
run_r <= 0;
rst_gen();
#100
run_w <= 1;
#CLK_WAIT
run_w <= 0;
#(CLK_WAIT * 30);
run_r <= 1;
#CLK_WAIT
run_r <= 0;
$finish();
end
endmodule
run_wとrun_rをアサートするだけ。
実行結果
書き込み
run_wがアサートされると、awvalid/ready, wvalid/readyが立ち上がり、アドレス0x40000000
に対してバーストライトが開始される。アドレス+0x20されているのではバースト単位で変化する模様。wdata
はテンプレートで自動生成されており、1からインクリメントされている。
[0], [1], [2], [3]
となっている部分がBRAMの生データで、1, 2, 3, 4となってるのでちゃんと動いてそうである。
読み出し
続いて読み出しをテスト
多倍長を扱いたいので、32ビット8ワードを256ビットレジスタに入れる。テンプレート中にM_AXI_RVALID
のif文があるので
/*
The Read Data channel returns the results of the read request
In this example the data checker is always able to accept
more data, so no need to throttle the RREADY signal
*/
reg [255:0] my_reg;
always @(posedge M_AXI_ACLK)
begin
if (M_AXI_ARESETN == 0 || init_txn_pulse == 1'b1 )
begin
axi_rready <= 1'b0;
my_reg <= 0;
end
// accept/acknowledge rdata/rresp with axi_rready by the master
// when M_AXI_RVALID is asserted by slave
else if (M_AXI_RVALID)
begin
my_reg[32*read_index+:32] <= M_AXI_RDATA;
if (M_AXI_RLAST && axi_rready)
begin
axi_rready <= 1'b0;
end
else
begin
axi_rready <= 1'b1;
end
end
// retain the previous value
end
こんな感じでRVALID
の時にmy_reg
の該当indexに32ビットを格納している。
実行結果はこのように、run_r
アサート後にアドレス0x40000000
からバーストリードが開始されている。これは先ほどBRAMに書き込んだアドレスなので、1, 2, 3,...がBRAMから読み出され、256ビットのmy_regが最終的に0x000000080000000700000006...
になってるのでちゃんと動いてるっぽい。
感想
AXI-FULLも意外と簡単に動かせる。いや、本当はページ境界とか考えといけないらしいがまあ動けばいっかの精神。
テストではアドレスとデータはテンプレートが生成していたので、実際利用するときはそれらを外から持ってきて、データ準備できたらRUNをアサートするようにすればよい
Discussion