目录
1. 理论学习
1.1 基本概念
SDRAM英文全称“Synchronous Dynamic Random Access Memory”,
1.2 SDRAM数据存取原理
简单来说,SDRAM 内部可以理解为一个存储阵列,这是 SDRAM 区别于管道式存储,实现随机地址存取的结构特点。将 SDRAM 内部存储阵列类比于一张表格,表格中的每一个单元格可以类比为存储阵列的单个存储单元。若想要实现存储阵列中的某一存储单元的数据读写操作,我们要通过行地址(Row Address)和列地址(Column Address)(先行后列)精确定位到这一存储单元,进而进行数据的读写操作,这就是所谓的随机地址存取。
对于 SDRAM,我们将类比于单元格的存储空间称之为存储单元,N(行列个数乘积)个存储单元构成一个存储阵列,这个存储阵列我们称之为一个逻辑 Bank(Logical Bank,简称L-Bank、Bank)。
SDRAM 内部并不是一个全容量的 L-Bank,而是分割为若干个 L-Bank,目前大多为 4个。若干 L-Bank 的分割,原因有二,一是技术、成本等诸多因素;二是由于SDRAM 的工作原理限制,单一 L-Bank 可能会造成非常严重的寻址冲突,大幅度降低内存效率。
在对 SDRAM 进行数据存取时,要先确定 L-Bank 地址,定位到指定逻辑Bank,再依次确定行地址和列地址,选中存储单元,进而进行数据的存取操作,而且一次只能对一个 L-Bank 的一个存储单元进行操作。
SDRAM 的基本存储单位是存储单元,而一个存储单元的容量为若干个 Bit,对于SDRAM 而言就是芯片的位宽,每个 Bit 存放于一个单独的存储体中,存储体是利用电容能够保持电荷以及可充放电的特性制成,主要由行选通三极管、列选通三极管、存储电容以及刷新放大器构成。电容所存储的电荷会随时间慢慢释放,这就需要不断刷新为电容充电,以保证存储数据可靠性。
将每个存储单元简化为单 Bit 的存储体,再将若干存储体排列为矩阵,同一行将行地址线相连,同一列将列地址线相连,就构成了一个存储阵列的简化模型。
1.3 SDRAM引脚说明
x4、x8、x16 分别表示位宽 4bit、8bit、16bit;#符号表示信号为低电平有效;短 划线 - 表示 x8和 x4 引脚功能与 x16 引脚功能相同。
引脚功能描述
引脚 | 位宽 | 类型 | 功能描述 |
---|---|---|---|
CLK | 1Bit | Input | 系统时钟:SDRAM 由系统时钟驱动,所有 SDRAM 输入信号都在时钟上升沿采样, |
CKE | 1Bit | Input | 时钟使能;高电平有效 |
CS#(CS_N) | 1Bit | Input | 片选信号;低电平有效。为高电平时,屏蔽所有命令,但已经突发的读/写操作不受影响。 |
CAS#(CAS_N) | 1Bit | Input | 列选通信号;低电平有效。为低电平时,A[8:0]输入的为列地址。 |
RAS#(RAS_N) | 1Bit | Input | 行选通信号;低电平有效。为低电平时,A[12:0]输入的为行地址 |
WE#(WE_N) | 1Bit | Input | 写使能信号;低电平有效。为低电平时,使能写操作和预充电。 |
DQM[1:0] | 1Bit | Input | 数据掩码;DQML(H),低(高)字节掩码,若信号为高电平,在下一个时钟周期的时钟上升沿,数据总线的低(高)字节为高阻态。 |
BA[1:0] | 2Bit | Input | L-Bank 地址;选择不同的逻辑 Bank 进行相关数据操作 |
A[12:0] | 13Bit | Input | 地址总线; |
DQ[15:0] | 16Bit | Input | 数据总线; |
{CS#、CAS#、RAS#、WE#}构成 SDRAM 操作命令。
SDRAM器件功能框图
①SDRAM 内部包含一个逻辑控制单元,内部包含模式寄存器和命令解码器。
②外部通过{CS#、CAS#、RAS#、WE#}以及地址总线向逻辑控制单元输入命令。
③命令经过命令解码器进行译码后,将控制参数保存到模式寄存器中,逻辑控制单元进而控制逻辑运行。
1.4 SDRAM的速率等级与存储容量
存储容量
以镁光公司的三款SDRAM芯片举例,
由图可知,“4Meg × 16 × 4Banks”才是表示 SDRAM存储容量的具体参数,
“4Meg” 表示单个 L-Bank 包含的存储单元的个数,计算方法为单个存储阵列行地址数和列地址数的乘积;以此芯片为例,行地址总线为 A0-A12,行地址位宽为13 位,行地址数为 8192 (2^13)行,列地址位宽为 9 位,列地址数为 512(2^9)列,乘积为4M;
“16”表示数据位宽,即每个存储单元存储数据的 bit数;
“4BANKS”表示一片 SDRAM 中包含的 L-Bank个数,此 SDRAM 芯片包含 4 个 L-Bank,
总的存储容量就是:256MBit(4Meg × 16 × 4BANKS),
容量计算方法可简化为:存储容量(Bit) = L-Bank 存储单元数 ×数据位宽(Bit) × L-Bank 个数。
速率等级
5个相关参数:
①时钟频率(Clock Frequency,单位:MHz)。SDRAM 正常工作的最高时钟频率,SDRAM 工作时只能等于或低于这一时钟频率。
②tRCD(单位: ns)。表示写入自激活命令到开始进行数据读写,中间所需的等待时间,列举的数值表示等待时间的最小值。
③ tRP (单位: ns)表示自预充电指令写入到预充电完成所需的等待时间,列举的数值表示等待时间的最小值。
④CL(CAS(READ)latency ,单位: ns)。列选通潜伏期,表示自数据读指令写入到第一个有效数据输出所需等待时间。
⑤Target tRCD -tRP -CL ,表示最大工作频率下tRCD 、tRP 、CL等待的最小时钟周期数。
1.5 操作命令
CS_N、RAS_N、CAS_N、WE_N 四路控制信号构成 SDRAM 指令集。
CKE、BA[1:0]、A[12:0]等信号,在 SDRAM 的操作中,也起到辅助作用。
1.5.1 禁止命令(Command Inhibit)
控制指令为{CS_N,RAS_N,CAS_N,WE_N} = 4’b1XXX。
不论 SDRAM 处于何种状态,此命令均可被执行,无需顾及 CKE是否有效,即 CLK是否使能,无需关心 DQM、ADDR、DQ 的信号输入;执行此命令后,SDRAM 芯片不被选择,新的命令无法写入,但已经执行的命令不受影响。
1.5.2 无操作命令
无操作命令(No-operation,简称为 NOP 命令)。
控制指令为{CS_N,RAS_N,CAS_N,WE_N} = 4’b0111。
目的是为了防止 SDRAM 处于空闲或等待状态时,其他命令被写入,此命令对正在执行的命令无影响。
1.5.3 配置模式寄存器命令
配置模式寄存器命令(Load Mode Register,也被称为 Mode Reigister Set,简称 LMR命令)。
控制指令为{CS_N,RAS_N,CAS_N,WE_N} = 4’b0000。
此命令只有所有 L-Bank 均处于空闲状态时才可被写入,否则配置出错。
在执行此命令后,SDRAM 必须等待相应的响应时间tRSC (Register Set Cycle,模式寄存器配置周期 ) 后,才可写入新的命令。
此时需要地址总线来辅助完成命令。A0-A11 赋值不同对应寄存器配置不同模式,未使用的地址总线设置为低电平。
突发长度(Burst Length)
突发(Burst)是指在同一行中相邻的存储单元连续进行数据传输的方式,连续传输所涉及到存储单元(列)的数量就是突发长度。
地址总线的低三位 A0-A2 是突发长度的控制位。
SDRAM 芯片的突发长度可设置为1、2、4、8 和整页,单位为字节,整页表示一次突发传输一整行的数据量。
若在数据读/写操作时不使用突发传输,此时可等效为突发长度为 1 字节,每次读/写数据时,都要对存储单元进行寻址,如果要实现连续的读/写操作,就要不断地发送列地址和读/写命令,这种方法控制资源占用极大,效率较低。
若使用突发传输,只要指定起始列地址和突发长度,内存就会依次地自动对后面相应数量的存储单元进行读/写操作而不再需要控制器连续地提供列地址,这样,除了第一笔数据传输需要若干个周期外,其后的每个数据只要一个周期即可获得。突发的数据长度一般是 4或 8,如果传输时实际需要的数据长度小于设定的 BL值,则调用“突发停止”命令结束传输。
突发类型
位置为A3,突发类型有两类:顺序和隔行。低电平为顺序类型,高电平为隔行类型,一般选择顺序类型。
列选通潜伏期
指从读命令被寄存到数据总线上到出现第一个有效数据之间的时钟周期间隔,列选通潜伏期可被设置为 2 个或 3 个时钟周期。
设置位为 A6,A5,A4,
当{A6,A5,A4}=3’b010 时,潜伏期为 2 个时钟周期;
当{A6,A5,A4}=3’b011 时,潜伏期为 3 个时钟周期。
运行模式
运行模式设置位为 A7,A8,SDRAM 存在标准模式、测试模式等多种模式,但对于普通用户,只开放了标准模式,在使用 SDRAM 时只需将 A7,A8 设置为低电平进入标准模式。
写模式
设置位为 A9,
A9 为低电平时,突发写& 突发读【突发长度由突发长度寄存器(A0-A2)设定】;
A9 位高电平时,突发读& 普通写【突发长度由突发长度寄存器(A0-A2)设定】,每一个写命令只能写入一个数据;
{不管怎么样,总是突发读模式}
A10-A12 为保留位,对模式寄存器的配置不起作用,赋值为 0 即可
1.5.4 预充电命令
预充电的作用就是关闭指定的 L-Bank 或者全部 L-Bank 中激活的行。
预充电命令执行后,必须等待对应的等待时间 tRP (Precharge command Period,预充电命令周期),相对应的 L-Bank 将可以被重新操作。
预充电包括两类:全部预充电、指定 L-Bank预充电
控制指令均为 {CS_N,RAS_N,CAS_N,WE_N} = 4’b0010;地址总线中的 A10 和 L-Bank 地址线 BA[1:0]辅助控制预充电类型。
当 A10 为高电平时,执行全部预充电命令,对所有的 L-Bank 进行预充电,无需关心 BA[1:0]信号的输入;
当 A10 为低电平时,只对由 BA[1:0]选定的 L-Bank 进行预充电。
1.5.5 刷新命令
SDRAM 通过刷新操作保证数据的可靠性。
SDRAM 的刷新操作是周期性的,在两次刷新的间隔可以进行数据的相关操作。
目前国际公认的标准是,存储体中电容的数据有效保存期上限是 64ms,也就是说每一行刷新的循环周期最大为 64ms,那么刷新速度就是:行数/64ms。
【我们在 SDRAM 的数据手册中经常会看到 4096 Refresh Cycles/64ms 或 8192 Refresh Cycles/64ms 的相关介绍,这里的 4096 与 8192 就代表 SDRAM 芯片中单个 L-Bank 的行数。刷新命令一次对一行有效,发送间隔也是随总行数而变化, 当单个 L-Bank 为 4096 行时,刷新间隔最大为 15.625μs,单个 L-Bank 为 8192 行时,刷新间隔最大为 7.8125μs。】
刷新命令(Refresh)分为两种:自动刷新命令(Auto Refresh)和自刷新(Self Refresh),
控制指令为 {CS_N,RAS_N,CAS_N,WE_N} = 4’b0001;
当 CKE 为高电平时,写入刷新指令,执行自动刷新操作;
当 CKE 为低电平时,写入刷新指令,执行自刷新操作。
1.5.6 激活命令
控制命令为{CS_N,RAS_N,CAS_N,WE_N} = 4’b0011;
1.5.7 写命令
控制命令为{CS_N,RAS_N,CAS_N,WE_N} = 4’b0100;
对已经激活的特定的L-bank行,执行突发写操作。BA[1:0]指定需要写入数据的特定 L-Bank,地址总线 A0-A9 指定需要写入存储单元的起始列地址;A10 的电平变化控制突发写操作完成后是否立即执行自动预充电操作;
若 A10 为高电平,突发写操作完成后,立即执行自动预充电操作,关闭当前行;
若 A10 为低电平,突发写操作完成后,当前行依然处于激活状态,以便对当前行执行新的读/写操作,想要关闭当前激活行,需写入预充电指令。
1.5.8 读命令
控制命令为{CS_N,RAS_N,CAS_N,WE_N} = 4’b0101;
对已激活的特定 L-Bank 的特定行的数据突发读取操作。BA[1:0]指定需要读取数据的特定 L-Bank,地址总线 A0-A9 指定需要读取存储单元的起始列地址;A10 的电平变化控制突发读操作完成后是否立即执行自动预充电操作。
若 A10 为高电平,突发读操作完成后,立即执行自动预充电操作,关闭当前行;
若 A10 为低电平,突发读操作完成后,当前行依然处于激活状态,以便对当前行执行新的读/写操作,想要关闭当前激活行,需写入预充电指令。
1.5.9 突发终止命令
控 制 命 令 为{CS_N,RAS_N,CAS_N,WE_N} = 4’b0110
SDRAM 处于读/写操作过程中可被写入,突发停止操作被用来截断固定长度或者整页长度的突发,执行突发停止命令后,最近执行的数据读/写操作被终止,此命令操作并不会通过预充电关闭当前激活行,需通过预充电操作关闭被激活行。
2.项目实战
设计并实现了一个SDR SDRAM数据读写控制器,可以实现10bit的数据的写入与读出。
SDRAM控制器模块包含五个模块:初始化模块、自动刷新模块、数据读模块、数据写模块和仲裁模块
控制器连接一个异步FIFO,对待写入的数据和读出的数据进行缓存,实现跨时钟域处理,并且为数据读、写模块提供SDRAM的读写地址,并产生读写请求;
再连接分频器、uart_tx、fifo_read、uart_rx模块。
模块名称 | 功能描述 |
---|---|
uart_sdram | 串口读写 SDRAM顶层模块 |
clk_gen | 时钟生成模块,为整个工程提供工作时钟 |
uart_rx | 串口数据接收模块,接收串口发送过来的数据 |
sdram_top | SDRAM控制器,实现 SDRAM数据读写 |
fifo_read | SDRAM读出数据缓存 |
uart_tx | 串口数据发送模块,发送读出 SDRAM的数据 |
模块名称 | 功能描述 |
---|---|
sdram_top | SDRAM控制器顶层模块 |
fifo_ctrl | FIFO 控制模块,实现 SDRAM读写数据的跨时钟域处理 |
sdram_ctrl | 实现 SDRAM的初始化、自动刷新和数据读写操作 |
模块名称 | 功能描述 |
---|---|
sdram_ctrl | SDRAM控制部分顶层模块 |
sdram_init | SDRAM初始化模块,实现 SDRAM 的初始化 |
sdram_aref | SDRAM自动刷新模块,实现 SDRAM的自动刷新操作 |
sdram_write | SDRAM数据写模块,实现 SDRAM 的数据写入 |
sdram_read | SDRAM数据读模块,实现 SDRAM 的数据读出 |
sdram_arbit | SDRAM仲裁模块,实现各操作优先级的仲裁 |
个人喜欢从外层往里层写代码,而野火相反。在做仿真的时候,我是喜欢从外往里面仿真,一点一点进行验证。读者可以自己挑选喜欢的方式。
2.1 uart_sdram
2.1.1 uart_sdram顶层模块
`timescale 1ns / 1ns
/*--------------------------------------------------------------------------------
-- Module : uart_sdram
-- Description :
------------------------------------------------------------------------------*/
module uart_sdram(
/*-------------------------- Interface definitation ------------------------*/
input sys_clk,
input sys_rst_n,
input rx,
output tx,
output sdram_clk,
output sdram_cke,
output sdram_cs_n,
output sdram_cas_n,
output sdram_ras_n,
output sdram_we_n,
output [1:0] sdram_ba,
output [12:0] sdram_addr,
output [1:0] sdram_dqm,
inout [15:0] sdram_dq
/*-----------------------------------------------------------*/
);
/*------------- Parameter and Internal Signal ---------------*/
parameter DATA_NUM = 24'd10;
parameter WAIT_MAX = 16'd750;
parameter UART_BPS = 14'd9600;
parameter CLK_FREQ = 26'd50_000_000;
/*------------- Internal Interface definitation -------------*/
//uart_rx
wire [7:0] rx_data;
wire rx_flag;
//fifo_read
wire [7:0] rfifo_wr_data;
wire rfifo_wr_en;
wire [7:0] rfifo_rd_data;
wire rfifo_rd_en;
wire [9:0] rd_fifo_num;
//clk_gen
wire clk_50m;
wire clk_100m;
wire clk_100m_shift;
wire locked;
wire rst_n;
//Inst_sdram_top
reg [23:0] data_num;
reg read_valid;
reg [15:0] cnt_wait;
/*-----------------------------------------------------------*/
//rst_n
assign rst_n = sys_rst_n && locked;
//data_num
always@(posedge clk_50m or negedge sys_rst_n)
begin
if(sys_rst_n == 0)
begin
data_num <= 24'b0;
end
else if(read_valid == 1'b1)
begin
data_num <= 24'b0;
end
else if(rx_flag == 1'b1)
begin
data_num <= data_num + 1'b1;
end
else
begin
data_num <= data_num;
end
end
//cnt_wait
always@(posedge clk_50m or negedge sys_rst_n)
begin
if(sys_rst_n == 0)
begin
cnt_wait <= 16'b0;
end
else if(cnt_wait == WAIT_MAX)
begin
cnt_wait <= 16'b0;
end
else if(data_num == DATA_NUM)
begin
cnt_wait <= cnt_wait + 1'b1;
end
end
//read_valid
always@(posedge clk_50m or negedge sys_rst_n)
begin
if(sys_rst_n == 0)
begin
read_valid <= 1'b0;
end
else if(cnt_wait ==WAIT_MAX)
begin
read_valid <= 1'b1;
end
else if(rd_fifo_num == DATA_NUM)
begin
read_valid <= 1'b0;
end
end
//clk_gen
clk_gen Inst_clk_gen(.inclk0(sys_clk),
.reset(~sys_rst_n),
.C0(clk_50m),
.C1(clk_100m),
.C2(clk_100m_shift),
.locked(locked));
//uart_rx
uart_rx#(.UART_BPS(UART_BPS),
.CLK_FREQ(CLK_FREQ))
Inst_uart_rx(.sys_clk(clk_50m),
.sys_rst_n(rst_n),
.rx(rx),
.po_data(rx_data),
.po_flag(rx_flag));
//uart_tx
uart_tx#(.UART_BPS(UART_BPS),
.CLK_FREQ(CLK_FREQ))
Inst_uart_tx(.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.pi_data(rfifo_rd_data),
.pi_flag(rfifo_rd_en),
.tx(tx));
//fifo_read
fifo_read Inst_fifo_read(.sys_clk(clk_50m),
.sys_rst_n(sys_rst_n),
.rd_fifo_num(rd_fifo_num),
.pi_data(rfifo_wr_data),
.burst_num(DATA_NUM),
.read_en(rfifo_wr_en),
.tx_data(rfifo_rd_data),
.tx_flag(rfifo_rd_en));
//sdram_top
sdram_top Inst_sdram_top(.sys_clk(clk_100m),
.clk_out(clk_100m_shift),
.sys_rst_n(rst_n),
.wr_fifo_wr_clk(clk_50m),
.wr_fifo_wr_req(rx_flag),
.wr_fifo_wr_data({8'b0,rx_data}),
.sdram_wr_b_addr(24'b0),
.sdram_wr_e_addr(DATA_NUM),
.wr_burst_len(DATA_NUM),
.wr_rst(),
.rd_fifo_rd_clk(clk_50m),
.rd_fifo_rd_req(rfifo_wr_en),
.rd_fifo_rd_data(rfifo_wr_data),
.sdram_rd_b_addr(24'b0),
.sdram_rd_e_addr(DATA_NUM),
.rd_burst_len(DATA_NUM),
.rd_rst(),
.rd_fifo_num(rd_fifo_num),
.read_valid(read_valid),
.init_end(),
.sdram_clk(sdram_clk),
.sdram_cke(sdram_cke),
.sdram_cs_n(sdram_cs_n),
.sdram_ras_n(sdram_ras_n),
.sdram_cas_n(sdram_cas_n),
.sdram_we_n(sdram_we_n),
.sdram_ba(sdram_ba),
.sdram_addr(sdram_addr),
.sdram_dq(sdram_dq),
.sdram_dqm(sdram_dqm));
endmodule
2.1.2 uart_tx 模块
`timescale 1ns / 1ns
/*--------------------------------------------------------------------------------
-- Module : uart_tx
-- Description :
------------------------------------------------------------------------------*/
module uart_tx(
/*-------------------------- Interface definitation ------------------------*/
input sys_clk,
input sys_rst_n,
input [7:0] pi_data,
input pi_flag,
output reg tx
/*-----------------------------------------------------------*/
);
/*------------- Parameter and Internal Signal ---------------*/
parameter UART_BPS = 14'd9600;
parameter CLK_FREQ = 26'd50_000_000;
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS;
/*------------- Internal Interface definitation -------------*/
reg [12:0] baud_cnt;
reg