XILINX关于DDR2的IP的读写控制仿真
最近没事。看了一下ddr2的读写访问。
平台:ISE
芯片:V4,XC4VLX25,ff668,-10
语言:VerilogHDL
参考文件:ug086.ug086.pdf • 查看器 • 文档门户 (xilinx.com)
关于DDR2的MIG核的新建
新建IP:参见UG086的using mig。输入频率200mhz,突发长度BL=4。
新建MIG IP核,此页面为IP的版本信息与FPGA器件。右下角可以下载IP的说明书。
MIG IP有几种不同的输出选项,这里我们选择create design。对于多控制器的应用,可以下下面munber of controllers中选择,这里我们使用一个。
这里可以选择PIN兼容的FPGA,这里只需要支持我们的FPGA就行了。不用选择。
内存选择界面。界面显示了FPGA支持的内存,可以看到支持ddr2和ddr。我们这里选择为ddr2。
capture method捕获方式,一般对于高频采样serdes时钟,而我们这里使用direct clocking时钟。frequency选择控制器输入的时钟。我们外部晶振提供了200MHZ时钟,这里输入200mhz。
Memory Type,Memory Part选择内存类型,和内存型号,我们这里选择的内存型号为MT47H256M8XX-3,也就是默认随便选择的。容量大小256Mbit*8=2Gbit=256MB。
create custom part创建自定义部件这里默认就好了。
data width选择为16位。后面部分保持默认就好。
data mask关于数据掩码,ddr2的访问都是以突发的形式连续访问同一行的相邻几个单元,数据掩码屏蔽了不需要的数据。
内存选择,此页面需要关注的是burst length突发长度。ddr2支持4和8的突发长度。这里可以修改。对于这两种突发模式,在对ddr2进行读写操作的时候地址和数据对应关系,对于突发长度为4,每个地址对应于数据fifo的两次写入,对于突发长度为8,每个地址对应于数据fifo的四次写入。其他参数默认即可。
FPGA选择,这里默认选择为使用DCM,即时钟需要经过DCM后输入到DDR2。系统时钟选择为单端,根据实际选择。
选择UCF引脚。这里我们只做仿真,不设置,如果有UCF引脚可以直接点击Read UCF File。
保持默认分配的引脚。
总结说明。之后一路next,生成IP。
关于DDR2的MIG核的仿真
打开仿真工程,添加文件如下。
E:\code1\FPGAzero_bace\Virtex4\DDR2_TEST\ddr2_test\ipcore_dir\ddr2\user_design\sim文件夹下的全部文件。文件添加成功后效果如下。
添加后效果如下,官方的例程结构是一个很好的参考工程。可以方便大家理解DDR2内部信号的传输。
可以看到成功拉高init_done初始化成功信号。通过ug086可以知道,test_bench_00模块交替执行了8个写入命令和8个读出命令。我们选择的突发长度为4,所以执行写入了32个数据字(16个上升沿数据字和16个下降沿数据字)。而对于突发长度为8的设计,执行8个写入命令将执行写入64个数据字。
在test_bench模块中,addr_gen和data_gen产生地址和数据,ddr2_cmp_rd_data模块验证收到的数据是否正确。
感兴趣的同学可以研究一下他们的测试代码。
下面我们使用自己的代码来代替官方的这部分仿真代码实现自由读写ddr2。
下面分析一下sim_tb_top,该文件是对ddr2的仿真,top端设置了时钟的产生。
//***************************************************************************
// Clock generation and reset
//***************************************************************************
initial
sys_clk = 1'b0;
always
sys_clk = #(CLK_PERIOD_NS/2) ~sys_clk;
//设置系统时钟CLK_PERIOD_NS= 5000.0 / 1000.0;时间刻度是ns,即系统时钟5ns翻
//转一次,sys_clk=100MHZ.
//差分时钟sys_clk_p/n也是一致,其中n相差180°
assign sys_clk_p = sys_clk;
assign sys_clk_n = ~sys_clk;
initial
sys_clk200 = 1'b0;
always
sys_clk200 = #(TCYC_200/2) ~sys_clk200;
//TCYC_200=5,sys_clk200为200mhz时钟。
assign clk200_p = sys_clk200;
assign clk200_n = ~sys_clk200;
//pci 时钟
initial
pci_clk = 1'b0;
always
pci_clk = # 15 ~pci_clk;
assign pci_rst = ~sys_rst_n;
initial begin
sys_rst_n = 1'b0;
#200;
sys_rst_n = 1'b1;
end
//测试复位200ns后拉高
assign sys_rst_out = `RESET_ACTIVE_LOW ? sys_rst_n : ~sys_rst_n;
//输出到DDR的复位高有效
模拟产生ddr的控制信号。
// =============================================================================
// BOARD Parameters
// 这部分模拟产生ddr的控制信号
// =============================================================================
// These parameter values can be changed to model varying board delays
// between the Virtex-4 device and the memory model
always @( * ) begin
ddr2_clk_sdram <= #(TPROP_PCB_CTRL) ddr2_clk_fpga;
ddr2_clk_n_sdram <= #(TPROP_PCB_CTRL) ddr2_clk_n_fpga;
ddr2_address_sdram <= #(TPROP_PCB_CTRL) ddr2_address_fpga;
ddr2_ba_sdram <= #(TPROP_PCB_CTRL) ddr2_ba_fpga;
ddr2_ras_n_sdram <= #(TPROP_PCB_CTRL) ddr2_ras_n_fpga;
ddr2_cas_n_sdram <= #(TPROP_PCB_CTRL) ddr2_cas_n_fpga;
ddr2_we_n_sdram <= #(TPROP_PCB_CTRL) ddr2_we_n_fpga;
ddr2_cs_n_sdram <= #(TPROP_PCB_CTRL) ddr2_cs_n_fpga;
ddr2_cke_sdram <= #(TPROP_PCB_CTRL) ddr2_cke_fpga;
ddr2_odt_sdram <= #(TPROP_PCB_CTRL) ddr2_odt_fpga;
ddr2_dm_sdram_tmp <= #(TPROP_PCB_DATA) ddr2_dm_fpga;//DM signal generation
end
assign ddr2_dm_sdram = ddr2_dm_sdram_tmp;
genvar dqwd;
generate
for (dqwd = 0;dqwd < `DATA_WIDTH;dqwd = dqwd+1) begin : dq_delay
WireDelay #
(
.Delay_g (TPROP_PCB_DATA),
.Delay_rd (TPROP_PCB_DATA_RD)
)
u_delay_dq
(
.A (ddr2_dq_fpga[dqwd]),
.B (ddr2_dq_sdram[dqwd]),
.reset (sys_rst_n)
);
end
endgenerate
genvar dqswd;
generate
for (dqswd = 0;dqswd < `DATA_STROBE_WIDTH;dqswd = dqswd+1) begin : dqs_delay
WireDelay #
(
.Delay_g (TPROP_DQS),
.Delay_rd (TPROP_DQS_RD)
)
u_delay_dqs
(
.A (ddr2_dqs_fpga[dqswd]),
.B (ddr2_dqs_sdram[dqswd]),
.reset (sys_rst_n)
);
WireDelay #
(
.Delay_g (TPROP_DQS),
.Delay_rd (TPROP_DQS_RD)
)
u_delay_dqs_n
(
.A (ddr2_dqs_n_fpga[dqswd]),
.B (ddr2_dqs_n_sdram[dqswd]),
.reset (sys_rst_n)
);
end
endgenerate
例化了ddr2的mig IP核,这部分有对外部ddr2芯片控制的接口,与内部用户侧接口。其中用户侧接口是我们重要关注的地方。
//***************************************************************************
// FPGA memory controller
// 例化DDRIP核。
//***************************************************************************
ddr2_top u_mem_controller
(
.sys_clk (sys_clk_p),
.idly_clk_200 (clk200_p),
.sys_reset_in_n (sys_rst_out),
.cntrl0_ddr2_ras_n (ddr2_ras_n_fpga),
.cntrl0_ddr2_cas_n (ddr2_cas_n_fpga),
.cntrl0_ddr2_we_n (ddr2_we_n_fpga),
.cntrl0_ddr2_cs_n (ddr2_cs_n_fpga),
.cntrl0_ddr2_cke (ddr2_cke_fpga),
.cntrl0_ddr2_odt (ddr2_odt_fpga),
.cntrl0_ddr2_dm (ddr2_dm_fpga),
.cntrl0_ddr2_dq (ddr2_dq_fpga),
.cntrl0_ddr2_dqs (ddr2_dqs_fpga),
.cntrl0_ddr2_dqs_n (ddr2_dqs_n_fpga),
.cntrl0_ddr2_ck (ddr2_clk_fpga),
.cntrl0_ddr2_ck_n (ddr2_clk_n_fpga),
.cntrl0_ddr2_ba (ddr2_ba_fpga),
.cntrl0_ddr2_a (ddr2_address_fpga),
//用户侧信号
.cntrl0_clk_tb (clk0_tb),//用户侧输出时钟
.cntrl0_reset_tb (rst0_tb),//用户复位信号高有效
.cntrl0_wdf_almost_full (wdf_almost_full),
.cntrl0_af_almost_full (af_almost_full),
.cntrl0_burst_length_div2 (burst_length_div2),//突发长度
.cntrl0_read_data_valid (read_data_valid),//读取读FIFO的数据有效
.cntrl0_read_data_fifo_out (read_data_fifo_out),//ddr读取的数据存在读fifo中
.cntrl0_app_af_addr (app_af_addr),
.cntrl0_app_af_wren (app_af_wren),
.cntrl0_app_wdf_data (app_wdf_data),
.cntrl0_app_wdf_wren (app_wdf_wren),
.cntrl0_app_mask_data (app_mask_data),
.cntrl0_init_done (init_done)
);
此部分模拟了数据位宽不同的存储器。
//***************************************************************************
// Memory model instances
// 模拟外部模型的实例
// 实现了数据位宽为16,8,4的存储器模拟
//
//***************************************************************************
genvar i, j;
generate
if (DEVICE_WIDTH == 16) begin
// if memory part is x16
if ( REG_ENABLE ) begin
// if the memory part is Registered DIMM
for(j = 0; j < `CS_WIDTH; j = j+1) begin : gen_chips
for(i = 0; i < `DATA_STROBE_WIDTH/2; i = i+1) begin : gen_bytes
ddr2_model u_mem0
(
.ck (ddr2_clk_sdram[`CLK_WIDTH*i/`DATA_STROBE_WIDTH]),
.ck_n (ddr2_clk_n_sdram[`CLK_WIDTH*i/`DATA_STROBE_WIDTH]),
.cke (ddr2_cke_reg[j]),
.cs_n (ddr2_cs_n_reg[j]),
.ras_n (ddr2_ras_n_reg),
.cas_n (ddr2_cas_n_reg),
.we_n (ddr2_we_n_reg),
.dm_rdqs (ddr2_dm_sdram[(2*(i+1))-1 : i*2]),
.ba (ddr2_ba_reg),
.addr (ddr2_address_reg),
.dq (ddr2_dq_sdram[(16*(i+1))-1 : i*16]),
.dqs (ddr2_dqs_sdram[(2*(i+1))-1 : i*2]),
.dqs_n (ddr2_dqs_n_sdram[(2*(i+1))-1 : i*2]),
.rdqs_n (),
.odt (ddr2_odt_reg[j])
);
end
end
end
....
最后就是用户侧部分,我们自定义了一个模块来对ddr2进行读写操作。
// synthesizable test bench provided for wotb designs
// 这部分代码做了一个ddr数据传输的测试。也就是我们需要看的用户侧接口,我们控制
// 这个用户侧接口实现对ddr的读写
// ddr2_top_test_bench_0 test_bench_00
// (
// .clk (clk0_tb),
// .reset (rst0_tb),
// .wdf_almost_full (wdf_almost_full),
// .af_almost_full (af_almost_full),
// .burst_length_div2 (burst_length_div2),
// .read_data_valid (read_data_valid),
// .read_data_fifo_out (read_data_fifo_out),
// .init_done (init_done),
// .app_af_addr (app_af_addr),
// .app_af_wren (app_af_wren),
// .app_wdf_data (app_wdf_data),
// .app_mask_data (app_mask_data),
// .app_wdf_wren (app_wdf_wren),
// .error (error)
// );
ddr2_top_ctr u_ddr2_top_ctr (
.pci_clk (pci_clk ),
.pci_rst (pci_rst ),
.clk (clk0_tb ),
.reset (rst0_tb ),
.wdf_almost_full (wdf_almost_full ),
.af_almost_full (af_almost_full ),
.burst_length_div2 (burst_length_div2 ),
.read_data_valid (read_data_valid ),
.read_data_fifo_out (read_data_fifo_out ),
.init_done (init_done ),
.app_af_addr (app_af_addr ),
.app_af_wren (app_af_wren ),
.app_wdf_data (app_wdf_data ),
.app_mask_data (app_mask_data ),
.app_wdf_wren (app_wdf_wren ),
.error (error )
);
关于DDR2接口与结构分析
下面结合ug086介绍DDR2 SDRAM系统和用户界面信号
系统接口信号是用户提供给FPGA的时钟和复位信号。
singal name | 方向 | 描述 |
sys_clk_p, sys_clk_n | Input | 差分输入时钟 |
clk200_p, clk200_n | Input | idelay_ctrl逻辑中使用的差分时钟。 |
sys_reset_in_n | Input | 复位低有效 |
用户侧信号
singal name | 方向 | 描述 |
CLK_TB | Output | 用户时钟 |
RESET_TB | Output | 用户复位 |
BURST_LENGTH_DIV2[2:0] | Output | 该信号决定了每个写地址的数据突发长度 010突发长度为4 100突发长度为8 |
WDF_ALMOST_FULL | Output | 该信号表示写入数据FIFO的ALMOST_FULL状态 |
APP_WDF_DATA[2n-1:0] | Input | 用户侧写数据 |
APP_MASK_DATA[2m-1:0] | Input | 数据掩码 |
APP_WDF_WREN | Input | 写数据使能 |
AF_ALMOST_FULL | Output | 该信号表示地址FIFO的ALMOST_FULL状态 |
APP_AF_ADDR[35:0] | Input | 用户地址和动态命令组合体。 bit[31:0]为芯片选择,行地址,列地址。 bit[34:32]为命令选择。 001自动刷新 010预充电 100写 101读 |
APP_AF_WREN | Input | 写地址使能 |
READ_DATA_FIFO_OUT[2n-1:0] | Output | 读数据 |
READ_DATA_VALID | Output | 读数据有效 |
INIT_DONE | INIT_DONE | 初始化成功 |
关于ddr2的整体结构。可以看手册上的描述如下。
关于DDR2读写时序分析
拥有三个通道,写地址fifo通道,写数据fifo通道,读数据fifo通道。
一个命令地址FIFO总线,他接收来自用户读写命令以及相关的内存地址。
一个写数据FIFO总线,他接收来自用户写入的数据。
一个读数据FIFO总线,他保存从内存上读出的数据。
用户操作的注意事项。
在校准完成之前需要保持以下接口信号为低:app_af_wren,app_wdf_wren,app_wdf_data,app_mask_data未保持将出现校准错误。
发出写入命令时,第一个写入数据字必须在发出写入命令后不超过两个时钟周期写入写入数据FIFO。
使用用户侧时钟同步。
用户侧写入接口结构。
对于突发长度为4和突发长度为8的用户侧时序。
用户侧读出侧结构。
用户侧读出突发长度为4和突发长度为8的时序。
需要注意的是,在读命令和读地址发送出去后,需要至少25个时钟周期才会接收到读出的数据。
这里ddr还支持连续读写突发。此部分暂时先不介绍了。
关于DDR2读写控制设计
根据DDR2的结构,最左侧为用户接口侧,也就是我们控制读写的时候需要控制的信号。右边部分是核的部分,了解核内结构可以让我快速控制DDR读写。
首先我们用户侧输入的数据地址,进入FIFO缓存,DDR2的地址是公用的。读写地址都是通过app_af_addr写,写数据和读数据有专门的缓存。
根据文档的介绍,app_af_addr[34:32]三位代表着读或者写DDR2,当我们发送地址到地址FIFO的时候,DDR2_SDRAM_controller读出地址FIFO里面的值,并解析是读取还是写入DDR2。根据文档的要求,写入命令发出成功后,数据不能超过两个时钟周期写入。所以测试代码里面直接生成地址的同时生成数据。
需要注意的是,在写入的时候,根据新建IP核时候生成的突发长度值不同,地址数据的读写也不同。
// 写:特性,当BL=4(burst_length_div2=3'b010)地址每写入一次,数据要写入两次
// 当BL=8(burst_length_div2=3'b100)地址每写入一次,数据要写入四次
// 读:特性,当BL=4(burst_length_div2=3'b010)写一个地址读出两个数据
// 当BL=8(burst_length_div2=3'b100)写一个地址读出四个数据
分析官方给到的接口。
//system clock
input pci_clk ,//pci侧时钟33mhz
input pci_rst ,
//---------------------------------------
//DDR2用户侧信号
//---------------------------------------
//时钟
input wire clk ,//200MHZ
input wire reset ,//ACTIVE HIGH
//ddr内部fifo相关信号
input wire wdf_almost_full ,
input wire af_almost_full ,
input wire[2:0] burst_length_div2 ,//burst_length_div2=3'b010,(BL=4)
input wire read_data_valid ,
input wire[31:0] read_data_fifo_out ,
input wire init_done ,
//输出数据地址信号
output wire[35:0] app_af_addr ,
output wire app_af_wren ,
output wire[31:0] app_wdf_data ,
output wire[3:0] app_mask_data ,
output wire app_wdf_wren ,
output wire error
内部信号接口分别为,fifo的状态信号以及,初始化信号。以及给到了FIFO的写使能和数据,最后设置了掩码和错误信号。
通过控制这些信号已经可以控制DDR2的读写,但是为了我们自己程序更加方便,我们需要把这个接口该的更加通用一点。方便我们更加准确的控制DDR2。
下面我准备这样来定义几个接口。
//write and read request
reg ddr2_write_req ;//写请求
reg ddr2_read_req ;//读请求
reg [31:0] ddr2_write_len ;//突发写数据长度
reg [31:0] ddr2_read_len ;//突发读数据长度
reg [31:0] ddr2_wr_brust_addr ;//突发写首地址
reg [31:0] ddr2_rd_brust_addr ;//突发读首地址
reg [35:0] app_af_addr_r ;//地址信号
reg app_af_wren_r ;//地址使能
reg [31:0] write_data ;//写数据
reg write_data_en ;//写数据使能
我准备来这样实现更加通用的接口,我们知道,DDR2是通过地址线的高位来给到DDR2控制器的读写信息。我们每次在配置地址信息的时候,需要配置这个地址信息是读还是写,非常的麻烦。于是把读写请求提出来。用DDR2传输一个数据的话则白白浪费了性能,一般都是突发传输一段数据,所以定义了,突发传输数据长度和突发读写的地址代表从哪个地方开始读写。同时读写完成需要对上层一个反馈,于是接出了两个完成信号。表示一阶段的任务完成。
设置状态机。
//定义状态机
localparam IDLE = 3'b001;
localparam WRITE = 3'b010;
localparam WRITE_END = 3'b011;
localparam READ = 3'b100;
localparam READ_WIAT = 3'b101;
localparam READ_END = 3'b110;
设置相关的计数器。
其实我这种方式也浪费了DDR2的性能。具体表现在DDR2读取数据的时候,一次突发数据会延迟25个时钟周期出来。这里我采用了比较保守的,牺牲了一部分性能,保证数据传输的完全性。毕竟DDR2传输本就不是很快,再快一点可以直接上DDR3了。后面思考一下如何把这部分性能也利用起来。
//count
reg [2:0] state ;
reg [2:0] brust_count ;//突发个数计数器
reg [31:0] addr_cnt ;//地址计数
reg [31:0] wr_data_cnt ;//写数据计数
reg [31:0] rd_data_cnt ;//读数据计数
对于DDR2的地址,读写数据,上文中提到过,在新建IP的时候有数据突发长度设置,我们设置了BL=4,反应在时序里面就是传递一个地址,伴随着两个数据。所以定义了突发个数计数器。这里我为了区分读写,设置了地址使能,读写数据个数计数,地址个数计数。
赋值DDR2端口信号。
assign app_af_addr = (app_af_wren_r == 1'b1) ? app_af_addr_r : 36'd0;
assign app_af_wren = app_af_wren_r;
assign app_wdf_wren = write_data_en;
assign app_wdf_data = write_data;
assign app_mask_data = {4{1'b0}};
//assign error //暂时不用
assign brust_len = burst_length_div2;//BL、这里还是赋原始值
//完成信号
assign ddr2_write_finish = (state == WRITE_END);
assign ddr2_read_finish = (state == READ_END);
设置状态机。
always@(posedge clk ,posedge reset)
begin
if(reset == 1'b1)
begin
state <= IDLE;
brust_count <= 3'b0;
write_data_en <= 1'b0;
app_af_wren_r <= 1'b0;
addr_cnt <= 32'b0;
wr_data_cnt <= 32'b0;
rd_data_cnt <= 32'b0;
app_af_addr_r <= 36'b0;
write_data <= 32'b0;
end
else if(init_done == 1'b1)
begin
case(state)
IDLE:
begin
if(ddr2_write_req)
begin
state <= WRITE;
app_af_addr_r <= {1'b0,3'b100,32'b0};
brust_count <= brust_len;
end
else if(ddr2_read_req)
begin
state <= READ;
app_af_addr_r <= {1'b0,3'b101,32'b0};
app_af_wren_r <= 1'b1;
end
else
begin
state <= IDLE;
brust_count <= 3'b0;
end
end
WRITE:
begin
if(wdf_almost_full == 1'b0 && af_almost_full == 1'b0)
begin
if(wr_data_cnt == 32'd128 && addr_cnt == 32'd64)//数据到达128个
begin
wr_data_cnt <= 32'b0;
addr_cnt <= 32'b0;
app_af_wren_r <= 1'b0;
write_data_en <= 1'b0;
write_data <= 32'b0;
app_af_addr_r <= 36'b0;
state <= WRITE_END;
end
else//这一段三个计数器来表示,代码问题产生了丢失一个数据线改动,已改正
begin
//对写数据赋值
if( brust_count == 3'b010)
begin
wr_data_cnt <= 32'd0;
write_data_en <= 1'b0;
write_data <= 32'd0;
end
else
begin
wr_data_cnt <= wr_data_cnt + 1'b1;
write_data_en <= 1'b1;
write_data <= write_data + 1'b1;
end
//对写地址赋值
if(brust_count == 3'b001)
begin
addr_cnt <= addr_cnt;
app_af_wren_r <= 1'b1;
end
else if(brust_count == 3'b010)
begin
addr_cnt <= 32'd0;
app_af_wren_r <= 1'b0;
end
else
begin
addr_cnt <= addr_cnt + 1'b1;
app_af_addr_r <= app_af_addr_r + 32'd4;//ddr为256*8,一个地址存储1byte,存储32bit的数据,所以加4
app_af_wren_r <= 1'b0;
end
//这里对brust_count进行赋值
if (brust_count[2:0] != 3'b000)
brust_count[2:0] <= brust_count[2:0] - 1'b1;
else
brust_count[2:0] <= brust_len - 1'b1;
end
end
else
begin
app_af_wren_r <= 1'b0;
write_data_en <= 1'b0;
end
end
WRITE_END:
begin
state <= IDLE;
end
READ:
begin
if( af_almost_full == 1'b0 )
begin
if(addr_cnt == 32'd64)//地址到达64个
begin
addr_cnt <= 32'b0;
app_af_wren_r <= 1'b0;
app_af_addr_r <= 36'b0;
state <= READ_WIAT;
end
else
begin
addr_cnt <= addr_cnt + 1'b1;
app_af_addr_r <= app_af_addr_r + 32'd4;
app_af_wren_r <= 1'b1;
end
end
else
app_af_wren_r <= 1'b0;
if(read_data_valid == 1'b1)
begin
if(rd_data_cnt == 32'd128)
rd_data_cnt <= 32'b0;
else
rd_data_cnt <= rd_data_cnt + 1'b1;
end
else
rd_data_cnt <= rd_data_cnt;
end
READ_WIAT://数据已经出来,但是读数据计数器没有开始工作。
begin
if(read_data_valid == 1'b1)
begin
if(rd_data_cnt == 32'd128)
begin
rd_data_cnt <= 32'b0;
state <= READ_END;
end
else
rd_data_cnt <= rd_data_cnt + 1'b1;
end
else
rd_data_cnt <= rd_data_cnt;
end
READ_END:
state <= IDLE;
default:
state <= IDLE;
endcase
end
end
下面看看仿真。
读数据
分析问题原因,是应为我们使用的地址位计数器brust_count。本意是在brust_count第一次等于1的时候,地址从零开始。后面累加。
方法:仔细分析brust_count信号。利用该信号在过程中的变化来改正。
优化代码....
写状态,在计数器数值达到要求复位计数信号。其次通过控制brust_count来实现对数据地址的精确控制。为了解决上述问题,将数值和地址分开赋值,地址阶段在brust_count=1时置高使能,其他清零。并在brust_count=0的时候累加地址。
在读的时候,加了个在进入读状态的时候就开始拉高地址使能,这时候保证读的第一地址是0。
再仿真
写正常
读:
读数据:
接下来,将这个模块改为一个更加通用的接口。
根据之前的设计经验,ddr2我们可以给他来两个FIFO来缓存数据,即在数据写入ddr之前,先写入写入FIFO缓存,等待DDR工作完成。读取FIFO里面的数据。
同理在读出数据的时候,读出来的数据先进读取FIFO缓存,方便后续的对数据使用。之所以用FIFO缓存是因为FIFO是一个基本的逻辑单元,处理跨时钟域数据非常方便。对于低速的数据我们这样做,对于速率高的情况下可以考虑采用DMA。
建立测试代码块。
// *********************************************************************************/
// Project Name :
// Author : i_huyi
// Email : i_huyi@qq.com
// Creat Time : 2021/6/16 9:46:21
// File Name : .v
// Module Name :
// Called By :
// Abstract : 此模块的作用是构建一个双缓存的结构,来实现DDR2数据传输的高速
// 控制。
// 方案的设计为,外部(pci)侧写入的数据先缓存在写入FIFO中,当写入的数据值达到
// 阈值,启动ddr将数据传输到外部ddr存储,等待从ddr里面读出数据。
// 从外部ddr里面读出的数据要经过PCI传输给上位机,先在读出数据fifo中缓存。等待
// PCI读出。
// 因为ddr和PCI侧是跨时钟处理,使用fifo比较方便。
// 第一步,先实现小规模,低速率传输。
// 第二步,学习pci的突发传输,将读数据FIFO内部的数据通过突发传输给pci总线。
//
// CopyRight(c) 2020, xxx xxx xxx Co., Ltd..
// All Rights Reserved
//
// *********************************************************************************/
// Modification History:
// 1. initial
// *********************************************************************************/
// *************************
// MODULE DEFINITION
// *************************
`timescale 1 ns / 1 ps
module ddr2_top_ctr#(
parameter U_DLY = 1
)
(
//system clock
input pci_clk ,//pci侧时钟33mhz
input pci_rst ,
//---------------------------------------
//DDR2用户侧信号
//---------------------------------------
//时钟
input wire clk ,//200MHZ
input wire reset ,//ACTIVE HIGH
//ddr内部fifo相关信号
input wire wdf_almost_full ,
input wire af_almost_full ,
input wire[2:0] burst_length_div2 ,//burst_length_div2=3'b010,(BL=4)
input wire read_data_valid ,
input wire[31:0] read_data_fifo_out ,
input wire init_done ,
//输出数据地址信号
output wire[35:0] app_af_addr ,
output wire app_af_wren ,
output wire[31:0] app_wdf_data ,
output wire[3:0] app_mask_data ,
output wire app_wdf_wren ,
output wire error
);
//--------------------------------------
// localparam
//--------------------------------------
//--------------------------------------
// register
//--------------------------------------
//设置写入数据寄存器
reg [31:0] wr_fifo_wr_data ;
reg wr_fifo_wr_data_en ;
wire wr_fifo_almost_full ;//写fifo的满信号
//读FIFO的读取数据
reg rd_fifo_rd_en ;
wire [31:0] rd_fifo_rd_data ;
wire rd_fifo_rd_data_vaild;
wire rd_fifo_almost_full ;
wire rd_fifo_almost_empty;
//--------------------------------------
// wire
//--------------------------------------
//自定义ddr2读写控制器的接口
reg ddr2_write_req ;
reg ddr2_read_req ;
wire [31:0] ddr2_write_len ;
wire [31:0] ddr2_read_len ;
wire [31:0] ddr2_wr_burst_addr ;
wire [31:0] ddr2_rd_burst_addr ;
wire [31:0] wr_burst_data ;
wire wr_burst_data_vaild ;
wire [31:0] rd_burst_data ;
wire rd_burst_data_vaild ;
wire wr_burst_data_req ;
wire ddr2_write_finish ;
wire ddr2_read_finish ;
ddr2用户侧信号
//wire clk ;//200MHZ
//wire reset ;//ACTIVE HIGH
//wire wdf_almost_full ;
//wire af_almost_full ;
//wire [2:0] burst_length_div2 ;//burst_length_div2=3'b010;(BL=4)
//wire read_data_valid ;
//wire [31:0] read_data_fifo_out ;
//wire init_done ;
//wire [35:0] app_af_addr ;
//wire app_af_wren ;
//wire [31:0] app_wdf_data ;
//wire [3:0] app_mask_data ;
//wire app_wdf_wren ;
//wire error ;
//--------------------------------------
// assign
//--------------------------------------
//设置突发读写个数
assign ddr2_write_len = 32'd128;
assign ddr2_read_len = 32'd128;
assign ddr2_wr_burst_addr = 32'd0;
assign ddr2_rd_burst_addr = 32'd0;
//------------------------------------------------------------
//------------------------------------------------------------
//模拟写入fifo的写入数据
always@(posedge pci_clk ,posedge pci_rst)
begin
if(pci_rst == 1'b1)
begin
wr_fifo_wr_data <= 32'b0;
wr_fifo_wr_data_en <= 1'b0;
end
else if(wr_fifo_almost_full == 1'b1)
begin
wr_fifo_wr_data <= 32'b0;
wr_fifo_wr_data_en <= 1'b0;
end
else
begin
wr_fifo_wr_data <= wr_fifo_wr_data + 32'b1;
wr_fifo_wr_data_en <= 1'b1;
end
end
//产生读写ddr请求信号,写FIFO满写,写完读。
always@(posedge clk ,posedge reset)
begin
if(reset == 1'b1)
begin
ddr2_write_req <= 1'b0;
ddr2_read_req <= 1'b0;
end
else if(wr_fifo_almost_full == 1'b1)
begin
ddr2_write_req <= 1'b1;
ddr2_read_req <= 1'b0;
end
else if(ddr2_write_finish == 1'b1)
begin
ddr2_write_req <= 1'b0;
ddr2_read_req <= 1'b1;
end
else if(ddr2_read_finish == 1'b1)
begin
ddr2_write_req <= 1'b0;
ddr2_read_req <= 1'b0;
end
else
begin
ddr2_write_req <= 1'b0;
ddr2_read_req <= 1'b0;
end
end
always@(posedge pci_clk, posedge pci_rst)
begin
if(pci_rst == 1'b1)
rd_fifo_rd_en <= 1'b0;
else if(rd_fifo_almost_full == 1'b1)
rd_fifo_rd_en <= 1'b1;
else if(rd_fifo_almost_empty == 1'b1)
rd_fifo_rd_en <= 1'b0;
else
rd_fifo_rd_en <= rd_fifo_rd_en;
end
//------------------------------------------------------------
//------------------------------------------------------------
//例化DDR2读写控制模块
ddr2_burst_ctr u_ddr2_burst_ctr (
.ddr2_write_req (ddr2_write_req ),
.ddr2_read_req (ddr2_read_req ),
.ddr2_write_len (ddr2_write_len ),
.ddr2_read_len (ddr2_read_len ),
.ddr2_wr_burst_addr (ddr2_wr_burst_addr ),
.ddr2_rd_burst_addr (ddr2_rd_burst_addr ),
.wr_burst_data (wr_burst_data ),
.wr_burst_data_vaild (wr_burst_data_vaild ),
.rd_burst_data (rd_burst_data ),
.rd_burst_data_vaild (rd_burst_data_vaild ),
.wr_burst_data_req (wr_burst_data_req ),
.ddr2_write_finish (ddr2_write_finish ),
.ddr2_read_finish (ddr2_read_finish ),
.clk (clk ),
.reset (reset ),
.wdf_almost_full (wdf_almost_full ),
.af_almost_full (af_almost_full ),
.burst_length_div2 (burst_length_div2 ),
.read_data_valid (read_data_valid ),
.read_data_fifo_out (read_data_fifo_out ),
.init_done (init_done ),
.app_af_addr (app_af_addr ),
.app_af_wren (app_af_wren ),
.app_wdf_data (app_wdf_data ),
.app_mask_data (app_mask_data ),
.app_wdf_wren (app_wdf_wren ),
.error (error )
);
//ddr数据写入缓存FIFO
ddr2_wr_fifo u_ddr2_wr_fifo (
.rst (pci_rst ), // input rst
.wr_clk (pci_clk ), // input wr_clk
.rd_clk (clk ), // input rd_clk
.din (wr_fifo_wr_data ), // input [31 : 0] din
.wr_en (wr_fifo_wr_data_en ), // input wr_en
.rd_en (wr_burst_data_req ), // input rd_en
.dout (wr_burst_data ), // output [31 : 0] dout
.full ( ), // output full
.almost_full (wr_fifo_almost_full ), // output almost_full
.empty ( ), // output empty
.almost_empty ( ), // output almost_empty
.valid (wr_burst_data_vaild ), // output valid
.rd_data_count ( ), // output [9 : 0] rd_data_count
.wr_data_count ( )// output [9 : 0] wr_data_count
);
//ddr数据读出缓存FIFO
ddr2_rd_fifo u_ddr2_rd_fifo (
.rst (pci_rst ), // input rst
.wr_clk (clk ), // input wr_clk
.rd_clk (pci_clk ), // input rd_clk
.din (rd_burst_data ), // input [31 : 0] din
.wr_en (rd_burst_data_vaild ), // input wr_en
.rd_en (rd_fifo_rd_en ), // input rd_en
.dout (rd_fifo_rd_data ), // output [31 : 0] dout
.full ( ), // output full
.almost_full (rd_fifo_almost_full ), // output almost_full
.empty ( ), // output empty
.almost_empty (rd_fifo_almost_empty ), // output almost_empty
.valid (rd_fifo_rd_data_vaild ), // output valid
.rd_data_count ( ), // output [9 : 0] rd_data_count
.wr_data_count ( )// output [9 : 0] wr_data_count
);
//------------------------------------------------------------
//------------------------------------------------------------
endmodule
ddr用户侧控制器。
// *********************************************************************************/
// Project Name :
// Author : i_huyi
// Email : i_huyi@qq.com
// Creat Time : 2021/6/15 9:06:55
// File Name : .v
// Module Name :
// Called By :
// Abstract :
//
// CopyRight(c) 2020, xxx xxx xxx Co., Ltd..
// All Rights Reserved
//
// *********************************************************************************/
// Modification History:
// 1. initial
// *********************************************************************************/
// *************************
// MODULE DEFINITION
// *************************
`timescale 1 ns / 1 ps
module ddr2_burst_ctr#(
parameter U_DLY = 1
)
(
//---------------------------------------
//ddr2 read and write ctr
//---------------------------------------
input wire ddr2_write_req ,//写请求
input wire ddr2_read_req ,//读请求
input wire[31:0] ddr2_write_len ,//突发写数据长度
input wire[31:0] ddr2_read_len ,//突发读数据长度
input wire[31:0] ddr2_wr_burst_addr ,//突发写首地址
input wire[31:0] ddr2_rd_burst_addr ,//突发读首地址
//数据信号
input wire[31:0] wr_burst_data ,//写数据
input wire wr_burst_data_vaild ,//写数据有效
output wire[31:0] rd_burst_data ,//读数据
output wire rd_burst_data_vaild ,//读数据有效
//数据准备信号
output wire wr_burst_data_req ,//ddr准备写入数据在写期间有效
//读写完成信号
output wire ddr2_write_finish ,//写完成
output wire ddr2_read_finish ,//读完成
//---------------------------------------
//DDR2用户侧信号
//---------------------------------------
//时钟
input wire clk ,//200MHZ
input wire reset ,//ACTIVE HIGH
//ddr内部fifo相关信号
input wire wdf_almost_full ,
input wire af_almost_full ,
input wire[2:0] burst_length_div2 ,//burst_length_div2=3'b010,(BL=4)
input wire read_data_valid ,
input wire[31:0] read_data_fifo_out ,
input wire init_done ,
//输出数据地址信号
output wire[35:0] app_af_addr ,
output wire app_af_wren ,
output wire[31:0] app_wdf_data ,
output wire[3:0] app_mask_data ,
output wire app_wdf_wren ,
output wire error
);
//--------------------------------------
// localparam
//--------------------------------------
//定义状态机
localparam IDLE = 3'b001 ;
localparam WRITE = 3'b010 ;
localparam WRITE_END = 3'b011 ;
localparam READ = 3'b100 ;
localparam READ_WIAT = 3'b101 ;
localparam READ_END = 3'b110 ;
//--------------------------------------
// register
//--------------------------------------
reg [35:0] app_af_addr_r ;//地址信号
reg app_af_wren_r ;//地址使能
//count
reg [2:0] state ;
reg [2:0] burst_count ;//突发个数计数器
reg [31:0] addr_cnt ;//地址计数
reg [31:0] wr_data_cnt ;//写数据计数
reg [31:0] rd_data_cnt ;//读数据计数
//ddr准备写入数据在写期间有效延时
reg wr_burst_data_req_r ;
//--------------------------------------
// wire
//--------------------------------------
wire [2:0] burst_len ;//突发长度
//--------------------------------------
// assign
//--------------------------------------
assign app_af_addr = (app_af_wren_r == 1'b1) ? app_af_addr_r : 36'd0;
assign app_af_wren = app_af_wren_r;
assign app_wdf_wren = wr_burst_data_vaild;
assign app_wdf_data = wr_burst_data;
assign app_mask_data = {4{1'b0}};
//assign error //暂时不用
assign burst_len = burst_length_div2;//BL、这里还是赋原始值
//完成信号
assign ddr2_write_finish = (state == WRITE_END);
assign ddr2_read_finish = (state == READ_END);
//读出的数据
assign rd_burst_data = read_data_fifo_out;
assign rd_burst_data_vaild = read_data_valid;
//ddr准备写入数据在写期间有效
assign wr_burst_data_req = wr_burst_data_req_r;
//------------------------------------------------------------
//------------------------------------------------------------
//------------------------------------------------------------
//------------------------------------------------------------
always@(posedge clk, posedge reset)
begin
if(reset == 1'b1)
begin
state <= IDLE;
burst_count <= 3'b0;
addr_cnt <= 32'b0;
wr_data_cnt <= 32'b0;
rd_data_cnt <= 32'b0;
app_af_addr_r <= 36'b0;
app_af_wren_r <= 1'b0;
wr_burst_data_req_r <= 1'b0;
end
else if(init_done == 1'b1)
begin
case(state)
IDLE:
begin
//接收到读写请求将读写首地址赋值
if(ddr2_write_req)
begin
state <= WRITE;
app_af_addr_r <= {1'b0,3'b100,ddr2_wr_burst_addr};
burst_count <= burst_len;
end
else if(ddr2_read_req)
begin
state <= READ;
app_af_addr_r <= {1'b0,3'b101,ddr2_rd_burst_addr};
app_af_wren_r <= 1'b1;
end
else
begin
state <= IDLE;
burst_count <= 3'b0;
end
end
WRITE:
begin
//写状态包括地址和数据
//判断数据FIFO和地址FIFO是否满
//判断地址和数据个数是否达到设置个数
if(wdf_almost_full == 1'b0 && af_almost_full == 1'b0)
begin
if(wr_data_cnt == ddr2_write_len && addr_cnt == {1'b0,ddr2_write_len[31:1]} )
begin
wr_data_cnt <= 32'b0;
addr_cnt <= 32'b0;
app_af_wren_r <= 1'b0;
app_af_addr_r <= 36'b0;
state <= WRITE_END;
wr_burst_data_req_r <= 1'b0;
end
//数据从外部进入,我们根据外部数据有效
//判断写数据个数
//这里应该在设置一个数据有效标志
//告诉外界准备数据
else
begin
wr_burst_data_req_r <= 1'b1;
if(wr_burst_data_vaild)
wr_data_cnt <= wr_data_cnt + 1'b1;
else
wr_data_cnt <= wr_data_cnt;
if(burst_count == 3'b001)
begin
addr_cnt <= addr_cnt;
app_af_wren_r <= 1'b1;
end
else if(burst_count == 3'b010)
begin
addr_cnt <= 32'b0;
app_af_wren_r <= 1'b0;
end
else
begin
addr_cnt <= addr_cnt + 1'b1;
app_af_addr_r <= app_af_addr_r + 32'd4;
app_af_wren_r <= 1'b0;
end
//对burst_count进行赋值
if(burst_count[2:0] != 3'b000)
burst_count[2:0] <= burst_count[2:0] - 1'b1;
else
burst_count[2:0] <= burst_len - 1'b1;
end
end
else
begin
app_af_wren_r <= 1'b0;
wr_burst_data_req_r <= 1'b0;
end
end
WRITE_END:
begin
state <= IDLE;
end
READ:
begin
if(af_almost_full == 1'b0)
begin
//地址个数为突发传输长度的一半
if(addr_cnt == {1'b0,ddr2_read_len[31:1]})
begin
addr_cnt <= 32'b0;
app_af_wren_r <= 1'b0;
app_af_addr_r <= 36'b0;
state <= READ_WIAT;
end
else
begin
addr_cnt <= addr_cnt + 1'b1;
app_af_addr_r <= app_af_addr_r + 32'd4;
app_af_wren_r <= 1'b1;
end
end
else
app_af_wren_r <= 1'b0;
if(read_data_valid == 1'b1)
begin
if(rd_data_cnt == ddr2_read_len)
rd_data_cnt <= 32'b0;
else
rd_data_cnt <= rd_data_cnt + 1'b1;
end
else
rd_data_cnt <= rd_data_cnt;
end
READ_WIAT://读地址写入,读出的数据延后出来
begin
if(read_data_valid == 1'b1)
begin
if(rd_data_cnt == ddr2_read_len )
begin
rd_data_cnt <= 32'b0;
state <= READ_END;
end
else
rd_data_cnt <= rd_data_cnt+1'b1;
end
else
rd_data_cnt <= rd_data_cnt;
end
READ_END:
state <= IDLE;
default:
state <= IDLE;
endcase
end
end
//------------------------------------------------------------
//------------------------------------------------------------
//------------------------------------------------------------
//------------------------------------------------------------
endmodule
仿真搭建。
在sim_tb_top中把自己写的测试部分代码例化。运行仿真。
写入
读出命令地址
读出的数据。
另外可以从仿真中看出来DDR2用户控制部分还有很大的空闲期间。此期间既没有写命令也没有读命令。这部分就是浪费的性能。思考一下后续怎么将此部分利用起来。