@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)と繋がっています。
