手撕sdram控制器,实现图像的缓存和vga实时显示
前言
1.采用器件
Xilinx Spartan 6 XC6SLX9-2TQG144C FPGA芯片,Hynix公司生产的型号为HY57V281620F的sdram芯片
2.参考的手册
《IS42S83200G, IS42S16160G》《Hynix 4Bank x 2M x 16bits Synchronous DRAM》《Proposed VESA and Industry Standards and Guidelines for Computer Display Monitor Timing (DMT) Version 1.0, Revision 12p, Draft 2》
3.sdram工作原理
网上的资料非常多,讲的很清楚,主要是要搞清楚SDRAM的操作时序;知道什是突发写;知道sdram的芯片需要不停的刷新操作保证数据不丢失;知道数据写入和读出可以按照地址自由的操作;知道SDRAM的读写是不能同时进行的是时分复用的,这个从sdram内部的结构框图就能看出,读写的地址线是共用的。
还有就是要学会画各种操作的时序,读写操作的命令一定要和数据保持正确的时序对应关系,不然会出错。
详细的设计方案
1.总体思考
1.1 整体的架构
sdram的控制器包括初始化、自刷新、突发读、突发写的操作,SDRAM 是动态 RAM 必须要通过刷新才能保证给每一个存储单元充电,并保证存储单元中的数据不丢失。 外部是一个VGA显示端,每个显示行都需要读数据,此时需要通过 SDRAM 读操作完成数据读出,但是如果读数据期间出现刷新请求,就需要中断当前读操作执行刷新请求,如果数据未读完,继续读出剩余数据,这里就需要用到仲裁机制。初始化、自刷新、突发读、突发写的操作有自己的操作时序,因此,采用主状态机描述初始化、自刷新、突发读、突发写操作之间的仲裁机制,每个单独的操作采用从状态机描述自己的操作时序。
顶层包括架构如下:
Entity: top_sdram_ctrl
Diagram
Generics
Generic name Type Value Description T_REFERSH 'd780 P_RST_CYCLE 'd5 NOP 20’b0111_0000_0000_0000_0000 PRECHARGE 20’b0010_0000_0100_0000_0000 REFRESH 20’b0001_0000_0000_0000_0000 MODE_REGISTER 20’b0000_0000_0000_0011_0010 WRITE_CMD 6’b0100_00 ACTIVE 6’b0011_00 RD_CMD 6’b0101_00 P_SYSTEM_CLK 50_000_000 P_UART_BUADRATE 921600 P_UART_DATA_WIDTH 8 P_UART_STOP_WIDTH 1 P_UART_CHECK 0 CHECK_TYPE 1
Ports
Port name Direction Type Description i_clk input wire i_uart_rx input wire Addr output [11 : 0] Ba output [1 : 0] sdclk output Cke output Cs_n output Ras_n output Cas_n output We_n output Dqm output [1: 0] vga output [7: 0] v_sync output h_sync output Dq inout [15 : 0]
Signals
Name Type Description o_rst wire o_vga_start wire o_rfifo_data wire [7:0] rd_fifo_en wire de wire locked wire clk_out50 wire clk_out25 wire o_user_rx_data wire [7:0] o_user_rx_validflag wire
Instantiations
clk_gen_instance: clk_wiz_50Mhz_25Mhz uart_rx_inst: uart_rx sdram_main_ctrl_inst: sdram_main_ctrl VGA_TIMING_inst: VGA_TIMING
1.2 带宽的思考
首先从整体来讲,要实现vga图像的实时显示,显示和写入数据带宽总和要小于sdram缓存的理论带宽。sdram的工作时钟是50MHz,每次的读写是16bit,其带宽为50Mhzx16bit=800Mbit/s。vga(工作时钟为25MHz,RGB332格式),640X480的像素点,刷新率为60帧,显示和写入数据带宽总和为2x640x480x60x8bit=295Mbit/s,所以是可以满足图像实时显示要求。 整个工程的场景是uart写数据到sdram,vga从sdram读数据,uart的写带宽是921600bit/s,vga(工作时钟为25MHz,RGB332格式)从sdram读数据,sdram的读数据带宽是25MHzx8,即(25x10^6x8),写比读慢,因此在整个工程上,应该等uart的数据全部写入sdram之后,再把数据从sdram里面读出来,用vga显示。 由于读写带宽不相同,所以在sdram读模块和sdram写模块中都需要fifo来缓存数据,利用data_count来判断读和写的时刻。其中sdram读模块的fifo是异步fifo,因为vga模块的工作时钟为25MHz,sdram的工作时钟为50MHz。
1.3 各模块设计
1.3.1 sdram_main_ctrl主状态机的设计
Entity: sdram_main_ctrl
Diagram
Generics
Generic name Type Value Description T_REFERSH 'd780 NOP 20’b0111_0000_0000_0000_0000 PRECHARGE 20’b0010_0000_0100_0000_0000 REFRESH 20’b0001_0000_0000_0000_0000 MODE_REGISTER 20’b0000_0000_0000_0011_0010 WRITE_CMD 6’b0100_00 ACTIVE 6’b0011_00 RD_CMD 6’b0101_00
Ports
Port name Direction Type Description i_clk input wire i_rst input wire i_vgaclk input wire i_write_data input wire [7:0] i_data_valid_flag input wire i_rd_en input wire o_cmd output wire [19:0] o_vga_start output wire o_rfifo_data output wire [7:0 ] dq inout wire [15:0]
Signals
Name Type Description ro_cmd reg [19:0] state reg [5:0] ini_flag reg ref_en reg w_en reg r_en reg ro_wfifo_data reg [15:0] ri_sdram_data reg [15:0] initial_end wire ref_req wire w_req wire r_req wire ref_end wire write_data_end wire write_ref_break_end wire read_data_end wire read_ref_break_end wire o_ref_cmd wire [19:0] o_initial_cmd wire [19:0] o_write_cmd wire [19:0] o_read_cmd wire [19:0] o_wfifo_data wire [15:0]
Constants
Name Type Value Description IDLE 6’b000001 INI 6’b000010 SW 6’b000100 REF 6’b001000 WRITE 6’b010000 READ 6’b100000
Instantiations
sdram_refresh_inst: sdram_refresh sdram_init_inst: sdram_init sdram_write_inst: sdram_write sdram_read_inst: sdram_read
State machines
1.3.2 sdram_init初始模块设计
Entity: sdram_init
Diagram
Generics
Generic name Type Value Description NOP 20’b0111_0000_0000_0000_0000 PRECHARGE 20’b0010_0000_0100_0000_0000 REFRESH 20’b0001_0000_0000_0000_0000 MODE_REGISTER 20’b0000_0000_0000_0011_0010
Ports
Port name Direction Type Description i_clk input wire i_rst input wire o_initial_end output wire o_initial_cmd output wire [19:0]
Signals
Name Type Description ro_initial_end reg ro_initial_cmd reg [19:0] init_cnt reg [15:0]
Constants
Name Type Value Description DELY_200us 'd10000 PRE_20ns 'd1 AFTER_PRE_40ns 'd2 REFRESH_20ns 'd1 AFTER_REFRESH_80ns 'd3 MODE_REGISTER_20ns 'd1 AFTER_MODE_REGISTER_40ns 'd2
1.3.3 sdram_refresh自刷新模块设计
Entity: sdram_refresh
Diagram
Generics
Generic name Type Value Description T_REFERSH 'd780 NOP 20’b0111_0000_0000_0000_0000 REFRESH 20’b0001_0000_0000_0000_0000
Ports
Port name Direction Type Description i_clk input wire i_rst input wire i_initial_end input wire i_ref_en input wire o_ref_req output wire o_ref_end output wire o_ref_cmd output wire [19:0]
Signals
Name Type Description ref_cnt reg [15:0] ro_ref_req reg ro_ref_end reg ro_ref_cmd reg [19:0] cmd_keep reg keep_cnt reg [3:0]
Constants
Name Type Value Description KEEP_CNT_MAX 'd10
1.3.4 sdram_write写模块从状态机设计
Entity: sdram_write
Diagram
Generics
Generic name Type Value Description T_REFERSH 'd780 NOP 20’b0111_0000_0000_0000_0000 REFRESH 20’b0001_0000_0000_0000_0000 WRITE_CMD 6’b0100_00 ACTIVE 6’b0011_00 PRECHARGE 20’b0010_0000_0100_0000_0000
Ports
Port name Direction Type Description i_clk input wire i_rst input wire i_ref_req input wire i_w_en input wire i_write_data input wire [7:0] i_data_valid_flag input wire o_w_req output wire o_write_data_end output wire o_write_ref_break_end output wire o_write_cmd output wire [19:0] o_wfifo_data output wire [15:0]
Signals
Name Type Description state reg [4:0] bank_cnt reg [1:0] row_cnt reg [11:0] burst_col_cnt reg [1:0] burst_cnt reg [11:0] rd_en reg active_end reg act_cnt reg [2:0] pre_end reg pre_cnt reg [3:0] ro_w_req reg write_data_end reg write_ref_break_end reg write_data_end_keep reg write_ref_break_keep reg ro_write_cmd reg [19:0] rd_data_count wire [10 : 0] dout wire [15 : 0]
Constants
Name Type Value Description IDLE 5’b00001 WRITE_REQ 5’b00010 ACT 5’b00100 WRI 5’b01000 PRE 5’b10000 REQ_FIFONUM 'd512 PRE_CNT_MAX 'd8 ACT_CNT_MAX 'd3 BURST_LENGTH 'd4 BURST_CNT_MAX 'd128 ROW_CNT_MAX 'd4096 ROW_CNT_MAX_300 'd300 BANK_CNT_MAX 'd4
Instantiations
fifo_instance1: sfifo_2048x8_write1024x16
State machines
1.3.5 sdram_read读模块从状态机设计
Entity: sdram_read
Diagram
Generics
Generic name Type Value Description T_REFERSH 'd780 NOP 20’b0111_0000_0000_0000_0000 REFRESH 20’b0001_0000_0000_0000_0000 ACTIVE 6’b0011_00 PRECHARGE 20’b0010_0000_0100_0000_0000 RD_CMD 6’b0101_00
Ports
Port name Direction Type Description i_clk input wire i_vgaclk input wire i_rst input wire i_ref_req input wire i_r_en input wire i_sdram_data input wire [15:0] i_rd_en input wire o_rfifo_data output wire [7:0] o_r_req output wire o_read_data_end output wire o_read_ref_break_end output wire o_read_cmd output wire [19:0] o_vga_start output wire
Signals
Name Type Description state reg [4:0] ro_vga_start reg bank_cnt reg [1:0] row_cnt reg [11:0] burst_col_cnt reg [1:0] burst_cnt reg [11:0] wr_en reg active_end reg act_cnt reg [2:0] pre_end reg pre_cnt reg [3:0] ro_r_req reg read_data_end reg read_ref_break_end reg read_data_end_keep reg read_ref_break_keep reg ro_read_cmd reg [19:0] state_dly5 reg [4 : 0] state_dly4 reg [4 : 0] state_dly3 reg [4 : 0] state_dly2 reg [4 : 0] state_dly1 reg [4 : 0] rd_data_count wire [11 : 0] wr_data_count wire [10 : 0] dout wire [7 : 0] full wire empty wire
Constants
Name Type Value Description IDLE 5’b00001 READ_REQ 5’b00010 ACT 5’b00100 RD 5’b01000 PRE 5’b10000 REQ_FIFONUM 'd512 PRE_CNT_MAX 'd8 ACT_CNT_MAX 'd3 BURST_LENGTH 'd4 BURST_CNT_MAX 'd128 ROW_CNT_MAX 'd4096 ROW_CNT_MAX_300 'd300 BANK_CNT_MAX 'd4
Instantiations
fifo_instance1: asfifo_2048x16read4096x8
State machines
1.3.6 VGA_TIMING模块
这个模块不做详细解释,主要对照手册设计,控制好行计数器和列计数器就行。
上板调试验证并演示
传了一个aliya的图,可爱捏!
总结分析
1.新技能
1.1状态机的输出采用与或的形式节省资源
单就这个项目来说,在状态结果输出这一步,其实就是把命令输出,我采用二段式状态机来来写代码,也就是非阻塞赋值来赋值输出,但其实可以用组合逻辑来赋值输出来写
1.2inout信号的三态门描述
oe控制信号为高的时候,FPGA芯片往sdram里面写数据ro_wfifo_data,即三态门输出数据;
oe控制信号为低的时候,三态门输出高阻态,sdram的数据输入到FPGA芯片,用reg寄存器ri_sdram_data来接收数据,即三态门输入数据。申明reg寄存器ri_sdram_data来接收数据是用来隔绝三态门输出数据对输入数据所连电路的影响。 https://blog.csdn.net/qq_28541715/article/details/118344356 这篇文章讲的很好。 inout端口的实现是使用三态门,inout端口模型必须要抓住三点:
inout端口不可能独立存在;
作为输入必须有reg型缓冲(一个inout两个控制信号);
相连的两个inout端口由一对信号交错控制;
误解:用一对相反的信号控制两个inout端口实现双向传输。造成这种误解的原因是在两个module中的inout端口中,三态门不可能同时导通,默认两个三态门总是一个导通另一个不导通,忽略了两个都不导通的情况。所以在实际电路中,用于控制两个inout端口的,必然是一对控制信号的交叉形式。
1.3独热码新打拍的写法
之前写信号的打拍一直打几拍就申明多少个变量很繁杂,
现在学了一种新的写法,比较高效
打几拍就把read_state_dly声明成多少位宽的变量,因为最后取高位,这样写节省资源
2.报错debug
2.1 因为在顶层中没有例化相应的信号,然后PAR的时候就出现引脚没有驱动源
https://support.xilinx.com/s/question/0D52E00006hpq1iSAA/%E6%B1%82%E5%8A%A9%E7%94%9F%E6%88%90bit%E6%B5%81%E6%96%87%E4%BB%B6%E9%94%99%E8%AF%AF?language=en_US
2.2 Assignment under multiple single edges is not supported for synthesis 错误
https://blog.csdn.net/zhufeizi123/article/details/88782415
原因是本该上升沿的 posedge i_rst
always @(posedge i_clk or posedge i_rst) begin
if (i_rst) begin
ro_wfifo_data <= 'd0;
end
else
ro_wfifo_data <= o_wfifo_data;
end
写成了
always @(posedge i_clk or negedge i_rst) begin
if (i_rst) begin
ro_wfifo_data <= 'd0;
end
else
ro_wfifo_data <= o_wfifo_data;
end
2.3 sdram的命令一定要和数据时序满足要求
sdram的写命令一定要和inout端口上的数据dq对齐 sdram的读命令因为CAS Latency我们设置的是3,因此读取的数据时返回数据具有 3 拍的延时,sdram的读模块中的fifo的使能要注意和从sdram里面来的延时的数据对齐。