@Vengineerの戯言 : Twitter
SystemVerilogの世界へようこそ、すべては、SystemC v0.9公開から始まった
はじめに
XilinxのQEMU + SystemC + Verilog HDL (Verilator) のデモの内容を探っていく(その4)。
その1、その2、その3 については、下記のブログを参照してください。
XilinxのQEMU + SystemC + Verilog HDL (Verilator) のデモの内容を探っていく(その1)
XilinxのQEMU + SystemC + Verilog HDL (Verilator) のデモの内容を探っていく(その2)
XilinxのQEMU + SystemC + Verilog HDL (Verilator) のデモの内容を探っていく(その3)
今回は下図の dma_mm2s_A と dma_s2mm_C について説明します。 dma_mm2s_A と dma_s2mm_C の実態は、xilinx-axidma.{h,cc} の axidma_mm2s と axidma_s2mm です。です。dma_mm2s と dma_s2mm は、axidma を継承しています。
axidma
axidma の 定義は、下記のようになっています。initiator port (init_socket)、target port (tgt_socket)、割り込み(irq) があります。protected 以降は axidma で使う各種メンバーですね。
class axidma : public sc_core::sc_module { public: tlm_utils::simple_initiator_socket<axidma> init_socket; tlm_utils::simple_target_socket<axidma> tgt_socket; sc_out<bool> irq; axidma(sc_core::sc_module_name name, bool use_memcpy = false); SC_HAS_PROCESS(axidma); protected: union { struct { uint32_t cr; uint32_t sr; uint32_t rsv0[AXIDMA_R_ADDR - AXIDMA_R_SR - 1]; uint32_t addr; uint32_t addr_msb; uint32_t rsv1[AXIDMA_R_LENGTH - AXIDMA_R_ADDR_MSB - 1]; uint32_t length; }; uint32_t u32[AXIDMA_R_MAX]; } regs; // S2M needs to keep track of the number of bytes actually copied. uint32_t length_copied; bool use_memcpy; sc_event ev_update_irqs; sc_event ev_dma_copy; virtual void do_dma_copy(void) {}; void do_dma_trans(tlm::tlm_command cmd, unsigned char *buf, sc_dt::uint64 addr, sc_dt::uint64 len, sc_time &delay); void update_irqs(void); private: virtual void b_transport(tlm::tlm_generic_payload& trans, sc_time& delay); };
axidma の constructer は下記のようになっています。constructor の第2引数は、内部メソッド(do_dma_copy) で使われるようです。
axidma は target なので、tgt_socket に register_b_transport にて、axidma::b_transport を登録しています。
割り込みの更新用メソッドとして update_irqs を SC_METHOD で登録しています。更新のトリガーは ev_update_irqs イベントです。
do_dma_copy メソッドは、SC_THREAD にて常時動いている感じですね。
axidma::axidma(sc_module_name name, bool use_memcpy) : sc_module(name), tgt_socket("tgt-socket"), irq("irq"), use_memcpy(use_memcpy) { tgt_socket.register_b_transport(this, &axidma::b_transport); memset(®s, 0, sizeof regs); SC_METHOD(update_irqs); dont_initialize(); sensitive << ev_update_irqs; SC_THREAD(do_dma_copy); }
b_transport
b_trasport メソッドは axidma のレジスタアクセス部分になっています。チェック部分では、transがバイトイネーブルになっているかをチェックしています。その後、転送サイズが4バイト以下になっているかもチェックしています。リードの時は regs の対応するアドレスからコピーするだけですが、ライトの時は4バイト単位のアドレスで AXIDMA_R_SRとAXIDMA_R_LENGTHでは対応処理を行い、それ以外のレジスタへはただ単にライトしているだけです。AXIDMA_R_LENGTHにアクセスすると、ev_dma_copyイベントに対して、notify() を発行します。このイベントは、 axidma_mm2s と axidma_s2mm の do_dma_copy メソッドで使われます。最後に、 ev_update_irqs.notify() で、irq の状態を更新しています。
void axidma::b_transport(tlm::tlm_generic_payload& trans, sc_time& delay) { tlm::tlm_command cmd = trans.get_command(); sc_dt::uint64 addr = trans.get_address(); unsigned char* data = trans.get_data_ptr(); unsigned int len = trans.get_data_length(); unsigned char* byt = trans.get_byte_enable_ptr(); unsigned int wid = trans.get_streaming_width(); if (byt != 0) { trans.set_response_status(tlm::TLM_BYTE_ENABLE_ERROR_RESPONSE); return; } if (len > 4 || wid < len) { trans.set_response_status(tlm::TLM_BURST_ERROR_RESPONSE); return; } addr >>= 2; if (trans.get_command() == tlm::TLM_READ_COMMAND) { memcpy(data, ®s.u32[addr], len); } else if (cmd == tlm::TLM_WRITE_COMMAND) { uint32_t v; memcpy(&v, data, len); switch (addr) { case AXIDMA_R_SR: regs.u32[addr] &= ~(v & AXIDMA_SR_IOC_IRQ); D(printf("%s: SR=%x.%x val=%x\n", name(), regs.sr, regs.u32[addr], v)); break; case AXIDMA_R_LENGTH: length_copied = 0; regs.length = v; regs.sr &= ~(AXIDMA_SR_IDLE); D(printf("%s: write LENGTH %d\n", name(), regs.length)); ev_dma_copy.notify(); break; default: /* No side-effect. */ regs.u32[addr] = v; break; } } ev_update_irqs.notify(); trans.set_response_status(tlm::TLM_OK_RESPONSE); }
update_irqs
ev_update_irqs.notify() が実行されると、下記の update_irqs が実行されます。reg.sr と regs.cr の状態から irq (割り込み) をドライブするかどうかを決めてます。
void axidma::update_irqs(void)
{
D(printf("DMA irq=%d\n", regs.sr & regs.cr & AXIDMA_CR_IOC_IRQ_EN));
irq.write(regs.sr & regs.cr & AXIDMA_CR_IOC_IRQ_EN);
}
axidma_mm2s
axdma_mm2s は、iconnect からデータをリードして、AXI Stream に書き込むDMAです。AXI Stream 用に initiator port (stream_sockt) が 追加されたのと、do_dma_copy メソッドが再定義されています。
class axidma_mm2s : public axidma { public: tlm_utils::simple_initiator_socket<axidma_mm2s> stream_socket; axidma_mm2s(sc_core::sc_module_name name, bool use_memcpy = false); protected: virtual void do_dma_copy(void); private: void do_stream_trans(tlm::tlm_command cmd, unsigned char *buf, sc_dt::uint64 addr, sc_dt::uint64 len, bool eop, sc_time &delay); }; <|| do_dma_copy メソッドでは、regs.length レジスタの値が 0 出ないときに、wait(ev_dma_copy)で ev_dma_copy.notify() が発行されるのを待ちます。その後に、constructor の第2引数で指定したuse_memcpy によって、memcpy 関数 または do_dma_trans メソッドを呼びます。do_dma_trans メソッドでは、init_socket に対して transaction を発生し、データをリードします。その次の do_stream_trans メソッドは AXIのStreamに対応のWriteを実行しています。 >|c++| void axidma_mm2s::do_dma_copy(void) { while (1) { unsigned char buf[2 * 1024]; uint64_t addr; sc_time delay = SC_ZERO_TIME; unsigned int tlen; bool eop; if (!regs.length) { wait(ev_dma_copy); } assert(!(regs.sr & AXIDMA_SR_IDLE)); tlen = regs.length > sizeof buf ? sizeof buf : regs.length; eop = tlen == regs.length; addr = regs.addr_msb; addr <<= 32; addr += regs.addr; if (use_memcpy) { memcpy(buf, (void *) addr, tlen); } else { do_dma_trans(tlm::TLM_READ_COMMAND, buf, addr, tlen, delay); } do_stream_trans(tlm::TLM_WRITE_COMMAND, buf, addr, tlen, eop, delay); addr += tlen; regs.length -= tlen; regs.addr = addr; regs.addr_msb = addr >> 32; if (regs.length == 0) { /* If the DMA was running, signal done. */ regs.sr |= AXIDMA_SR_IDLE | AXIDMA_SR_IOC_IRQ; ev_update_irqs.notify(); } } }
axidma の do_dma_trans メソッドは、init_socket に対して データのリードあるいはライト(cmd)を行います。
void axidma::do_dma_trans(tlm::tlm_command cmd, unsigned char *buf, sc_dt::uint64 addr, sc_dt::uint64 len, sc_time &delay) { tlm::tlm_generic_payload tr; tr.set_command(cmd); tr.set_address(addr); tr.set_data_ptr(buf); tr.set_data_length(len); tr.set_streaming_width(len); tr.set_dmi_allowed(false); tr.set_response_status(tlm::TLM_INCOMPLETE_RESPONSE); init_socket->b_transport(tr, delay); if (tr.get_response_status() != tlm::TLM_OK_RESPONSE) { printf("%s:%d DMA transaction error!\n", __func__, __LINE__); } }
do_dma_copy にてリードしたデータを コピーし、genattr にて AXI Stream 用の拡張機能として tr.set_extension(genattr) にて eop を設定します。eop は最後の転送の時に true になるんでしょうね。
void axidma_mm2s::do_stream_trans(tlm::tlm_command cmd, unsigned char *buf, sc_dt::uint64 addr, sc_dt::uint64 len, bool eop, sc_time &delay) { tlm::tlm_generic_payload tr; genattr_extension *genattr = new genattr_extension(); tr.set_command(cmd); tr.set_address(addr); tr.set_data_ptr(buf); tr.set_data_length(len); tr.set_streaming_width(len); tr.set_dmi_allowed(false); tr.set_response_status(tlm::TLM_INCOMPLETE_RESPONSE); genattr->set_eop(eop); tr.set_extension(genattr); stream_socket->b_transport(tr, delay); if (tr.get_response_status() != tlm::TLM_OK_RESPONSE) { printf("%s:%d DMA transaction error!\n", __func__, __LINE__); } tr.release_extension(genattr); }
axidma_s2mm
axidma_s2mm は、AXI Stream からデータを読みだして、iconnect へデータを書き込むDMAです。target port として stream_socket が追加されているのと、do_dma_copy メソッド、s_b_transport メソッドが定義されています。
class axidma_s2mm : public axidma { public: tlm_utils::simple_target_socket<axidma_s2mm> stream_socket; axidma_s2mm(sc_core::sc_module_name name, bool use_memcpy = false); protected: virtual void do_dma_copy(void); private: void s_b_transport(tlm::tlm_generic_payload& trans, sc_time& delay); };
do_dma_copy メソッドは下記のように何もしていません。
void axidma_s2mm::do_dma_copy(void) {}
s_b_transport メソッドは下記のようになっています。DMAがIdle状態の時は、wait(ev_dma_copy) で待ちます。DMAがIdle状態で無いときは、use_memcpy の値によって、memcopy 関数と do_dma_trans メソッドのいずれかを実行します。最後に、ev_update_irqs.notify() にて割り込み信号(irq)の更新を行います。do_dma_trans メソッドでは、init_socket に対して transaction を発生し、データをリードします。
void axidma_s2mm::s_b_transport(tlm::tlm_generic_payload& trans, sc_time& delay) { unsigned char *data = trans.get_data_ptr(); unsigned int len = trans.get_data_length(); genattr_extension *genattr; unsigned int len_to_copy; uint64_t addr; bool eop = true; trans.get_extension(genattr); if (genattr) { eop = genattr->get_eop(); } if (regs.sr & AXIDMA_SR_IDLE) { /* Put back-pressure. */ D(printf("%s: DMA IS IDLE length=%x\n", name(), regs.length)); do { wait(ev_dma_copy); } while (regs.sr & AXIDMA_SR_IDLE); } addr = regs.addr_msb; addr <<= 32; addr += regs.addr; len_to_copy = regs.length >= len ? len : regs.length; if (use_memcpy) { memcpy((void *) addr, data, len_to_copy); } else { do_dma_trans(tlm::TLM_WRITE_COMMAND, data, addr, len_to_copy, delay); } length_copied += len_to_copy; addr += len_to_copy; regs.length -= len_to_copy; regs.addr_msb = addr >> 32; regs.addr = addr; if (regs.length == 0 || eop) { regs.sr |= AXIDMA_SR_IDLE | AXIDMA_SR_IOC_IRQ; regs.length = length_copied; } ev_update_irqs.notify(); trans.set_response_status(tlm::TLM_OK_RESPONSE); }