Verilogでは以下のように、いくつかの書き方でメモリを作ることができる。memory1
は通常のは配列の構造、memory2
は連想配列となっている。SystemVerilogの仕様的に通常の配列はそのまま利領域がメモリに確保され、連想配列は動的に配列の要素が確保される仕組みになっている。
logic [31: 0] memory1[1024]; logic [31: 0] memory2[logic[9: 0]];
これらのメモリにアクセスするとき、VerilatorはどのようなC++ファイルを生成するのだろうか。実際に以下のような回路を記述して調査してみることにした。
module memory ( input logic i_clk, input logic i_reset_n, input logic i_wr, input logic [9: 0] i_addr, input logic [31: 0] i_data, output logic [31: 0] o_out1, output logic [31: 0] o_out2 ); logic [31: 0] memory1[1024]; logic [31: 0] memory2[logic[9: 0]]; always_ff @ (posedge i_clk, negedge i_reset_n) begin if (i_wr) begin memory1[i_addr] <= i_data; memory2[i_addr] <= i_data; end o_out1 <= memory1[i_addr]; /* verilator lint_off WIDTH */ o_out2 <= memory2.exists(i_addr) ? memory2[i_addr] : 'h0; end endmodule // memory
$ verilator --cc --trace-fst --trace-params --trace-structs --trace-underscore memory.sv
obj_dir/Vmemory.h
を確認してみる。
VL_MODULE(Vmemory) { public: /* ... 中略 ...*/ // LOCAL SIGNALS // Internals; generally not touched by application code IData/*31:0*/ memory__DOT__memory1[1024]; VlAssocArray<SData/*9:0*/, IData/*31:0*/> memory__DOT__memory2;
通常のメモリはC++の配列として表現されていた。一方で連想配列はVlAssocArray
という型を使っているようだった。これはどういうものだろう?
verilated_heavy.h
に以下のように定義されていた。
//=================================================================== // Verilog associative array container // There are no multithreaded locks on this; the base variable must // be protected by other means // template <class T_Key, class T_Value> class VlAssocArray final { private: // TYPES using Map = std::map<T_Key, T_Value>; public: using const_iterator = typename Map::const_iterator; private: // MEMBERS Map m_map; // State of the assoc array T_Value m_defaultValue; // Default value public: // CONSTRUCTORS // m_defaultValue isn't defaulted. Caller's constructor must do it. VlAssocArray() = default; ~VlAssocArray() = default; VlAssocArray(const VlAssocArray&) = default; VlAssocArray(VlAssocArray&&) = default; VlAssocArray& operator=(const VlAssocArray&) = default; VlAssocArray& operator=(VlAssocArray&&) = default;
なるほど、テンプレートクラスなのか。そしてexists()
やclear()
, next()
などのメソッドが用意されている。これに置き換わるということか。分かってきた。
ということは、極端にメモリの確保量が大きくなければ、連想配列と通常の配列の使い分けとして、通常配列を使っておけばいいということが分かる。当然アクセス速度は通常配列の方が高そうなので、巨大でSparseなものでなければ、通常の配列を使っておけば良さそうだ。