FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

VerilogのメモリをVerilatorではどのように処理するのか

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

これをコンパイルしてC++ファイルを生成してみる。

$ 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という型を使っているようだった。これはどういうものだろう?

https://github.com/verilator/verilator/blob/0f7ec6c9ba52160573df8a7ee90bcc38c837eee7/include/verilated_heavy.h

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なものでなければ、通常の配列を使っておけば良さそうだ。