zoukankan      html  css  js  c++  java
  • DDR2(3):自定义读写控制器和DDR2 IP信号说明

      官方的例程还是比较难懂,现在试着在上次的工程上进行修改,做一个简单的读写测试。

    一、新建顶层工程

      建立工程 top.v,其效果即原先的 DDR2_example_top.v,记得右键设置为顶层模块,主要修改了以下几点:

    (1)端口信号名字;

    (2)增加 PLL 生成 100Mhz 时钟供给 DDR2 IP 用;

    (3)增加自己写的 DDR2_ctrl.v 代替之前的 DDR2_example_driver.v;

      代码如下所示:

      1 //**************************************************************************
      2 // *** 名称 : top.v
      3 // *** 作者 : xianyu_FPGA
      4 // *** 博客 : https://www.cnblogs.com/xianyufpga/
      5 // *** 日期 : 2020-6-10
      6 // *** 工具 : Quartus 13.0
      7 // *** 芯片 : Cyclone IV E
      8 // *** 型号 : EP4CE10F30C6
      9 // *** 描述 : DDR2 控制器的顶层文件
     10 //**************************************************************************
     11 
     12 module top
     13 //========================< 端口 >==========================================
     14 (
     15 //system --------------------------------------------
     16 input   wire                CLK_50M                 , //时钟
     17 input   wire                RST_N                   , //复位
     18 //DDR2 ----------------------------------------------
     19 output  wire                DDR2_ODT                , //DDR2片内终止控制
     20 output  wire                DDR2_CS_N               , //DDR2片选
     21 output  wire                DDR2_CKE                , //DDR2时钟使能
     22 output  wire    [15:0]      DDR2_ADDR               , //DDR2地址总线
     23 output  wire    [ 2:0]      DDR2_BA                 , //DDR2组地址总线
     24 output  wire                DDR2_RAS_N              , //DDR2行地址选择
     25 output  wire                DDR2_CAS_N              , //DDR2列地址选择
     26 output  wire                DDR2_WE_N               , //DDR2写使能
     27 output  wire    [ 1:0]      DDR2_DM                 , //DDR2数据屏蔽
     28 inout   wire                DDR2_CLK                , //DDR2时钟
     29 inout   wire                DDR2_CLK_N              , //DDR2时钟反相
     30 inout   wire    [15:0]      DDR2_DQ                 , //DDR2数据总线
     31 inout   wire    [ 1:0]      DDR2_DQS                  //DDR2数据源同步
     32 );
     33 //========================< 信号 >==========================================
     34 wire                        CLK_100M                ; //PLL分出100M时钟
     35 wire                        phy_clk                 ; //读写DDR2的工作时钟
     36 wire                        local_init_done         ; //初始化完成
     37 wire            [27:0]      local_address           ; //地址总线
     38 wire                        local_write_req         ; //数据写入请求
     39 wire                        local_read_req          ; //数据读出请求
     40 wire                        local_burstbegin        ; //突发起始
     41 wire            [31:0]      local_wdata             ; //写数据总线
     42 wire            [ 3:0]      local_be                ; //字节使能标志
     43 wire            [ 2:0]      local_size              ; //突发大小
     44 wire                        local_ready             ; //读写请求被接收指示
     45 wire            [31:0]      local_rdata             ; //读数据总线
     46 //==========================================================================
     47 //==                        PLL
     48 //==========================================================================
     49 pll u_pll
     50 (
     51     .inclk0                 (CLK_50M                ), //时钟输入端口
     52     .c0                     (CLK_100M               )  //100M时钟输出
     53 );
     54 //==========================================================================
     55 //==                        DDR2 IP
     56 //==========================================================================
     57 DDR2 u_DDR2
     58 (
     59     .pll_ref_clk            (CLK_100M               ), //IP核中的PLL输入参考时钟
     60     .global_reset_n         (RST_N                  ), //全局异步复位
     61     .soft_reset_n           (RST_N                  ), //全局异步复位(不复位PLL)
     62     .phy_clk                (phy_clk                ), //读写DDR2的工作时钟
     63     .reset_phy_clk_n        (                       ), //IP核提供的复位
     64     .reset_request_n        (                       ), //IP核中的PLL锁定
     65     .aux_full_rate_clk      (                       ), //全速率时钟
     66     .aux_half_rate_clk      (                       ), //半速率时钟
     67     //用户控制 --------------------------------------
     68     .local_address          (local_address          ), //地址总线
     69     .local_write_req        (local_write_req        ), //数据写入请求
     70     .local_read_req         (local_read_req         ), //数据读出请求
     71     .local_burstbegin       (local_burstbegin       ), //突发起始
     72     .local_wdata            (local_wdata            ), //写数据总线
     73     .local_be               (local_be               ), //字节使能标志
     74     .local_size             (local_size             ), //突发大小
     75     .local_ready            (local_ready            ), //读写请求被接收指示
     76     .local_rdata            (local_rdata            ), //读数据总线
     77     .local_rdata_valid      (local_rdata_valid      ), //读数据有效
     78     .local_refresh_ack      (                       ), //刷新请求
     79     .local_init_done        (local_init_done        ), //初始化完成
     80     //外部引脚 --------------------------------------
     81     .mem_odt                (DDR2_ODT               ), //DDR2片内终止控制
     82     .mem_cs_n               (DDR2_CS_N              ), //DDR2片选
     83     .mem_cke                (DDR2_CKE               ), //DDR2时钟使能
     84     .mem_addr               (DDR2_ADDR              ), //DDR2地址总线
     85     .mem_ba                 (DDR2_BA                ), //DDR2组地址总线
     86     .mem_ras_n              (DDR2_RAS_N             ), //DDR2行地址选择
     87     .mem_cas_n              (DDR2_CAS_N             ), //DDR2列地址选择
     88     .mem_we_n               (DDR2_WE_N              ), //DDR2写使能
     89     .mem_dm                 (DDR2_DM                ), //DDR2数据屏蔽
     90     .mem_clk                (DDR2_CLK               ), //DDR2时钟
     91     .mem_clk_n              (DDR2_CLK_N             ), //DDR2时钟反相
     92     .mem_dq                 (DDR2_DQ                ), //DDR2数据总线
     93     .mem_dqs                (DDR2_DQS               )  //DDR2数据源同步
     94 );
     95 //==========================================================================
     96 //==                        DDR2 IP核控制模块
     97 //==========================================================================
     98 DDR2_ctrl u_DDR2_ctrl
     99 (
    100     .phy_clk                (phy_clk                ), //读写DDR2的工作时钟
    101     .rst_n                  (local_init_done        ), //初始化完成
    102     .local_address          (local_address          ), //地址总线
    103     .local_write_req        (local_write_req        ), //数据写入请求
    104     .local_read_req         (local_read_req         ), //数据读出请求
    105     .local_burstbegin       (local_burstbegin       ), //突发起始
    106     .local_wdata            (local_wdata            ), //写数据总线
    107     .local_be               (local_be               ), //字节使能标志
    108     .local_size             (local_size             ), //突发大小
    109     .local_ready            (local_ready            ), //读写请求被接收指示
    110     .local_rdata            (local_rdata            )  //读数据总线
    111 );
    112 
    113 endmodule

    二、DDR2_ctrl 读写测试

      DDR2_ctrl.v 文件用于 DDR2 IP 的读写测试,替换之前的 DDR2_example_driver.v。实现的功能将 16 个偶数依次写入地址 0-15,然后再读出,循环反复。

      代码如下所示:

     1 //**************************************************************************
     2 // *** 名称 : DDR2_ctrl.v
     3 // *** 作者 : xianyu_FPGA
     4 // *** 博客 : https://www.cnblogs.com/xianyufpga/
     5 // *** 日期 : 2020年6月
     6 // *** 描述 : DDR2控制模块,写入16个偶数,再读出来
     7 //**************************************************************************
     8 
     9 module DDR2_ctrl
    10 //========================< 端口 >==========================================
    11 (
    12 input   wire                phy_clk                 , //时钟
    13 input   wire                rst_n                   , //复位
    14 //---------------------------------------------------
    15 input   wire                local_ready             , //读写请求被接收指示
    16 input   wire    [31:0]      local_rdata             , //读数据总线
    17 output  wire                local_write_req         , //数据写入请求
    18 output  wire                local_read_req          , //数据读出请求
    19 output  wire                local_burstbegin        , //突发起始
    20 output  reg     [27:0]      local_address           , //地址总线
    21 output  reg     [31:0]      local_wdata             , //写数据总线
    22 output  wire    [ 3:0]      local_be                , //字节使能标志
    23 output  wire    [ 2:0]      local_size                //突发大小
    24 );
    25 //========================< 信号 >==========================================
    26 reg             [ 7:0]      time_cnt                ; //计数器
    27 //==========================================================================
    28 //==    用户信号
    29 //==========================================================================
    30 assign local_be         = 4'hF;  //始终使能全部数据
    31 assign local_size       = 3'h1;  //突发长度始终为1
    32 //==========================================================================
    33 //==    计数器,local_ready为高时加1,计100下
    34 //==========================================================================
    35 always @ (posedge phy_clk or negedge rst_n) begin
    36     if(!rst_n) begin
    37         time_cnt <= 8'h0;
    38     end
    39     else if(time_cnt == 8'd99 && local_ready) begin
    40         time_cnt <= 8'h0;
    41     end
    42     else if(local_ready) begin
    43         time_cnt <= time_cnt + 8'h1;
    44     end
    45 end
    46 //==========================================================================
    47 //==    设计读写请求
    48 //==========================================================================
    49 assign local_write_req  = (time_cnt >= 8'h0  && time_cnt <= 8'd15); //写请求
    50 assign local_read_req   = (time_cnt >= 8'd30 && time_cnt <= 8'd45); //读请求
    51 assign local_burstbegin = local_write_req || local_read_req;        //突发开始
    52 //==========================================================================
    53 //==    设计地址
    54 //==========================================================================
    55 always @ (posedge phy_clk or negedge rst_n) begin
    56     if(!rst_n) begin
    57         local_address <= 28'h0;
    58     end
    59     else if(local_ready) begin
    60         if(time_cnt == 8'd15 || time_cnt == 8'd45) //清0,注意时序对齐
    61             local_address <= 28'h0;
    62         else if(local_write_req || local_read_req)
    63             local_address <= local_address + 28'h1;
    64     end
    65 end
    66 //==========================================================================
    67 //==    设计写数据,递增2,刚好对齐地址的0-15
    68 //==========================================================================
    69 always @ (posedge phy_clk or negedge rst_n) begin
    70     if(!rst_n) begin
    71         local_wdata <= 32'h0;
    72     end
    73     else if(local_ready) begin
    74         if(time_cnt < 8'd15)
    75             local_wdata <= local_wdata + 32'h2;
    76         else
    77             local_wdata <= 32'h0;
    78     end
    79 end
    80 
    81 
    82 endmodule

    三、testbench

    1、将之前工程的 DDR2_example_top_tb.v 文件复制一份命名为 top_tb.v,打开将里面的模块名和例化名改成和 top.v 一致即可。

     2、修改 QuartusII 的 testbench 文件,将原先的删除,引入这次的 top_tb.v 和仿真模型 DDR2_mem_model.v。

    四、打开仿真查看波形

    1、点击仿真打开仿真软件,出波形界面后点 stop,将所有信号删除,转而将 top 模块和 DDR2_ctrl 模块的信号加到波形那,然后仿真时间改为 302us,run。

    2、总体波形如下所示:

    3、写过程细节

    4、读过程细节

      读过程需要注意一点,local_read_req拉高时,local_rdata 数据不是立刻出来的,而是延时一段时间后和 local_rdata_valid 信号一起出来。从波形中可以看出,本次 DDR2 IP 读写测试成功。

      经过自己写一遍读写控制,虽然简单,但对理解信号还是有很大帮助的。上板就不做了,编译太慢了。

    ps:自己开发 DDR2 时需要仿真,不会像上面那样写设计文件测试,而是自己写仿真文件testbench,即自己建立一个 top_tb,将仿真模型引入即可,详细的可以看 DDR3 部分的开发过程。

    五、DDR2 IP 核信号说明

       特权同学写了这样一段解释:

      用户逻辑和 DDR2 IP 核之间的接口并不是什么新发明的特殊接口,不过是 Avalon-MM 总线而已。有人说这个美眉(Memory-Map) 会不会太慢了,关键时刻耽误事?非也,MM 总线的 burst 模式也可以流水线式连续传输数据,丝毫不逊色于ST(stream)传输方式。
      这里我们可以简单了解一下带【local_*】的 Avalon-MM 总线 burst 模式传输协议的使用方法。可以比较简单山寨的理解前面已经给出的带【local_*】的 Avalon-MM 信号接口:

    • local_size:burst 读写的最大数据数量。通常 IP 核内部有 FIFO 用于支持这样的连续数据读写,在Megafunction中设定好的最大数据数量是 Avl_size 的上限值。
    • local_be:byte enable 信号,用于使能或说是屏蔽读写数据的各个高低字节。
    • local_ready:总线当前状态指示。这里高电平表示 ready,此时的 local_read_req 和 local_write_req 能够被锁存。
    • local_burstbegin:突发传输起始标志位。它不受 local_ready 的影响,在发起一次读或写操作的第一个时钟周期,只需保持一个时钟周期的 local_burstbegin 为高电平状态,并且不用管此时的local_ ready状态如何。
    • local_addr:读写共用的总线地址,位宽由 DDR2 的存储总量和总线上读写数据的位宽来决定。如1Gbit的DDR2,外部芯片的数据位宽为 16bit,Avalon-MM 读写的数据位宽 64bit,那么它的地址不是以 16bit 位宽来计算的,而是以 64bit 位宽来计算的,即16M(24位)。
    • local_read_req:读请求, 配合地址local_addr 和突发传输起始标志位 local_burstbegin 发起一次 burst 读操作。在 local_burstbegin 拉高后,只需要确保在同一个时钟周期或其后第一次 local_ready 有效的时钟周期拉高一次 local_read_req 信号即可。
    • local_rdata_valid:读出数据的有效标志位。IP 核在收到 burst 读请求(local_read_req) 后的若千个时钟周期开始连续送出数据(数据可能分多次连续送出),该信号和读出数据配合,高电平表示当前读出数据有效。
    • local_rdata:读出数据。和local_rdata_valid 配合送给用户逻辑。
    • local_write_req:写请求信号。若发起一次 n 个数据写入的 burst 传输,第一个传输时钟周期首先拉高 local_burstbegin 以及 local_write_req,且local_write_req 必须保持到 n 个数据写入完成。只有在 local_ready 有效时,当前的 local_write_req、local_addr 和 local_wdata 才是有效的。
    • local_wdata:写入数据。

      其实我觉得吧,还是看官方英文原版手册更好一点,《emi_ddr_ug》对这些说得很清楚。

    六、local_size再说明

      这里还要重点说一下【local_size】,该数据的最大突发长度在 DDR2 IP 里的设置如下所示:

     

    • Local Maximum Burst Count:指定突发数来配置控制器从端口能接收的最大 Avalon 突发数,选择 4(100),则外部 local_size 位宽为3。

      上篇博客说过,full rate 下,IP 核突发长度只能是4,half rate下,IP核突发长度只能是8:

      而 local_size 一般翻译为本地突发长度,即连续读或写到 IP 里的字的个数。手册中有如下一段话:

    • 如果选择memory burst length=4,half rate,local burst lenth(我需要的突发长度) 是1,所以 local_zise 总是1。
    • 如果选择memory burst length=4,full rate,local burst lenth(我需要的突发长度)是2,所以 local_size 应该设置为 1 或 2 对应每次的写读请求。

      另一篇手册中,话变得不一样了:

      所以除了上述给的两个说明,别的组合应该也是可以的,例如特权同学给出的案例为 half rate 下 IP 核突发长度为8,而 local_size 取的是 4 。
     
      说简单一点,即 local_size 和 IP 突发长度的概念是不一样的!local_size 是 Avalon 的突发,而 IP 突发长度是固定死的 4 或 8。

     七、DDR2 读写时序

      DDR2 IP 核的出现让我们不需要再像 SDRAM 一样关注它的初始化时序、刷新时序、各种命令组成等,只需要关心读写时序即可。

    1、写时序(by锆石科技,4突发,full速率)

      从上图可以看出,除了 phy_clk 时钟信号外,写时序涉及到了 7 个信号,分别是 local_ready、local_burstbegin、local_write_req、local_size、local_address、local_be 和 local_wdata。在这 7 个信号中只有 local_ready 是输出信号,这意味着我们需要通过 local_ready 来得知控制器是否准备好了接收我们的指令。如果 local_ready 为高,则拉高 local_burstbegin 和 local_write_req 可以向控制器发出一次突发写指令,由于一次突发指令可能不止传输一个数据,因此 local_burstbegin 只需在突发开始时拉高一个时钟周期,而 local_write_req 在整个写数据期间都需拉高。在一次突发开始时需要指定突发的起始地址 local_address、突发大小 local_size,而在整个突发写期间,将每个数据以及它对应的字节使能信号顺序放在local_wdata 和 local_be 总线上。如果 local_ready 为低,则表示控制器不能接收指令,在此期间除了 local_burstbegin 其他的信号都必须保持原来的状态,直至 local_ready 为高。

    2、读时序(by锆石科技,4突发,full速率)

      从上图可以看出,读时序和写时序类似,也有 8 个信号,除了 phy_clk 时钟信号,其他的信号有 local_ready、local_burstbegin、local_read_req 、local_size 、local_address、local_rdata_valid 和 local_rdata。如果 local_ready 为高,则拉高local_burstbegin 和 local_read_req 一个时钟周期可以向控制器发出一次读突发指令,与此同时,必须给定突发起始地址 local_address 和突发大小 local_size。由于不需要像写时序那样逐个把数据放在 local_wdata 上,因此通常我们会连续发出读指令,当然前提是 local_ready 一直为高。如果 local_ready 为低,则除了 local_ burstbegin 其他的信号都必须保持原状态,直到 local_ready 变为高。发出读指令一段时间后,local_rdata_valid 才会被拉高,表示 local_rdata 总线上的数据被读出。最后需要说明的是,实际测试发现,local_ burstbegin 信号和控制器内部逻辑没有任何关联,也就是说不管 local_burstbegin 信号是什么状态都不影响读写数据过程。为此可以这么理解,local_ burstbegin信号的存在仅仅是为了兼容Avalon-MM总线,可实际上它却没有起到作用。

     3、正常 4 个数据的 burst 写操作(by特权同学,8突发,half速率)

      默认情况下,local_addr 设定的是写入的首个数据对应的地址,随后每次写入数据后地址自动递增。
    4、遇到 local_ready 拉低的 burst 写操作(by特权同学,8突发,half速率)
      Local_ready 拉高时,local_write_req、local_addr 和 local_wdata 所对应的地址和数据才是有效的。
    5、正常 4 个数据的 burst 读操作(by特权同学,8突发,half速率)
      默认情况下,local_addr 为读出的首个数据对应的地址,随后将读出递增地址的数据。

    6、遇到 local_ready 拉低的 burst 读操作(by特权同学,8突发,half速率)

      必须保持 local_read_req、local_size 和 local_addr 到 local_ready 拉高为止。 

      贴了两家的时序解释,结合上面的实验,对 DDR2 IP 的读写时序应该就比较清楚了。

    参考资料:

    1、锆石科技FPGA教程

    2、特权同学《vip_ex2 DDR2控制器读写测试》

    3、emi_ddr_ug

  • 相关阅读:
    通过Powershell开启RDP
    映射网络驱动器
    简易图书管理系统
    使用IDEA快速创建Spring Boot项目
    oracle11g安装步骤详细图文教程
    使用JDOM创建XML文档
    使用JDOM解析XML文档
    HTML+CSS之金立官网部分实现
    webstorm2019安装与使用详细教程
    第二章 JavaScript基础指令
  • 原文地址:https://www.cnblogs.com/xianyufpga/p/13080797.html
Copyright © 2011-2022 走看看