@Vengineerの戯言 : Twitter
SystemVerilogの世界へようこそ、すべては、SystemC v0.9公開から始まった
はじめに
前回は、XilinxのQEMU+SystemC + Verilog HDL (Verilator) のデモの内容の内、下図のQEMU と SystemC の zynqmp を繋ぐ部分を探ってみました。
今回は、SytemC側をもう少し詳しく探っていきます。
SystemCモデルの中身
iconnect との接続
最初は、zynqpm、dma_mms2_A、dma_s2mm_C、tlm2apb_lmac が接続している bus (iconnect) の部分です。下記のように接続しています。上の部分が Initiatorポートで 下の部分が target ポートになっています。
SystemCのコードは、下記の部分になります。bus は、iconnect のインスタンスで initiator ポートの数が NR_MASTERS (3個)、target ポートの数が NR_DEVICES (4個)になります。busへの接続は、bus->memmap の部分が target ポートで、.bind の部分が initiator ポートの接続になります。
bus->memmap では、この bus にアクセスした場合の targetポートとしてデコードする開始アドレスとサイズ、target ポートにアクセスするときに開始アドレスを引いたアドレスとしてアクセスするかどうか?、その次はとりあえず、-1 を設定、最後は target ポートに接続するモジュールの target ポートになります。
bus = new iconnect<NR_MASTERS, NR_DEVICES> ("bus"); bus->memmap(BASE_ADDR + 0x30000ULL, 0x4000 - 1, ADDRMODE_RELATIVE, -1, tlm2apb_lmac->tgt_socket); bus->memmap(BASE_ADDR + 0x34000ULL, 0x100 - 1, ADDRMODE_RELATIVE, -1, dma_mm2s_A.tgt_socket); bus->memmap(BASE_ADDR + 0x35000ULL, 0x100 - 1, ADDRMODE_RELATIVE, -1, dma_s2mm_C.tgt_socket); bus->memmap(0x0LL, 0xffffffff - 1, ADDRMODE_RELATIVE, -1, *(zynq.s_axi_hpc_fpd[0])); zynq.s_axi_hpm_fpd[0]->bind(*(bus->t_sk[0])); dma_mm2s_A.init_socket.bind(*(bus->t_sk[1])); dma_s2mm_C.init_socket.bind(*(bus->t_sk[2]));
例えば、下記に抜き出した最初の部分は、BASE_ADDR (0xa0000000ULL) + 0x30000ULL が tlm2apb_lmac の開始アドレスで、サイズは 0x4000 - 1 (サイズ - 1)、tlm2apb_lmac にアクセスするときは、BASE_ADDR (0xa0000000ULL) + 0x30000ULL からのオフセットアドレスを出力する。tlm2apb_lmac の tgt_socket に接続するという感じです。
bus->memmap(BASE_ADDR + 0x30000ULL, 0x4000 - 1, ADDRMODE_RELATIVE, -1, tlm2apb_lmac->tgt_socket);
initiator ポートの例としては、下記の zynqmp との接続で、zynqmp の axi_hpm_fpd[0] という initiator ポートを bus の t_sk[0] という target ポートに bind (接続)しています。
zynq.s_axi_hpm_fpd[0]->bind(*(bus->t_sk[0]));
ZynqMP への割り込み
2つのDMA (dma_mm2s_A と dma_s2mm_C) からの割り込み信号 (irq) は、下図のように ZynqMP の irq[2] と irq[4] に接続しています。
SystemCのコードは、次のところです。DMA側の irq を ZynqMP の irq([2], [4]) を接続するようになっていますね。
dma_mm2s_A.irq(zynq.pl2ps_irq[2]); dma_s2mm_C.irq(zynq.pl2ps_irq[4]);
DMA と lmac の接続
DMA と lmac の接続部分の図を下記に示します。左側が bus => lmac のパスで、右側が lmac => bus のパスです。
SystemCのコードは、次のところです。最初の2行は dma_mm2s_A => tlm2axis への接続と axis2tlm => dma_s2mm_C への接続部分です。tlm2axis は TLM から AXIS への変換モジュール、axis2tlm は AXIS から TLM への変換モジュールになります。TLM は Transaction Level Model で、AXIS は ARM の AXI Stream プロトコルです。TLM => AXIS は TransactionからAXISバスプロトコルへの変換、AXIS => TLM はAXISバスプロトコルから Transactionへの変換になります。
次の部分は、tlm2axisとaxis2tlm の AXISバスプロトコル部分の接続です。AXISバスプロトコルで使用する信号線を一本一本接続しています。
最後が lmac の AXSIバスプロトコル部分の接続で TX と RX を接続しています。
dma_mm2s_A.stream_socket.bind(tlm2axis.tgt_socket); axis2tlm.socket.bind(dma_s2mm_C.stream_socket); 途中略 tlm2axis.clk(*clk); tlm2axis.resetn(rst_n); tlm2axis.tvalid(tx_axis_mac_tvalid); tlm2axis.tready(tx_axis_mac_tready); tlm2axis.tdata(tx_axis_mac_tdata); tlm2axis.tstrb(tx_axis_mac_tstrb); tlm2axis.tuser(tx_axis_mac_tuser); tlm2axis.tlast(tx_axis_mac_tlast); axis2tlm.clk(*clk); axis2tlm.resetn(rst_n); axis2tlm.tvalid(rx_axis_mac_tvalid); axis2tlm.tready(rx_axis_mac_tready); axis2tlm.tdata(rx_axis_mac_tdata); axis2tlm.tstrb(rx_axis_mac_tstrb); axis2tlm.tuser(rx_axis_mac_tuser); axis2tlm.tlast(rx_axis_mac_tlast); 途中略 lmac->tx_axis_mac_tdata(tx_axis_mac_tdata); lmac->tx_axis_mac_tvalid(tx_axis_mac_tvalid); lmac->tx_axis_mac_tlast(tx_axis_mac_tlast); lmac->tx_axis_mac_tuser(tx_axis_mac_tuser); lmac->tx_axis_mac_tready(tx_axis_mac_tready); lmac->tx_axis_mac_tstrb(tx_axis_mac_tstrb); lmac->rx_axis_mac_tdata(rx_axis_mac_tdata); lmac->rx_axis_mac_tvalid(rx_axis_mac_tvalid); lmac->rx_axis_mac_tlast(rx_axis_mac_tlast); lmac->rx_axis_mac_tuser(rx_axis_mac_tuser); lmac->rx_axis_mac_tready(rx_axis_mac_tready); lmac->rx_axis_mac_tstrb(rx_axis_mac_tstrb);
lmac のレジスタバスの接続
下記は、lmac のレジスタバスの接続部の図です。 bus から tlm2apb_lmac モジュール経由で lmac に接続しています。tlm2apb_lmac は TLM => APB (ARM Peripheral bus) への変換モジュールです。
tlm2apb_lmac->clk(*clk); tlm2apb_lmac->psel(apbsig_lmac_psel); tlm2apb_lmac->penable(apbsig_lmac_penable); tlm2apb_lmac->pwrite(apbsig_lmac_pwrite); tlm2apb_lmac->paddr(apbsig_lmac_paddr); tlm2apb_lmac->pwdata(apbsig_lmac_pwdata); tlm2apb_lmac->prdata(apbsig_lmac_prdata); tlm2apb_lmac->pready(apbsig_lmac_pready);
lmac と Phyの接続
下図は、lmac と Phy の接続部分です。Phy は lmac だけでなく、zynq の user_master[0] と user_slave[0] に接続しています。
SystemCのコードを見ると分かるのですが、user_master[0] は phy.rx.tgt_socket に接続し、user_slave[0] は ph.tx.init_socket に接続しています。
これは、zynq の user_master[0] から送られてきたデータを Phy を経由し、lmac に受信データとして渡し、lmac からの送信データは Phy 経由で user_slave[0] に出力し、zynq に渡すという構造になっています。
phy.tx.clk(*clk); phy.tx.xxd(phy_txd); phy.tx.xxc(phy_txc); phy.rx.clk(*clk); phy.rx.xxd(phy_rxd); phy.rx.xxc(phy_rxc); zynq.user_master[0]->bind(phy.rx.tgt_socket); phy.tx.init_socket.bind(*zynq.user_slave[0]);
ZynqMPの user_master[0] と user_slave[0] は、libsystemctlm-soc の ZynqMP のモデルの下記のコードで rp_user_master[0].sk と rp_user_slave[0].sk に接続しています。
for (i = 0; i < rp_user_master.size(); i++) { user_master[i] = &rp_user_master[i].sk; user_slave[i] = &rp_user_slave[i].sk; }
rp_usr_master と rp_user_slave は、下記のコードのように定義されています。QEMUとの接続で メモリデバイスとなっていて、rp_user_master は masterデバイス、rp_user_slave は slaveデバイスになっています。masterデバイスは QEMU => SystemC、slaveデバイスは SystemC => QEMU へのデータ転送を行います。
sc_vector<remoteport_tlm_memory_master > rp_user_master; sc_vector<remoteport_tlm_memory_slave > rp_user_slave;
QEMU からは、下記のコードで登録された番号のデバイスとして振舞うようです。
for (i = 0; i < rp_user_master.size(); i++) { register_dev(256 + i, &rp_user_master[i]); register_dev(256 + 10 + i, &rp_user_slave[i]); }