なんでもいいからZynq上のPS-PLをAXIでつないでみる
参考
上のサイトを参考に、AXI-lite IPテンプレートを作成する。
作成したIPはIPカタログのUser Repositoryに追加されるので、そこからEdit in Packagerを選ぶと編集できる。
ソースコードは主に二つあって
- ***_v1_0.v: 単なるラッパー
- ***_v1_0_S00_AXI.v: 本体
という感じ。
またAXI-liteには5つのチャンネル
- AW: 書き込みアドレス
- W: 書き込みデータ
- B: 書き込み成否
- AR: 読み出しアドレス
- R: 読み出しデータ
があり、それぞれに少なくともvalid/ready/signalの信号線がある。valid/readyが両方上がった時に通信を行うプロトコルになってるらしく、そういうHDLになってる。
AXI write
該当箇所はこの辺。
まずslv_reg_wren信号は、AW/Wチャネル両方のready/validが立った時にアサートされる。つまりデータとアドレス両方送受信準備できたことを示す信号。
case文でどのレジスタに書き込むかを指定している。axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB]はアドレスの一部のビットを切り出しているが、今回ADDR_LSB=2, OPT_MEM_ADDR_BITS=1だったので、axi_awaddr[3:2]つまり書き込みアドレスの2ビットを選択している。今回レジスタ数は4に指定しているのでこれでいいんだろう。
for文は1バイトずつ書き込みを行うだけ。1バイトごとにstrobe信号がある模様。
assign slv_reg_wren = axi_wready && S_AXI_WVALID && axi_awready && S_AXI_AWVALID;
always @( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 1'b0 )
begin
slv_reg0 <= 0;
slv_reg1 <= 0;
slv_reg2 <= 0;
slv_reg3 <= 0;
end
else begin
if (slv_reg_wren)
begin
case ( axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
2'h0:
for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 )
if ( S_AXI_WSTRB[byte_index] == 1 ) begin
// Respective byte enables are asserted as per write strobes
// Slave register 0
slv_reg0[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
end
...
AXI read
読み込みはこんな感じ。
読み出しレジスタをaxi_araddrでセレクトして、slv_reg_rdenが立ったらaxi_rdataに出力している。
slv_reg_rdenは3つの信号しかみておらず、rreadyは見てない。
assign slv_reg_rden = axi_arready & S_AXI_ARVALID & ~axi_rvalid;
always @(*)
begin
// Address decoding for reading registers
case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
2'h0 : reg_data_out <= slv_reg0;
2'h1 : reg_data_out <= slv_reg1;
2'h2 : reg_data_out <= slv_reg2;
2'h3 : reg_data_out <= slv_reg3;
default : reg_data_out <= 0;
endcase
end
// Output register or memory read data
always @( posedge S_AXI_ACLK )
begin
if ( S_AXI_ARESETN == 1'b0 )
begin
axi_rdata <= 0;
end
else
begin
// When there is a valid read address (S_AXI_ARVALID) with
// acceptance of read address by the slave (axi_arready),
// output the read dada
if (slv_reg_rden)
begin
axi_rdata <= reg_data_out; // register read data
end
end
end
とりあえずなんか繋げてみる。
以下のように、レジスタ2と3に、レジスタ0の2倍と3倍+1が格納されるように変更。
always @(*)
begin
// Address decoding for reading registers
case ( axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
2'h0 : reg_data_out <= slv_reg0;
2'h1 : reg_data_out <= slv_reg1;
2'h2 : reg_data_out <= slv_reg0 * 2;
2'h3 : reg_data_out <= slv_reg0 * 3 + 1;
default : reg_data_out <= 0;
endcase
end
変更を反映してリパッケージ。
作ったIPをブロックデザインに追加してAutoConnectするといい感じに繋いでくれるはず。
PSはGP0のマスタだけ有効にしてある。FCLK_CLK0は100 MHzで出してるのでAXIは100Mで動く。
インプリしてハードウェアエキスポート、petalinuxに読み込んでビルド(必要?)、BOOT.BINに.bitをパッケージする。
petalinux側ではdevmemでこんな感じで動作確認できる。
test_AA:~$ sudo devmem 0x43c00000
0x00000000
test_AA:~$ sudo devmem 0x43c00000 32 2
test_AA:~$ sudo devmem 0x43c00000
0x00000002
test_AA:~$ sudo devmem 0x43c00004
0x00000000
test_AA:~$ sudo devmem 0x43c00008
0x00000004
test_AA:~$ sudo devmem 0x43c0000c
0x00000007
test_AA:~$ sudo devmem 0x43c00010
0x00000002
デバイスツリー確認
いろんな情報によると、PL側の回路もふつうデバイスツリーに追加されるものらしい。
しかし上で動かしたときデバイスツリーにPL回路は含まれていなかったので気持ち悪い。そして動かなかったとき何が悪いのか全然わからなかった。
そういえばpetalinux-configでRemove PL from device tree
オプションを有効にしてたことに気づく。
petalinux-buildが失敗するので有効にしていたが、今回は無しでもビルドできた。
これによりめでたく\components\plnx_workspace\device-tree\device-tree\
にpl.dtsiが完成する。
/*
* CAUTION: This file is automatically generated by Xilinx.
* Version: XSCT
* Today is: Fri May 19 15:16:59 2023
*/
/ {
amba_pl: amba_pl {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
ranges ;
test_AXI_0: test_AXI@43c00000 {
clock-names = "s00_axi_aclk";
clocks = <&clkc 15>;
compatible = "xlnx,test-AXI-1.1";
reg = <0x43c00000 0x10000>;
xlnx,s00-axi-addr-width = <0x4>;
xlnx,s00-axi-data-width = <0x20>;
};
zyxclmm_drm {
compatible = "xlnx,zocl";
};
};
};
またpetalinux上では
test_AA:~$ ls /proc/device-tree/amba_pl/
#address-cells compatible phandle test_AXI@43c00000
#size-cells name ranges zyxclmm_drm
のようにデバイスファイルが生成される。
Discussion