Tang Primer 138K Pro DockにはDDR3 SDRAMが搭載されている.
SipeedのExample の中にddr_test というものがあり,DDR3 SDRAMが正常に動作するかどうかを確認できる.
Micron用 と skhynix用 のデザインが用意されているが,ボード本体を見てもどちらのデザインに対応しているかは分からない.両方動かしてみたところ,手元のボードではskhynix に対応しているようだった(ボード上のLEDで確認).
Gowin IDEでプロジェクトを開くと,5個の Verilog ソースコードが含まれている.
- button.v
ボタン入力の検知用.おそらくこのデザインでは使われていない…(なぜプロジェクトに入れた?) - ddr3_memory_interface
GowinのDDR3 Controller IP(暗号化されている) - ddrtest.v
DDR3メモリテスト用のコード - gowin_pll.v
GowinのPLL IP - top.v
トップ・モジュール
このデザインを読み解くために必要なのは, ddrtest.v と top.v なので,コード中にコメントを入れたもの本稿に張り付ける.
サンプル・デザインの動作
デザインの動作状態はボード上の6個のLEDで示される.
DDR3の検証結果についてはLED3の点灯状態で示される(エラーなしを示す).LED3が点灯すれば正常である.筆者のボードでは Micronのデザインでは点灯しないので,点灯しない場合は,まずはもう一方のデザインを試した方がよさそう.
LED0が点灯すれば初期化完了.LED4は動作中には約1秒周期で点滅する.
ビットストリームが書き込まれると,すぐにテストが動作するが,リセットしたい場合は
ボード上の S0 がリセットにアサインされているので,これを押す.
リファレンス
DDR3 コントローラIP
GowinのDDR3 Memory Interface IPのドキュメント が参考になる.このドキュメントはSipeedの example の docs フォルダに含まれている(いいの?)が,日本語版も存在するので,Gowin公式サイトからたどるとよい(上記にリンクも張ったが,本来ログインしてから参照できるドキュメントだと思われ).
Verilogコード
topモジュール
module top(
clk,
rst_n,
ddr_addr,
ddr_bank,
ddr_cs,
ddr_ras,
ddr_cas,
ddr_we,
ddr_ck,
ddr_ck_n,
ddr_cke,
ddr_odt,
ddr_reset_n,
ddr_dm,
ddr_dq,
ddr_dqs,
ddr_dqs_n,
test_pt,
state_led
);
input clk;
output [15-1:0] ddr_addr; //ROW_WIDTH=15
output [3-1:0] ddr_bank; //BANK_WIDTH=3
output ddr_cs;
output ddr_ras;
output ddr_cas;
output ddr_we;
output ddr_ck;
output ddr_ck_n;
output ddr_cke;
output ddr_odt;
output ddr_reset_n;
output [4-1:0] ddr_dm; //DM_WIDTH=2
inout [32-1:0] ddr_dq; //DQ_WIDTH=32
inout [4-1:0] ddr_dqs; //DQS_WIDTH=2
inout [4-1:0] ddr_dqs_n; //DQS_WIDTH=2
input rst_n;
output test_pt;
output [5:0] state_led;
wire app_wdf_wren;
wire [32-1:0] app_wdf_mask; //APP_MASK_WIDTH=16
wire app_wdf_end;
wire [256-1:0] app_wdf_data; //APP_DATA_WIDTH=256
wire app_en;
wire [2:0] app_cmd;
wire [29-1:0] app_addr; //ADDR_WIDTH=29
wire app_sre_req;
wire app_ref_req;
wire app_burst;
wire app_sre_act;
wire app_ref_ack;
wire app_wdf_rdy;
wire app_rdy;
wire app_rd_data_valid;
wire app_rd_data_end;
wire [256-1:0] app_rd_data; //APP_DATA_WIDTH=256
assign test_pt = clk_x1;
assign state_led[5] = ~app_wdf_rdy;
assign state_led[4] = ~led;
assign state_led[3] = ~error;
assign state_led[2] = ~pll_stop;
assign state_led[1] = ~pll_lock;
assign state_led[0] = ~init_calib_complete; //DDR3_init_indicator
wire clk;
wire pll_lock;
wire memory_clk;
wire err;
wire clk_x1;
wire clk50m;
wire init_calib_complete;
wire pll_stop;
reg led;
assign error = ~err;
//assign error1 = err;
/* LED点滅 */
reg [31:0] led_cnt;
always@(posedge clk_x1)begin // clk_x1 (DDR IPから出力されるクロック)
if(led_cnt >= 50_000_000) begin
led <= ~led;
led_cnt <= 'd0;
end
else
led_cnt <= led_cnt + 1'b1;
end
Gowin_PLL Gowin_PLL_inst(
.lock(pll_lock),
.clkout0(),
.clkout1(clk50m),
.clkout2(memory_clk),
.clkin(clk),
.reset(1'b0),
.enclk0(1'b1), //input enclk0
.enclk1(1'b1), //input enclk1
.enclk2(pll_stop) //input enclk2
);
/* テストベンチ */
ddr3_test1 #
(
.ADDR_WIDTH(29) , //ADDR_WIDTH=29
.APP_DATA_WIDTH(256) , //APP_DATA_WIDTH=256
.APP_MASK_WIDTH (32), //APP_MASK_WIDTH=32
.USER_REFRESH("OFF")
)u_rd(
// テストベンチ入力
.clk (clk_x1), // <- DDR IP
.rst (~rst_n), // <- ボード
.app_rdy (app_rdy), // <- DDR IP
.app_rd_data_valid (app_rd_data_valid), // <- DDR IP 読み出しデータ有効
.app_rd_data (app_rd_data), // <- DDR IP 読み出しデータ
.init_calib_complete(init_calib_complete),// <- DDR IP IPの初期化完了
.wr_data_rdy (app_wdf_rdy), // <- DDR IP データ受信可能
//出力
.app_en (app_en),
.app_cmd (app_cmd),
.app_addr (app_addr), // -> DDR IP
.app_wdf_data (app_wdf_data), // -> DDR IP 書き込みデータ
.app_wdf_wren (app_wdf_wren),
.app_wdf_end (app_wdf_end),
.app_wdf_mask (app_wdf_mask),
.app_burst (app_burst),
.sr_req (sr_req),
.error (err), // -> board LED
.ref_req (ref_req)
);
/* DDR3 IP */
//DDR3_Memory_Interface_Top u_ddr3 (
D3_400 u_ddr3 (
.memory_clk (memory_clk), // メモリ用クロック(入力)
.pll_stop (pll_stop),
.clk (clk), // リファレンスクロック(入力)
.rst_n (rst_n), //rst_n システムリセット(入力)
.cmd_ready (app_rdy), // コマンドおよびアドレスを受信可能(出力)
.cmd (app_cmd), // コマンド(入力) 1:read 0:write
.cmd_en (app_en), // アドレスおよびコマンド・イネーブル 1:有効(入力)
.addr (app_addr), // アドレス入力 Rank + Bank + Row + Column
.wr_data_rdy (app_wdf_rdy), // データ受信可能(出力)
.wr_data (app_wdf_data), // 書き込みデータ
.wr_data_en (app_wdf_wren), // 書き込みイネーブル(入力)
.wr_data_end (app_wdf_end), // バースト転送の最終サイクルを示す 1:最終サイクル
.wr_data_mask (app_wdf_mask), //
.rd_data (app_rd_data), // 読み出しデータ(出力)
.rd_data_valid (app_rd_data_valid), // rd_data 有効(出力)
.rd_data_end (app_rd_data_end), // 書き込みの最終サイクルであることを示す(入力)
.sr_req (1'b0), // セルフリフレッシュ要求(入力)
.ref_req (1'b0), // ユーザーリフレッシュ要求(入力)
//.zq_req (1'b0),
.sr_ack (app_sre_act), // セルフリフレッシュ応答(出力)
.ref_ack (app_ref_ack), // ユーザーリフレッシュ応答(出力)
.init_calib_complete(init_calib_complete), // キャリブレーション完了(出力)
`ifdef DEBUG_PORT_ENABLE
.dbector4_out (),
.dbg_vector3_out (),
.dbg_vector2_out (),
.dbg_vector1_out (),
`endif
.clk_out (clk_x1), // ユーザデザインのクロック(出力)
.pll_lock (pll_lock), // PLLロック(入力) 使わない場合は1
//.pll_lock (1'b1),
//`ifdef ECC
//.ecc_err (ecc_err),
//`endif
.burst (app_burst), // OTF制御ポート 1:BL8モード, 0:BC4モード. OTFモードでのみ有効
// mem interface メモリ・インターフェース
.ddr_rst (ddr_rst), // IP内で使われるグローバルリセット,ユーザ回路にも出力
.O_ddr_addr (ddr_addr), // Rowアドレス(アクティブコマンド)、Columnアドレス(読み出し、書き込みコマンド)
.O_ddr_ba (ddr_bank), // Bankアドレス
.O_ddr_cs_n (ddr_cs), // チップセレクト信号、アクティブLow
.O_ddr_ras_n (ddr_ras),
.O_ddr_cas_n (ddr_cas),
.O_ddr_we_n (ddr_we),
.O_ddr_clk (ddr_ck),
.O_ddr_clk_n (ddr_ck_n),
.O_ddr_cke (ddr_cke),
.O_ddr_odt (ddr_odt),
.O_ddr_reset_n (ddr_reset_n), // _DDR3 SDRAMリセット信号
.O_ddr_dqm (ddr_dm),
.IO_ddr_dq (ddr_dq),
.IO_ddr_dqs (ddr_dqs),
.IO_ddr_dqs_n (ddr_dqs_n)
);
endmodule
ddr3_testモジュール
`timescale 1ps/1ps
//////////////////////////////////////////////////////////////////////////////////
// Create Date: 2016/07/27 10:58:47
// Module Name: user_test
// Revision 0.01 - File Created
//////////////////////////////////////////////////////////////////////////////////
module ddr3_test1 #(
parameter ADDR_WIDTH = 28,
parameter APP_DATA_WIDTH = 256,
parameter APP_MASK_WIDTH = 32,
parameter USER_REFRESH = "OFF"
)
(
//input
clk,
rst,
app_rdy,
app_rd_data_valid,
app_rd_data,
init_calib_complete,
wr_data_rdy,
//output
app_en,
app_cmd,
app_addr,
app_wdf_data,
app_wdf_wren,
app_wdf_end,
app_wdf_mask,
app_burst,
sr_req,
ref_req,
error
);
input clk;
input rst;
input app_rdy;
input app_rd_data_valid;
input wr_data_rdy;
input [APP_DATA_WIDTH-1:0] app_rd_data; // <- DDRR3 IP 読み出しデータ
input init_calib_complete;
output reg app_en;
output reg [2:0] app_cmd;
output reg [ADDR_WIDTH-1:0] app_addr ; // -> DDR3 IP
output reg [APP_DATA_WIDTH-1:0] app_wdf_data ;
output reg app_wdf_wren; // -> DDR3 IP 書き込みイネーブル
output app_wdf_end;
output [APP_MASK_WIDTH-1:0] app_wdf_mask ;
output app_burst;
output sr_req;
output ref_req;
output error;
reg [15:0] error_int1;
reg app_rd_data_valid_r;
reg [APP_DATA_WIDTH-1:0] app_rd_data_r/* synthesis syn_preserve = 1 */;
reg app_rd_data_valid_rr;
reg [APP_DATA_WIDTH-1:0] app_rd_data_rr/* synthesis syn_preserve = 1 */;
/* 読み出しデータ FF 2段で受ける */
always@(posedge clk or posedge rst)begin
if(rst)begin
app_rd_data_valid_r <= 1'b0;
app_rd_data_r <= 'd0;
app_rd_data_valid_rr <= 1'b0;
app_rd_data_rr <= 'd0;
end
else begin
app_rd_data_valid_r <= app_rd_data_valid;
app_rd_data_r <= app_rd_data;
app_rd_data_valid_rr <= app_rd_data_valid_r;
app_rd_data_rr <= app_rd_data_r;
end
end
assign app_wdf_mask = 0;
assign sr_req = 0;
assign ref_req = 0;
assign app_burst = 0;
wire [63:0] EYE_MEM [0:7];
/* 書き込みデータ群 */
assign EYE_MEM[0] = 64'h5883adb4c88ad596;
assign EYE_MEM[1] = 64'h1122334455667788;
assign EYE_MEM[2] = 64'h99aabbccddeeff00;
assign EYE_MEM[3] = 64'h0000ffff0000ffff;
assign EYE_MEM[4] = 64'hffff0000ffff0000;
assign EYE_MEM[5] = 64'h00000000ffff0000;
assign EYE_MEM[6] = 64'haf5d632fc8b91658;
assign EYE_MEM[7] = 64'hffffffff0000ffff;
wire [63:0] EYE_MEM_C [0:7];
/* 検証 比較用データ群 */
assign EYE_MEM_C[0] = 64'h5883adb4c88ad596;
// assign EYE_MEM_C[0] = 64'h4883adb4c88ad596;
assign EYE_MEM_C[1] = 64'h1122334455667788;
assign EYE_MEM_C[2] = 64'h99aabbccddeeff00;
assign EYE_MEM_C[3] = 64'h0000ffff0000ffff;
assign EYE_MEM_C[4] = 64'hffff0000ffff0000;
assign EYE_MEM_C[5] = 64'h00000000ffff0000;
assign EYE_MEM_C[6] = 64'haf5d632fc8b91658;
assign EYE_MEM_C[7] = 64'hffffffff0000ffff;
reg [63:0] comp_data;
localparam IDLE = 7'b0000001;
localparam WR_BANK_CH = 7'b0000010;//BANK0~7,ROW0,COL0
localparam RD_BANK_CH = 7'b0000100;
localparam WR_ROW_CH = 7'b0001000;//BANK0,ROW0~16384,COL0
localparam RD_ROW_CH = 7'b0010000;
localparam WR_COL_CH = 7'b0100000;//BANK0,ROW0,COL0~1024
localparam RD_COL_CH = 7'b1000000;
reg [6:0] c_s;
reg [6:0] n_s;
reg [2:0] bank;
reg [13:0] row;
reg [9:0] col;
reg [2:0] cnt_r;
reg [2:0] cnt1; // bank データ選択カウンタ
reg [13:0] cnt2; // row データ選択カウンタ 0~16384
reg [6:0] cnt3; // col データ選択カウンタ 0~1024
always@(posedge clk or posedge rst)begin
if(rst)
c_s <= IDLE;
else
c_s <= n_s;
end
/* テスト項目 順次切り替え ステートマシン */
always@(*)begin
case(c_s)
IDLE :begin
if(init_calib_complete)
n_s = WR_BANK_CH;
else
n_s = IDLE;
end
WR_BANK_CH :begin //BANK0~7,ROW0,COL0
if((app_rdy & wr_data_rdy) & (&cnt1))
n_s = RD_BANK_CH;
else
n_s = WR_BANK_CH;
end
RD_BANK_CH :begin
if(app_rdy & (&cnt1))
n_s = WR_ROW_CH;
// n_s = IDLE;
else
n_s = RD_BANK_CH;
end
WR_ROW_CH :begin
if((app_rdy & wr_data_rdy) & (&cnt2))
n_s = RD_ROW_CH;
else
n_s = WR_ROW_CH;
end
RD_ROW_CH :begin
if(app_rdy & (&cnt2))
n_s = WR_COL_CH;
else
n_s = RD_ROW_CH;
end
WR_COL_CH :begin
if((app_rdy & wr_data_rdy) & (&cnt3))
n_s = RD_COL_CH;
else
n_s = WR_COL_CH;
end
RD_COL_CH :begin
if(app_rdy & (&cnt3))
n_s = IDLE;
else
n_s = RD_COL_CH;
end
default:n_s = IDLE;
endcase
end
//assign app_en = (c_s == WR_BANK_CH | c_s == WR_ROW_CH | c_s == WR_COL_CH) ? (app_rdy & wr_data_rdy) :
// (c_s == RD_BANK_CH | c_s == RD_ROW_CH | c_s == RD_COL_CH) ? app_rdy : 1'b0;
always@(posedge clk or posedge rst)
if(rst)
app_en <= 1'b0;
else if((c_s == WR_BANK_CH | c_s == WR_ROW_CH | c_s == WR_COL_CH) & app_rdy & wr_data_rdy)
app_en <= 1'b1;
else if((c_s == RD_BANK_CH | c_s == RD_ROW_CH | c_s == RD_COL_CH) & app_rdy)
app_en <= 1'b1;
else
app_en <= 1'b0;
//assign app_cmd = (c_s == WR_BANK_CH | c_s == WR_ROW_CH | c_s == WR_COL_CH) ? 3'b000 : 3'b001;
/* 読み書き信号切り替え */
always@(posedge clk or posedge rst)
if(rst)
app_cmd <= 3'b000;
else if(c_s == WR_BANK_CH | c_s == WR_ROW_CH | c_s == WR_COL_CH)
app_cmd <= 3'b000;
else
app_cmd <= 3'b001;
//assign app_wdf_wren = (c_s == WR_BANK_CH | c_s == WR_ROW_CH | c_s == WR_COL_CH) ? (app_rdy & wr_data_rdy) : 1'b0;
/* 書き込みイネーブル信号 */
always@(posedge clk or posedge rst)
if(rst)
app_wdf_wren <= 1'b0;
else if((c_s == WR_BANK_CH | c_s == WR_ROW_CH | c_s == WR_COL_CH) & app_rdy & wr_data_rdy)
app_wdf_wren <= 1'b1;
else
app_wdf_wren <= 1'b0;
assign app_wdf_end = app_wdf_wren;
/* bank カウンタ インクリメント */
always@(posedge clk or posedge rst)begin
if(rst)
cnt1 <= 'd0;
else if((c_s == WR_BANK_CH & app_rdy & wr_data_rdy) | (c_s == RD_BANK_CH & app_rdy))begin
if(&cnt1)
cnt1 <= 'd0;
else
cnt1 <= cnt1 + 1'b1;
end
end
/* row カウンタ インクリメント */
always@(posedge clk or posedge rst)begin
if(rst)
cnt2 <= 'd0;
else if((c_s == WR_ROW_CH & app_rdy & wr_data_rdy) | (c_s == RD_ROW_CH & app_rdy))begin
if(&cnt2)
cnt2 <= 'd0;
else
cnt2 <= cnt2 + 1'b1;
end
end
/* col カウンタ インクリメント */
always@(posedge clk or posedge rst)begin
if(rst)
cnt3 <= 'd0;
else if((c_s == WR_COL_CH & app_rdy & wr_data_rdy) | (c_s == RD_COL_CH & app_rdy))begin
if(&cnt3)
cnt3 <= 'd0;
else
cnt3 <= cnt3 + 1'b1;
end
end
/* 書き込みアドレス選択 */
//assign app_addr = {1'b0,bank,row,col};
always@(posedge clk or posedge rst)
if(rst)
app_addr <= 'd0;
else
app_addr <= {1'b0,bank,row,col};
/* アドレスインクリメント */
always@(posedge clk or posedge rst)begin
if(rst)begin
bank <= 3'd0;
row <= 14'd0;
col <= 10'd0;
end
else begin
case(c_s)
IDLE :begin
bank <= 3'd0;
row <= 14'd0;
col <= 10'd0;
end
WR_BANK_CH :begin //BANK0~7,ROW0,COL0
if(app_rdy & wr_data_rdy)begin
if(&cnt1)
bank <= 'd0;
else
bank <= bank + 1'b1;
end
end
RD_BANK_CH :begin
if(app_rdy)begin
if(&cnt1)
bank <= 'd0;
else
bank <= bank + 1'b1;
end
end
WR_ROW_CH :begin
if(app_rdy & wr_data_rdy)begin
if(&cnt2)
row <= 'd0;
else
row <= row + 1'b1;
end
end
RD_ROW_CH :begin
if(app_rdy)begin
if(&cnt2)
row <= 'd0;
else
row <= row + 1'b1;
end
end
WR_COL_CH :begin
if(app_rdy & wr_data_rdy)begin
if(&cnt3)
col <= 'd0;
else
col <= col + 4'd8;
end
end
RD_COL_CH :begin
if(app_rdy)begin
if(&cnt3)
col <= 'd0;
else
col <= col + 4'd8;
end
end
default:;
endcase
end
end
/* 書き込みデータ 組み立て */
always@(posedge clk or posedge rst)
if(rst)
app_wdf_data <= 'd0;
else if(c_s == WR_BANK_CH)
app_wdf_data <= {EYE_MEM[cnt1],EYE_MEM[cnt1],EYE_MEM[cnt1],EYE_MEM[cnt1]};
else if(c_s == WR_ROW_CH)
app_wdf_data <= {EYE_MEM[cnt2[2:0]],EYE_MEM[cnt2[2:0]],EYE_MEM[cnt2[2:0]],EYE_MEM[cnt2[2:0]]};
else if(c_s == WR_COL_CH)
app_wdf_data <= {EYE_MEM[cnt3[2:0]],EYE_MEM[cnt3[2:0]],EYE_MEM[cnt3[2:0]],EYE_MEM[cnt3[2:0]]};
else
app_wdf_data <= 'd0;
always@(posedge clk or posedge rst)begin
if(rst)
cnt_r <= 'd0;
else if(app_rd_data_valid_r)
cnt_r <= cnt_r + 1'b1;
end
/* 比較データ読み出し */
always@(posedge clk or posedge rst)begin
if(rst)
comp_data <= 'd0;
else
comp_data <= EYE_MEM_C[cnt_r];
end
/* データ比較 */
generate
genvar uei,uej;
for(uei=0;uei<4;uei=uei+1)begin:user_err_gen1
for(uej=0;uej<4;uej=uej+1)begin:user_err_gen2
always@(posedge clk or posedge rst)begin
if(rst)
error_int1[uei*4+uej] <= 1'b0;
else if(app_rd_data_valid_rr & app_rd_data_rr[(uei*64+uej*16)+:16] != comp_data[uej*16+:16])
error_int1[uei*4+uej] <= 1'b1;
// else
// error_int1[uei*4+uej] <= 1'b0;
end
end
end
endgenerate
assign error = |error_int1;
endmodule
(1)
https://wiki.sipeed.com/hardware/en/tang/tang-mega-138k/mega-138k-pro.html
(2)Tang Mege 138K Pro Dockを味見
https://fpga.tokyo/138k-2/
(3)Gowin DDR3 Memory Interface
https://www.gowinsemi.com/ja/support/ip_detail/14/