大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。
Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺
FPGA综合项目——SDRAM控制器
目录
1.整体框架
本项目的目的,通过串口来对SDRAM进行读写。
首先是串口通信模块,常见的串行通信方式。
再者就是通信处理模块,具体的通信设置,发送什么命令是写?什么命令是读?发的什么数据?等等。
其次就是SDRAM模块,包括初始化、自动刷新、读、写等。(重点)
最后就是fifo的使用,因为SDRAM的读写是远远快于串口通讯的速度的,因此我们需要对写和读的数据进行缓存,不然就会丢失数据。
2.串口接收模块
串口通信我就不多说了,直接看时序图吧:
①当rx输入有下降沿,传输就开始了
②传输的速率看波特率,根据波特率计算每个数据发送所需要的时间(时间计算在代码注释中)
③连停止位一共传输9位数据,传完结束,完成一整次的传输
读完时序我们来思考一下我们需要怎么实现这个时序:
首先,下降沿开始传输——那么我们就要做一个监沿器来识别下降沿,这里还要额外考虑一个问题,那就是rx信号是由电脑发过来的,和FPGA是不一样的时钟,因此我们要先做异步处理,再对rx进行监沿。所以,这里需要2个D触发器进行异步处理,再加一个D触发器构成监沿器。
其次,根据波特率算出来的时间,用一个计数器计一个数据的时间,再用一个计数器来计数据个数。
接着,将输入给到输出,注意,这里应当用中间取值法,避免边沿的跳变。
最后,是代码:
module rx
(
input clk,
input rst_n,
input rx,
output reg [7:0]rx_data,
output reg rx_over
);
//参数
parameter _9600 = 5208; //9600波特率
parameter _8bit = 9; //加上起始位共9位
parameter MIDDLE = _9600/2 - 1; //中间取值
//内部信号
reg rx_ff0;
reg rx_ff1;
reg rx_ff2;
wire pose;
wire nege;
reg flag_rx;
reg [3:0] cnt_8bit;
reg bit_cnt_flag;
reg [12:0] cnt_9600;
//功能块
always@(posedge clk )begin
rx_ff0 <= rx;
rx_ff1 <= rx_ff0;
rx_ff2 <= rx_ff1;
end
assign pose = rx_ff2 == 0 && rx_ff1 == 1;
assign nege = rx_ff2 == 1 && rx_ff1 == 0;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
flag_rx <= 0;
else if(nege)
flag_rx <= 1;
else if(cnt_8bit == 0 && cnt_9600 == _9600-1 )
flag_rx <= 0;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_9600 <= 0;
else if(cnt_9600 == _9600-1)
cnt_9600 <= 0;
else if(flag_rx)
cnt_9600 <= cnt_9600 + 1;
else
cnt_9600 <= 0;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
bit_cnt_flag <= 0;
else if(cnt_9600 == MIDDLE)
bit_cnt_flag <= 1;
else
bit_cnt_flag <= 0;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_8bit <= 0;
else if(bit_cnt_flag && cnt_8bit == _8bit - 1 )
cnt_8bit <= 0;
else if(bit_cnt_flag)
cnt_8bit <= cnt_8bit + 1;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
rx_over <= 0;
else if(bit_cnt_flag && cnt_8bit == _8bit - 1)
rx_over <= 1;
else
rx_over <= 0;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
rx_data <= 0;
else if(bit_cnt_flag && cnt_8bit >= 1 )
rx_data <= {rx_ff1,rx_data[7:1]};
end
endmodule
3.接收模块测试仿真
测试模块主要的作用是编写测试的输入,以方便进行仿真。基本的测试编写可见测试文件编写以及modelsim仿真。
测试文件的功能在于,一位一位的给出rx信号,通过modelsim对源文件进行验证仿真;
①先用$ readmemh(’’ ./x.txt’’,mem) 命令将txt文本的数据以十六进制读到寄存器mem中
②再一位一位拆分输入的数据
③按照’’ 0 ,8个数据, 1’’ 这样的格式给rx赋值
④注意一个bit持续的时间
以下是测试代码:
`timescale 1 ns/1 ns
module tb_rx();
//输入信号
reg clk ;
reg rst_n;
reg rx;
//输出信号
wire rx_over;
wire[7:0] rx_data;
//8位寄存器 4个
reg [7:0] mem_a[3:0];
//参数
parameter CYCLE = 20;
//待测试的模块例化
rx u1
(
.clk (clk ),
.rst_n (rst_n ),
.rx (rx ),
.rx_data (rx_data ),
.rx_over (rx_over )
);
//生成50M时钟
initial begin
clk = 0;
forever
#(CYCLE/2)
clk=~clk;
end
//产生复位信号
initial begin
rst_n = 1;
#2;
rst_n = 0;
#2
rst_n = 1;
end
//把文件里的数据写入mem_a存储器中
//此文件写好放在sim的文件夹中
initial $readmemh("./rx.txt",mem_a);
initial begin
rx = 0;
#20
rx = 1;
#20
byte(); //task
end
//task1 , 将txt文件的4个数存到寄存器a中
task byte();
integer i;
for(i =0;i<4;i = i+1)begin
bit(mem_a[i]);
end
endtask
//task2 , 将txt的每一个数据分为8位,一位一位取值
task bit(input [7:0] data_inter );
integer i;
for(i =0;i<10;i = i+1)begin
case(i)
0: rx <= 1'b0 ;
1: rx <= data_inter[0];
2: rx <= data_inter[1];
3: rx <= data_inter[2];
4: rx <= data_inter[3];
5: rx <= data_inter[4];
6: rx <= data_inter[5];
7: rx <= data_inter[6];
8: rx <= data_inter[7];
default: rx <= 1;
endcase
#104160; //一个bit数据持续的时间 5208个时钟
end
endtask
endmodule
仿真结果:
4.串口发送模块
发送的时序和rx相仿,只不过多了一个触发信号trigger,时序图如下:
①同样使用两个计数器,分别计数一个bit的时间和bit的个数
②使用一个寄存器存下输入进来的发送数据
③发送标志flag_tx的有效区域
④tx的发送 使用一个case语句完成,且在flag_tx下完成,初值为1
代码如下:
module tx
(
input clk,
input rst_n,
input [7:0] tx_data,
input trig,
output reg tx
);
//参数
parameter _9600 = 5208;
parameter _8bit = 10;
//内部信
reg [7:0] reg_tx_data;
reg flag_tx;
reg [12:0] cnt_9600;
reg [3:0] cnt_8bit;
//将输入数据存入寄存器
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
reg_tx_data <= 0;
else if(trig && !flag_tx)
reg_tx_data <= tx_data;
end
//传输有效区域信号
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
flag_tx <= 0;
else if(trig && !flag_tx)
flag_tx <= 1;
else if(flag_tx && cnt_8bit == _8bit-1 && cnt_9600 == _9600-1)
flag_tx <=0;
end
//一位数据传输时间计数
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_9600 <= 0;
else if(flag_tx)begin
if(cnt_9600 == _9600-1)
cnt_9600 <= 0;
else
cnt_9600 <= cnt_9600 + 1;
end
end
//数据个数计数
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_8bit <= 0;
else if(flag_tx && cnt_9600 == _9600-1)begin
if(cnt_8bit == _8bit-1)
cnt_8bit <= 0;
else
cnt_8bit <= cnt_8bit + 1;
end
end
//tx
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
tx <= 0;
else if(flag_tx)
case(cnt_8bit)
0: tx <= 0;
1: tx <= reg_tx_data[0];
2: tx <= reg_tx_data[1];
3: tx <= reg_tx_data[2];
4: tx <= reg_tx_data[3];
5: tx <= reg_tx_data[4];
6: tx <= reg_tx_data[5];
7: tx <= reg_tx_data[6];
8: tx <= reg_tx_data[7];
default:tx <= 1;
endcase
else
tx <= 1;
end
endmodule
5.发送模块测试仿真
发送模块的仿真测试文件就比rx的简单很多了,关键在于延迟的计算,如果延迟不对,那么波特率就不是9600,那么数据就会乱套;代码如下:
`timescale 1 ns/1 ns
module tb_tx();
//输入信号
reg[7:0] tx_data ;
reg trig;
reg clk ;
reg rst_n;
//输出信号
wire tx;
//参数
parameter CYCLE = 20;
//待测试的模块例化
tx u2
(
.clk (clk ),
.rst_n (rst_n ),
.trig (trig ),
.tx_data (tx_data ),
.tx (tx )
);
//生成本地时钟50M
initial begin
clk = 0;
forever
#(CYCLE/2)
clk=~clk;
end
//产生复位信号
initial begin
rst_n = 1;
#2;
rst_n = 0;
#2
rst_n = 1;
end
initial begin
tx_data <= 8'd0;
trig <= 0;
#20;
tx_data <= 8'b01010101;
trig <= 1'b1;
#937440; //一共9个比特 ,因此是937440(5208*20*9)的时间
trig <= 1'b0;
#2;
tx_data <= 8'b10101010;
trig <= 1'b1;
#937440;
trig <= 1'b0;
end
endmodule
仿真图如下:
6.SDRAM基础学习
对SDRAM的基础学习,只要弄清楚我下面的几个问题,就基本上可以了。
①SDRAM是什么?
Synchronous Dynamic Random Access Memory,同步动态随机存储器,说白了,就是用来存数据用的外设(具体看此链接的6.2 DRAM 小结)。那么怎么存呢?看下一个问题。
②SDRAM有什么?
既然作为存东西的外设,那么必定有存放数据的结构,而这个就够就叫做bank,简单的说,一个bank就是一个存储阵列,利用行地址和列地址来进行排号标记,而一个SDRAM一般都有4个bank,它的容量就取决于bank的容量以及bank的数量。如图:
③SDRAM我们要懂什么?
首先,要明白他存储的原理,也就是为什么要刷新:存储电容会漏电,所以必须一定的间隔进行充电,这个过程就叫刷新。
其次,所用SDRAM的引脚以及数据手册要熟悉,引脚的作用,寄存器的配置都要从数据手册上获得信息。
最后就是各个功能的时序,也要从数据手册找出。以下以ISSI的SDRAM芯片为例讲解,这是此芯片的引脚:
我们进而查看命令所对应的引脚:
此表是命令列表,并详细说明了如何通过信号组合来形式命令。当然还要去读一些命令所要求的时序,由于手册太长,在这不可能一一列举,我们直接从项目出发,用到什么说什么,看下一节具体操作即可,此节只需了解以下SDRAM的基本原理。
7.SDRAM顶层模块
作为SDRAM的顶层模块,在写的时候是要一步一步更新的,在每个模块调试时,都会加入其顶层模块进行仿真;
8.SDRAM初始化模块设计与仿真测试
初始化SDRAM,我们要参照数据手册的初始化的时序图,如下:
图中的时间没有具体说明,因此我们还要在数据手册上查找相应的时间:
由此,我们时钟是20ns一个周期,所以trc取3个周期、trp取1个周期、tmrd取2个周期。那么总体的时序如下:
①由时序图可知,电源上电需要缓冲100us,我们这保守起见,给他200us延迟
②在200us过后,执行红色框框中的命令PRECARGE(bank充电),找到此命令对应的四个引脚的高低电平
③接着经过一个周期T,给到自动刷新命令
④接着4T后,再给一次自动刷新命令
⑤又4T后,进行模式寄存器命令完成初始化过程,而配置寄存器模式需要看相应的sdram地址,下图是地址对应的配置信息:
而我们设置的sdram_addr为0000_0011_0010
代码及注释如下:
module sdram_initial
(
input clk,
input rst_n,
output reg [3:0] cmd_reg, //用来寄存命令的信号
output reg [11:0] sdram_addr, //时序图中的A0-A11
output wire init_end //初始化结束信号
);
//参数,包括延迟200us、4个指令对应的引脚高低电平
localparam DELAY_200US = 10_000;
localparam NOP = 4'b0111;
localparam PRECHARGE = 4'b0010;
localparam AUTOREFLASH = 4'b0001;
localparam MODESET = 4'b0000;
//内部信号
reg [13:0] cnt0;
reg [3:0] cnt1;
wire flag_200us;
//200us计数
assign flag_200us = (cnt0 >= DELAY_200US-1)? 1'b1:1'b0;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt0 <= 0;
end
else if(!flag_200us)begin
cnt0 <= cnt0 + 1;
end
end
//周期个数计数
assign init_end = (cnt1>='d10)?1:0;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt1 <= 0;
end
else if(flag_200us && !init_end)begin
if(cnt1==11-1)
cnt1 <= 0;
else
cnt1 <= cnt1 + 1;
end
end
//命令操作
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
cmd_reg <= NOP;
end
else if(flag_200us)begin
case(cnt1)
0: cmd_reg <= PRECHARGE;
1: cmd_reg <= AUTOREFLASH;
5: cmd_reg <= AUTOREFLASH;
9: cmd_reg <= MODESET;
default: cmd_reg <= NOP;
endcase
end
end
//sdram_addr
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
sdram_addr <= 0;
end
else if(flag_200us)begin
case(cnt1)
9: sdram_addr <= 12'b0000_0011_0010;
default: sdram_addr <= 12'b0100_0000_0000;
endcase
end
end
endmodule
再完成顶层模块的编写:
module sdram_top
(
input clk,
input rst_n,
output wire sdram_clk,
output wire sdram_cke,
output wire sdram_cs,
output wire sdram_cas,
output wire sdram_ras,
output wire sdram_we,
output wire [1 :0 ] sdram_bank,
output wire [11:0 ] sdram_addr,
output wire [1 :0 ] sdram_dqm,
inout [15:0 ] sdram_dq
);
//参数
//状态机采用独热码定义
localparam IDLE = 5'b0_0001;
localparam HANDLE = 5'b0_0010;
localparam AUTOREFRESH = 5'b0_0100;
localparam WRITE = 5'b0_1000;
localparam READ = 5'b1_0000;
localparam CMD_NOP = 4'b0111 ;
//内部信号
wire init_end;
wire [3:0] init_cmd;
wire [11:0] init_addr;
//例化各模块
sdram_initial u1_init
(
.clk (clk),
.rst_n (rst_n),
.cmd_reg (init_cmd),
.sdram_addr (init_addr),
.init_end (init_end)
);
assign sdram_cke = 1'b1;
assign sdram_clk = ~clk;
assign sdram_dqm = 2'b00;
assign sdram_addr = init_addr;
assign {sdram_cs,sdram_ras,sdram_cas,sdram_we} = init_cmd;
endmodule
至此,初始化完成、顶层完成后,接下来就是对初始化模块的测试了,我们对顶层模块做测试,每加一个模块,我们就测试一次。测试我们需要用到一个插件:
完成之后呢,编写测试文件:
`timescale 1 ns/1 ns
module sdram_top_tb();
//输入信号
reg clk ;
reg rst_n;
//输出信号
wire sdram_clk ;
wire sdram_cke ;
wire sdram_cs ;
wire sdram_cas ;
wire sdram_ras ;
wire sdram_we ;
wire [1:0] sdram_bank ;
wire [11:0] sdram_addr ;
wire [1:0] sdram_dqm ;
wire [15:0] sdram_dq ;
//参数
parameter CYCLE = 20;
parameter RST_TIME = 3 ;
//待测试的模块例化
sdram_top u1_sdram_top
(
.clk (clk ),
.rst_n (rst_n ),
.sdram_clk (sdram_clk ),
.sdram_cke (sdram_cke ),
.sdram_cs (sdram_cs ),
.sdram_cas (sdram_cas ),
.sdram_ras (sdram_ras ),
.sdram_we (sdram_we ),
.sdram_bank (sdram_bank ),
.sdram_addr (sdram_addr ),
.sdram_dqm (sdram_dqm ),
.sdram_dq (sdram_dq )
);
//插件例化,相当于是一个sdram硬件
sdram_model_plus u2_plus
( .Dq (sdram_dq),
.Addr (sdram_addr),
.Ba (sdram_bank),
.Clk (sdram_clk),
.Cke (sdram_cke),
.Cs_n (sdram_cs),
.Ras_n (sdram_ras),
.Cas_n (sdram_cas),
.We_n (sdram_we),
.Dqm (sdram_dqm),
.Debug (1'b1)
);
//重定义具体sdram的参数,因为插件的和实际的并不一样
defparam u2_plus.addr_bits = 12;
defparam u2_plus.data_bits = 16;
defparam u2_plus.col_bits = 9;
defparam u2_plus.mem_sizes = 2*1024*1024; //2M 一个bank
// 生成本地时钟50M
initial begin
clk = 0;
forever
#(CYCLE/2)
clk=~clk;
end
//产生复位信号
initial begin
rst_n = 1;
#2;
rst_n = 0;
#(CYCLE*RST_TIME);
rst_n = 1;
end
endmodule
仿真结果:充电范围所有bank、自动刷新一次、自动刷新两次、模式设置为输出延迟3T、突发长度4、连续突发类型;
9.自动刷新模块设计与测试
那么刷新的频率怎么算呢?首先数据手册上写这4096/64ms,意思就是64ms内对4096行进行刷新,也就是64000/4096 约为15us一次刷新。
4096行是怎么来的呢?我们的行地址不是有12位嘛,所以2^12为4096行。
自动刷新的操作:每15us发送请求进行刷新操作
同样的,自动刷新模块照样需要我们取查数据手册的自动刷新时序图,如下所示:
同样的,trc取3个时钟周期、trp取1个时钟周期,时序图中的顺序是:
①在初始化完成后,进行自动刷新(刷新完成信号)。先预充电,地址为A10为1,选中全部bank
②过一个时钟周期,进行自动刷新
③图上是进行两次自动刷新,实际上一次就行
④刷新模块的作用:每15us对全部bank(A10置1)进行一次刷新
⑤从这里开始引入了仲裁模块(直接写在顶层模块中了HANDLE状态),其他执行模块与仲裁模块之间有三线连接,分别是请求、使能、结束标志;仲裁模块通过自身判断再给出使能信号,给出使能信号后状态跳转到其他执行状态,完成执行任务后再跳回仲裁状态
④刷新模块代码如下:
module autorefresh
(
input clk,
input rst_n,
input init_end,
input ref_en,
output wire ref_req,
output wire ref_end,
output reg [3:0] autorefresh_cmd,
output wire [11:0] sdram_addr
);
//计数参数
localparam DELAY_15US = 750;
//命令参数
localparam NOP = 4'b0111;
localparam PRECHARGE = 4'b0010;
localparam AUTOREFLASH = 4'b0001;
localparam MODESET = 4'b0000;
//内部信号
reg [9:0] cnt0;
reg [3:0] cnt1;
reg flag_ref; //刷新进行中标志
//功能
//15us计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt0 <= 0;
else if(init_end)begin
if(cnt0== DELAY_15US-1)
cnt0 <= 0;
else
cnt0 <= cnt0 + 1;
end
end
//刷新请求,每15us一次
assign ref_req = (cnt0 >= DELAY_15US-1)? 1'b1 : 1'b0;
//flag_ref
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag_ref <= 0;
end
else if(ref_en) begin
flag_ref <= 1;
end
else if(ref_end) begin
flag_ref <=0;
end
end
//时钟T个数计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt1 <= 0;
end
else if(flag_ref)begin
if(cnt1== 4-1)
cnt1 <= 0;
else
cnt1 <= cnt1 + 1;
end
end
//给出命令
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
autorefresh_cmd <= NOP;
end
else case(cnt1)
1: autorefresh_cmd <= PRECHARGE;
2: autorefresh_cmd <= AUTOREFLASH;
default: autorefresh_cmd <= NOP;
endcase
end
//sdram_addr,A10置1选中所有bank
assign sdram_addr = 12'b0100_0000_0000;
//ref_end
assign ref_end = (cnt1>='d3)? 1 : 0;
endmodule
接着就是更新加入仲裁机制后的顶层模块:
module sdram_top
(
input clk,
input rst_n,
output wire sdram_clk,
output wire sdram_cke,
output wire sdram_cs,
output wire sdram_cas,
output wire sdram_ras,
output wire sdram_we,
output wire [1 :0 ] sdram_bank,
output wire [11:0 ] sdram_addr,
output wire [1 :0 ] sdram_dqm,
inout [15:0 ] sdram_dq
);
//状态机采用独热码定义
localparam IDLE = 5'b0_0001;
localparam HANDLE = 5'b0_0010;
localparam AUTOREFRESH = 5'b0_0100;
localparam WRITE = 5'b0_1000;
localparam READ = 5'b1_0000;
//空命令
localparam CMD_NOP = 4'b0111 ;
//内部信号
//初始化
wire init_end;
wire [3:0] init_cmd;
wire [11:0] init_addr;
//状态机
reg [4:0] state;
//自动刷新
wire ref_req;
reg ref_en;
wire ref_end;
wire [3:0] ref_cmd;
wire [11:0] ref_addr;
//例化模块
sdram_initial u1_init
(
.clk (clk),
.rst_n (rst_n),
.cmd_reg (init_cmd),
.sdram_addr (init_addr),
.init_end (init_end)
);
autorefresh u2_autorefresh
(
.clk (clk),
.rst_n (rst_n),
.init_end (init_end),
.ref_en (ref_en),
.ref_req (ref_req),
.ref_end (ref_end),
.autorefresh_cmd (ref_cmd),
.sdram_addr (ref_addr)
);
//功能
//仲裁模块状态机
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
state <= IDLE;
else case(state)
IDLE:
if(init_end)
state <= HANDLE;
else
state <= IDLE;
HANDLE: if(ref_en)
state <= AUTOREFRESH;
else
state <= HANDLE;
AUTOREFRESH:
if(ref_end)
state <= HANDLE;
else
state <= AUTOREFRESH;
default:
state <=IDLE;
endcase
end
//刷新使能
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
ref_en <= 0;
else if(ref_req && state == HANDLE)
ref_en <= 1;
else
ref_en <= 0;
end
assign sdram_cke = 1'b1;
assign sdram_clk = ~clk;
assign sdram_dqm = 2'b00;
assign sdram_addr = (state == IDLE)?init_addr:ref_addr;
assign {sdram_cs,sdram_ras,sdram_cas,sdram_we} = (state == IDLE)? init_cmd:ref_cmd;
endmodule
下面进行仿真测试,测试文件不用修改;
仿真结果如下:
10.写模块设计与测试
写模块的设计主要考虑三个方面:第一,写模块自身的时序。第二,写模块与top模块状态机的连接(特别是从写状态跳出到仲裁状态)。第三,和自动刷新模块的连接。
①首先,从SDRAM本身的时序来说,如下第一个图,写有两种模式,一种是WRITE,另一种是WRITEA,前者可以连续写更加高效,所以我们选择用WRITE。(粗线为自动跳转)因为WRITEA会自动跳转到充电状态,所以效率不高。
那么写时序从数据手册读出时序图如下:先激活,再写,突发长度为4。我们写模块的功能就是写两行(测试用),同时要兼顾自动刷新!
写模块的状态机如下:
② 写模块与top模块的连接,通过一个flag_wr_end信号,使得top的状态机从写状态跳到处理(仲裁)状态,来完成自动刷新的动作,这里需要对刷新模块的刷新使能进行修改,由原来的组合逻辑改为时序逻辑产生,修改后的刷新模块如下:
module autorefresh
(
input clk,
input rst_n,
input init_end,
input ref_en,
output reg ref_req,
output wire ref_end,
output reg [3:0] autorefresh_cmd,
output wire [11:0] sdram_addr
);
//计数参数
localparam DELAY_15US = 750;
//命令参数
localparam NOP = 4'b0111;
localparam PRECHARGE = 4'b0010;
localparam AUTOREFLASH = 4'b0001;
localparam MODESET = 4'b0000;
//内部信号
reg [9:0] cnt0;
reg [3:0] cnt1;
reg flag_ref; //刷新进行中标志
//功能
//15us计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt0 <= 0;
else if(init_end)begin
if(cnt0== DELAY_15US-1)
cnt0 <= 0;
else
cnt0 <= cnt0 + 1;
end
end
//刷新请求,每15us一次 ,直到刷新使能来之前,都为1
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
ref_req <= 1'b0;
else if(ref_en == 1'b1)
ref_req <= 1'b0;
else if(cnt0 >= DELAY_15US-1)
ref_req <= 1'b1;
end
//flag_ref
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag_ref <= 0;
end
else if(ref_en) begin
flag_ref <= 1;
end
else if(ref_end) begin
flag_ref <=0;
end
end
//时钟T个数计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt1 <= 0;
end
else if(flag_ref)begin
if(cnt1== 4-1)
cnt1 <= 0;
else
cnt1 <= cnt1 + 1;
end
end
//给出命令
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
autorefresh_cmd <= NOP;
end
else case(cnt1)
1: autorefresh_cmd <= PRECHARGE;
2: autorefresh_cmd <= AUTOREFLASH;
default: autorefresh_cmd <= NOP;
endcase
end
//sdram_addr,A10置1选中所有bank
assign sdram_addr = 12'b0100_0000_0000;
//ref_end
assign ref_end = (cnt1>='d3)? 1 : 0;
endmodule
③写模块与刷新模块的连接,通过刷新请求和刷新结束标志连接,当刷新请求来了,写模块完成当前的突发后停止,进入REQ状态,并使top的状态跳转至处理状态,以便进行刷新。
④写模块代码如下,有详细注释
module sdram_write
(
input clk, //输入,50M时钟
input rst_n, //输入,异步复位
input wr_en, //输入,写使能
input ref_req, //输入,刷新请求,用来中断写
input wr_trigger, //输入,写触发信号
input ref_end, //输入,刷新结束,可以继续写
output reg wr_req, //输出,写请求
output reg flag_wr_end,//输出,top状态机跳出写状态信号
output reg [3:0] wr_cmd, //输出,写命令
output wire [1:0] bank_addr, //输出,bank地址
output reg [11:0] wr_addr, //输出,写地址
output reg [15:0] wr_data //输出,写数据,此代码在本模块内产生
);
//状态参数
localparam IDLE = 5'b0_0001;
localparam REQ = 5'b0_0010;
localparam ACTIVE = 5'b0_0100;
localparam WRITE = 5'b0_1000;
localparam PRE = 5'b1_0000;
//命令参数
localparam CMD_NOP = 4'b0111;
localparam CMD_PRECHARGE = 4'b0010;
localparam CMD_AUTOREFLASH = 4'b0001;
localparam CMD_ACTIVE = 4'b0011;
localparam CMD_WRITE = 4'b0100;
//内部信号
reg flag_wr; //写区域
reg [4:0] state_wr; //写时序状态
reg flag_ac_end; //激活状态计数,计数完成跳转下一状态
reg flag_pre_end; //充电状态计数,计数完成跳转下一状态
reg change_row; //换行,一行写满(512个数据),要换行,再激活新的一行
reg wr_data_end; //数据写完信号
reg [1:0] burst_cnt; //突发计数器,一次写入多少数据的控制
reg [1:0] burst_cnt_ff; //打一拍
reg [3:0] active_cnt; //激活计数器
reg [3:0] pre_cnt; //充电计数器
reg [6:0] col_cnt; //数据计数器,满512换行,归零
reg [11:0] row_addr; //行地址
wire [8:0] col_addr; //列地址
//功能
//状态机
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
state_wr <= IDLE;
end
else begin
case(state_wr)
IDLE:
if(wr_trigger)
state_wr <= REQ;
else
state_wr <= IDLE;
REQ:
if(wr_en)
state_wr <= ACTIVE;
else
state_wr <= REQ;
ACTIVE:
if(flag_ac_end)
state_wr <= WRITE;
else
state_wr <= ACTIVE;
WRITE:
if(wr_data_end)
state_wr <= PRE;
else if(ref_req && burst_cnt_ff == 3 && flag_wr )
state_wr <= PRE;
else if(change_row && flag_wr)
state_wr <= PRE;
PRE:
if(ref_req && flag_wr)
state_wr <= REQ;
else if(flag_pre_end && flag_wr)
state_wr <= ACTIVE;
else if(wr_data_end)
state_wr <= IDLE;
default:
state_wr <= IDLE;
endcase
end
end
//burst_cnt_ff 打一拍
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
burst_cnt_ff <= 0;
end
else
burst_cnt_ff <= burst_cnt;
end
//数据输出
always @(*)begin
case(burst_cnt_ff)
0: wr_data = 'd1;
1: wr_data = 'd2;
2: wr_data = 'd3;
3: wr_data = 'd4;
endcase
end
//命令
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wr_cmd <= CMD_NOP;
end
else case(state_wr)
ACTIVE:
if(active_cnt == 'd0)
wr_cmd <= CMD_ACTIVE;
else
wr_cmd <= CMD_NOP;
WRITE:
if(burst_cnt == 'd0)
wr_cmd <= CMD_WRITE;
else
wr_cmd <= CMD_NOP;
PRE:
if(pre_cnt == 'd0)
wr_cmd <= CMD_PRECHARGE;
else
wr_cmd <= CMD_NOP;
default:
wr_cmd <= CMD_NOP;
endcase
end
//写地址
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wr_addr <= 0;
end
else case(state_wr)
ACTIVE:
if(active_cnt == 'd0)
wr_addr <= row_addr;
WRITE:
wr_addr <= {3'b000,col_addr};
PRE:
if(pre_cnt == 'd0)
wr_addr <= {12'b0100_0000_0000};
default:
wr_addr <= 0;
endcase
end
//bank地址
assign bank_addr = 2'b00;
//flag_wr
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag_wr <= 0;
end
else if(wr_trigger && !flag_wr)
flag_wr <= 1;
else if(wr_data_end)
flag_wr <= 0;
end
//act计数器,计数激活所需要的时间
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
active_cnt <= 0;
end
else if(state_wr == ACTIVE)
active_cnt <= active_cnt + 1;
else
active_cnt <= 0;
end
//刷新计数
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
pre_cnt <= 0;
end
else if(state_wr == PRE) begin
pre_cnt <= pre_cnt + 1;
end
else
pre_cnt <= 0;
end
//col计数器,列计数
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
col_cnt <= 0;
end
else if(col_addr == 'd511)
col_cnt <= 0;
else if(burst_cnt =='d3)
col_cnt <= col_cnt + 1;
end
//产生flag_ac_end信号
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag_ac_end <= 0;
end
else if(active_cnt >= 'd3) begin
flag_ac_end <= 1;
end
else
flag_ac_end <= 0;
end
//产生flag_pre_end信号
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag_pre_end <= 0;
end
else if(pre_cnt >= 'd3)
flag_pre_end <= 1;
else
flag_pre_end <= 0;
end
//产生flag_wr_end信号(写一次结束)
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag_wr_end <= 0;
end
else if(state_wr == PRE && ref_req )
flag_wr_end <= 1;
else if(state_wr == PRE && wr_data_end)
flag_wr_end <= 1;
else
flag_wr_end <= 0;
end
//写所有结束标志
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wr_data_end <= 0;
end
else if(row_addr == 'd1 && col_addr == 'd511 ) begin
wr_data_end <= 1;
end
else
wr_data_end <= 0;
end
//突发长度计数
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
burst_cnt <= 0;
end
else if(state_wr == WRITE) begin
burst_cnt <= burst_cnt +1;
end
else
burst_cnt <= 0;
end
//col_addr 列地址计算
assign col_addr = {col_cnt,burst_cnt};
//row_addr 行地址
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
row_addr <= 0;
end
else if(change_row)
row_addr <= row_addr + 1;
end
//换行请求
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
change_row <= 0;
end
else if(col_addr == 'd510 )
change_row <= 1;
else
change_row <= 0;
end
//写请求
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wr_req <= 0;
end
else if(state_wr == REQ && ref_end)
wr_req <= 1;
else
wr_req <= 0;
end
endmodule
在top模块中加入写模块的例化,在测试文件中写好wr_trigger的波形,便继续仿真:
module sdram_top
(
input clk,
input rst_n,
input wr_trigger,
output wire sdram_clk,
output wire sdram_cke,
output wire sdram_cs,
output wire sdram_cas,
output wire sdram_ras,
output wire sdram_we,
output wire [1 :0 ] sdram_bank,
output reg [11:0 ] sdram_addr,
output wire [1 :0 ] sdram_dqm,
inout [15:0 ] sdram_dq
);
//状态机采用独热码定义
localparam IDLE = 5'b0_0001;
localparam HANDLE = 5'b0_0010;
localparam AUTOREFRESH = 5'b0_0100;
localparam WRITE = 5'b0_1000;
localparam READ = 5'b1_0000;
//空命令
localparam CMD_NOP = 4'b0111 ;
//内部信号
//初始化
wire init_end;
wire [3:0] init_cmd;
wire [11:0] init_addr;
//状态机
reg [4:0] state;
//自动刷新
wire ref_req;
reg ref_en;
wire ref_end;
wire [3:0] ref_cmd;
wire [11:0] ref_addr;
//写模块
reg wr_en;
wire wr_req;
wire [3:0] wr_cmd;
wire [11:0] wr_addr;
wire [1:0] wr_bank_addr;
wire flag_wr_end;
wire [15:0] wr_data;
//命令赋值信号
reg [3:0] sdram_cmd;
assign {sdram_cs,sdram_ras,sdram_cas,sdram_we} = sdram_cmd;
//例化模块
sdram_initial u1_init
(
.clk (clk),
.rst_n (rst_n),
.cmd_reg (init_cmd),
.sdram_addr (init_addr),
.init_end (init_end)
);
autorefresh u2_autorefresh
(
.clk (clk),
.rst_n (rst_n),
.init_end (init_end),
.ref_en (ref_en),
.ref_req (ref_req),
.ref_end (ref_end),
.autorefresh_cmd (ref_cmd),
.sdram_addr (ref_addr)
);
sdram_write u3
(
.clk (clk),
.rst_n (rst_n),
.wr_en (wr_en),
.ref_req (ref_req),
.wr_trigger (wr_trigger),
.ref_end (ref_end),
.wr_req (wr_req),
.flag_wr_end (flag_wr_end),
.wr_cmd (wr_cmd),
.bank_addr (wr_bank_addr),
.wr_addr (wr_addr),
.wr_data (wr_data)
);
//功能
//仲裁模块状态机
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
state <= IDLE;
else case(state)
IDLE:
if(init_end)
state <= HANDLE;
else
state <= IDLE;
HANDLE: if(ref_en)
state <= AUTOREFRESH;
else if(wr_en)
state <= WRITE;
else
state <= HANDLE;
AUTOREFRESH:
if(ref_end)
state <= HANDLE;
else
state <= AUTOREFRESH;
WRITE:
if(flag_wr_end)
state <= HANDLE;
else
state <= WRITE;
default:
state <=IDLE;
endcase
end
always@(*)begin
case(state)
IDLE:
begin
sdram_cmd <= init_cmd;
sdram_addr <= init_addr;
end
AUTOREFRESH:
begin
sdram_cmd <= ref_cmd;
sdram_addr <= ref_addr;
end
WRITE:
begin
sdram_cmd <= wr_cmd;
sdram_addr <= wr_addr;
end
default:
begin
sdram_cmd <= CMD_NOP;
sdram_addr <= 0;
end
endcase
end
//刷新使能
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
ref_en <= 0;
else if(ref_req && state == HANDLE)
ref_en <= 1;
else
ref_en <= 0;
end
//写使能
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
wr_en <= 0;
else if(wr_req && state == HANDLE && !ref_req)
wr_en <= 1;
else
wr_en <= 0;
end
assign sdram_cke = 1'b1;
assign sdram_clk = ~clk;
assign sdram_dqm = 2'b00;
assign sdram_dq = (state == WRITE)?wr_data:{16{1'bz}};
assign sdram_bank = (state == WRITE)?wr_bank_addr:0;
endmodule
至此,写模块设计完成,在整个设计过程中呢,博主写错了一个状态机的名字,一个小小的问题找了一晚上的错,最终解决,也以此告诫大家一定义要细心!仿真结果如下:
11.读模块设计与仿真测试
我们完成了写模块的设计后,读模块就简单的要命了。打开写好的写模块,将所有的写命令全部改成读命令即可。接着补充top模块中的代码,将读模块例化,补全内部信号。
而读模块与写模块不一样的地方在于时序有一个小差别:读命令后两个时钟周期才将数据读到,也就是会有2个时钟周期的延迟;
还有一个就是读和写的优先级,这里优先写(先判断写在到读)
读模块代码如下:
module sdram_read
(
input clk, //输入,50M时钟
input rst_n, //输入,异步复位
input rd_en, //输入,读使能
input ref_req, //输入,刷新请求,用来中断读
input rd_trigger, //输入,读触发信号
input ref_end, //输入,刷新结束,可以继续读
output reg rd_req, //输出,读请求
output reg flag_rd_end,//输出,top状态机跳出读状态信号
output reg [3:0] rd_cmd, //输出,读命令
output wire [1:0] bank_addr, //输出,bank地址
output reg [11:0] rd_addr //输出,读地址
);
//状态参数
localparam IDLE = 5'b0_0001;
localparam REQ = 5'b0_0010;
localparam ACTIVE = 5'b0_0100;
localparam READ = 5'b0_1000;
localparam PRE = 5'b1_0000;
//命令参数
localparam CMD_NOP = 4'b0111;
localparam CMD_PRECHARGE = 4'b0010;
localparam CMD_AUTOREFLASH = 4'b0001;
localparam CMD_ACTIVE = 4'b0011;
localparam CMD_READ = 4'b0101;
//内部信号
reg flag_rd; //读区域
reg [4:0] state_rd; //读时序状态
reg flag_ac_end; //激活状态计数,计数完成跳转下一状态
reg flag_pre_end; //充电状态计数,计数完成跳转下一状态
reg change_row; //读完要换行,再激活新的一行
reg rd_data_end; //数据写完信号
reg [1:0] burst_cnt; //突发计数器,一次读多少数据的控制
reg [1:0] burst_cnt_ff; //打一拍
reg [3:0] active_cnt; //激活计数器
reg [3:0] pre_cnt; //充电计数器
reg [6:0] col_cnt; //数据计数器,满512换行,归零
reg [11:0] row_addr; //行地址
wire [8:0] col_addr; //列地址
//功能
//状态机
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
state_rd <= IDLE;
end
else begin
case(state_rd)
IDLE:
if(rd_trigger)
state_rd <= REQ;
else
state_rd <= IDLE;
REQ:
if(rd_en)
state_rd <= ACTIVE;
else
state_rd <= REQ;
ACTIVE:
if(flag_ac_end)
state_rd <= READ;
else
state_rd <= ACTIVE;
READ:
if(rd_data_end)
state_rd <= PRE;
else if(ref_req && burst_cnt_ff == 'd3 && flag_rd )
state_rd <= PRE;
else if(change_row && flag_rd)
state_rd <= PRE;
PRE:
if(ref_req && flag_rd)
state_rd <= REQ;
else if(flag_pre_end && flag_rd)
state_rd <= ACTIVE;
else if(rd_data_end)
state_rd <= IDLE;
default:
state_rd <= IDLE;
endcase
end
end
//burst_cnt_ff 打一拍
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
burst_cnt_ff <= 0;
end
else begin
burst_cnt_ff <= burst_cnt;
end
end
//命令
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rd_cmd <= CMD_NOP;
end
else case(state_rd)
ACTIVE:
if(active_cnt == 'd0)
rd_cmd <= CMD_ACTIVE;
else
rd_cmd <= CMD_NOP;
READ:
if(burst_cnt == 'd0)
rd_cmd <= CMD_READ;
else
rd_cmd <= CMD_NOP;
PRE:
if(pre_cnt == 'd0)
rd_cmd <= CMD_PRECHARGE;
else
rd_cmd <= CMD_NOP;
default:
rd_cmd <= CMD_NOP;
endcase
end
//读地址
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rd_addr <= 0;
end
else case(state_rd)
ACTIVE:
if(active_cnt == 'd0)
rd_addr <= row_addr;
READ:
rd_addr <= {3'b000,col_addr};
PRE:
if(pre_cnt == 'd0)
rd_addr <= {12'b0100_0000_0000};
default:
rd_addr <= 0;
endcase
end
//bank地址
assign bank_addr = 2'b00;
//flag_rd
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag_rd <= 0;
end
else if(rd_trigger && !flag_rd)
flag_rd <= 1;
else if(rd_data_end)
flag_rd <= 0;
end
//act计数器,计数激活所需要的时间
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
active_cnt <= 0;
end
else if(state_rd == ACTIVE)
active_cnt <= active_cnt + 1;
else
active_cnt <= 0;
end
//刷新计数
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
pre_cnt <= 0;
end
else if(state_rd == PRE) begin
pre_cnt <= pre_cnt + 1;
end
else
pre_cnt <= 0;
end
//col计数器,列计数
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
col_cnt <= 0;
end
else if(col_addr == 'd511)
col_cnt <= 0;
else if(burst_cnt =='d3)
col_cnt <= col_cnt + 1;
end
//产生flag_ac_end信号
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag_ac_end <= 0;
end
else if(active_cnt >= 'd3) begin
flag_ac_end <= 1;
end
else
flag_ac_end <= 0;
end
//产生flag_pre_end信号
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag_pre_end <= 0;
end
else if(pre_cnt >= 'd3) begin
flag_pre_end <= 1;
end
else
flag_pre_end <= 0;
end
//产生flag_rd_end信号
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
flag_rd_end <= 0;
end
else if(state_rd == PRE && ref_req )
flag_rd_end <= 1;
else if(state_rd == PRE && rd_data_end)
flag_rd_end <= 1;
else
flag_rd_end <= 0;
end
//突发长度计数
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
burst_cnt <= 0;
end
else if(state_rd == READ) begin
burst_cnt <= burst_cnt +1;
end
else
burst_cnt <= 0;
end
//读结束标志
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rd_data_end <= 0;
end
else if(row_addr == 'd1 && col_addr == 'd511 ) begin
rd_data_end <= 1;
end
else
rd_data_end <= 0;
end
//col_addr
assign col_addr = {col_cnt,burst_cnt};
//row_addr
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)
row_addr <= 0;
else if(change_row)
row_addr <= row_addr + 1;
end
//换行请求
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
change_row <= 0;
end
else if(col_addr == 'd510 )
change_row <= 1;
else
change_row <= 0;
end
//读请求
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rd_req <= 0;
end
else if(state_rd == REQ && ref_end)
rd_req <= 1;
else
rd_req <= 0;
end
endmodule
为了方便测试,在测试文件中多引入了读触发信号,写触发在205000ns,而读触发在325000ns。
`timescale 1 ns/1 ns
module sdram_top_tb();
//输入信号
reg clk ;
reg rst_n;
reg wr_trigger;
reg rd_trigger;
//输出信号
wire sdram_clk ;
wire sdram_cke ;
wire sdram_cs ;
wire sdram_cas ;
wire sdram_ras ;
wire sdram_we ;
wire [1:0] sdram_bank ;
wire [11:0] sdram_addr ;
wire [1:0] sdram_dqm ;
wire [15:0] sdram_dq ;
//参数
parameter CYCLE = 20;
parameter RST_TIME = 3 ;
//待测试的模块例化
sdram_top u1_sdram_top
(
.clk (clk ),
.rst_n (rst_n ),
.wr_trigger (wr_trigger ),
.rd_trigger (rd_trigger ),
.sdram_clk (sdram_clk ),
.sdram_cke (sdram_cke ),
.sdram_cs (sdram_cs ),
.sdram_cas (sdram_cas ),
.sdram_ras (sdram_ras ),
.sdram_we (sdram_we ),
.sdram_bank (sdram_bank ),
.sdram_addr (sdram_addr ),
.sdram_dqm (sdram_dqm ),
.sdram_dq (sdram_dq )
);
//插件例化,相当于是一个sdram硬件
sdram_model_plus u2_plus
( .Dq (sdram_dq),
.Addr (sdram_addr),
.Ba (sdram_bank),
.Clk (sdram_clk),
.Cke (sdram_cke),
.Cs_n (sdram_cs),
.Ras_n (sdram_ras),
.Cas_n (sdram_cas),
.We_n (sdram_we),
.Dqm (sdram_dqm),
.Debug (1'b1)
);
//重定义具体sdram的参数,因为插件的和实际的并不一样
defparam u2_plus.addr_bits = 12;
defparam u2_plus.data_bits = 16;
defparam u2_plus.col_bits = 9;
defparam u2_plus.mem_sizes = 2*1024*1024; //2M 一个bank
// 生成本地时钟50M
initial begin
clk = 0;
forever
#(CYCLE/2)
clk=~clk;
end
//产生复位信号
initial begin
rst_n = 1;
#2;
rst_n = 0;
#(CYCLE*RST_TIME);
rst_n = 1;
end
initial begin
wr_trigger <= 0;
#205000
wr_trigger <= 1;
#20
wr_trigger <= 0;
end
initial begin
rd_trigger <= 0;
#325000
rd_trigger <= 1;
#20
rd_trigger <= 0;
end
endmodule
仿真结果如图:
初始化后,由于要等此次刷新结束后才开始写,所以205000ns时并不能立即写,要延后到下一个刷新结束,激活第0行开始写数据8411。
写满一行时还没到15us一次的刷新,因此进行激活第1行操作,继续写。
230000ns又要刷新了,跳出写,进行刷新命令。
写完后,中间无命令的一段时间每15us进行一次刷新。
320000us后开始读操作,数据8411,读数正常。
读完一行开始换行,需要激活下一行。因为上述的延迟2周期的数据,出现上图情况,也是正常情况。
读数据过程中遇到刷新命令,中断读而进行刷新,完成刷新后继续读。
数据读完后没有其他命令,则进行每15us的自动刷新操作。至此仿真结束,没有问题。整个SDRAM的功能模块也写完,接下来编写完成整个控制器系统。
12.通信处理模块
在写完SDRAM模块后呢,其他的代码就简单很多啦,先写一个串口通信的通信处理模块,其作用是将rx模块发送过来的8位数据写到wfifo中,并给出读写的触发信号到sdram模块。代码及注释如下:
module ctrl_rxtx
(
input clk,
input rst_n,
input flag_uart,
input [7:0] uart_data,
output wire wr_trigger,
output wire rd_trigger,
output wire wfifo_wr_en,
output wire [7:0] wfifo_wr_data
);
//参数
localparam RX_NUM_END = 'd4;
//内部信号
reg [2:0] rx_num; //接收数据的个数,此数据是有rx模块给出的8位数据,一共接收五个
reg [7:0] cmd_reg;//数据寄存器
//功能
//接收数据计数
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rx_num <= 0;
end
else if(flag_uart && rx_num == 'd0 && uart_data == 8'haa)
rx_num <= 0;
else if(flag_uart && rx_num == RX_NUM_END)
rx_num <= 0;
else if(flag_uart)
rx_num <= rx_num + 1;
end
//数据寄存,命令也是从数据这出来的
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
cmd_reg <= 8'h00;
end
else if(rx_num == 'd0 && flag_uart)
cmd_reg <= uart_data;
end
//接受到第四个数据且为命令指示数据55时,给出写触发信号
assign wr_trigger = (rx_num == RX_NUM_END && cmd_reg == 8'h55)?flag_uart:0;
//没有接收新数据且为命令指示数据aa时,给出读触发信号
assign rd_trigger = (rx_num == 'd0 && uart_data == 8'haa)?flag_uart:0;
//写到wfifo的使能,有数据过来时,就写到wfifo中
assign wfifo_wr_en = (rx_num >= 'd1)?flag_uart:0;
//写入wfifo的数据
assign wfifo_wr_data = uart_data;
endmodule
13.顶层模块top
按照第一节的图,在每个模块之间多加了关于读写fifo的接口,在top模块中连线如下:
首先我们要生成一个fifo的ip,调用其FIFO的.v文件,具体的创建参照FPGA实例03——FIFO的IP核创建及16位输入转8位输出
具体的fifo IP文件如下,我们只需要例化出一个写fifo和一个读fifo即可,此fifo为16深度,数据8位,同步fifo(读写都是相同的时钟)
//synopsys translate_off
`timescale 1 ps / 1 ps
// synopsys translate_on
module fifo_ip (
clock,
data,
rdreq,
wrreq,
empty,
q);
input clock;
input [7:0] data;
input rdreq;
input wrreq;
output empty;
output [7:0] q;
wire sub_wire0;
wire [7:0] sub_wire1;
wire empty = sub_wire0;
wire [7:0] q = sub_wire1[7:0];
scfifo scfifo_component (
.clock (clock),
.data (data),
.rdreq (rdreq),
.wrreq (wrreq),
.empty (sub_wire0),
.q (sub_wire1),
.aclr (),
.almost_empty (),
.almost_full (),
.full (),
.sclr (),
.usedw ());
defparam
scfifo_component.add_ram_output_register = "OFF",
scfifo_component.intended_device_family = "Cyclone IV E",
scfifo_component.lpm_numwords = 16,
scfifo_component.lpm_showahead = "OFF",
scfifo_component.lpm_type = "scfifo",
scfifo_component.lpm_width = 8,
scfifo_component.lpm_widthu = 4,
scfifo_component.overflow_checking = "ON",
scfifo_component.underflow_checking = "ON",
scfifo_component.use_eab = "ON";
endmodule
接着我们需要在sdram的读和写模块中加入fifo的使能和数据接口:
①由写模块给写使能到写fifo中,使得缓存在写fifo中的数据像sdram的写模块传输,注意数据要与写突发对齐,不然数据必定出错
②由读模块给读使能到读fifo中,使得读fifo读取sdram的数据,注意数据要与读突发对齐,不然数据必定出错
③在各个模块直接接入写fifo以及读fifo的接口(有点繁杂)
④将sdram模块本来的写2行操作改为写4个数据
⑤分析整个控制器的数据流:
最后的顶层代码如下:
module top
(
input clk,
input rst_n,
input rx,
output tx,
output wire sdram_clk,
output wire sdram_cke,
output wire sdram_cs,
output wire sdram_cas,
output wire sdram_ras,
output wire sdram_we,
output wire [1 :0 ] sdram_bank,
output wire [11:0 ] sdram_addr,
output wire [1 :0 ] sdram_dqm,
inout [15:0 ] sdram_dq
);
//内部信号,中间连接点
wire [7:0] data; //rx出来的8位数据,先给到通信模块,再给到wfifo,在给到SDRAM模块,再到rfifo,最后输出
wire tx_trigger;
wire sdram_wr_trigger;
wire sdram_rd_trigger;
wire wfifo_wr_en;
wire [7:0] wfifo_wr_data;
wire wfifo_rd_en;
wire [7:0] wfifo_rd_data;
wire [7:0] rfifo_wr_data;
wire rfifo_wr_en;
wire [7:0] rfifo_rd_data;
wire rfifo_rd_en;
wire rfifo_empty;
uart_rx u1_rx
(
.clk (clk),
.rst_n (rst_n),
.rx_uart (rx),
.data (data),
.rx_over (tx_trigger)
);
ctrl_rxtx u2_control
(
.clk (clk),
.rst_n (rst_n),
.flag_uart (tx_trigger),
.uart_data (data),
.wr_trigger (sdram_wr_trigger),
.rd_trigger (sdram_rd_trigger),
.wfifo_wr_en (wfifo_wr_en),
.wfifo_wr_data (wfifo_wr_data)
);
ip_fifo u3_wfifo
(
.clock (clk),
.data (wfifo_wr_data),
.rdreq (wfifo_rd_en),
.wrreq (wfifo_wr_en),
.empty ( ),
.q (wfifo_rd_data)
);
ip_fifo u4_rfifo
(
.clock (clk),
.data (rfifo_wr_data),
.rdreq (rfifo_rd_en),
.wrreq (rfifo_wr_en),
.empty (rfifo_empty),
.q (rfifo_rd_data)
);
uart_tx u5_tx
(
.clk (clk),
.rst_n (rst_n),
.tx (tx),
.rfifo_empty (rfifo_empty),
.rfifo_rd_data (rfifo_rd_data),
.rfifo_rd_en (rfifo_rd_en)
);
sdram_top u6_sdram
(
.clk (clk),
.rst_n (rst_n),
.wr_trigger (sdram_wr_trigger),
.rd_trigger (sdram_rd_trigger),
.wfifo_rd_data (wfifo_rd_data),
.wfifo_rd_en (wfifo_rd_en ),
.rfifo_wr_en (rfifo_wr_en ),
.rfifo_wr_data (rfifo_wr_data),
.sdram_clk (sdram_clk ),
.sdram_cke (sdram_cke ),
.sdram_cs (sdram_cs ),
.sdram_cas (sdram_cas ),
.sdram_ras (sdram_ras ),
.sdram_we (sdram_we ),
.sdram_bank (sdram_bank ),
.sdram_addr (sdram_addr ),
.sdram_dqm (sdram_dqm ),
.sdram_dq (sdram_dq )
);
endmodule
14.上板!
根据我们的fpga板子的原理图,对应设置好管脚,最后进行上版实验,当然我这里是最终的结果,并不是一次就实现了功能,其中修修改改了好多细节的地方,所以说时序的设计要想的非常的全面,不然就得慢慢调试,这个过程太苦了!下面是最终的功能:发送55表示写入,再写入4个数据,aa表示读出
板级调试结束!
至此,整个项目结束。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/160074.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...