简单介绍一下紫光的DDR3 IP使用。基于紫光HMIC_S IP,完成对片外DDR3的读写测试。
01 软硬件平台
软件平台:PDS_2022.1
硬件平台:小眼睛科技盘古50K开发板
02 IP介绍
紫光的HMIC_S IP只支持DDR3,IP顶层使用了精简的 AXI4 总线接口,最大支持32bit的数据位宽。IP的一次读写操作是burst地址8,选择32bit数据位宽下的IP数据接口位宽是32*8=256bit。
03 IP接口
如图所示,整个IP包括Simplified AXI4 接口、Config接口,还有物理接口。物理接口是连接片外DDR3的接口;Config接口为 APB 配置接口,通过该接口,用户可读取 DDR SDRAM 的状态,实现低功耗和 MRS 的控制;Simplified AXI4 接口包括写地址通道、读地址通道、写数据通道和读数据通道四个部分。具体可参考紫光IP Compiler 下的DDR使用手册。
04 IP核参数配置
由于开发板外接了两块16bit数据位宽的DDR3,所以总数据位宽需要选择32。
因开发板的DDR3型号与默认项一致,step2不需要更改配置。
由于开发板的DDR3的控制/地址信号与默认项不一致,所以需要根据实际开发板的接口来自定义相关配置,最简单的方法复制一份有DDR3的开发板例程中fdc,勾选Enable fdc file select,导入fdc文件,DDR3的相关配置便会自动更改,而不需要手动一个一个更改。
05 测试读写逻辑
自增计数对DDR所有地址写一遍,写完后对所有地址读一遍,读出来的数据与写进去的数据做一个比较,比较错误则产生一个错误标志error_flag,error_flag接到LED1。
module ddr3_test_top(
input i_clk ,
input i_rst_n ,
output mem_rst_n ,
output mem_ck ,
output mem_ck_n ,
output mem_cke ,
output mem_cs_n ,
output mem_ras_n ,
output mem_cas_n ,
output mem_we_n ,
output mem_odt ,
output [14:0] mem_a ,
output [ 2:0] mem_ba ,
inout [ 3:0] mem_dqs ,
inout [ 3:0] mem_dqs_n ,
inout [31:0] mem_dq ,
output [ 3:0] mem_dm ,
output reg led_flag ,
output reg err_flag ,
output wr_finish ,
output rd_finish
);
wire core_clk ;
reg [27:0] axi_awaddr/*synthesis PAP_MARK_DEBUG="1"*/ ;
wire axi_awready/*synthesis PAP_MARK_DEBUG="1"*/ ;
reg axi_awvalid ;
wire [255:0] axi_wdata ;
wire axi_wready/*synthesis PAP_MARK_DEBUG="1"*/ ;
wire axi_wusero_last/*synthesis PAP_MARK_DEBUG="1"*/ ;
reg [27:0] axi_araddr/*synthesis PAP_MARK_DEBUG="1"*/ ;
wire axi_arready/*synthesis PAP_MARK_DEBUG="1"*/ ;
reg axi_arvalid ;
wire [255:0] axi_rdata ;
wire axi_rlast/*synthesis PAP_MARK_DEBUG="1"*/ ;
wire axi_rvalid/*synthesis PAP_MARK_DEBUG="1"*/ ;
wire ddr_init_ok_pos/*synthesis PAP_MARK_DEBUG="1"*/ ;
reg [25:0] wr_data/*synthesis PAP_MARK_DEBUG="1"*/ ;
wire [25:0] rd_data/*synthesis PAP_MARK_DEBUG="1"*/ ;
reg [25:0] rd_cnt ;
reg [27:0] wr_addr_cnt ;
reg [27:0] rd_addr_cnt ;
reg [27:0] led_cnt ;
wire ddr_init_done ;
reg ddr_init_done0 ;
reg ddr_init_ok ;
always @(posedge core_clk)
if(!ddr_init_ok) led_cnt <= 28'd0;
else if(led_cnt == 28'h5f5e100) led_cnt <= 28'd0;
else led_cnt <= led_cnt + 1'b1;
always @(posedge core_clk)
if(!ddr_init_ok) led_flag <= 1'b0;
else if(led_cnt == 28'h5f5e100) led_flag <= ~led_flag;
assign axi_wdata = wr_data[25:0];
assign rd_data = axi_rdata[25:0];
assign ddr_init_ok_pos = ddr_init_done0 & !ddr_init_ok;
assign wr_finish = axi_awaddr >= 28'hffff_f80;
assign rd_finish = axi_araddr >= 28'hffff_f80;
always @(posedge core_clk)
begin
ddr_init_done0 <= ddr_init_done;
ddr_init_ok <= ddr_init_done0;
end
// write ddr3
always @(posedge core_clk or negedge i_rst_n)
begin
if (!i_rst_n) begin
axi_awvalid <= 1'b0;
axi_awaddr <= 'b0;
wr_addr_cnt <= 'd0;
end
else if(wr_finish) begin
axi_awvalid <= 1'b0;
axi_awaddr <= axi_awaddr;
wr_addr_cnt <= wr_addr_cnt;
end
else if(ddr_init_ok_pos) begin//初始化上升沿或者写完成进行写地址
axi_awvalid <= 1'b1;
axi_awaddr <= wr_addr_cnt;
wr_addr_cnt <= wr_addr_cnt + 'd128;//burst占用地址空间为8*aradrr
end
else if(axi_awvalid & axi_awready) begin
axi_awvalid <= axi_awvalid;
axi_awaddr <= wr_addr_cnt;
wr_addr_cnt <= wr_addr_cnt + 'd128;
end
end
always @(posedge core_clk or negedge i_rst_n)
if(!i_rst_n)
wr_data <= 'd0;
else if(axi_wready)
wr_data <= wr_data + 1;
// read ddr3
always @(posedge core_clk or negedge i_rst_n)
begin
if (!i_rst_n) begin
axi_arvalid <= 1'b0;
axi_araddr <= 'd0;
rd_addr_cnt <= 'd0;
end
else if(rd_finish) begin
axi_arvalid <= 1'b0;
axi_araddr <= axi_araddr;
rd_addr_cnt <= rd_addr_cnt;
end
else if(axi_wusero_last & wr_data > 33554408) begin
axi_arvalid <= 1'b1;
axi_araddr <= rd_addr_cnt;
rd_addr_cnt <= rd_addr_cnt + 'd128;
end
else if(axi_arvalid & axi_arready) begin
axi_arvalid <= axi_arvalid;
axi_araddr <= rd_addr_cnt;
rd_addr_cnt <= rd_addr_cnt + 'd128;
end
end
always @(posedge core_clk or negedge i_rst_n)
if(!i_rst_n)
rd_cnt <= 'd0;
else if(axi_rvalid)
rd_cnt <= rd_cnt + 1'b1;
else
rd_cnt <= rd_cnt;
always @(posedge core_clk or negedge i_rst_n)
if(!i_rst_n)
err_flag <= 1'b0;
else if(axi_rvalid & rd_data != rd_cnt)
err_flag <= 1'b1;
ddr_ctrller ddr3_ctrller_inst(
.ref_clk (i_clk ),
.resetn (i_rst_n ),
.ddr_init_done (ddr_init_done ),
.ddrphy_clkin (core_clk ),
.pll_lock (pll_lock ),
.axi_awaddr (axi_awaddr ),
.axi_awuser_ap (1'b0 ),
.axi_awuser_id (4'b0000 ),
.axi_awlen (4'd15 ),
.axi_awready (axi_awready ),
.axi_awvalid (axi_awvalid ),
.axi_wdata (axi_wdata ),
.axi_wstrb (32'hffff_ffff ),
.axi_wready (axi_wready ),
.axi_wusero_id ( ),
.axi_wusero_last (axi_wusero_last ),
.axi_araddr (axi_araddr ),
.axi_aruser_ap (1'b0 ),
.axi_aruser_id (4'b0000 ),
.axi_arlen (4'd15 ),
.axi_arready (axi_arready ),
.axi_arvalid (axi_arvalid ),
.axi_rdata (axi_rdata ),
.axi_rid (axi_rid ),
.axi_rlast (axi_rlast ),
.axi_rvalid (axi_rvalid ),
.apb_clk (1'b0 ),
.apb_rst_n (1'b1 ),
.apb_sel (1'b0 ),
.apb_enable (1'b0 ),
.apb_addr (8'b0 ),
.apb_write (1'b0 ),
.apb_ready ( ),
.apb_wdata (16'd0 ),
.apb_rdata ( ),
.apb_int ( ),
.debug_data ( ),
.debug_slice_state ( ),
.debug_calib_ctrl ( ),
.ck_dly_set_bin ( ),
.force_ck_dly_en (1'b0 ),
.force_ck_dly_set_bin (8'h05 ),
.dll_step ( ),
.dll_lock ( ),
.init_read_clk_ctrl (2'b0 ),
.init_slip_step (4'b0 ),
.force_read_clk_ctrl (1'b0 ),
.ddrphy_gate_update_en (1'b0 ),
.update_com_val_err_flag( ),
.rd_fake_stop (1'b0 ),
.mem_rst_n (mem_rst_n ),
.mem_ck (mem_ck ),
.mem_ck_n (mem_ck_n ),
.mem_cke (mem_cke ),
.mem_cs_n (mem_cs_n ),
.mem_ras_n (mem_ras_n ),
.mem_cas_n (mem_cas_n ),
.mem_we_n (mem_we_n ),
.mem_odt (mem_odt ),
.mem_a (mem_a ),
.mem_ba (mem_ba ),
.mem_dqs (mem_dqs ),
.mem_dqs_n (mem_dqs_n ),
.mem_dq (mem_dq ),
.mem_dm (mem_dm )
);
endmodule
06 测试结果
如图所示,LED1为ddr3_init_done 的心跳灯,会以1s切换一次状态;LED2为error_flag指示灯,DDR3读写正确error_flag保持熄灭状态;LED3为写完成指示灯;LED4为读完成指示灯。
如图所示,为写DDR的时序图,当awvalid 和awready信号同时置1时,写地址发生传输。当wready信号置1时,数据发生传输,wusero_last信号为写数据最后一个标志。
如图所示,为读DDR的时序图,当arvalid和arready信号置1时,读地址发生传输。当rvalid信号置1时,代表读数据有效,rlast信号为读数据最后一个标志。
注意的是IP的用户接口交互时钟是IP内部产生的100MHz,且不能配置更改。紫光Debugger中waveform的刻度与信号是不对齐的,且有时候不容易看出信号有效周期是多少,在调试时可以在Debugger中的设置里的waveform里选择Pulse Width,且在Hardware Parameter里设置对应的采样时钟频率,这样可以帮助我们更容易分析单bit信号的正确性。