【FPGA】DDR3学习笔记(二)丨从SDRAM到DDR3的IP核设计

本篇文章包含的内容

  • 一、DDR SDRAM
    • 1.1 基本概述
    • 1.2 工作时序(以读取为例)
  • 二、DDR2 SDRAM
    • 2.1 基本概述
    • 2.2 工作时序
  • 三、DDR3 SDRAM
    • 3.1 基本概述
    • 3.2 硬件设计
    • 3.3 读写时序
    • 3.4 MIG IP核设计
    • 3.5 读写代码设计


  • 开发板:正点原子的达芬奇开发板(或MicroPhase的Z7-Lite 7020开发板)
  • FPGA型号:XC7A35TFGG484-2(或XC7Z020CLG400-2)
  • Vivado版本:2020.2
  • 参考课程链接:正点原子手把手教你学FPGA-基于达芬奇开发板 A7

一、DDR SDRAM

1.1 基本概述

在这里插入图片描述

  DDR SDRAM(Double Data Rate SDRAM),即(第一代)双倍速率同步动态随机存储器,它的主要结构示意图如上图所示。相较于SDRAM,它的主要有以下几点不同:

  • 差分时钟:DDR SDRAM最核心的特点就是通过引入差分时钟,同时获取时钟信号CK的上升沿和下降沿,在上升沿和下降沿都可以传输数据。
  • 数据预取和预存(Prefetch):在读取过程中,为了提高数据传输效率,先将数据放到输出锁存中,之后再通过多路选择器将数据输出到数据线DQ上。写入操作也与之类似,先将数据读入到缓存电路中,将数据拼接后存入对应的存储单元中。
  • 同步数据信号DQS:数据变化只在DQS的边沿改变,此信号只由数据的发送方给出,提供给数据的接收方用于同步数据信号,目的就是为了在高速的数据传输中保持数据传输的稳定性。
  • 掩码控制只对写入起作用:如上图所示,数据的掩码控制只对写入起作用,对读出不再支持掩码控制操作。

1.2 工作时序(以读取为例)

在这里插入图片描述

二、DDR2 SDRAM

2.1 基本概述

在这里插入图片描述
  DDR2的主体结构与第一代DDR没有太大的不同,但是读取速度提升了一倍。可以看到DDR2的内部结构相较于之前也更复杂,在这里仅仅对一些知识点作一些科普性的说明:

  • 片外驱动调教(OCD,Off-Chip Driver):DDR2上电后同样有初始化过程,在这个过程中除了需要配置模式寄存器MRS(Mode Register Settings)之外,还添加了扩展模式寄存器设置过程EMRS(Extended Mode Register Settings),增加了新的设置选项。在EMRS阶段,DDR2添加了可选的OCD功能,主要作用是调整IO接口端的电压,用于补偿上拉电阻和下拉电阻对IO端口造成的影响,目的是使DQS和DQ之间的时间偏差降低到最小,以保证数据的稳定传输。调教期间,分别测试DQS高电平-DQ高电平与DQS低电平-DQ高电平时的同步情况,如果不满足要求,则通过设定突发长度的地址线来传送上拉/下拉电阻的等级,直到测试合格才会退出OCD操作。
    在这里插入图片描述
  • 片内终结(ODT,On-Die Termination):所谓的终结,就是让信号被电路的终端被吸收掉,而不会在电路上形成反射,造成对后面信号的影响。在 DDR 时代,控制与数据信号的终结在主板上完成,每块 DDR 主板在 DIMM 槽的旁边都会有一个终结电压岛(电压岛就是指一块特殊的电路,没有其他特殊含义)的设计,它主要由一排终结电阻构成。长期以来,这个电压岛一直是 DDR 主板设计上的一个难点。而 ODT 的出现,则将这个难点消灭了。顾名思义,ODT 就是将终结电阻移植到了芯片内部,主板上不在有终结电路。
  • 前置CAS、附加潜伏期AL和写入潜伏期WL:前置 CAS(Posted CAS)是为了解决 DDR 内存中指令冲突而设计的功能。它允许 CAS 信号紧随 RAS (ACTIVE命令)发送,相对于以往的 DDR 等于将 CAS 前置了。这样,地址线可以立刻空出来,便于后面的行有效命令发出,避免造成命令冲突而被迫延后的情况发生,但读/写操作并没有因此而提前,仍有要保证有足够的延迟/潜伏期,为此,DDR-Ⅱ引入了附加潜伏期的概念(AL,Additive Latency),与 CL 一样,单位为时钟周期数。AL+CL 被定义为读取潜伏期(RL,Read Latency),相应的,DDR-Ⅱ还对写入潜伏期(WL,Write Latency)制定了标准,WL 是指从写入命令发出到第一笔数据输入的潜伏期,不要将它和 tDQSS 弄混了,后者是指 DQS 而不是数据。按规定,WL=RL-1,即 AL+CL-1。

