当前位置: 首页 > article >正文

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通道。

  1. 一个命令地址FIFO总线,他接收来自用户读写命令以及相关的内存地址。

  1. 一个写数据FIFO总线,他接收来自用户写入的数据。

  1. 一个读数据FIFO总线,他保存从内存上读出的数据。

用户操作的注意事项。

  1. 在校准完成之前需要保持以下接口信号为低:app_af_wren,app_wdf_wren,app_wdf_data,app_mask_data未保持将出现校准错误。

  1. 发出写入命令时,第一个写入数据字必须在发出写入命令后不超过两个时钟周期写入写入数据FIFO。

  1. 使用用户侧时钟同步。

用户侧写入接口结构。

对于突发长度为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用户控制部分还有很大的空闲期间。此期间既没有写命令也没有读命令。这部分就是浪费的性能。思考一下后续怎么将此部分利用起来。


http://www.kler.cn/a/2858.html

相关文章:

  • Redis——数据过期策略
  • 自学记录HarmonyOS Next的HMS AI API 13:语音合成与语音识别
  • Java中使用四叶天动态代理IP构建ip代理池,实现httpClient和Jsoup代理ip爬虫
  • 如何从 0 到 1 ,打造全新一代分布式数据架构
  • jumpserver docker安装
  • 机器视觉检测相机基础知识 | 颜色 | 光源 | 镜头 | 分辨率 / 精度 / 公差
  • 【Spring Cloud Alibaba】2.服务注册与发现(Nacos安装)
  • 部署私有npm 库
  • 水文-编程命令快查手册
  • 支持RT-Thread最新版本的瑞萨RA2E1开发板终于要大展身手了
  • 学习 Python 之 Pygame 开发魂斗罗(十)
  • 如何系统型地学习深度学习?
  • Python日志logging实战教程
  • 利用Cookie劫持+HTML注入进行钓鱼攻击
  • 服务端测试知识汇总
  • 基于原生Javascript的放大镜插件的设计和实现
  • 贪心算法(一)
  • 蓝桥杯刷题冲刺 | 倒计时18天
  • MD5加密竟然不安全,应届生表示无法理解?
  • Java每日一练(20230324)
  • hive之视图
  • 手写一个Promise
  • maya python 中的maya.cmds 与maya.mel模块的区别笔记
  • 新闻文本分类任务:使用Transformer实现
  • A.机器学习入门算法(六)基于天气数据集的XGBoost分类预测
  • 用嘴写代码?继ChatGPT和NewBing之后,微软又开始整活了,Github Copilot X!