目录
以下内容学习自开源骚客教程: SDRAM那些事儿第一季。至此,SDRAM那些事儿第一季教程 —— 小结 的各模块设计细节的修缮与补完已经全部完成。
一、命令解析模块
1.1 模块时序图
整完 SDRAM 再来整这个可太简单了(虽然 SDRAM 仔细理解以后也没觉得特别复杂):
1.2 模块源码
1.2.1 cmd_decode.v
module cmd_decode
(
// system signals
input sclk , // 板载系统时钟 50MHz
input s_rst_n , // 复位信号,低电平有效
// from UART_RX module
input uart_flag ,
input [7:0] uart_data ,
//
output wire wr_trig ,
output wire rd_trig ,
output wire wfifo_wr_en ,
output wire [7:0] wfifo_data // 可有可无
);
/**************************************************************************/
/***************** Define Parameter and Internal Signals ******************/
/**************************************************************************/
localparam REC_NUM_END = 'd4 ;
reg [2:0] rec_num ;
reg [7:0] cmd_reg ;
/**************************************************************************/
/******************************* Main Code ********************************/
/**************************************************************************/
// rec_num
always @(posedge sclk or negedge s_rst_n)
begin
if(s_rst_n == 1'b0)
rec_num <= 'd0;
else if(uart_flag == 1'b1 && rec_num == 'd0 && uart_data == 8'haa)
rec_num <= 'd0;
else if(uart_flag == 1'b1 && rec_num == REC_NUM_END)
rec_num <= 'd0;
else if(uart_flag == 1'b1)
rec_num <= rec_num + 1'b1;
end
// cmd_reg
always @(posedge sclk or negedge s_rst_n)
begin
if(s_rst_n == 1'b0)
cmd_reg <= 8'h00;
else if(rec_num <= 'd0 && uart_flag == 1'b1)
cmd_reg <= uart_data;
end
assign wr_trig = (rec_num == REC_NUM_END && cmd_reg == 8'h55) ? uart_flag : 1'b0;
assign rd_trig = (rec_num == 'd0 && uart_data == 8'haa) ? uart_flag : 1'b0;
assign wfifo_wr_en = (rec_num >= 'd1) ? uart_flag : 1'b0;
assign wfifo_data = uart_data;
endmodule
1.2.2 tb_cmd_decode.v
`timescale 1ns/1ns
module tb_cmd_decode;
reg sclk;
reg s_rst_n;
reg uart_flag;
reg [7:0] uart_data;
wire wr_trig;
wire rd_trig;
wire wfifo_wr_en;
wire [7:0] wfifo_data;
initial begin
sclk = 1;
s_rst_n <= 0;
#100 // 延时 100ns
s_rst_n <= 1; // 释放复位信号
end
always #5 sclk = ~sclk; // 模拟系统时钟,延时 5ns 翻转一次,10ns 一个周期
initial
begin
uart_flag <= 0;
uart_data <= 0;
#200
uart_flag <= 1;
uart_data <= 8'h55; // 写命令
#10
uart_flag <= 0;
#200
uart_flag <= 1;
uart_data <= 8'h12;
#10
uart_flag <= 0;
#200
uart_flag <= 1;
uart_data <= 8'h34;
#10
uart_flag <= 0;
#200
uart_flag <= 1;
uart_data <= 8'h56;
#10
uart_flag <= 0;
#200
uart_flag <= 1;
uart_data <= 8'h78;
#10
uart_flag <= 0;
#200
uart_flag <= 1;
uart_data <= 8'haa; // 读命令
#10
uart_flag <= 0;
end
cmd_decode cmd_decode_inst
(
// system signals
.sclk (sclk), // 板载系统时钟 50MHz
.s_rst_n (s_rst_n), // 复位信号,低电平有效
// from UART_RX module
.uart_flag (uart_flag),
.uart_data (uart_data),
//
.wr_trig (wr_trig),
.rd_trig (rd_trig),
.wfifo_wr_en (wfifo_wr_en),
.wfifo_data (wfifo_data) // 可有可无
);
endmodule
1.3 Modelsim仿真
改一改 run.do 文件:
##create work library
vlib work
vlog "./tb_cmd_decode.v"
vlog "../../rtl/cmd_decode.v"
vsim -voptargs=+acc work.tb_cmd_decode
# Set the window types
view wave
view structure
view signals
add wave -divider {tb_cmd_decode}
add wave tb_cmd_decode/*
add wave -divider {cmd_decode}
add wave tb_cmd_decode/cmd_decode_inst/*
.main clear
run 10us
仿真波形如下图:
二、读写FIFO与整体仿真
2.1 模块时序图
2.2 使用FIFO IP核
打开 Quartus II 软件,选择菜单栏 Tools
⟶
\longrightarrow
⟶ MegaWizard Plug-In Manager:
检索要生成的 IP 核,并指定存放位置,之后会进入到 IP 核属性的配置页面:
第一页,右侧配置 FIFO 的深度和数据位宽,中部可选择读写是否公用时钟信号:
第二页可以选择是否使用一些标志位和信息,比如满标志、空标志以及 FIFO 已经存储的数据数量:
最后一页可以选择生成例化模板文件:
之后就可以在设计中例化 FIFO 模块了。
2.3 Modelsim仿真
因为最终源码占用的篇幅太大,而且 SDRAM 那些事儿第一季教程里设计出来的 SDRAM 控制器本身实际上并不完善(属于定制型而非 IP 核式的),所以博主最后权衡许久,还是感觉贴上一堆让人不明所以的改动后源码不如仔细分析一次整体的仿真时序图更有实际价值一些。
2.3.1 uart_rx 与 cmd_decode
这两个模块之间的连接非常顺利,uart_rx 模块接收到的数据和指令在 cmd_decode 模块里成功实现了分离与解析(8’h55 后计入 4 个数据触发写入,8’haa触发读出)。
2.3.2 sdram_write 与 sdram_top
接收到 cmd_decode 模块发送的写入信号,sdram_write 模块进行正常的写操作(要想正确地将数据写入 SDRAM,必须保证 wfifo 的读使能、读数据和 SDRAM 的写数据、列地址信号对齐)。
写完以后 sdram_write 模块的状态机恢复到 IDLE 状态,sdram_top 模块下的状态机恢复到 ARBIT 状态。
2.3.3 sdram_read 与 sdram_top
写触发信号之后紧接的就是读触发信号。sdram_read 模块的工作机制与 sdram_write 模块完全相同,只是这里需要额外注意 rfifo 的写使能信号与 SDRAM 的读数据对齐(如果没有对齐,强行延拍对齐也可以,毕竟 CL = 3 是硬性限制)。
同样在读完以后 sdram_write 模块的状态机恢复到 IDLE 状态,sdram_top 模块下的状态机恢复到 ARBIT 状态。
2.3.4 uart_tx
uart_tx 模块调整的幅度稍大一些,也是因为数据对齐的问题。主要是需要保证每次 rfifo 的读使能只从 rfifo 里读出一个数据,发完再读下一个数据。