@Vengineerの戯言 : Twitter
SystemVerilogの世界へようこそ、すべては、SystemC v0.9公開から始まった
はじめに
XilinxのQEMU + SystemC + Verilog HDL (Verilator) のデモの内容を探っていく(その5)。
その1、その2、その3 については、下記のブログを参照してください。
XilinxのQEMU + SystemC + Verilog HDL (Verilator) のデモの内容を探っていく(その1)
XilinxのQEMU + SystemC + Verilog HDL (Verilator) のデモの内容を探っていく(その2)
XilinxのQEMU + SystemC + Verilog HDL (Verilator) のデモの内容を探っていく(その3)
XilinxのQEMU + SystemC + Verilog HDL (Verilator) のデモの内容を探っていく(その4)
今回は、ZynqMP モデルを見ていきます。ZynqMPモデルはデモではなく、libsystemctlm-soc
の xilinx-zynqmp.{h,cc} です。
xilinx_zynq
xilinx_zynqmp は下記のように、remoteport_tlm を継承しています。
class xilinx_zynqmp : public remoteport_tlm { private: 途中略 public: };
remoteport_tlm は、ここ にヘッダファイルがあります。
remoteport_tlm の constructor は下記のようになっています。第2引数の fd が -1 で無いときは、第3引数の sk_descr で sk_socket で通信用ソケットをオープンします。
第4引数 sync は はソフトウェア側(QEMU)との同期に使うもので、 this->sync に設定します。sync が nullptr の時は remoteport_tlm_sync_loosely_timed_ptr を this->sync に設定します。
ここ にあるように、remoteport_tlm_sync_loosely_timed_ptr またはremoteport_tlm_sync_untimed_ptr が設定できるようです。
process を SC_THREAD で登録し、blocking_socket が false の時は thread_trampoline 関数 を pthread として起動します。
remoteport_tlm::remoteport_tlm(sc_module_name name, int fd, const char *sk_descr, Iremoteport_tlm_sync *sync, bool blocking_socket) : sc_module(name), rst("rst"), blocking_socket(blocking_socket), rp_pkt_event("rp-pkt-ev") { this->fd = fd; this->sk_descr = sk_descr; this->rp_pkt_id = 0; this->sync = sync; if (!this->sync) { // Default this->sync = remoteport_tlm_sync_loosely_timed_ptr; } memset(devs, 0, sizeof devs); memset(&peer, 0, sizeof peer); dev_null.adaptor = this; if (fd == -1) { printf("open socket\n"); this->fd = sk_open(sk_descr); if (this->fd == -1) { printf("Failed to create remote-port socket connection!\n"); if (sk_descr) { perror(sk_descr); } exit(EXIT_FAILURE); } } pthread_mutex_init(&rp_pkt_mutex, NULL); SC_THREAD(process); if (!blocking_socket) pthread_create(&rp_pkt_thread, NULL, thread_trampoline, this); }
SC_THREADに登録する process は下記のようなメソッドです。sync.reset() 後、rst 信号が High => Low になるまで待って、rp.say_hello() にてソフトウェア側(QEMU)と通信をして、whileループで rp_process(true) を実行します。rp_process メソッドは後程説明します。
void remoteport_tlm::process(void) { adaptor_proc = sc_get_current_process_handle(); sync->reset(); wait(rst.negedge_event()); rp_say_hello(); while (1) { rp_process(true); } sync->sync(); return; } <|| pthread として起動した thread_trampoline 関数は下記のようになっています。引数 arg は remoteport_tlm の this を上記の pthread_create 関数で設定していますので、キャストしています。 その後、t->rp_pkt_main() を呼び出しています。 >|c++| static void *thread_trampoline(void *arg) { class remoteport_tlm *t = (class remoteport_tlm *)(arg); t->rp_pkt_main(); return NULL; }
rp_pkt_main 関数は下記のように socket から select でチェックし、アクセスがある場合は rp_pkt_event.notify(SC_ZERO_TIME) を呼び出しています。
void remoteport_tlm::rp_pkt_main(void) { fd_set rd; int r; while (true) { FD_ZERO(&rd); FD_SET(fd, &rd); pthread_mutex_lock(&rp_pkt_mutex); r = select(fd + 1, &rd, NULL, NULL, NULL); if (r == -1 && errno == EINTR) continue; if (r == -1) { perror("select()"); exit(EXIT_FAILURE); } rp_pkt_event.notify(SC_ZERO_TIME); pthread_mutex_unlock(&rp_pkt_mutex); } }
rp_process
process メソッドの中で呼ばれる rp_process メソッドは下記のようになっています。blocking_socket が false の時は wait(rp_pkt_event) を呼び出します。rp_pkt_event は rp_pkt_main がソフトウェア側(QEMU)から パケットが送られた時に、rp_pkt_event.notify(SC_ZERO_TIME) によって通知されます。rp_read によってソフトウェア側からのパケットを読み込み、rp_decode_hdrでヘッダを解析し、受信パケットを rp_read で読み出し、rp_decode_payload で payload を解析しています。
if(pkt_rx.pkt->hdr.flags & RP_PKT_FLAGS_response) が成り立つ条件の時は、XXXをします。
そうでない時は、pkt_rx.pkt->hdr.cmd のコマンドの処理を下記のように行っています。
- RP_CMD_hello は、ソフトウェア側(QEMU)との最初に通信確立の時の処理を行います。process メソッドで rp_say_hello で ソフトウェア側(QEMU)に送信したのに対応した受信側になります。
- RP_CMD_write は、dev->cmd_write にて ライト処理を行います。
- RP_CMD_read は、dev->cmd_read にて リード処理を行います。
- RP_CMD_interrupt は、dev->cmd_interrupt にて 割り込み処理を行います。
- RP_CMD_sync は、rp_cmd_sync にて 同期処理を行います。
bool remoteport_tlm::rp_process(bool can_sync) { remoteport_packet pkt_rx; ssize_t r; pkt_rx.alloc(sizeof(pkt_rx.pkt->hdr) + 128); while (1) { remoteport_tlm_dev *dev; unsigned char *data; uint32_t dlen; size_t datalen; if (!blocking_socket) wait(rp_pkt_event); pthread_mutex_lock(&rp_pkt_mutex); r = rp_read(&pkt_rx.pkt->hdr, sizeof pkt_rx.pkt->hdr); if (r < 0) perror(__func__); rp_decode_hdr(pkt_rx.pkt); pkt_rx.alloc(sizeof pkt_rx.pkt->hdr + pkt_rx.pkt->hdr.len); r = rp_read(&pkt_rx.pkt->hdr + 1, pkt_rx.pkt->hdr.len); pthread_mutex_unlock(&rp_pkt_mutex); dlen = rp_decode_payload(pkt_rx.pkt); data = pkt_rx.u8 + sizeof pkt_rx.pkt->hdr + dlen; datalen = pkt_rx.pkt->hdr.len - dlen; dev = devs[pkt_rx.pkt->hdr.dev]; if (!dev) { dev = &dev_null; } if (pkt_rx.pkt->hdr.flags & RP_PKT_FLAGS_response) { unsigned int ri; if (pkt_rx.pkt->hdr.flags & RP_PKT_FLAGS_posted) { // Drop responses for posted packets. return true; } sync->pre_any_cmd(&pkt_rx, can_sync); pkt_rx.data_offset = sizeof pkt_rx.pkt->hdr + dlen; ri = dev->response_lookup(pkt_rx.pkt->hdr.id); if (ri == ~0U) { printf("unhandled response: id=%d dev=%d\n", pkt_rx.pkt->hdr.id, pkt_rx.pkt->hdr.dev); assert(ri != ~0U); } pkt_rx.copy(dev->resp[ri].pkt); dev->resp[ri].valid = true; dev->resp[ri].ev.notify(); sync->post_any_cmd(&pkt_rx, can_sync); return true; } // printf("%s: cmd=%d dev=%d\n", __func__, pkt_rx.pkt->hdr.cmd, pkt_rx.pkt->hdr.dev); sync->pre_any_cmd(&pkt_rx, can_sync); switch (pkt_rx.pkt->hdr.cmd) { case RP_CMD_hello: rp_cmd_hello(*pkt_rx.pkt); break; case RP_CMD_write: dev->cmd_write(*pkt_rx.pkt, can_sync, data, datalen); break; case RP_CMD_read: dev->cmd_read(*pkt_rx.pkt, can_sync); break; case RP_CMD_interrupt: dev->cmd_interrupt(*pkt_rx.pkt, can_sync); break; case RP_CMD_sync: rp_cmd_sync(*pkt_rx.pkt, can_sync); break; default: assert(0); break; } sync->post_any_cmd(&pkt_rx, can_sync); } return false; }
xilinx_zynqmp の constructor
xilinx_zynqmp の constructor の最初の部分は下記のようになっています。親クラスの remoteport_tlm の constructor の第2引数には -1 を設定しているので、sk_descr で指定したソケット名をオープンすることになります。第3引数の sync、第4引数の blocking_socket はそのまま remoteport_tlm に渡されます。
xilinx_zynqmp::xilinx_zynqmp(sc_module_name name, const char *sk_descr, Iremoteport_tlm_sync *sync, bool blocking_socket) : remoteport_tlm(name, -1, sk_descr, sync, blocking_socket),
ソフトウェア側(QEMU)と initiator port の接続
xilinx_zynqmp の constructor にて、次のように行っています。
ソフトウェア側(QEMU) の .sk を各 initiator port にアサインしているだけです。
>|c++
s_axi_hpm_fpd[0] = &rp_axi_hpm0_fpd.sk;
s_axi_hpm_fpd[1] = &rp_axi_hpm1_fpd.sk;
s_axi_hpm_lpd = &rp_axi_hpm_lpd.sk;
s_lpd_reserved = &rp_lpd_reserved.sk;
|
target port とソフトウェア側(QEMU)の接続
xilinx_zynqmp の target port をソフトウェア側(QEMU) に接続するのは、tie_off メソッドで行っています。
xilinx_zynqmp の tie_off メソッドでは、親クラスの tie_off メソッドを呼んでいます。
void xilinx_zynqmp::tie_off(void) { tlm_utils::simple_initiator_socket<xilinx_zynqmp> *tieoff_sk; unsigned int i; remoteport_tlm::tie_off(); for (i = 0; i < proxy_in.size(); i++) { if (proxy_in[i].size()) continue; tieoff_sk = new tlm_utils::simple_initiator_socket<xilinx_zynqmp>(); tieoff_sk->bind(proxy_in[i]); } }
remoteport_tlm の tie_off メソッドは自分が持っているすべての devs (remoteport_tlm_dev) の tie_off メソッドを呼んでいるだけですね。
void remoteport_tlm::tie_off(void) { unsigned int i; for (i = 0; i < RP_MAX_DEVS; i++) { if (devs[i]) { devs[i]->tie_off(); } } }
remoteport_tlm_dev の tie_off メソッドは、下記のように再定義していなければ何もしないですね。
virtual void tie_off(void) {} ;
xilinx_zynqmp::tie_offの後半では proxy_in[i].size が0の時は、tlm_utils::simple_initiator_socket を new し、bind(proxy_in[i]) を実行しています。
ちなみに、proxy_in は、xilinx_zynqmp.h の ここで tlm_utils::simple_target_socket_tagged の sc_vector で定義されています。
sc_vector<tlm_utils::simple_target_socket_tagged<xilinx_zynqmp> > proxy_in;
xilinx_zynqmp.cc の constructor の部分で 9個のベクターとして宣言されています。
proxy_in("proxy-in", 9),
そして、xilinx_zynqmp.cc の constructor の中で proxy_in に対して、register_b_transport と register_transport_dbg を、proxy_out に対しては、bind を実行しています。
for (i = 0; i < proxy_in.size(); i++) { char name[32]; sprintf(name, "proxy_in-%d", i); proxy_in[i].register_b_transport(this, &xilinx_zynqmp::b_transport, i); proxy_in[i].register_transport_dbg(this, &xilinx_zynqmp::transport_dbg, i); named[i][0] = &proxy_in[i]; proxy_out[i].bind(*out[i]); }
named[i][0] = &proxy_in[i]; で、named[i][0] に代入しています。named は、下記のように ZynqMP への target port になっています。なので、register_b_transport および register_transport_dbg を実行しています。
tlm_utils::simple_target_socket_tagged<xilinx_zynqmp> ** const named[] = { &s_axi_hpc_fpd[0], &s_axi_hpc_fpd[1], &s_axi_hp_fpd[0], &s_axi_hp_fpd[1], &s_axi_hp_fpd[2], &s_axi_hp_fpd[3], &s_axi_lpd, &s_axi_acp_fpd, &s_axi_ace_fpd, };
最後に、out[i] を proxy_out[i] に bind しています。
proxy_out[i] は、下記のように、tlm_utils::simple_initiator_socket_tagged の sc_vector で、サイズは、proxy_in の size と同じです。
sc_vector<tlm_utils::simple_initiator_socket_tagged<xilinx_zynqmp> > proxy_out;
proxy_out("proxy-out", proxy_in.size()),
out は、下記のように ソフトウェア側(QEMU)との通信用ポート (remoteport_tlm_memory_slave) に接続されています。
tlm_utils::simple_target_socket<remoteport_tlm_memory_slave> * const out[] = { &rp_axi_hpc0_fpd.sk, &rp_axi_hpc1_fpd.sk, &rp_axi_hp0_fpd.sk, &rp_axi_hp1_fpd.sk, &rp_axi_hp2_fpd.sk, &rp_axi_hp3_fpd.sk, &rp_axi_lpd.sk, &rp_axi_acp_fpd.sk, &rp_axi_ace_fpd.sk, };
結果として、ZynqMP の target port は下図のようにソフトウェア側(QEMU)と繋がっています。