diff --git a/README.md b/README.md index fe0e00c..93cbb5e 100644 --- a/README.md +++ b/README.md @@ -6,16 +6,16 @@ FPGA DDR-SDRAM # 简介 - |-------------------------| |--------------| - | | | | - ----->| 驱动时钟和复位 | | | - | | | | - ------------| | DDR-SDRAM接口 |--------->| | - | AXI4 | | | | - AXI4 master |----------->| AXI4 slave | | | - | | | | | - ------------| |-------------------------| |--------------| - 用户逻辑 DDR1 控制器 DDR1 芯片 + |------------------------------| |-----------| + | | | | + ----->| driving clock and reset | | | + | | | | + ------------| | DDR-SDRAM interface |--------->| | + | AXI4 | | | | + AXI4 master |-------->| AXI4 slave | | | + | | | | | + ------------| |------------------------------| |-----------| + 用户逻辑 DDR1 控制器 DDR1 芯片 很多低端 FPGA 开发板使用 SDR-SDRAM 作为片外存储,而 DDR-SDRAM (DDR1) 比 SDR-SDRAM 容量更大,价格更低。且与SDR-SDRAM一样,DDR1也能使用低端FPGA的普通的IO管脚直接驱动。我编写了一个软核的 AXI4 接口的 DDR1 控制器。该控制器的特点有: @@ -80,7 +80,7 @@ FPGA DDR-SDRAM | :---: | | 图:FPGA + DDR1 测试板 | -原理图见 PCB/sch.pdf ,PCB制造文件见 PCB/gerber.zip ,在布局布线时,使用双层板即可,不需像 DDR2 以上的设计那样刻意注意等长和阻抗匹配,因为该电路工作频率为双边沿 75MHz,不是特别高,只需注意让FPGA与DDR距离尽量近,布线尽量短即可。比如我在布局时,把DDR1芯片放在了FPGA芯片正对的背面,从而保证布线都较短。 +该板子的原理图见 PCB/sch.pdf ,PCB制造文件见 PCB/gerber.zip 。它是个双层板,不需像 DDR2 、 DDR3 那样刻意注意等长和阻抗匹配,因为该电路工作频率为双边沿 75MHz,不是特别高,只需注意让FPGA与DDR距离尽量近,布线尽量短即可。比如我把DDR1芯片放在了FPGA芯片正对的背面,从而保证布线都较短。 该板子的设计在立创EDA中开放,见 [oshwhub.com/wangxuan/fpga-ddr-ce-shi-ban](https://oshwhub.com/wangxuan/fpga-ddr-ce-shi-ban)。 @@ -122,28 +122,28 @@ module ddr_sdram_ctrl #( ## 模块接口:驱动时钟和复位 -该模块需要一个时钟和一个复位进行驱动,如下: +该模块需要一个驱动时钟和一个驱动复位,如下: ```Verilog input wire rstn_async, - input wire clk, + input wire drv_clk, ``` -rstn_async 是低电平复位信号,正常工作时应该置高。clk 是驱动时钟,频率是用户时钟的 4 倍。 +rstn_async 是低电平复位信号,正常工作时应该置高。drv_clk 是驱动时钟,频率是用户时钟的 4 倍。 模块开始工作前,rstn_async 信号应该置低,让模块复位,然后把 rstn_async 置高,解除复位。 ## 时钟频率的选取 -模块内将驱动时钟 clk 分频 4 倍产生 DDR1 时钟(ddr_ck_p/ddr_ck_n)和 AXI4 总线用户时钟(aclk)。本节讲述如何决定驱动时钟 clk 的频率。 +模块内将驱动时钟 drv_clk 分频 4 倍产生 DDR1 时钟(ddr_ck_p/ddr_ck_n)和 AXI4 总线用户时钟(clk)。本节讲述如何决定驱动时钟 drv_clk 的频率。 -首先,时钟频率受限于 DDR1 芯片。考虑到所有的 DDR1 的接口频率至少为 75MHz ,则 clk 的下限是 75\*4=300MHz。 +首先,时钟频率受限于 DDR1 芯片。考虑到所有的 DDR1 的接口频率至少为 75MHz ,则 drv_clk 的下限是 75\*4=300MHz。 -而 clk 的上限就也取决于 DDR1 的芯片型号,例如对于 MT46V64M8P-5B ,查芯片手册可知,-5B 后缀的 DDR1 在 CAS Latency (CL)=2 时最高时钟频率是 133MHz,则 clk 的上限是 133\*4=532MHz 。 +而 drv_clk 的上限就也取决于 DDR1 的芯片型号,例如对于 MT46V64M8P-5B ,查芯片手册可知,-5B 后缀的 DDR1 在 CAS Latency (CL)=2 时最高时钟频率是 133MHz,则 drv_clk 的上限是 133\*4=532MHz 。 > 注意:本控制器固定 CAS Latency (CL) = 2。 -另外,时钟频率的上限还受限于 FPGA 的速度,太高的时钟频率容易导致时序不收敛。本设计充分考虑时序安全设计,大多数寄存器工作在频率较低用户时钟域;个别寄存器工作在用户时钟的 2 倍频率的时钟下,且输入端口的组合逻辑非常短;还有一个寄存器工作在高频的 clk 下,但输入端口直接来自于上一级的寄存器输出(没有组合逻辑)。因此,即使在速度级别很低的 EP4CE6E22C8N 上,在 300MHz 的驱动时钟下也能保证模块正确运行。在速度等级更高的 FPGA (例如 EP4CE22F17C6N)上,驱动时钟的频率可以更高(例如400MHz)。 +另外,时钟频率的上限还受限于 FPGA 的速度,太高的时钟频率容易导致时序不收敛。本设计充分考虑时序安全设计,大多数寄存器工作在频率较低 clk 时钟域;个别寄存器工作在 clk 时钟的 2 倍频率的时钟下,且输入端口的组合逻辑非常短;还有一个寄存器工作在高频的 drv_clk 下,但输入端口直接来自于上一级的寄存器输出(没有组合逻辑)。因此,即使在速度级别很低的 EP4CE6E22C8N 上,在 300MHz 的驱动时钟下也能保证模块正确运行。在速度等级更高的 FPGA (例如 EP4CE22F17C6N)上,驱动时钟的频率可以更高(例如400MHz)。 ## 模块接口:DDR1接口 @@ -172,11 +172,11 @@ rstn_async 是低电平复位信号,正常工作时应该置高。clk 是驱 DDR1 控制器对外提供 AXI4 从接口(AXI4 slave)的时钟和复位信号,如下。AXI4 主机应该用它们作为自己的时钟和复位。 ```Verilog - output reg aresetn, - output reg aclk, + output reg rstn, + output reg clk, ``` -以下是该模块的 AXI4 从接口,它们都与 aclk 时钟的上升沿同步,应该连接到 FPGA 内的的 AXI4 主机。 +以下是该模块的 AXI4 从接口,它们都与 clk 时钟的上升沿同步,应该连接到 FPGA 内的的 AXI4 主机。 ```Verilog input wire awvalid, @@ -214,7 +214,7 @@ AXI4 总线的地址(awaddr和araddr)统一是字节地址,模块会根据 ### AXI4 写操作时序 __ __ __ __ __ __ __ __ __ __ __ __ _ - aclk __/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ + clk __/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ ___________ awvalid ___/ \____________________________________________________________ _____ @@ -245,7 +245,7 @@ AXI4 总线的地址(awaddr和araddr)统一是字节地址,模块会根据 ### AXI4 读操作时序 __ __ __ __ __ __ __ __ __ __ __ __ _ - aclk __/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ + clk __/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ ___________ arvalid ___/ \____________________________________________________________ _____ @@ -295,9 +295,9 @@ AXI4 总线的地址(awaddr和araddr)统一是字节地址,模块会根据 本节讲述如何确定 **tREFC**、**tW2I** 和 **tR2I** 这 3 个时序参数。 -我们知道,DDR1 需要周期性的刷新动作,**tREFC** 就规定了刷新的时钟周期间隔(以 aclk为准)。例如,若用户时钟为 75MHz,根据 MT46V64M8 的芯片手册,需要至多 7.8125us 刷新一次,考虑到 75MHz * 7.8125us = 585.9,则该参数可以设置为一个小于 585 的值,例如 10'd512。 +我们知道,DDR1 需要周期性的刷新动作,**tREFC** 就规定了刷新的时钟周期间隔(以 clk 为准)。例如,若用户时钟为 75MHz,根据 MT46V64M8 的芯片手册,需要至多 7.8125us 刷新一次,考虑到 75MHz * 7.8125us = 585.9,则该参数可以设置为一个小于 585 的值,例如 10'd512。 -**tW2I** 规定了一个写操作的最后一个写命令到下一个操作的激活命令(ACT)的时钟周期数(以 aclk 为准)的最小值。下图展示了一个 DDR1 接口上的写操作,ddr_cas_n 的第一个上升沿代表了一个写操作的最后一个写命令结束,ddr_ras_n 的第二个下降沿代表了下一个操作(可能是读、写、刷新)的开始,它们之间有 5 个时钟周期,**tW2I** 就是用来规定该周期数的下限的。**tW2I** 的默认值 8'd7 是一个兼容绝大多数 DDR1 的保守值,对于不同的 DDR1 芯片,有不同的缩小的余地(详见 DDR1 芯片 datasheet)。 +**tW2I** 规定了一个写操作的最后一个写命令到下一个操作的激活命令(ACT)的时钟周期数(以 clk 为准)的最小值。下图展示了一个 DDR1 接口上的写操作,ddr_cas_n 的第一个上升沿代表了一个写操作的最后一个写命令结束,ddr_ras_n 的第二个下降沿代表了下一个操作(可能是读、写、刷新)的开始,它们之间有 5 个时钟周期,**tW2I** 就是用来规定该周期数的下限的。**tW2I** 的默认值 8'd7 是一个兼容绝大多数 DDR1 的保守值,对于不同的 DDR1 芯片,有不同的缩小的余地(详见 DDR1 芯片 datasheet)。 __ __ __ __ __ __ __ __ __ __ __ __ ddr_ck_p __/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__ @@ -314,7 +314,7 @@ AXI4 总线的地址(awaddr和araddr)统一是字节地址,模块会根据 _______________________ ddr_a XXXXXX__RA_XXXXXXX_CA0_X_CA1_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -**tR2I** 规定了一个读操作的最后一个读命令到下一个操作的激活命令(ACT)的时钟周期数(以 aclk 为准)的最小值。下图展示了一个 DDR1 接口上的读操作,ddr_cas_n 的第一个上升沿代表了一个读操作的最后一个读命令结束,ddr_ras_n 的第二个下降沿代表了下一个操作(可能是读、写、刷新)的开始,它们之间有 5 个时钟周期,**tR2I** 就是用来规定该周期数的下限的。**tR2I** 的默认值 8'd7 是一个兼容绝大多数 DDR1 的保守值,对于不同的 DDR1 芯片,有不同的缩小的余地(详见 DDR1 芯片 datasheet)。 +**tR2I** 规定了一个读操作的最后一个读命令到下一个操作的激活命令(ACT)的时钟周期数(以 clk 为准)的最小值。下图展示了一个 DDR1 接口上的读操作,ddr_cas_n 的第一个上升沿代表了一个读操作的最后一个读命令结束,ddr_ras_n 的第二个下降沿代表了下一个操作(可能是读、写、刷新)的开始,它们之间有 5 个时钟周期,**tR2I** 就是用来规定该周期数的下限的。**tR2I** 的默认值 8'd7 是一个兼容绝大多数 DDR1 的保守值,对于不同的 DDR1 芯片,有不同的缩小的余地(详见 DDR1 芯片 datasheet)。 __ __ __ __ __ __ __ __ __ __ __ __ ddr_ck_p __/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__ @@ -345,8 +345,7 @@ AXI4 总线的地址(awaddr和araddr)统一是字节地址,模块会根据 | :---- | :--- | | example-selftest/RTL/top.sv | 顶层 | | example-selftest/RTL/axi_self_test_master.sv | 是 AXI4 主机,通过 AXI4 先把有规律的数据写入 DDR1,然后读回,比较读回的数据是否符合规律,并对不匹配的情况进行计数。 | -| example-selftest/RTL/pll.v | 使用板上 50MHz 晶振产生 300MHz 时钟,输出给 DDR1 控制器 | -| /RTL/ddr_sdram_ctrl.sv | DDR1 控制器 | +| RTL/ddr_sdram_ctrl.sv | DDR1 控制器 | 该示例程序的行为是: @@ -370,7 +369,6 @@ AXI4 总线的地址(awaddr和araddr)统一是字节地址,模块会根据 | :---- | :--- | | example-uart-read-write/RTL/top.sv | 顶层 | | example-uart-read-write/RTL/uart2axi4.sv | 是 AXI4 主机,能把 UART RX 收到的命令转换成 AXI4 读写操作,并把读操作读出的数据通过 UART TX 发送出去 | -| example-uart-read-write/RTL/pll.v | 使用板上 50MHz 晶振产生 300MHz 时钟,输出给 DDR1 控制器 | | RTL/ddr_sdram_ctrl.sv | DDR1 控制器 | [FPGA+DDR1测试板](#硬件设计示例)上有一个 CH340E 芯片(USB 转 UART),因此插上 USB 线后就可以在电脑上看见 UART 对应的 COM 口(需要先在 [www.wch.cn/product/CH340.html](http://www.wch.cn/product/CH340.html) 下载安装 CH341 的驱动)。 diff --git a/RTL/ddr_sdram_ctrl.sv b/RTL/ddr_sdram_ctrl.sv index 865bb03..1d0e82d 100644 --- a/RTL/ddr_sdram_ctrl.sv +++ b/RTL/ddr_sdram_ctrl.sv @@ -22,10 +22,11 @@ module ddr_sdram_ctrl #( ) ( // driving clock and reset input wire rstn_async, - input wire clk, // driving clock, typically 300~532MHz - // user interface ( AXI4 ) - output reg aresetn, - output reg aclk, // freq = F(clk)/4 + input wire drv_clk, // driving clock, typically 300~532MHz + // generate clock for AXI4 + output reg rstn, + output reg clk, // freq = F(drv_clk)/4 + // user interface (AXI4) input wire awvalid, output wire awready, input wire [BA_BITS+ROW_BITS+COL_BITS+DQ_LEVEL-2:0] awaddr, // byte address, not word address. @@ -45,7 +46,7 @@ module ddr_sdram_ctrl #( output wire rlast, output wire [(8<=CLK_DIV) : (uart_rx_cnt>=CLK_DIV-1) ) begin + if(uart_rx_status==0) begin + if(uart_rx_shift == 6'b111_000) + uart_rx_status <= 1; + end else begin + if(uart_rx_status[5] == 1'b0) begin + if(uart_rx_status[1:0] == 2'b11) + uart_rx_databuf <= {recvbit, uart_rx_databuf[7:1]}; + uart_rx_status <= uart_rx_status + 5'b1; + end else begin + if(uart_rx_status<62) begin + uart_rx_status <= 62; + uart_rx_data <= uart_rx_databuf; + uart_rx_done <= 1'b1; + end else begin + uart_rx_status <= uart_rx_status + 6'd1; + end + end + end + uart_rx_shift <= {uart_rx_shift[4:0], uart_rxr}; + uart_rx_supercnt <= uart_rx_supercnt + 3'h1; + uart_rx_cnt <= 0; + end else + uart_rx_cnt <= uart_rx_cnt + 1; + end + + axis2uarttx #( .CLK_DIV ( 651 ), .DATA_WIDTH ( D_WIDTH ), .FIFO_ASIZE ( 10 ) ) uart_tx_i ( - .aresetn ( aresetn ), - .aclk ( aclk ), + .rstn ( rstn ), + .clk ( clk ), .tvalid ( rvalid ), .tready ( rready ), .tlast ( rlast ), @@ -242,136 +292,20 @@ endmodule - - -module ram_for_axi4write #( - parameter ADDR_LEN = 12, - parameter DATA_LEN = 8 -) ( - input logic clk, - input logic wr_req, - input logic [ADDR_LEN-1:0] rd_addr, wr_addr, - output logic [DATA_LEN-1:0] rd_data, - input logic [DATA_LEN-1:0] wr_data -); - -localparam RAM_SIZE = (1<=CLK_DIV) : (cnt>=CLK_DIV-1) ) begin - if(status==0) begin - if(shift == 6'b111_000) - status <= 1; - end else begin - if(status[5] == 1'b0) begin - if(status[1:0] == 2'b11) - databuf <= {recvbit, databuf[7:1]}; - status <= status + 5'b1; - end else begin - if(status<62) begin - status <= 62; - data <= databuf; - done <= 1'b1; - end else begin - status <= status + 6'd1; - end - end - end - shift <= {shift[4:0], rxr}; - supercnt <= supercnt + 3'h1; - cnt <= 0; - end else - cnt <= cnt + 1; - end - -endmodule - - - - - - - - - - module axis2uarttx #( parameter CLK_DIV = 434, parameter DATA_WIDTH = 32, parameter FIFO_ASIZE = 8 ) ( // AXI-stream (slave) side - input logic aclk, aresetn, - input logic tvalid, tlast, - output logic tready, + input logic rstn, + input logic clk, + input logic tvalid, + input logic tlast, + output logic tready, input logic [DATA_WIDTH-1:0] tdata, // UART TX signal - output logic uart_tx + output logic uart_tx ); localparam TX_WIDTH = (DATA_WIDTH+3) / 4; @@ -390,30 +324,30 @@ logic [DATA_WIDTH-1:0] fifo_data; logic endofline = 1'b0; logic [TX_WIDTH*4-1:0] data='0; wire emptyn = (fifo_rpt != fifo_wpt); -assign tready = (fifo_rpt != fifo_wpt_next) & aresetn; +assign tready = (fifo_rpt != fifo_wpt_next) & rstn; -always @ (posedge aclk or negedge aresetn) - if(~aresetn) +always @ (posedge clk or negedge rstn) + if(~rstn) uart_tx <= 1'b1; else begin uart_tx <= uart_txb; end -always @ (posedge aclk or negedge aresetn) - if(~aresetn) +always @ (posedge clk or negedge rstn) + if(~rstn) fifo_wpt <= '0; else begin if(tvalid & tready) fifo_wpt <= fifo_wpt_next; end -always @ (posedge aclk or negedge aresetn) - if(~aresetn) +always @ (posedge clk or negedge rstn) + if(~rstn) cyccnt <= 0; else cyccnt <= (cyccnt