一.实验任务
实现PC向FPGA发送数据保存进SDRAM,再通过按键控制读出SDRAM中的数据发送给PC端,实现数据回环。
二.SDRAM介绍
同步动态随机存取内存(synchronous dynamic random-access memory,简称SDRAM)是有一个同步接口的动态随机存取内存(DRAM)。
本次实验使用的SDRAM芯片(HY57V561620FTP)内部被分成了4个bank,每个bank有64Mbit大小,SDRAM与输入时钟同步,时钟信号上升沿采样,行列地址线复用,数据线输入输出复用。读写访问是突发的,突发(Burst)是指在同一行中相邻的存储单元连续进行数据传输的方式,连续传输所涉及到存储单元(列)的数量就是突发长度(Burst Lengths,简称BL)。本次SDRAM芯片对应的突发长度有1、2、4、8、全页,自动预充电在突发访问结束自动进行预充电。访问从激活命令开始,紧接着是读写命令。激活命令选择激活的bank和行,而读写命令选择激活的bank和列。
SDRAM的数据存储是利用电容实现的,因为电容存在放电的特性,所以需要定时刷新芯片才可以保证数据不丢失。查看芯片手册,可以看见刷新周期为64ms,也就是说在64ms内要保证每一块内存都要刷新一次,内存共8192行,平均下来,只要每7.8us刷新一行,就能保证64ms内刷新所有内存。
SDRAM原理框图:
由上图可以看见行列地址线被复用了,SDRAM接收到地址,会将数据传递给相应的地址预译码器和地址计数器,行地址预译码器会激活bank的行,列地址预译码器会激活bank的列,同时模式寄存器设置的突发长度会联合列地址计数器共同决定当前操作的列地址。外部状态机给出时钟信号,时钟使能信号,片选信号,行地址,列地址,数据允许信号,数据屏蔽信号,输入数据;SDRAM输出数据。
每块bank大小为4M*16bit,如果想要扩展内存,有两种方法:
①扩展深度,将两块bank的地址线并联,片选信号并联,将数据线串联能够实现8M16bit。
②扩展宽度,将两块bank的地址线并联,数据线并联,通过一个2输入译码器选择片选信号就能实现4M32bit。
如果既想扩展深度,又想扩展宽度,可以将上述两个方法结合起来。
注意:时钟和其他输入信号被异步重新启用,需要等待200us,同时FPGA和SDRAM的时钟存在时钟偏移,这是为了SDRAM能够采到稳定的数据,在芯片手册中有说到,具体偏移角度须自己调试。
SDRAM内部存在一个有限状态机,其状态跳转如下:
本次实验实现的是蓝色箭头表示的过程。
在读写命令之前,必须先激活相应的bank和行地址,激活命令和读写命令之间需要一个tRCD的间隔,READ突发读取,BA0和BA10用于选择bank,A10的值决定是否使用自动预充电,自动预充电用于突发结束时自动预充电,不选择自动预充电,则行保持打开供后续访问READ命令发出后,经过列激活延迟才可以得到数据。写命令用于启动对活动行的突发写访问。BA0和BA1的值用于选择数据开始传输的bank地址和列地址。A10的值决定了是否使用自动预充电。如果选择了自动预充电,则正在访问的行将在写突发结束时进行预充电;如果未选择自动预充电,则该行将保持打开状态以供后续访问。
SDRAM控制操作比较复杂,所以我们使用了SDRAM的接口IP,这大大减少了我们的工作量,在调用IP的基础上,我们只需要设计一个控制模块就能操作SDRAM进行读写操作了。
三.设计思路
程序框图:
四.代码实现
调用IP核(这里IP核的调用不同于之前的PLL,RAM等):
设置配置文件参数:
添加相关延时:
设置时钟为100MHZ:
完成相关连线和端口命名:
顶层模块设计:
/**********************************************************
// Copyright 2022.05-2025.05
// Contact with xxxxxxxxx@qq.com
================ xxx.v ======================
>> Author : lj
>> Date : 20XX/XX/XX
>> Description :
>> note :
>> :
>> V180121 :
************************************************************/
module sdram_top(
input clk ,//时钟信号 50MHZ
input rst_n ,//复位信号
input rx ,//串口接收数据
input key_in ,//按键输入 作为读请求信号
output [12:0] mem_addr ,
output [1 :0] mem_bank ,
output mem_cas_n ,
output mem_cke ,
output mem_cs_n ,
inout [15:0] mem_dq ,
output [1 :0] mem_dqm ,
output mem_ras_n ,
output mem_we_n ,
output sdram_clk ,
output tx //串口发送数据
);
//参数定义
//信号定义
wire key_done ;
wire clk_100m ;
wire clk_100m_s ;
wire [7:0] din ;
wire din_vld ;
wire ready ;
wire [7:0] dout ;
wire dout_vld ;
wire locked ;
//模块例化
//按键消抖模块
key u_key(
/*input */.clk (clk ),//时钟信号
/*input */.rst_n (rst_n ),//复位信号
/*input [1:0] */.key_in (key_in ),//按键输入信号
/*output reg [1:0] */.key_done (key_done) //输出信号
);
//锁相环
pll pll_inst (
.areset ( ~rst_n ),
.inclk0 ( clk ),
.c0 ( clk_100m ),
.c1 ( clk_100m_s ),//100MHZ时钟,带有相位偏移
.locked (locked )
);
//串口接收模块
uart_rx u_uart_rx( //将接收的串行数据转换成并行数据
.clk (clk ),
.rst_n (rst_n ),
.rx (rx ),//串行数据
.set_bps (0 ),
.dout (din ),//并行数据
.dout_vld (din_vld )
);
//sdram控制
sdram_controller u_sdram_controller(
/*input */.clk (clk_100m ),//时钟信号 100MHZ
/*input */.rst_n (rst_n ),//复位信号
/*input */.clk_in (clk ),//50MHZ 时钟
/*input */.clk_out (clk ),//50MHZ 时钟
/*input */.ready (ready ),//串口发送模块准备好
/*input [7:0] */.din (din ),//输入数据 来自串口接收模块
/*input */.din_vld (din_vld ),//输入数据有效
/*input */.rd_req (key_done ),//读请求信号 来自按键
/*output [7:0] */.dout (dout ),//输出数据 发送给串口发送模块
/*output */.dout_vld (dout_vld ),//输出数据有效
/*output [12:0] */.mem_addr (mem_addr ),
/*output [1 :0] */.mem_bank (mem_bank ),
/*output */.mem_cas_n (mem_cas_n ),
/*output */.mem_cke (mem_cke ),
/*output */.mem_cs_n (mem_cs_n ),
/*inout [15:0] */.mem_dq (mem_dq ),
/*output [1 :0] */.mem_dqm (mem_dqm ),
/*output */.mem_ras_n (mem_ras_n ),
/*output */.mem_we_n (mem_we_n )
);
//串口发送模块
uart_tx u_uart_tx( //将接收的并行数据转换成串行数据传输
.clk (clk ),
.rst_n (rst_n ),
.din (dout ),//并行数据
.din_vld (dout_vld ),
.set_bps (0 ),
.tx (tx ), //串行数据
.ready (ready )
);
assign sdram_clk = clk_100m_s;
endmodule
控制模块调用SDRAM接口IP核:
/**********************************************************
// Copyright 2022.05-2025.05
// Contact with xxxxxxxxx@qq.com
================ xxx.v ======================
>> Author : lj
>> Date : 20XX/XX/XX
>> Description :
>> note :
>> :
>> V180121 :
************************************************************/
module sdram_controller(
input clk ,//时钟信号 100MHZ
input rst_n ,//复位信号
input clk_in ,//50MHZ 时钟
input clk_out ,//50MHZ 时钟
input ready ,//串口发送模块准备好
input [7:0] din ,//输入数据 来自串口接收模块
input din_vld ,//输入数据有效
input rd_req ,//读请求信号 来自按键
output [7:0] dout ,//输出数据 发送给串口发送模块
output dout_vld ,//输出数据有效
output [12:0] mem_addr ,
output [1 :0] mem_bank ,
output mem_cas_n,
output mem_cke ,
output mem_cs_n ,
inout [15:0] mem_dq ,
output [1 :0] mem_dqm ,
output mem_ras_n,
output mem_we_n
);
//参数定义
//信号定义
wire [23:0] avs_port_address ;
wire avs_port_write_n ;
wire [15:0] avs_port_writedata ;
wire avs_port_read_n ;
wire [15:0] avs_port_readdata ;
wire avs_port_readdatavalid;
wire avs_port_waitrequest ;
//模块例化
sdram_ctrl u_sdram_ctrl(
/*input */.clk (clk ),//时钟信号 100MHZ
/*input */.rst_n (rst_n ),//复位信号
/*input */.clk_in (clk_in ),//50MHZ
/*input */.clk_out (clk_out ),//50MHZ
/*input [7:0] */.din (din ),//输入数据 来自串口接收模块
/*input */.din_vld (din_vld ),//输入数据有效
/*output [7:0] */.dout (dout ),//输出数据 发给串口发送模块
/*output */.dout_vld (dout_vld ),//输出数据有效
/*input */.ready (ready ),//串口发送模块准备好
/*input */.rd_req (rd_req ),//读请求信号 来自按键
/*output [23:0] */.avm_addr (avs_port_address ),//地址
/*output */.avm_wr_n (avs_port_write_n ),//写请求 低有效
/*output [15:0] */.avm_wr_data (avs_port_writedata ),//写数据
/*output */.avm_rd_n (avs_port_read_n ),//读请求 低有效
/*input [15:0] */.avm_rd_data (avs_port_readdata ),//读出的数据
/*input */.avm_rd_data_vld (avs_port_readdatavalid),//读出数据有效
/*input */.avm_waitrequest (avs_port_waitrequest ) //从机等待请求信号
);
sdram_interface u0 (
.clk_clk (clk ),// clk.clk
.reset_reset_n (rst_n ),// reset.reset_n
.mem_port_addr (mem_addr ),// mem_port.addr
.mem_port_ba (mem_bank ),// .ba
.mem_port_cas_n (mem_cas_n ),// .cas_n
.mem_port_cke (mem_cke ),// .cke
.mem_port_cs_n (mem_cs_n ),// .cs_n
.mem_port_dq (mem_dq ),// .dq
.mem_port_dqm (mem_dqm ),// .dqm
.mem_port_ras_n (mem_ras_n ),// .ras_n
.mem_port_we_n (mem_we_n ),// .we_n
.avs_port_address (avs_port_address ),// avs_port.address
.avs_port_byteenable_n (2'b00 ),// .byteenable_n
.avs_port_chipselect (1'b1 ),// .chipselect
.avs_port_writedata (avs_port_writedata ),// .writedata
.avs_port_read_n (avs_port_read_n ),// .read_n
.avs_port_write_n (avs_port_write_n ),// .write_n
.avs_port_readdata (avs_port_readdata ),// .readdata
.avs_port_readdatavalid (avs_port_readdatavalid),// .readdatavalid
.avs_port_waitrequest (avs_port_waitrequest ) // .waitrequest
);
endmodule
控制模块具体状态设计:
由于读写操作不能同时进行,为了防止一直读或一直写的情况,实现读写仲裁让读写操作交替进行。
具体实现如下:
//读写优先级仲裁
//wr_flag
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
wr_flag <= 1'b0;
end
else if(wrfifo_usedw > BURST_LEN)begin
wr_flag <= 1'b1;
end
else begin
wr_flag <= 1'b0;
end
end
//rd_flag
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rd_flag <= 1'b0;
end
else if(~rd_flag && rd_req)begin
rd_flag <= 1'b1;
end
else begin
rd_flag <= 1'b0;
end
end
//flag_sel 0:上一次写 1:上一次读
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
flag_sel <= 1'b0;
end
else if(write2idle)begin
flag_sel <= 1'b0;
end
else if(read2idle) begin
flag_sel <= 1'b1;
end
end
//prior_flag 0:写 1:读
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
prior_flag <= 1'b0;
end
else if(wr_flag && (flag_sel || ~rd_flag))begin
prior_flag <= 1'b0;
end
else if(rd_flag && (~flag_sel || ~wr_flag)) begin
prior_flag <= 1'b1;
end
end
使用SDRAM接口IP注意地址拼接:
{bank[1],addr[21:9],bank[0],addr[8:0]}
其余代码:略文章来源:https://www.toymoban.com/news/detail-728484.html
五.测试
PC端利用串口调试助手向FPGA发送数据保存到SDRAM芯片中,FPFA开发板按下按键从SDRAM芯片中读出数据发送给PC端。文章来源地址https://www.toymoban.com/news/detail-728484.html
到了这里,关于FPGA驱动SDRAM的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!