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)
// ...
endmodule5. 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