SDRAM工作原理
A. 核心架构:三维矩阵的“图书馆”
可以将一块SDRAM芯片想象成一座多层的图书馆,你需要按特定流程才能取出想要的书(数据)。
Bank (存储体): 图书馆的楼层。一块SDRAM芯片通常包含2、4或8个Bank。Bank之间可以独立操作(例如,在一个Bank进行预充电时,可以在另一个Bank读取数据),这是实现高性能流水线操作的关键。
Row (行): 每个楼层上的书架。行地址决定了选择哪个书架。
Column (列): 每个书架上的书。列地址决定了从选定的书架上取出哪本书。
B. 操作流程:命令驱动的“四步取书法”
你不能直接冲到书架上拿书,必须遵循以下命令序列:
1. `ACTIVATE` (激活/行选通): 你告诉图书管理员(SDRAM控制器),“我要去三楼 (Bank地址)的第18排书架 (Row地址)”。管理员随即打开这个书架的所有照明,并将一整排书架(一整行数据,通常为几KB)的内容全部复制到该Bank的行缓冲/感应放大器中。这是一个相对耗时的操作,其延迟由`tRCD`(行到列延迟)规定。
2. `READ` / `WRITE` (读/写): 现在书架的内容已经“缓存”好了,你可以快速地发出读或写命令。你告诉管理员,“请从这个打开的书架上,给我从第5本书 (Column地址)开始的连续8本书”。
3. Burst (突发传输): 这是SDRAM效率的精髓。你不需要为每一本书单独发命令。一旦`READ`命令发出,SDRAM会在接下来的几个时钟周期内,自动地、连续地将从起始列地址开始的一连串数据(一个Burst,通常是2, 4, 8个字)吐出来。`CAS Latency (CL)`规定了从`READ`命令发出到第一个数据出现所需的时钟周期数。
4. `PRECHARGE` (预充电): 当你在这个书架上的操作完成后,你必须告诉管理员,“这个书架我用完了,请关闭照明,准备让我访问别的书架”。这个命令会将行缓冲中的数据写回存储矩阵,并关闭当前行,为下一次`ACTIVATE`命令做准备。其延迟由`tRP`(行预充电延迟)规定。
C. 生存法则:不可或缺的`REFRESH` (刷新)
SDRAM的“D”代表动态(Dynamic)。其存储单元是基于微小的电容。电容会随时间漏电,如果不充电,数据就会丢失。因此,你必须周期性地(通常是每64ms)对SDRAM的所有行进行一次`REFRESH`操作。这个操作由SDRAM控制器自动、强制地执行,在刷新期间,SDRAM暂时无法进行正常的读写。
第三部分:SDRAM项目设计 - 视频帧缓冲 (Video Frame Buffer)
场景: 设计一个系统,能够实时地将一个动态生成的视频流存入SDRAM,并同时从SDRAM中读出,最终在显示器上显示。
1. 核心问题: 视频数据流是连续不断的,而FPGA内部的BRAM资源有限,无法存下一整帧(或多帧)高清图像。例如,一帧640x480x16bit的图像就需要 `640 * 480 * 2 = 614.4 KB`,远超一般FPGA的BRAM容量。
2. 为何需要SDRAM: 我们需要一个大容量、高带宽的外部存储器。让我们计算带宽需求:对于640x480 @ 60Hz的VGA信号,像素时钟约为25MHz。假设每个像素16位(2字节)。
写带宽需求 = `25,000,000 像素/秒 * 2 字节/像素 = 50 MB/s`
读带宽需求 = `50 MB/s`
总带宽需求约为`100 MB/s`。这个带宽对于现代SDRAM(尤其是DDR系列,其带宽可达数GB/s)来说是轻而易举的,但对于SPI Flash或EEPROM则是天方夜谭。
3. 系统如何工作:数据流被分为写路径和读路径。
写路径: `视频数据源 -> FIFO -> SDRAM控制器 -> SDRAM`。视频数据源(可以是摄像头接口,或者我们项目中的一个测试图案生成器)以像素时钟的速率产生数据。数据先写入一个小的FIFO,用于缓冲。上层逻辑当FIFO中的数据达到一定量(例如一个Burst的长度)时,向SDRAM控制器发出一个写请求。
读路径: `VGA时序发生器 -> 地址计算 -> SDRAM控制器 -> FIFO -> VGA输出`。VGA时序发生器根据当前的行场扫描位置,计算出需要显示的像素在SDRAM中的地址。然后向上层逻辑发出读请求。SDRAM控制器从SDRAM中读出一个Burst的数据,并存入另一个FIFO。VGA输出逻辑则从这个FIFO中以像素时钟的速率平滑地取出数据并显示。
FIFO的作用: FIFO是连接平滑数据流(视频)和突发数据流(SDRAM)之间的关键桥梁,用于数据速率匹配和时钟域交叉。
1. sdram_controller_ip.v
module sdram_controller_ip ();
2. async_fifo_ip.v
module async_fifo_ip #();
3. vga_timing_generator.v
`timescale 1ns / 1ps module vga_timing_generator ( input wire i_pixel_clk, input wire i_rst_n, output reg o_h_sync, output reg o_v_sync, output wire o_display_on, output wire [9:0] o_pixel_x, output wire [9:0] o_pixel_y ); // VGA 640x480 @ 60Hz Timing Parameters localparam H_DISPLAY = 640; localparam H_FRONT_PORCH = 16; localparam H_SYNC_PULSE = 96; localparam H_BACK_PORCH = 48; localparam H_TOTAL = H_DISPLAY + H_FRONT_PORCH + H_SYNC_PULSE + H_BACK_PORCH; // 800 localparam V_DISPLAY = 480; localparam V_FRONT_PORCH = 10; localparam V_SYNC_PULSE = 2; localparam V_BACK_PORCH = 33; localparam V_TOTAL = V_DISPLAY + V_FRONT_PORCH + V_SYNC_PULSE + V_BACK_PORCH; // 525 reg [9:0] r_h_count = 0; reg [9:0] r_v_count = 0; assign o_pixel_x = r_h_count; assign o_pixel_y = r_v_count; assign o_display_on = (r_h_count < H_DISPLAY) && (r_v_count < V_DISPLAY); always @(posedge i_pixel_clk or negedge i_rst_n) begin if (!i_rst_n) begin r_h_count <= 0; r_v_count <= 0; end else begin if (r_h_count == H_TOTAL - 1) begin r_h_count <= 0; if (r_v_count == V_TOTAL - 1) begin r_v_count <= 0; end else begin r_v_count <= r_v_count + 1; end end else begin r_h_count <= r_h_count + 1; end end end always @(posedge i_pixel_clk or negedge i_rst_n) begin if (!i_rst_n) begin o_h_sync <= 1; o_v_sync <= 1; end else begin // Horizontal Sync (active low) if (r_h_count >= H_DISPLAY + H_FRONT_PORCH && r_h_count < H_DISPLAY + H_FRONT_PORCH + H_SYNC_PULSE) o_h_sync <= 0; else o_h_sync <= 1; // Vertical Sync (active low) if (r_v_count >= V_DISPLAY + V_FRONT_PORCH && r_v_count < V_DISPLAY + V_FRONT_PORCH + V_SYNC_PULSE) o_v_sync <= 0; else o_v_sync <= 1; end end endmodule
4. frame_buffer_control.v
`timescale 1ns / 1ps module frame_buffer_control #( parameter H_ACTIVE = 640, parameter V_ACTIVE = 480, parameter PIXEL_WIDTH = 16, parameter SDRAM_DATA_WIDTH = 128, // Corresponds to app_wdf_data width parameter SDRAM_ADDR_WIDTH = 28 )( // Pixel Clock Domain Interface input wire i_pixel_clk, input wire i_pixel_rst_n, input wire [PIXEL_WIDTH-1:0] i_pixel_data, input wire i_pixel_data_valid, output wire [PIXEL_WIDTH-1:0] o_pixel_data, input wire i_vga_data_req, input wire [9:0] i_read_pixel_x, input wire [9:0] i_read_pixel_y, // SDRAM Clock Domain Interface input wire i_sdram_clk, input wire i_sdram_rst_n, // SDRAM Controller User Interface output reg o_app_en, output reg [2:0] o_app_cmd, output reg [SDRAM_ADDR_WIDTH-1:0] o_app_addr, input wire i_app_rdy, output reg [SDRAM_DATA_WIDTH-1:0] o_app_wdf_data, output reg o_app_wdf_wren, output reg o_app_wdf_end, input wire i_app_wdf_rdy, input wire [SDRAM_DATA_WIDTH-1:0] i_app_rd_data, input wire i_app_rd_data_valid ); localparam PIXELS_PER_BEAT = SDRAM_DATA_WIDTH / PIXEL_WIDTH; // e.g., 128/16 = 8 localparam BURST_LEN_IN_BEATS = 8; // Must match SDRAM controller config localparam BURST_LEN_IN_PIXELS = BURST_LEN_IN_BEATS * PIXELS_PER_BEAT; // e.g., 8*8 = 64 pixels // --- FIFO Instantiations --- wire wr_fifo_full; wire [10:0] wr_fifo_count; wire rd_fifo_empty; wire [10:0] rd_fifo_count; wire [SDRAM_DATA_WIDTH-1:0] wr_fifo_rd_data; // Write FIFO (Pixel Clock -> SDRAM Clock) // We need to pack pixels into the SDRAM bus width reg [SDRAM_DATA_WIDTH-1:0] r_pixel_pack_reg; reg [$clog2(PIXELS_PER_BEAT)-1:0] r_pack_cnt = 0; reg wr_fifo_wr_en = 0; always @(posedge i_pixel_clk or negedge i_pixel_rst_n) begin if(!i_pixel_rst_n) begin r_pack_cnt <= 0; wr_fifo_wr_en <= 0; end else begin wr_fifo_wr_en <= 0; // Default if (i_pixel_data_valid) begin r_pixel_pack_reg <= {i_pixel_data, r_pixel_pack_reg[SDRAM_DATA_WIDTH-1:PIXEL_WIDTH]}; if (r_pack_cnt == PIXELS_PER_BEAT - 1) begin r_pack_cnt <= 0; wr_fifo_wr_en <= 1; // Push a full word into FIFO end else begin r_pack_cnt <= r_pack_cnt + 1; end end end end async_fifo_ip #( .DATA_WIDTH(SDRAM_DATA_WIDTH), .ADDR_WIDTH(8) // Depth 256 ) wr_fifo ( .wr_clk(i_pixel_clk), .wr_rst_n(i_pixel_rst_n), .wr_en(wr_fifo_wr_en), .wr_data(r_pixel_pack_reg), .full(wr_fifo_full), .wr_data_count(), .rd_clk(i_sdram_clk), .rd_rst_n(i_sdram_rst_n), .rd_en(sdram_wr_fifo_rd_en), .rd_data(wr_fifo_rd_data), .empty(), .rd_data_count(wr_fifo_count) ); // Read FIFO (SDRAM Clock -> Pixel Clock) reg [PIXEL_WIDTH-1:0] rd_fifo_rd_data_unpacked; assign o_pixel_data = rd_fifo_rd_data_unpacked; // ... Unpacking logic similar to packing logic ... // --- SDRAM Control Logic (in sdram_clk domain) --- reg [SDRAM_ADDR_WIDTH-1:0] r_wr_addr = 0; reg [SDRAM_ADDR_WIDTH-1:0] r_rd_addr = 0; // Write State Machine // ... // When wr_fifo_count >= BURST_LEN_IN_BEATS and read FSM is idle // and i_app_rdy is high, start a write transaction. // Pop data from wr_fifo and drive o_app_wdf_data // Read State Machine // ... // When rd_fifo_count < (FIFO_DEPTH - BURST_LEN_IN_BEATS) // and i_app_rdy is high, start a read transaction. // Calculate address based on i_read_pixel_x/y // When i_app_rd_data_valid is high, push data to rd_fifo. // Arbiter (Read has priority to prevent screen tearing) // ... endmodule
5. video_frame_buffer_top.v
`timescale 1ns / 1ps module video_frame_buffer_top ( // System Inputs input wire i_sys_clk, // Main high-speed clock (e.g., 100MHz for SDRAM) input wire i_sys_rst_n, // VGA Outputs output wire o_vga_h_sync, output wire o_vga_v_sync, output wire [4:0] o_vga_r, output wire [5:0] o_vga_g, output wire [4:0] o_vga_b // SDRAM Physical Interface // ... connect all ddr3_* ports from sdram_controller_ip here ); // --- Clock Generation --- // In a real design, a PLL IP would be used to generate // sdram_clk (e.g., 100MHz) and pixel_clk (25.175MHz) from i_sys_clk. wire pixel_clk; wire sdram_clk = i_sys_clk; // For simplicity, assume sys_clk is the SDRAM clock // pll_ip u_pll (...); // --- VGA and Video Pattern --- wire [9:0] w_pixel_x, w_pixel_y; wire w_display_on; wire [15:0] w_pattern_data; wire w_pattern_valid; vga_timing_generator u_vga_timing ( .i_pixel_clk(pixel_clk), .i_rst_n(i_sys_rst_n), // Use synchronized reset .o_h_sync(o_vga_h_sync), .o_v_sync(o_vga_v_sync), .o_display_on(w_display_on), .o_pixel_x(w_pixel_x), .o_pixel_y(w_pixel_y) ); video_pattern_generator u_pattern_gen ( .i_display_on(w_display_on), .i_pixel_x(w_pixel_x), .i_pixel_y(w_pixel_y), .o_pixel_data(w_pattern_data), .o_data_valid(w_pattern_valid) ); // --- Frame Buffer Core Logic --- wire [15:0] w_vga_out_data; // ... wires for sdram controller interface ... frame_buffer_control u_fb_ctrl ( .i_pixel_clk(pixel_clk), .i_pixel_rst_n(i_sys_rst_n), .i_pixel_data(w_pattern_data), .i_pixel_data_valid(w_pattern_valid), .o_pixel_data(w_vga_out_data), .i_vga_data_req(w_display_on), // Request data when in display area .i_read_pixel_x(w_pixel_x), .i_read_pixel_y(w_pixel_y), .i_sdram_clk(sdram_clk), .i_sdram_rst_n(i_sys_rst_n), // Connections to SDRAM Controller IP .o_app_en(app_en), //... ); // --- VGA Output Driver --- assign {o_vga_r, o_vga_g, o_vga_b} = w_display_on ? w_vga_out_data : 16'h0000; // --- SDRAM Controller IP Instantiation --- sdram_controller_ip u_sdram_ctrl ( .sys_clk(sdram_clk), .sys_rst_n(i_sys_rst_n), //... connect all app_* ports to u_fb_ctrl //... connect all ddr3_* ports to top-level I/O ); endmodule