DDR3 SDRAMモデルをつないでみる。

##はじめに
AlteraのQsysでサクッとサブシステム(モデルがあるので合成不可)を作って、DDR3 SDRAMモデルをつないでシミュレーションをやってみました。
NIOS IIの代わりにAvalon MM BFMを使用してみたのですが、あまり使われていなさそうで情報があまりなく、メモ代わりです。

##Qsysでサブシステム作成
DDR3コントローラ以外はモデルで構成されています。

![qsys.png](https://qiita-image-store.s3.amazonaws.com/0/32925/3071ec7b-b9c1-d661-6b2c-cb4120f0deed.png)

DDR3コントローラはPHYはUniPHYを使用しています。UniPHYだとPLLやDLLの調整が隠蔽され、レジスタ設定は特になく楽です。
プリセットはMicron社の2Gbのx16品を使用しました。

DDR3メモリバスばデータ幅を16bitにしています。
DDR3コントローラは使用できるのがハーフ・レートモードのみなので、内部のAvalonバスのデータ幅は64bitになります。

Avalonバス側は8バーストになるように調整してみました。

##DDR3 SDRAMモデル
オープンソースで公開されている[Micron社のVerilogモデル](http://www.micron.com/-/media/documents/products/sim%20model/dram/ddr3/ddr3%20sdram%20verilog%20model.zip)を使用します。
2Gbのx16、933MHzを使用すると仮定すると、den2048Mb, x16, sg107 をdefineさせてコンパイルします。
オプションとしてはこんな感じでしょうか。

```bash
  +define+den2048Mb \
  +define+sg107 \
  +define+x16 \
  +incdir+$DDR3DIR/ddr3_model \
  $DDR3DIR/ddr3_model/ddr3.v 
```

「$DDR3DIR」はモデルまでのパスです。

モデルの中身を見るとSystemVerilogのシンタックスがちょこちょこ含まれています。
411行目のこれはちょっとアレなので修正しました。

```sv:修正前
        string char;
```

```sv:修正後
        string char0;
```

Micron社のVerilogモデルは、DDR3コントローラの設定がおかしいとキャリブレーション段階でエラーを出してくれて便利です。

##テスト
Qsysから生成したVerilogシミュレーション用コードと、DDR3 SDRAMモデルをつないで、テストを作成してみました。

```sv:tb.sv
`define BFM      dut.av_mst_model
`define DDR3CON  dut.ddr3_0

module tb();

  import verbosity_pkg::*;
  import avalon_mm_pkg::*;

  localparam AV_ADDRESS_W     = 32;
  localparam AV_DATA_W        = 64;
  localparam AV_BURST_LENGTH  =  8;

  wire        clk_clk;            //    clk.clk
  wire        reset_reset_n;      //  reset.reset_n
  wire [13:0] memory_mem_a;       // memory.mem_a
  wire [2:0]  memory_mem_ba;      //       .mem_ba
  wire [0:0]  memory_mem_ck;      //       .mem_ck
  wire [0:0]  memory_mem_ck_n;    //       .mem_ck_n
  wire [0:0]  memory_mem_cke;     //       .mem_cke
  wire [0:0]  memory_mem_cs_n;    //       .mem_cs_n
  wire [1:0]  memory_mem_dm;      //       .mem_dm
  wire [0:0]  memory_mem_ras_n;   //       .mem_ras_n
  wire [0:0]  memory_mem_cas_n;   //       .mem_cas_n
  wire [0:0]  memory_mem_we_n;    //       .mem_we_n
  wire        memory_mem_reset_n; //       .mem_reset_n
  wire [15:0] memory_mem_dq;      //       .mem_dq
  wire [1:0]  memory_mem_dqs;     //       .mem_dqs
  wire [1:0]  memory_mem_dqs_n;   //       .mem_dqs_n
  wire [0:0]  memory_mem_odt;     //       .mem_odt
  wire        oct_rzqin;           //    oct.rzqin

  wand        memory_mem_dm_w;
  tri0        memory_mem_dm_0;

  assign memory_mem_dm_0 = memory_mem_dm_w;
  assign memory_mem_dm   = memory_mem_dm_0;

  logic [AV_DATA_W-1 : 0] wdata [int] ;
  logic [AV_DATA_W-1 : 0] rdata [int] ;

  DDR3_SUBSYS dut(.*);

  ddr3 u_ddr3_mem(
    .rst_n(memory_mem_reset_n),
    .ck(memory_mem_ck),
    .ck_n(memory_mem_ck_n),
    .cke(memory_mem_cke),
    .cs_n(memory_mem_cs_n),
    .ras_n(memory_mem_ras_n),
    .cas_n(memory_mem_cas_n),
    .we_n(memory_mem_we_n),
    .dm_tdqs(memory_mem_dm),
    .ba(memory_mem_ba),
    .addr(memory_mem_a),
    .dq(memory_mem_dq),
    .dqs(memory_mem_dqs),
    .dqs_n(memory_mem_dqs_n),
    .tdqs_n(),
    .odt(memory_mem_odt)
  );

  // ============================================================
  // Tasks
  // ============================================================

  task data_init(output [AV_DATA_W-1:0] data [int]);
    for(int i=0; i<AV_BURST_LENGTH; i++)
      data[i] = {$random(), $random()};
  endtask

  task data_print([AV_DATA_W-1:0] data0 [int], [AV_DATA_W-1:0] data1 [int]);
    for(int i=0; i<AV_BURST_LENGTH; i++)
      $display("Data0 == 0x%16h : Data1 == 0x%16h", data0[i], data1[i]);
  endtask

  task avalon_write ([AV_ADDRESS_W-1:0] addr, [AV_DATA_W-1:0]  data [int]);
    // Construct the BFM request
    `BFM.set_command_request(REQ_WRITE);
    `BFM.set_command_idle(0, 0);
    `BFM.set_command_init_latency(0);
    `BFM.set_command_address(addr);
    `BFM.set_command_byte_enable('1,0);
    `BFM.set_command_burst_size(AV_BURST_LENGTH);
    `BFM.set_command_burst_count(AV_BURST_LENGTH);

    for(int i=0; i<AV_BURST_LENGTH; i++)
      `BFM.set_command_data(data[i], i);

    // Queue the command
    `BFM.push_command();

    // Wait until the transaction has completed
    while (`BFM.get_response_queue_size() != 1)
      @(posedge `BFM.clk);

      // Dequeue the response and discard
      `BFM.pop_response();
  endtask

  task avalon_read ([AV_ADDRESS_W-1:0] addr, output [AV_DATA_W-1:0]  data [int]);
    // Construct the BFM request
    `BFM.set_command_request(REQ_READ);
    `BFM.set_command_idle(0, 0);
    `BFM.set_command_init_latency(0);
    `BFM.set_command_address(addr);
    `BFM.set_command_byte_enable('1,0);
    `BFM.set_command_data(0, 0);
    `BFM.set_command_burst_size(AV_BURST_LENGTH);
    `BFM.set_command_burst_count(AV_BURST_LENGTH);

    // Queue the command
    `BFM.push_command();

    // Wait until the transaction has completed
    while (`BFM.get_response_queue_size() != 1)
      @(posedge `BFM.clk);

    // Dequeue the response and return the data
    `BFM.pop_response();

    for(int i=0; i<AV_BURST_LENGTH; i++)
      data[i] = `BFM.get_response_data(i);

  endtask

  // ============================================================
  // Test
  // ============================================================

  initial begin
    data_init(wdata);
    wait(`DDR3CON.local_init_done);
    avalon_write(32'h0000_0000, wdata);
    avalon_read(32'h0000_0000, rdata);
    data_print(wdata, rdata);
  end

endmodule
```

テスト内容は、ランダムなデータをAvalonバス側から8バーストでライトしてDDR3メモリモデルにデータを格納し、その後、リードしてデータを読み込み、表示しているだけです。

DDR3コントローラはdmが出力だけに対して、DDR3メモリモデルは双方向のため、wand信号を作ってDDR3メモリモデルの入力信号は0固定しています。

###結果
DDR3コントローラとメモリのキャリブレーションが成功し、リードライトのデータが一致しているのを確認しました。

###おわりに
すでにボードがあって、そこに載ったFPGAにデプロイするだけならここまでやる必要はないと思います。
しかしカスタムボードを作成してボード担当と連携を取りながらやる場合、ロジック側もIPを信用しすぎることなくテスト環境作らなくてはと思い作成してみました。
BFM代わりにNIOS IIとか使用した方が楽かもしれません。