2.2 工作时序

在这里插入图片描述
在这里插入图片描述

三、DDR3 SDRAM

3.1 基本概述

在这里插入图片描述

  可以看到,DDR3 相较于前几代版本越来越复杂,很多工程师添加和很多的新功能,提升了数据的存储容量和传输效率。但是笔者学习了相关内容之后发现,想要自行编写 DDR3 的 Verilog HDL 程序是非常不现实的,其中涉及非常多时序等待时间的计算和校准,调用官方自带的IP核对于开发效率的提升还是很大的。

  DDR3相较于前几代添加的核心概念有以下几点(科普性质内容,了解即可):

  • 突发长度(Burst Length,BL)与前几代不同:由于DDR3的预取为8bit,所以突发传输周期(Burst Length,BL)也固定为8,而对于DDR2和早期的DDR架构系统,BL=4也是常用的,DDR3为此增加了一个4bit Burst Chop(突发突变)模式,即由一个BL=4的读取操作加上一个BL=4的写入操作来合成一个BL=8的数据突发传输,届时可通过地址线来控制这一突发模式。而且需要指出的是,任何突发中断操作都将在ddr3中予以禁止,且不予支持,取而代之的是更灵活的突发传输控制(如4bit顺序突发)。

  • 寻址时序(Timing)改变:就像DDR2从DDR转变而来后延迟周期数增加一样,DDR3的CL周期也将比DDR2有所提高。DDR2的CL范围一般在2~5之间,而DDR3则在5~11之间,且附加延迟(AL)的设计也有所变化。DDR2时AL的范围是0~4,而DDR3时AL有三种选项,分别是0、CL-1和CL-2。另外,DDR3还新增加了一个时序参数——写入延迟(CWD),这一参数将根据具体的工作频率而定。

  • 新增重置(Reset)功能:重置是DDR3新增的一项重要功能,并为此专门准备了一个引脚。DRAM业界很早以前就要求增加这一功能,如今终于在DDR3上实现了。这一引脚将使DDR3的初始化处理变得简单。当Reset命令有效时,DDR3内存将停止所有操作,并切换至最少量活动状态,以节约电力。
    在Reset期间,DDR3内存将关闭内在的大部分功能,所有数据接收与发送器都将关闭,所有内部的程序装置将复位,DLL(延迟锁相环路)与时钟电路将停止工作,而且不理睬数据总线上的任何动静。这样一来,将使DDR3达到最节省电力的目的。

  • 新增ZQ校准功能:ZQ也是一个新增的引脚,在这个引脚上接有一个240欧姆的低公差参考电阻。这个引脚通过一个命令集,通过片上校准引擎(On-Die Calibration Engine,ODCE)来自动校验数据输出驱动器导通电阻与ODT的终结电阻值。当系统发出这一指令后,将用相应的时钟周期(在加电与初始化之后用512个时钟周期,在退出自刷新操作后用256个时钟周期、在其他情况下用64个时钟周期)对导通电阻和ODT电阻进行重新校准。、

  • 两个参考电压:在DDR3系统中,对于内存系统工作非常重要的参考电压信号VREF将分为两个信号,即为命令与地址信号服务的VREFCA和为数据总线服务的VREFDQ,这将有效地提高系统数据总线的信噪等级。

  • 点对点连接(Point-to-Point,P2P):这是为了提高系统性能而进行的重要改动,也是DDR3与DDR2的一个关键区别。在DDR3系统中,一个内存控制器只与一个内存通道打交道,而且这个内存通道只能有一个插槽,因此,内存控制器与DDR3内存模组之间是点对点(P2P)的关系(单物理Bank的模组),或者是点对双点(Point-to-two-Point,P22P)的关系(双物理Bank的模组),从而大大地减轻了地址/命令/控制与数据总线的负载。而在内存模组方面,与DDR2的类别相类似,也有标准DIMM(台式PC)、SO-DIMM/Micro-DIMM(笔记本电脑)、FB-DIMM2(服务器)之分,其中第二代FB-DIMM将采用规格更高的AMB2(高级内存缓冲器)。
    面向64位构架的DDR3显然在频率和速度上拥有更多的优势,此外,由于DDR3所采用的根据温度自动自刷新、局部自刷新等其它一些功能,在功耗方面DDR3也要出色得多,因此,它可能首先受到移动设备的欢迎,就像最先迎接DDR2内存的不是台式机而是服务器一样。在CPU外频提升最迅速的PC台式机领域,DDR3未来也是一片光明。Intel所推出的新芯片-熊湖(Bear Lake),其将支持DDR3规格,而AMD也预计同时在K9平台上支持DDR2及DDR3两种规格。

