##はじめに
AlteraのQsysでサクッとサブシステム(モデルがあるので合成不可)を作って、DDR3 SDRAMモデルをつないでシミュレーションをやってみました。
NIOS IIの代わりにAvalon MM BFMを使用してみたのですが、あまり使われていなさそうで情報があまりなく、メモ代わりです。
##Qsysでサブシステム作成
DDR3コントローラ以外はモデルで構成されています。
DDR3コントローラはPHYはUniPHYを使用しています。UniPHYだとPLLやDLLの調整が隠蔽され、レジスタ設定は特になく楽です。
プリセットはMicron社の2Gbのx16品を使用しました。
DDR3メモリバスばデータ幅を16bitにしています。
DDR3コントローラは使用できるのがハーフ・レートモードのみなので、内部のAvalonバスのデータ幅は64bitになります。
Avalonバス側は8バーストになるように調整してみました。
##DDR3 SDRAMモデル
オープンソースで公開されているMicron社のVerilogモデルを使用します。
2Gbのx16、933MHzを使用すると仮定すると、den2048Mb, x16, sg107 をdefineさせてコンパイルします。
オプションとしてはこんな感じでしょうか。
+define+den2048Mb \
+define+sg107 \
+define+x16 \
+incdir+$DDR3DIR/ddr3_model \
$DDR3DIR/ddr3_model/ddr3.v
「$DDR3DIR」はモデルまでのパスです。
モデルの中身を見るとSystemVerilogのシンタックスがちょこちょこ含まれています。
411行目のこれはちょっとアレなので修正しました。
string char;
string char0;
Micron社のVerilogモデルは、DDR3コントローラの設定がおかしいとキャリブレーション段階でエラーを出してくれて便利です。
##テスト
Qsysから生成したVerilogシミュレーション用コードと、DDR3 SDRAMモデルをつないで、テストを作成してみました。
`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とか使用した方が楽かもしれません。