Vengineerの妄想

人生は短いけど、長いです。人生を楽しみましょう!

XilinxのQEMU + SystemC + Verilog HDL (Verilator) のデモの内容を探っていく(その2)

@Vengineerの戯言 : Twitter
SystemVerilogの世界へようこそすべては、SystemC v0.9公開から始まった 

はじめに

前回は、XilinxQEMU+SystemC + Verilog HDL (Verilator) のデモの内容の内、下図のQEMU と SystemC の zynqmp を繋ぐ部分を探ってみました。
今回は、SytemC側をもう少し詳しく探っていきます。

f:id:Vengineer:20210321112405p:plain

SystemCモデルの中身

iconnect との接続

最初は、zynqpm、dma_mms2_A、dma_s2mm_C、tlm2apb_lmac が接続している bus (iconnect) の部分です。下記のように接続しています。上の部分が Initiatorポートで 下の部分が target ポートになっています。

f:id:Vengineer:20210321112118p:plain

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] に接続しています。

f:id:Vengineer:20210321113917p:plain

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 のパスです。

f:id:Vengineer:20210321122259p:plain

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) への変換モジュールです。

f:id:Vengineer:20210321122631p:plain

		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] に接続しています。

f:id:Vengineer:20210321125557p:plain

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]);
	}

終わりに

今回は、SystemC側のコードを見てみました。ZynqMPから・へのアクセスは TLM ですが、途中に TLM <=> AXIS, APB の変換モジュールを挟むことで、Verilog HDLのモジュールを接続しています。Verilog HDLのモジュールでなくても、SystemCで バスプロトコルを使うモデルを接続する場合は同様に変換モジュールを使います