3.2 硬件设计

  达芬奇板载的DDR3 SDRAM电路图如下,可以当作DDR3硬件设计的一个参考示例:
请添加图片描述

3.3 读写时序

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

3.4 MIG IP核设计

  MIG(Memory Interface Generate)是Xilinx Vivado自带的一个IP核,它的结构以及连接关系如下图所示。可以看到,MIG IP核相当于一个连接FPGA用户逻辑和DDR3存储器的桥梁。用户仅需要关心左侧的用户接口的定义和时序即可。
在这里插入图片描述

  1. 在IP Catalog中搜索mig,即可找到MIG IP核。
    在这里插入图片描述
    在这里插入图片描述

  2. 开始配置MIG,首先配置下列信息
    请添加图片描述

  3. 选择是否兼容其他型号的FPGA(本次实验不需要)
    请添加图片描述

  4. 选择DDR3控制器
    在这里插入图片描述

  5. 配置时钟选项。包括器件时钟、器件时钟与用户时钟的比率(本次实验器件时钟为400MHz,用户时钟为100MHz)。Vccaux_io是一个与MIG性能有关的电压,本次实验同样保持默认。
    在这里插入图片描述

  6. 配置存储器选项、ECC和数据屏蔽。Memory Type选择Components;存储芯片只能选择美光(MT)的器件,具体的存储芯片型号要和实际的存储大小一致(达芬奇开发板保证设置为128M16即可)。ECC选项只有当数据位宽达到72位才可以开启。数据屏蔽功能本实验没有用到,这里开启或关闭对本次实验没有影响。
    在这里插入图片描述

  7. Bank Machine的数量决定了MIG的性能,但相对的,使用过多也会增大芯片内部的逻辑资源消耗,一般选择一个适中的数值。ORDERING规定是否允许MIG对指令进行重排序,在这里选择开启。
    在这里插入图片描述

  8. 配置输入时钟频率(在这里为200MHz,有可能会出现Bug,选择为200MHz后打开依然是400MHz)、突发类型选择(这里选择顺序突发)、输出阻抗控制(默认选择RZQ/7)、终结电阻控制(默认选择RZQ/4)、片选信号输出使能(是否将#CS信号引出,选择Enable)、寻址模式(颠倒Bank和行的寻址次序可以提高效率,连续地址可以并行处理)
    在这里插入图片描述

  9. 配置FPGA内部相关信息。
    请添加图片描述

  10. 阻抗匹配设置,这里保持默认。
    在这里插入图片描述

  11. 引脚模式设置
    在这里插入图片描述

  12. 设置引脚。可以事先写好.xdc文件,再导入直接读取引脚电平设置,点击Validate使引脚设置生效即可。
    在这里插入图片描述

  13. 之后的选项均为总结页面,协议声明等,全部保持默认即可。

3.5 读写代码设计

  • ddr3_rw_top.v
module ddr3_rw_top(
        input                 sys_clk,        // 系统时钟
        input                 sys_rst_n,      // 复位,低有效
        // DDR3
        inout   [31:0]        ddr3_dq      ,  // DDR3 数据
        inout   [3:0]         ddr3_dqs_n   ,  // DDR3 dqs负
        inout   [3:0]         ddr3_dqs_p   ,  // DDR3 dqs正
        output  [13:0]        ddr3_addr    ,  // DDR3 地址
        output  [2:0]         ddr3_ba      ,  // DDR3 banck 选择
        output                ddr3_ras_n   ,  // DDR3 行选择
        output                ddr3_cas_n   ,  // DDR3 列选择
        output                ddr3_we_n    ,  // DDR3 读写选择
        output                ddr3_reset_n ,  // DDR3 复位
        output  [0:0]         ddr3_ck_p    ,  // DDR3 时钟正
        output  [0:0]         ddr3_ck_n    ,  // DDR3 时钟负
        output  [0:0]         ddr3_cke     ,  // DDR3 时钟使能
        output  [0:0]         ddr3_cs_n    ,  // DDR3 片选
        output  [3:0]         ddr3_dm      ,  // DDR3_dm
        output  [0:0]         ddr3_odt     ,  // DDR3_odt
        //用户
        output                 led             // 错误指示信号
    );

    // wire define
    wire                  error_flag;
    wire                  ui_clk ;             // 用户时钟
    wire [27:0]           app_addr;            // DDR3 地址
    wire [2:0]            app_cmd;             // 用户读写命令
    wire                  app_en;              // MIG IP核使能
    wire                  app_rdy;             // MIG IP核空闲
    wire [255:0]          app_rd_data;         // 用户读数据
    wire                  app_rd_data_end;     // 突发读当前时钟最后一个数据
    wire                  app_rd_data_valid;   // 读数据有效
    wire [255:0]          app_wdf_data;        // 用户写数据
    wire                  app_wdf_end;         // 突发写当前时钟最后一个数据
    wire [31:0]           app_wdf_mask;        // 写数据屏蔽
    wire                  app_wdf_rdy;         // 写空闲
    wire                  app_sr_active;       // 保留
    wire                  app_ref_ack;         // 刷新请求
    wire                  app_zq_ack;          // ZQ 校准请求
    wire                  app_wdf_wren;        // DDR3 写使能
    wire                  locked;              // 锁相环频率稳定标志
    wire                  clk_ref_i;           // DDR3参考时钟
    wire                  sys_clk_i;           // MIG IP核输入时钟
    wire                  clk_200;             // 200M时钟
    wire                  ui_clk_sync_rst;     // 用户复位信号
    wire                  init_calib_complete; // 校准完成信号

    //*****************************************************
    //**                    main code
    //*****************************************************

    //读写模块
    ddr3_rw u_ddr3_rw(
                .ui_clk               (ui_clk),
                .ui_clk_sync_rst      (ui_clk_sync_rst),
                .init_calib_complete  (init_calib_complete),
                .app_rdy              (app_rdy),
                .app_wdf_rdy          (app_wdf_rdy),
                .app_rd_data_valid    (app_rd_data_valid),
                .app_rd_data          (app_rd_data),

                .app_addr             (app_addr),
                .app_en               (app_en),
                .app_wdf_wren         (app_wdf_wren),
                .app_wdf_end          (app_wdf_end),
                .app_cmd              (app_cmd),
                .app_wdf_data         (app_wdf_data),
                .state                (state),
                .rd_addr_cnt          (rd_addr_cnt),
                .wr_addr_cnt          (wr_addr_cnt),
                .rd_cnt               (rd_cnt),

                .error_flag           (error_flag),
                .led                  (led)
            );

    // MIG IP核模块
    mig_7series_0 u_mig_7series_0 (
                      // Memory interface ports
                      .ddr3_addr                      (ddr3_addr),   // output [14:0]	ddr3_addr
                      .ddr3_ba                        (ddr3_ba),     // output [2:0]	ddr3_ba
                      .ddr3_cas_n                     (ddr3_cas_n),  // output		ddr3_cas_n
                      .ddr3_ck_n                      (ddr3_ck_n),   // output [0:0]	ddr3_ck_n
                      .ddr3_ck_p                      (ddr3_ck_p),   // output [0:0]	ddr3_ck_p
                      .ddr3_cke                       (ddr3_cke),    // output [0:0]	ddr3_cke
                      .ddr3_ras_n                     (ddr3_ras_n),  // output		ddr3_ras_n
                      .ddr3_reset_n                   (ddr3_reset_n),// output		ddr3_reset_n
                      .ddr3_we_n                      (ddr3_we_n),   // output		ddr3_we_n
                      .ddr3_dq                        (ddr3_dq),     // inout [31:0]	ddr3_dq
                      .ddr3_dqs_n                     (ddr3_dqs_n),  // inout [3:0]	ddr3_dqs_n
                      .ddr3_dqs_p                     (ddr3_dqs_p),  // inout [3:0]	ddr3_dqs_p
                      .init_calib_complete            (init_calib_complete),
                      // init_calib_complete
                      .ddr3_cs_n                      (ddr3_cs_n),   // output [0:0]	ddr3_cs_n
                      .ddr3_dm                        (ddr3_dm),     // output [3:0]	ddr3_dm
                      .ddr3_odt                       (ddr3_odt),    // output [0:0]	ddr3_odt
                      // Application interface ports
                      .app_addr                       (app_addr),    // input [28:0]	app_addr
                      .app_cmd                        (app_cmd),     // input [2:0]	app_cmd
                      .app_en                         (app_en),      // input			app_en
                      .app_wdf_data                   (app_wdf_data),// input [255:0] app_wdf_data
                      .app_wdf_end                    (app_wdf_end), // input         app_wdf_end
                      .app_wdf_wren                   (app_wdf_wren),// input	        app_wdf_wren
                      .app_rd_data                    (app_rd_data), // output [255:0]app_rd_data
                      .app_rd_data_end                (app_rd_data_end),
                      // output	    app_rd_data_end
                      .app_rd_data_valid              (app_rd_data_valid),
                      // output	    app_rd_data_valid
                      .app_rdy                        (app_rdy),     // output	    app_rdy
                      .app_wdf_rdy                    (app_wdf_rdy), // output	    app_wdf_rdy
                      .app_sr_req                     (),            // input	        app_sr_req
                      .app_ref_req                    (),            // input	        app_ref_req
                      .app_zq_req                     (),            // input	        app_zq_req
                      .app_sr_active                  (app_sr_active),// output	    app_sr_active
                      .app_ref_ack                    (app_ref_ack),  // output	    app_ref_ack
                      .app_zq_ack                     (app_zq_ack),   // output	    app_zq_ack
                      .ui_clk                         (ui_clk),       // output	    ui_clk
                      .ui_clk_sync_rst                (ui_clk_sync_rst),
                      // output       ui_clk_sync_rst
                      .app_wdf_mask                   (32'b0),        // input [31:0]	app_wdf_mask
                      // System Clock Ports
                      .sys_clk_i                      (clk_200),
                      // Reference Clock Ports
                      .clk_ref_i                      (clk_200),
                      .sys_rst                        (sys_rst_n)     // input         sys_rst
                  );

    //PLL模块
    clk_wiz_0 u_clk_wiz_0 (
                  // Clock out ports
                  .clk_out1(clk_200),     // output clk_out1
                  .clk_out2(clk_50),
                  // Status and control signals
                  .reset(1'b0),           // input resetn
                  .locked(locked),        // output locked
                  // Clock in ports
                  .clk_in1(sys_clk)
              );                      // input clk_in1

endmodule

  • ddr3_rw.v
module ddr3_rw (
        input                    ui_clk,                //用户时钟
        input                    ui_clk_sync_rst,       //复位,高有效
        input                    init_calib_complete,   //DDR3初始化完成
        input                    app_rdy,               //MIG 命令接收准备好标致
        input                    app_wdf_rdy,           //MIG数据接收准备好
        input                    app_rd_data_valid,     //读数据有效
        input          [255:0]   app_rd_data,           //用户读数据
        output reg     [27:0]    app_addr,              //DDR3地址
        output                   app_en,                //MIG IP发送命令使能
        output                   app_wdf_wren,          //用户写数据使能
        output                   app_wdf_end,           //突发写当前时钟最后一个数据
        output         [2:0]     app_cmd,               //MIG IP核操作命令,读或者写
        output reg     [255:0]   app_wdf_data,          //用户写数据
        output reg     [1 :0]    state,                 //读写状态
        output reg     [23:0]    rd_addr_cnt,           //用户读地址计数
        output reg     [23:0]    wr_addr_cnt,           //用户写地址计数
        output reg     [20:0]    rd_cnt,                //实际读地址标记
        output reg               error_flag,            //读写错误标志
        output reg               led                    //读写测试结果指示灯
    );

    //parameter define
    parameter  TEST_LENGTH = 1000;
    parameter  L_TIME = 25'd25_000_000;
    parameter  IDLE        = 2'd0;            //空闲状态
    parameter  WRITE       = 2'd1;            //写状态
    parameter  WAIT        = 2'd2;            //读到写过度等待
    parameter  READ        = 2'd3;            //读状态

    //reg define
    reg  [24:0]  led_cnt;    //led计数

    //wire define
    wire         error;     //读写错误标记
    wire         rst_n;     //复位,低有效

    //*****************************************************
    //**                    main code
    //*****************************************************

    assign rst_n = ~ui_clk_sync_rst;
    //读信号有效,且读出的数不是写入的数时,将错误标志位拉高
    assign error = (app_rd_data_valid && (rd_cnt!=app_rd_data));

    //在写状态MIG IP 命令接收和数据接收都准备好,或者在读状态命令接收准备好,此时拉高使能信号,
    assign app_en = ((state == WRITE && (app_rdy && app_wdf_rdy))
                     ||(state == READ && app_rdy)) ? 1'b1:1'b0;

    //在写状态,命令接收和数据接收都准备好,此时拉高写使能
    assign app_wdf_wren = (state == WRITE && (app_rdy && app_wdf_rdy)) ? 1'b1:1'b0;

    //由于DDR3芯片时钟和用户时钟的分频选择4:1,突发长度为8,故两个信号相同
    assign app_wdf_end = app_wdf_wren;

    //处于读的时候命令值为1,其他时候命令值为0
    assign app_cmd = (state == READ) ? 3'd1 :3'd0;

    //DDR3读写逻辑实现
    always @(posedge ui_clk or negedge rst_n)
    begin
        if((~rst_n)||(error_flag))
        begin
            state    <= IDLE;
            app_wdf_data <= 128'd0;
            wr_addr_cnt  <= 24'd0;
            rd_addr_cnt  <= 24'd0;
            app_addr     <= 28'd0;
        end
        else if(init_calib_complete)
        begin               //MIG IP核初始化完成
            case(state)
                IDLE:
                begin
                    state <= WRITE;
                    app_wdf_data <= 256'd0;
                    wr_addr_cnt  <= 24'd0;
                    rd_addr_cnt  <= 24'd0;
                    app_addr     <= 28'd0;
                end
                WRITE:
                begin
                    if(wr_addr_cnt == TEST_LENGTH - 1 &&(app_rdy && app_wdf_rdy))
                        state <= WAIT;                  	//写到设定的长度跳到等待状态
                    else if(app_rdy && app_wdf_rdy)
                    begin   //写条件满足
                        app_wdf_data <= app_wdf_data + 1;  //写数据自加
                        wr_addr_cnt  <= wr_addr_cnt + 1;   //写地址自加
                        app_addr     <= app_addr + 8;      //DDR3 地址加8
                    end
                    else
                    begin                             //写条件不满足,保持当前值
                        app_wdf_data <= app_wdf_data;
                        wr_addr_cnt  <= wr_addr_cnt;
                        app_addr     <= app_addr;
                    end
                end
                WAIT:
                begin
                    state   <= READ;                     //下一个时钟,跳到读状态
                    rd_addr_cnt <= 24'd0;                //读地址复位
                    app_addr    <= 28'd0;                //DDR3读从地址0开始
                end
                READ:
                begin                               //读到设定的地址长度
                    if(rd_addr_cnt == TEST_LENGTH - 1 && app_rdy)
                        state   <= IDLE;                   //则跳到空闲状态
                    else if(app_rdy)
                    begin                  //若MIG已经准备好,则开始读
                        rd_addr_cnt <= rd_addr_cnt + 1'd1; //用户地址每次加一
                        app_addr    <= app_addr + 8;       //DDR3地址加8
                    end
                    else
                    begin                             //若MIG没准备好,则保持原值
                        rd_addr_cnt <= rd_addr_cnt;
                        app_addr    <= app_addr;
                    end
                end
                default:
                begin
                    state    <= IDLE;
                    app_wdf_data <= 256'd0;
                    wr_addr_cnt  <= 24'd0;
                    rd_addr_cnt  <= 24'd0;
                    app_addr     <= 28'd0;
                end
            endcase
        end
    end

    //对DDR3实际读数据个数编号计数
    always @(posedge ui_clk or negedge rst_n)
    begin
        if(~rst_n)
            rd_cnt  <= 0;              //若计数到读写长度,且读有效,地址计数器则置0
        else if(app_rd_data_valid && rd_cnt == TEST_LENGTH - 1)
            rd_cnt <= 0;              //其他条件只要读有效,每个时钟自增1
        else if (app_rd_data_valid )
            rd_cnt <= rd_cnt + 1;
    end

    //寄存状态标志位
    always @(posedge ui_clk or negedge rst_n)
    begin
        if(~rst_n)
            error_flag <= 0;
        else if(error)
            error_flag <= 1;
    end

    //led指示效果控制
    always @(posedge ui_clk or negedge rst_n)
    begin
        if((~rst_n) || (~init_calib_complete ))
        begin
            led_cnt <= 25'd0;
            led <= 1'b0;
        end
        else
        begin
            if(~error_flag)                        //读写测试正确
                led <= 1'b1;                       //led灯常亮
            else
            begin                            //读写测试错误
                led_cnt <= led_cnt + 25'd1;
                if(led_cnt == L_TIME - 1'b1)
                begin
                    led_cnt <= 25'd0;
                    led <= ~led;                      //led灯闪烁
                end
            end
        end
    end

endmodule


  原创笔记,码字不易,欢迎点赞,收藏~ 如有谬误敬请在评论区不吝告知,感激不尽!博主将持续更新有关嵌入式开发、FPGA方面的学习笔记。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/457463.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:GridCol)

栅格子组件&#xff0c;必须作为栅格容器组件(GridRow)的子组件使用。 说明&#xff1a; 该组件从API Version 9开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 可以包含单个子组件。 接口 GridCol(option?:{span?: number | …

#QT(一种朴素的计算器实现方法)

1.IDE&#xff1a;QTCreator 2.实验&#xff1a;这是全靠自己想法写的计算器&#xff0c;没有参考任何教程。 &#xff08;1&#xff09;这个计算器只要有运算符敲入就会进行一次运算&#xff0c;所以他没有先后之后&#xff0c;无法满足运算优先级。 &#xff08;2&#xff…

linux安全--Nginx与Tomcat实现负载均衡

目录 1.实验拓扑原理图&#xff0c;前提实现全网互通 2.找到nginx的conf目录中的nginx.conf文件 3.实验效果 1.实验拓扑原理图&#xff0c;前提实现全网互通 搭建全网互通可以看https://blog.csdn.net/m0_74313947/article/details/136008513?spm1001.2014.3001.5501 搭建N…

JavaEE之多线程(创建线程的五种写法)详解

&#x1f63d;博主CSDN主页: 小源_&#x1f63d; &#x1f58b;️个人专栏: JavaEE &#x1f600;努力追逐大佬们的步伐~ 目录 1. 前言 2. 操作系统"内核" 3. 创建线程的五种写法 (我们重点要掌握最后一种写法!!) 3.1 继承 Thread, 重写 run 3. 2 实现 Runnabl…

北京保险服务中心携手镜舟科技,助推新能源车险市场规范化

2022 年&#xff0c;一辆新能源汽车在泥泞的小路上不慎拖底&#xff0c;动力电池底壳受损&#xff0c;电池电量低。车主向保险公司报案&#xff0c;希望能够得到赔偿。然而&#xff0c;在定损过程中&#xff0c;保司发现这辆车的电池故障并非由拖底事件引起&#xff0c;而是由于…

电脑坏了去维修,第一家报价800,第三家说报废!

这篇文章主要讲的是修理坏掉的电脑。 第一家报价300&#xff0c;第二家报价800&#xff0c;第三家说要报废&#xff01; 相信很多朋友对于修电脑坏了要多少钱有很多困惑&#xff0c;修电脑坏了要多少钱&#xff0c;到底去正规售后服务还是去非品牌店维修一台坏掉的电脑。 今天高…

基于单片机的电子琴设计

基于单片机的电子琴设计 摘 要 读书、看电影、听音乐&#xff0c;都是最常见的丰富内心世界的良剂。听音乐&#xff0c;作为陶冶情操、提升境界最便捷的方式&#xff0c;正受到越来越多人们的欢迎。音乐可以很轻松的融入各种场合&#xff0c;给人们带来很轻松的氛围&#xff…

基础---nginx 启动不了,跟 Apache2 服务冲突

文章目录 查看 nginx 服务状态nginx 启动后 访问页面 127.0.0.1停止 nginx 服务&#xff0c;访问不了页面停止/启动 Apache2 服务&#xff0c;启动 Apache2 页面访问显示正确nginx 莫名启动不了卸载 Apache2 服务器 启动 nginx &#xff0c;但是总是不能实现反向代理&#xff0…

微服务分布式springcloud的体育场地预约系统演kdm1z

体育场馆设施预约系统是在实际应用和软件工程的开发原理之上&#xff0c;运用java语言以及Springcloud框架进行开发。首先要进行需求分析&#xff0c;分析出体育场馆设施预约系统的主要功能&#xff0c;然后设计了系统结构。整体设计包括系统的功能、系统总体结构、系统数据结构…

Annaconda环境下ChromeDriver配置及爬虫编写

Anaconda环境的chromedriver安装配置_anaconda 配置chromedriver-CSDN博客 Chromedriver驱动( 121.0.6167.85 ) - 知乎 下载好的驱动文件解压&#xff0c;将exe程序复制到Annaconda/Scripts目录以及Chrome/Application目录下 注意要提前pip install selenium包才能运行成功&a…

若依Vue3图片预览大图遮罩层和表格的border css层级冲突

样式层级出现问题&#xff0c;表格的层级高于图片的层级 1.解决方式一&#xff1a;设置此文件的该属性&#xff08;z-index&#xff09;为继承&#xff0c;则显示正常 .el-table .el-table__cell { z-index: inherit; } 2.解决方式二&#xff1a;将此属性设置为true(本人试了…

【Python】快捷找到最大最小 N 个元素

heapq 简单数据结构取出最大最小N个元素复杂数据结构中取出最大最小N个元素代码解析&#xff1a;lambda Python 中有 heapq 模块可以快捷找到数组中最大最小的 N 个元素&#xff1b; heapq.nlargest(num, arr) # 从arr数组中取出最大num个元素 heapq.nsmallest(num, arr) # …

[论文笔记]LLaMA: Open and Efficient Foundation Language Models

引言 今天带来经典论文 LLaMA: Open and Efficient Foundation Language Models 的笔记&#xff0c;论文标题翻译过来就是 LLaMA:开放和高效的基础语言模型。 LLaMA提供了不可多得的大模型开发思路&#xff0c;为很多国产化大模型打开了一片新的天地&#xff0c;论文和代码值…

npm报错,显示certificate has expired

从报错信息就可以知道是因为之前设置的淘宝镜像已过期&#xff0c;解决方法就是要把之前设置的淘宝镜像改成新的 第一种方法 第一步&#xff1a;清空缓存 npm cache clean --force第二步&#xff1a;重新设置新的镜像源 npm config set registry https://registry.npmmirror…

使用canvas实现图纸标记及回显

图纸 图纸标记后的效果图 最近做的一个qms项目里面&#xff0c;需要前端在图纸上实现标记及标记后的内容还要能够回显&#xff0c;然后后端通过标记的点&#xff0c;去读取标记图纸的内容&#xff0c;如一些公式、数据之类的&#xff0c;目前实现的功能有 在图纸上面进行矩形…

点云配准论文阅读1-Research on Three-Dimensional Point Cloud Registration Algorithm

Research on Three-Dimensional Point Cloud Registration Algorithm三维点云配准算法研究 Publisher: IEEE发行者 &#xff1a; IEEE Cite This引用此内容 PDF Yuqing Zhang; Shilong Sun; Jingjing Shang; Minghan Yang张玉清;孙世龙; 尚晶晶;杨明翰 Abstract: Accordi…

88. 合并两个有序数组 (Swift版本)

题目 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中&#xff0c;使合并后的数组同样按 非递减顺序 排列。 注意&#xff1a;最终&#xff0c;合并…

【C++ 设计模式】简单工厂模式

文章目录 前言一、简单工厂模式是什么&#xff1f;二、实现原理三、UML类图四、简单工厂模式具体代码总结 前言 在软件开发中&#xff0c;设计模式是解决特定问题的可复用解决方案。其中&#xff0c;简单工厂模式是一种创建型设计模式&#xff0c;旨在封装对象的创建过程&…

Python数学建模-2.3函数(下)

2.3.2模块的导入与使用 模块在Python中是一个包含Python定义和语句的文件&#xff0c;通常用来组织代码&#xff0c;使得代码更易于管理和复用。下面&#xff0c;我将详细讲解Python函数中模块的使用知识。 1. 模块的导入 在Python中&#xff0c;你可以使用import语句来导入…

PyCharm创建一个简单的Django项目

1.Django简介 Django 是一个开放源代码的 Web 应用程序框架&#xff0c;由 Python 编写而成。它遵循 MVC&#xff08;模型-视图-控制器&#xff09;的软件设计模式&#xff0c;采用了 MTV&#xff08;模型-模板-视图&#xff09;的架构。Django 的设计目标是使开发复杂的、数据…