参考
黑金AX301的SDRAM
开发板板载了一片 SDRAM,型号:HY57V2562GTR,容量:256Mit
(16M*16bit),16bit 总线。SDRAM 在整个 FPGA 系统中扮演的角色相当于
电脑中的内存条,是整个 FPGA 的缓存区,可以暂存数据。当我们使用 NIOS II
软核的时候,由于占用的资源多,FPGA 内部的 ONCHIP RAM 无法满足需求,
所以,SDRAM 作为系统的"内存",保证了整个系统的稳定运行。
目标
点击KEY2, 向地址 0 ~ 15 写入16字节数据
然后读出, 如果读写一致则 LED1 亮, 否则LED1闪
HC_FPGA_Demo_Top.v
verilog
//=============================================================================
// 文件名 : HC_FPGA_Demo_Top.v
// 功能 : SDRAM 读写测试顶层模块
// 说明 : 本工程只使用按键 / LED / 串口 / SDRAM,不依赖 VGA、OV7670、LCD。
//
// 使用方法:
// 1) 下载程序后,打开串口终端,设置 115200 波特率 8N1;
// SDRAM 初始化完成后会收到 "SDRAM TEST READY";
// 2) 按下 KEY2:向 SDRAM 地址 0 写入 16 字节(8 个 16bit 字),
// 再读回比对;
// PASS -> LED1 常亮,串口打印 "PASS" 和 "RDATA ..."(读回全 8 字);
// FAIL -> LED1 闪烁,串口打印 "FAIL n=.. exp=.. got=..";
// 3) 未按键或测试进行中时 LED1 保持熄灭(默认灯灭)。
//=============================================================================
module HC_FPGA_Demo_Top
(
input CLOCK_XTAL_50MHz, // 板载 50MHz 晶振输入
input RESET, // 外部复位按键(低有效)
//---------- 按键 ----------
input KEY2, // 测试触发按键
//---------- 串口 ----------
output TXD, // 串口发送(115200, 8N1)
//---------- SDRAM 引脚 ----------
output S_CLK, // SDRAM 时钟
output S_CKE, // 时钟使能
output S_NCS, // 片选(低有效)
output S_NRAS, // 行选通(低有效)
output S_NCAS, // 列选通(低有效)
output S_NWE, // 写使能(低有效)
output [12:0] S_A, // 地址总线
output [1:0] S_BA, // Bank 地址
output [1:0] S_DQM, // 数据掩码(低有效,本设计恒 0 使能)
inout [15:0] S_DB, // 16bit 数据总线
//---------- LED ----------
output LED1 // 测试结果指示:常亮=通过,闪烁=失败
);
//-----------------------------------------------------------------------------
// PLL 时钟 / 系统复位
// CLOCK_133MHz : SDRAM 时钟(相位 0)
// CLOCK_133MHz_SHIFT : SDRAM 控制器时钟(相位 -180°)
// CLOCK_25MHz / CLOCK_24MHz : 保留时钟(本工程未使用)
// SYS_RESET_N : 系统复位(低有效)
//-----------------------------------------------------------------------------
wire CLOCK_133MHz;
wire CLOCK_133MHz_SHIFT;
wire CLOCK_25MHz;
wire CLOCK_24MHz;
wire SYS_RESET_N;
sys_ctrl sys_inst
(
.ext_clk (CLOCK_XTAL_50MHz),
.ext_rst_n (RESET),
.sys_rst_n (SYS_RESET_N),
.clk_133mhz (CLOCK_133MHz),
.clk_133mhz_shift(CLOCK_133MHz_SHIFT),
.clk_25mhz (CLOCK_25MHz),
.clk_24mhz (CLOCK_24MHz)
);
//-----------------------------------------------------------------------------
// 按键消抖:按下 KEY2 输出 1 拍脉冲 key_pulse
//-----------------------------------------------------------------------------
wire key_pulse;
key_debounce key_debounce_inst
(
.i_clk (CLOCK_XTAL_50MHz),
.i_rst_n (SYS_RESET_N),
.i_key_n (KEY2),
.o_key_pulse(key_pulse)
);
//-----------------------------------------------------------------------------
// 按键脉冲同步到 133MHz 控制器时钟域,作为测试触发
//-----------------------------------------------------------------------------
wire test_trigger;
sync_pulse sync_key_inst
(
.i_clk_dst (CLOCK_133MHz_SHIFT),
.i_rst_n (SYS_RESET_N),
.i_pulse_in (key_pulse),
.o_pulse_out (test_trigger)
);
//-----------------------------------------------------------------------------
// SDRAM 测试控制器(133MHz 时钟域,直连 sdram_top,无 FIFO)
//-----------------------------------------------------------------------------
wire sdram_wr_req; // 写请求
wire sdram_rd_req; // 读请求
wire sdram_wr_ack; // 写应答
wire sdram_rd_ack; // 读应答
wire [23:0] sys_wraddr; // 写地址
wire [23:0] sys_rdaddr; // 读地址
wire [15:0] sys_data_in; // 写数据
wire [15:0] sys_data_out; // 读数据
wire [9:0] sdwr_byte; // 写突发长度
wire [9:0] sdrd_byte; // 读突发长度
wire sdram_init_done;// SDRAM 初始化完成
wire test_pass; // 测试通过
wire test_fail; // 测试失败
wire [3:0] fail_idx; // 出错字序号
wire [15:0] fail_exp; // 期望值
wire [15:0] fail_got; // 实际值
wire [127:0] rd_data; // 读回数据打包(8 个字)
sdram_test u_sdram_test
(
.i_clk (CLOCK_133MHz_SHIFT),
.i_rst_n (SYS_RESET_N),
.i_trigger (test_trigger),
.i_sdram_init_done (sdram_init_done),
.o_sdram_wr_req (sdram_wr_req),
.o_sdram_rd_req (sdram_rd_req),
.i_sdram_wr_ack (sdram_wr_ack),
.i_sdram_rd_ack (sdram_rd_ack),
.o_sys_wraddr (sys_wraddr),
.o_sys_rdaddr (sys_rdaddr),
.o_sys_data_in (sys_data_in),
.i_sys_data_out (sys_data_out),
.o_sdwr_byte (sdwr_byte),
.o_sdrd_byte (sdrd_byte),
.o_test_pass (test_pass),
.o_test_fail (test_fail),
.o_test_done (),
.o_fail_idx (fail_idx),
.o_fail_exp (fail_exp),
.o_fail_got (fail_got),
.o_rd_data (rd_data)
);
//-----------------------------------------------------------------------------
// SDRAM 控制器(直接例化 sdram_top)
// S_CLK 直接由 133MHz 时钟提供;DQM 恒为 0,两个字节都使能
//-----------------------------------------------------------------------------
assign S_CLK = CLOCK_133MHz;
assign S_DQM = 2'b00;
sdram_top u_sdram_top
(
.i_clk (CLOCK_133MHz_SHIFT),
.i_rst_n (SYS_RESET_N),
.i_sdram_wr_req (sdram_wr_req),
.i_sdram_rd_req (sdram_rd_req),
.o_sdram_wr_ack (sdram_wr_ack),
.o_sdram_rd_ack (sdram_rd_ack),
.i_sys_wraddr (sys_wraddr),
.i_sys_rdaddr (sys_rdaddr),
.i_sys_data_in (sys_data_in),
.o_sys_data_out (sys_data_out),
.i_sdwr_byte (sdwr_byte),
.i_sdrd_byte (sdrd_byte),
.o_sdram_init_done (sdram_init_done),
.o_sdram_cke (S_CKE),
.o_sdram_cs_n (S_NCS),
.o_sdram_ras_n (S_NRAS),
.o_sdram_cas_n (S_NCAS),
.o_sdram_we_n (S_NWE),
.o_sdram_ba (S_BA),
.o_sdram_addr (S_A),
.io_sdram_data (S_DB)
);
//-----------------------------------------------------------------------------
// SDRAM 初始化完成信号同步到 50MHz 域,用于串口打印 READY
//-----------------------------------------------------------------------------
wire init_done_pulse;
sync_pulse sync_init_inst
(
.i_clk_dst (CLOCK_XTAL_50MHz),
.i_rst_n (SYS_RESET_N),
.i_pulse_in (sdram_init_done),
.o_pulse_out (init_done_pulse)
);
//-----------------------------------------------------------------------------
// 测试结果同步到 50MHz 域(电平信号,跨时钟安全)
//-----------------------------------------------------------------------------
wire test_pass_s;
wire test_fail_s;
sync_level sync_pass_inst
(
.i_clk_dst (CLOCK_XTAL_50MHz),
.i_rst_n (SYS_RESET_N),
.i_level_in (test_pass),
.o_level_out (test_pass_s)
);
sync_level sync_fail_inst
(
.i_clk_dst (CLOCK_XTAL_50MHz),
.i_rst_n (SYS_RESET_N),
.i_level_in (test_fail),
.o_level_out (test_fail_s)
);
//-----------------------------------------------------------------------------
// 结果脉冲:测试结果电平保持到下次触发,因此在 50MHz 域检测
// (pass|fail) 的 0->1 上升沿作为"测试完成"脉冲
//-----------------------------------------------------------------------------
wire result_any = test_pass_s | test_fail_s;
reg result_any_d;
always @(posedge CLOCK_XTAL_50MHz or negedge SYS_RESET_N)
begin
if(!SYS_RESET_N)
result_any_d <= 1'b0;
else
result_any_d <= result_any;
end
wire result_pulse = result_any & ~result_any_d;
//-----------------------------------------------------------------------------
// 在结果脉冲到来时锁存失败信息与读回数据(供串口打印)
// 说明:锁存后电平一直保持到下次测试,串口稍后读取时值仍然有效
//-----------------------------------------------------------------------------
reg [3:0] fail_idx_50;
reg [15:0] fail_exp_50;
reg [15:0] fail_got_50;
reg [127:0] rd_data_50;
always @(posedge CLOCK_XTAL_50MHz or negedge SYS_RESET_N)
begin
if(!SYS_RESET_N)
begin
fail_idx_50 <= 4'd0;
fail_exp_50 <= 16'd0;
fail_got_50 <= 16'd0;
rd_data_50 <= 128'd0;
end
else if(result_pulse)
begin
fail_idx_50 <= fail_idx;
fail_exp_50 <= fail_exp;
fail_got_50 <= fail_got;
rd_data_50 <= rd_data;
end
end
//-----------------------------------------------------------------------------
// LED1 指示:通过=常亮,失败=闪烁(周期 1s),空闲=熄灭
//-----------------------------------------------------------------------------
reg [25:0] blink_cnt; // 闪烁计数(50MHz)
reg led1_blink; // 闪烁电平
always @(posedge CLOCK_XTAL_50MHz or negedge SYS_RESET_N)
begin
if(!SYS_RESET_N)
begin
blink_cnt <= 26'd0;
led1_blink <= 1'b0;
end
else if(blink_cnt == 26'd25_000_000 - 1) // 0.5s 翻转一次
begin
blink_cnt <= 26'd0;
led1_blink <= ~led1_blink;
end
else
blink_cnt <= blink_cnt + 1'b1;
end
assign LED1 = test_pass_s ? 1'b1 : (test_fail_s ? led1_blink : 1'b0);
//-----------------------------------------------------------------------------
// 串口报告:初始化完成 / 测试开始 / 测试结果 / 读回数据
//-----------------------------------------------------------------------------
wire trig_pass = result_pulse & test_pass_s;
wire trig_fail = result_pulse & test_fail_s;
uart_tx_string u_uart_tx_string
(
.i_clk (CLOCK_XTAL_50MHz),
.i_rst_n (SYS_RESET_N),
.i_trig_ready(init_done_pulse),
.i_trig_start(key_pulse),
.i_trig_pass (trig_pass),
.i_trig_fail (trig_fail),
.i_fail_idx (fail_idx_50),
.i_fail_exp (fail_exp_50),
.i_fail_got (fail_got_50),
.i_rd_data (rd_data_50),
.o_txd (TXD)
);
endmodule
key_debounce.v
verilog
`timescale 1ns / 1ps
//=============================================================================
// 文件名 : key_debounce.v
// 功能 : 按键消抖模块
// 说明 : 按键按下为低电平。输入经过两级同步器消除亚稳态,再判断电平
// 稳定 20ms 后才认为按键有效,输出 1 拍的按下脉冲 o_key_pulse。
//=============================================================================
module key_debounce (
input i_clk, // 系统时钟(50MHz)
input i_rst_n, // 复位信号,低电平有效
input i_key_n, // 按键输入(按下为低)
output o_key_pulse // 按下瞬间输出 1 拍脉冲
);
// 消抖时间:20ms @50MHz = 1000000 个时钟
localparam L_DEBOUNCE_CNT = 20'd1_000_000;
//-----------------------------------------------------------------------------
// 寄存器定义
//-----------------------------------------------------------------------------
reg r_key_r1; // 同步器第一级
reg r_key_r2; // 同步器第二级(稳定后的按键电平)
reg [19:0] r_cnt; // 消抖计数
reg r_key_stable; // 消抖后的稳定电平
reg r_key_stable_d; // 稳定电平打拍(用于检测按下沿)
//-----------------------------------------------------------------------------
// 两级同步器:消除按键输入的亚稳态
//-----------------------------------------------------------------------------
always @(posedge i_clk or negedge i_rst_n)
begin
if(!i_rst_n)
begin
r_key_r1 <= 1'b1;
r_key_r2 <= 1'b1;
end
else
begin
r_key_r1 <= i_key_n;
r_key_r2 <= r_key_r1;
end
end
//-----------------------------------------------------------------------------
// 消抖:只有电平连续稳定 20ms 才更新稳定电平
// (按键抖动时电平频繁变化,计数会被不断清零)
//-----------------------------------------------------------------------------
always @(posedge i_clk or negedge i_rst_n)
begin
if(!i_rst_n)
begin
r_cnt <= 20'd0;
r_key_stable <= 1'b1;
end
else if(r_key_r2 != r_key_stable)
begin
if(r_cnt == L_DEBOUNCE_CNT - 1)
begin
r_cnt <= 20'd0;
r_key_stable <= r_key_r2; // 稳定满 20ms,接受新电平
end
else
r_cnt <= r_cnt + 1'b1;
end
else
r_cnt <= 20'd0;
end
// 稳定电平打拍,用于检测按下沿
always @(posedge i_clk or negedge i_rst_n)
begin
if(!i_rst_n)
r_key_stable_d <= 1'b1;
else
r_key_stable_d <= r_key_stable;
end
// 按下沿:稳定电平由高变低(按键按下)
assign o_key_pulse = ~r_key_stable & r_key_stable_d;
endmodule
sync_level.v
verilog
`timescale 1ns / 1ps
//=============================================================================
// 文件名 : sync_level.v
// 功能 : 跨时钟域电平同步模块
// 说明 : 用两级同步器把源时钟域的电平信号安全地同步到目标时钟域,
// 消除亚稳态。适用于持续保持的电平信号(如状态标志)。
//=============================================================================
module sync_level (
input i_clk_dst, // 目标时钟域时钟
input i_rst_n, // 复位信号,低电平有效
input i_level_in, // 源时钟域电平输入
output reg o_level_out // 目标时钟域电平输出
);
//-----------------------------------------------------------------------------
// 寄存器定义
//-----------------------------------------------------------------------------
reg r_s1; // 同步器第一级
// 两级同步器
always @(posedge i_clk_dst or negedge i_rst_n)
begin
if(!i_rst_n)
begin
r_s1 <= 1'b0;
o_level_out <= 1'b0;
end
else
begin
r_s1 <= i_level_in;
o_level_out <= r_s1;
end
end
endmodule
sync_pulse.v
verilog
`timescale 1ns / 1ps
//=============================================================================
// 文件名 : sync_pulse.v
// 功能 : 跨时钟域脉冲同步模块
// 说明 : 把源时钟域的脉冲信号同步到目标时钟域。
// i_pulse_in 可以为源时钟域的一个或多个时钟周期宽;
// o_pulse_out 在目标时钟域输出恰好 1 拍脉冲。
//=============================================================================
module sync_pulse (
input i_clk_dst, // 目标时钟域时钟
input i_rst_n, // 复位信号,低电平有效
input i_pulse_in, // 源时钟域脉冲输入
output o_pulse_out // 目标时钟域脉冲输出(1 拍)
);
//-----------------------------------------------------------------------------
// 寄存器定义
//-----------------------------------------------------------------------------
reg r_s1; // 同步器第一级
reg r_s2; // 同步器第二级(稳定后的脉冲电平)
reg r_s2_d; // 第二级打拍(用于上升沿检测)
// 两级同步器
always @(posedge i_clk_dst or negedge i_rst_n)
begin
if(!i_rst_n)
begin
r_s1 <= 1'b0;
r_s2 <= 1'b0;
end
else
begin
r_s1 <= i_pulse_in;
r_s2 <= r_s1;
end
end
// 第二级打拍
always @(posedge i_clk_dst or negedge i_rst_n)
begin
if(!i_rst_n)
r_s2_d <= 1'b0;
else
r_s2_d <= r_s2;
end
// 上升沿检测:输出 1 拍脉冲
assign o_pulse_out = r_s2 & ~r_s2_d;
endmodule
sys_ctrl.v
verilog
`timescale 1ns / 1ps
//=============================================================================
// 文件名 : sys_ctrl.v
// 功能 : 系统时钟与复位控制模块
// 说明 : 1) 对外部复位做两级同步,产生 PLL 的复位信号;
// 2) 例化两个 PLL:
// pll : 输出 25MHz(VGA 用)和 24MHz(摄像头用),
// 本工程仅用其产生系统复位时序;
// pll_sdram : 输出 133MHz(SDRAM 时钟)和
// 133MHz 相移时钟(SDRAM 控制器时钟);
// 3) 两个 PLL 都锁定后释放系统复位 sys_rst_n(异步复位、
// 同步释放),保证复位可靠。
//=============================================================================
module sys_ctrl
(
input ext_clk, // 外部 50MHz 输入时钟
input ext_rst_n, // 外部复位按键(低有效)
// PLL 输出时钟与系统复位
output reg sys_rst_n, // 系统复位信号(低有效)
output clk_133mhz, // 133MHz 时钟(SDRAM 时钟)
output clk_133mhz_shift, // 133MHz 相移时钟(SDRAM 控制器时钟)
output clk_25mhz, // 25MHz 时钟
output clk_24mhz // 24MHz 时钟
);
//-----------------------------------------------------------------------------
// 外部复位两级同步(在 50MHz 域)
//-----------------------------------------------------------------------------
reg rst_r1, rst_r2;
always @(posedge ext_clk or negedge ext_rst_n)
if(!ext_rst_n) rst_r1 <= 1'b0;
else rst_r1 <= 1'b1;
always @(posedge ext_clk or negedge ext_rst_n)
if(!ext_rst_n) rst_r2 <= 1'b0;
else rst_r2 <= rst_r1;
//-----------------------------------------------------------------------------
// PLL1:25MHz / 24MHz(本工程未直接使用,仅用于复位时序)
//-----------------------------------------------------------------------------
wire locked; // PLL1 锁定标志
pll pll_inst
(
.areset (!rst_r2),
.inclk0 (ext_clk),
.c0 (clk_25mhz),
.c1 (clk_24mhz),
.locked (locked)
);
//-----------------------------------------------------------------------------
// PLL2:133MHz / 133MHz 相移(SDRAM 用)
//-----------------------------------------------------------------------------
wire locked_sdram; // PLL2 锁定标志
pll_sdram pll_sdram_inst
(
.areset (!rst_r2),
.inclk0 (ext_clk),
.c0 (clk_133mhz),
.c1 (clk_133mhz_shift),
.locked (locked_sdram)
);
//-----------------------------------------------------------------------------
// 系统复位产生:两个 PLL 都锁定后才拉高(在 25MHz 域)
// 先产生内部复位寄存器 sys_rst_nr,再做异步复位、同步释放
//-----------------------------------------------------------------------------
reg sys_rst_nr;
always @(posedge clk_25mhz)
if(!(locked&locked_sdram)) sys_rst_nr <= 1'b0;
else sys_rst_nr <= 1'b1;
always @(posedge clk_25mhz or negedge sys_rst_nr)
if(!sys_rst_nr) sys_rst_n <= 1'b0;
else sys_rst_n <= sys_rst_nr;
endmodule
uart_tx_string.v
verilog
`timescale 1ns / 1ps
//=============================================================================
// 文件名 : uart_tx_string.v
// 功能 : 通过参数化 uart_tx 模块发送短 ASCII 字符串(115200,8N1)
// 消息 :
// i_trig_ready : "SDRAM TEST READY\r\n"
// i_trig_start : "TEST START\r\n"
// i_trig_pass : "PASS\r\n"
// i_trig_fail : "FAIL n=X exp=HHHH got=HHHH\r\n"
// 结果之后总是发 : "RDATA w0 w1 w2 w3 w4 w5 w6 w7\r\n"(读回全 8 字)
// 说明 : 发送过程中到达的触发信号会被缓存(pending 标志),不会丢失。
// SDRAM 测试结果在按键后几微秒就产生,而 "TEST START" 要发约
// 1ms,缓存机制保证结果消息一定发得出去。
//=============================================================================
module uart_tx_string (
//---------- 系统接口 ----------
input i_clk, // 系统时钟(50MHz)
input i_rst_n, // 复位信号,低电平有效
//---------- 触发信号(各 1 拍脉冲)----------
input i_trig_ready, // 触发:SDRAM 初始化完成
input i_trig_start, // 触发:测试开始
input i_trig_pass, // 触发:测试通过
input i_trig_fail, // 触发:测试失败
//---------- 失败信息 ----------
input [3 :0] i_fail_idx, // 出错字序号
input [15:0] i_fail_exp, // 期望值
input [15:0] i_fail_got, // 实际值
//---------- 读回数据(w0 在 [127:112])----------
input [127:0] i_rd_data, // 全部 8 个读回字
//---------- 输出 ----------
output o_txd // 串行发送引脚
);
//-----------------------------------------------------------------------------
// uart_tx 发送器例化
//-----------------------------------------------------------------------------
wire w_tx_busy; // 发送忙标志
reg r_tx_en; // 发送使能(1 拍脉冲)
reg [7:0] r_tx_data; // 当前待发送字节
uart_tx #(
.P_CLK_FREQ (50_000_000), // 系统时钟 50MHz
.P_UART_BPS (115200) // 波特率 115200
) u_uart_tx (
.i_clk (i_clk),
.i_rst_n (i_rst_n),
.i_uart_tx_en (r_tx_en),
.i_uart_tx_data (r_tx_data),
.o_uart_tx_busy (w_tx_busy),
.o_uart_txd (o_txd)
);
//-----------------------------------------------------------------------------
// 状态定义
//-----------------------------------------------------------------------------
localparam L_S_IDLE = 2'd0; // 空闲:检查待发消息
localparam L_S_SEND = 2'd1; // 发送中:逐字节发送当前消息
//-----------------------------------------------------------------------------
// 寄存器定义
//-----------------------------------------------------------------------------
reg [7:0] r_tx_buf [0:63]; // 消息缓冲区(最大 64 字节)
reg [5:0] r_msg_len; // 当前消息长度
reg [5:0] r_ptr; // 已发送字节指针
reg [1:0] r_state; // 状态机
reg r_pending_ready; // 待发:READY
reg r_pending_start; // 待发:START
reg r_pending_pass; // 待发:PASS
reg r_pending_fail; // 待发:FAIL
reg r_pending_rdata; // 待发:RDATA
reg r_tx_busy_d; // 忙标志打拍(检测发送完成下降沿)
//-----------------------------------------------------------------------------
// 忙标志下降沿 = 一个字节发送完成
//-----------------------------------------------------------------------------
always @(posedge i_clk or negedge i_rst_n)
begin
if(!i_rst_n)
r_tx_busy_d <= 1'b0;
else
r_tx_busy_d <= w_tx_busy;
end
wire w_tx_busy_fall = r_tx_busy_d & ~w_tx_busy;
//-----------------------------------------------------------------------------
// 消息缓冲区初始化(默认填 0)
//-----------------------------------------------------------------------------
integer k;
initial
for(k = 0; k < 64; k = k + 1)
r_tx_buf[k] = 8'h00;
//-----------------------------------------------------------------------------
// 4bit 十六进制数转 ASCII 字符(0~9 -> '0'~'9',A~F -> 'A'~'F')
//-----------------------------------------------------------------------------
function [7:0] f_hex2ascii;
input [3:0] nibble;
begin
f_hex2ascii = (nibble < 4'd10) ? (8'h30 + nibble) : (8'h41 + nibble - 4'd10);
end
endfunction
//-----------------------------------------------------------------------------
// 主状态机
//-----------------------------------------------------------------------------
always @(posedge i_clk or negedge i_rst_n)
begin
if(!i_rst_n)
begin
r_state <= L_S_IDLE;
r_tx_en <= 1'b0;
r_tx_data <= 8'h00;
r_ptr <= 6'd0;
r_msg_len <= 6'd0;
r_pending_ready <= 1'b0;
r_pending_start <= 1'b0;
r_pending_pass <= 1'b0;
r_pending_fail <= 1'b0;
r_pending_rdata <= 1'b0;
end
else
begin
// 默认:发送使能拉低(每次只发 1 拍脉冲)
r_tx_en <= 1'b0;
// 缓存触发信号,发送忙时也不丢失
if(i_trig_ready) r_pending_ready <= 1'b1;
if(i_trig_start) r_pending_start <= 1'b1;
if(i_trig_pass) r_pending_pass <= 1'b1;
if(i_trig_fail) r_pending_fail <= 1'b1;
// 只要有测试结果(通过/失败),RDATA 消息一起排队
if(i_trig_pass | i_trig_fail)
r_pending_rdata <= 1'b1;
case(r_state)
//---------- 空闲:按优先级发送待发消息 ----------
L_S_IDLE:
begin
if(r_pending_ready)
begin
// 消息 "SDRAM TEST READY\r\n"(18 字节)
r_tx_buf[0] <= "S"; r_tx_buf[1] <= "D"; r_tx_buf[2] <= "R"; r_tx_buf[3] <= "A";
r_tx_buf[4] <= "M"; r_tx_buf[5] <= " "; r_tx_buf[6] <= "T"; r_tx_buf[7] <= "E";
r_tx_buf[8] <= "S"; r_tx_buf[9] <= "T"; r_tx_buf[10] <= " "; r_tx_buf[11] <= "R";
r_tx_buf[12] <= "E"; r_tx_buf[13] <= "A"; r_tx_buf[14] <= "D"; r_tx_buf[15] <= "Y";
r_tx_buf[16] <= 8'h0D; r_tx_buf[17] <= 8'h0A;
r_msg_len <= 6'd18;
r_tx_data <= "S"; // 先发第一个字符
r_tx_en <= 1'b1;
r_ptr <= 6'd1; // 下一个要发的字节序号
r_pending_ready <= 1'b0;
r_state <= L_S_SEND;
end
else if(r_pending_start)
begin
// 消息 "TEST START\r\n"(12 字节)
r_tx_buf[0] <= "T"; r_tx_buf[1] <= "E"; r_tx_buf[2] <= "S"; r_tx_buf[3] <= "T";
r_tx_buf[4] <= " "; r_tx_buf[5] <= "S"; r_tx_buf[6] <= "T"; r_tx_buf[7] <= "A";
r_tx_buf[8] <= "R"; r_tx_buf[9] <= "T";
r_tx_buf[10] <= 8'h0D; r_tx_buf[11] <= 8'h0A;
r_msg_len <= 6'd12;
r_tx_data <= "T";
r_tx_en <= 1'b1;
r_ptr <= 6'd1;
r_pending_start <= 1'b0;
r_state <= L_S_SEND;
end
else if(r_pending_pass)
begin
// 消息 "PASS\r\n"(6 字节)
r_tx_buf[0] <= "P"; r_tx_buf[1] <= "A"; r_tx_buf[2] <= "S"; r_tx_buf[3] <= "S";
r_tx_buf[4] <= 8'h0D; r_tx_buf[5] <= 8'h0A;
r_msg_len <= 6'd6;
r_tx_data <= "P";
r_tx_en <= 1'b1;
r_ptr <= 6'd1;
r_pending_pass <= 1'b0;
r_state <= L_S_SEND;
end
else if(r_pending_fail)
begin
// 消息 "FAIL n=X exp=HHHH got=HHHH\r\n"(28 字节)
r_tx_buf[0] <= "F"; r_tx_buf[1] <= "A"; r_tx_buf[2] <= "I"; r_tx_buf[3] <= "L";
r_tx_buf[4] <= " "; r_tx_buf[5] <= "n"; r_tx_buf[6] <= "=";
r_tx_buf[7] <= 8'h30 + i_fail_idx; // 出错序号转 ASCII
r_tx_buf[8] <= " ";
r_tx_buf[9] <= "e"; r_tx_buf[10] <= "x"; r_tx_buf[11] <= "p"; r_tx_buf[12] <= "=";
r_tx_buf[13] <= f_hex2ascii(i_fail_exp[15:12]); // 期望值 4 位 hex
r_tx_buf[14] <= f_hex2ascii(i_fail_exp[11:8]);
r_tx_buf[15] <= f_hex2ascii(i_fail_exp[7:4]);
r_tx_buf[16] <= f_hex2ascii(i_fail_exp[3:0]);
r_tx_buf[17] <= " ";
r_tx_buf[18] <= "g"; r_tx_buf[19] <= "o"; r_tx_buf[20] <= "t"; r_tx_buf[21] <= "=";
r_tx_buf[22] <= f_hex2ascii(i_fail_got[15:12]); // 实际值 4 位 hex
r_tx_buf[23] <= f_hex2ascii(i_fail_got[11:8]);
r_tx_buf[24] <= f_hex2ascii(i_fail_got[7:4]);
r_tx_buf[25] <= f_hex2ascii(i_fail_got[3:0]);
r_tx_buf[26] <= 8'h0D; r_tx_buf[27] <= 8'h0A;
r_msg_len <= 6'd28;
r_tx_data <= "F";
r_tx_en <= 1'b1;
r_ptr <= 6'd1;
r_pending_fail <= 1'b0;
r_state <= L_S_SEND;
end
else if(r_pending_rdata)
begin
// 消息 "RDATA w0 w1 w2 w3 w4 w5 w6 w7\r\n"(47 字节)
r_tx_buf[0] <= "R"; r_tx_buf[1] <= "D"; r_tx_buf[2] <= "A";
r_tx_buf[3] <= "T"; r_tx_buf[4] <= "A"; r_tx_buf[5] <= " ";
r_tx_buf[6] <= f_hex2ascii(i_rd_data[127:124]); // 字 0
r_tx_buf[7] <= f_hex2ascii(i_rd_data[123:120]);
r_tx_buf[8] <= f_hex2ascii(i_rd_data[119:116]);
r_tx_buf[9] <= f_hex2ascii(i_rd_data[115:112]);
r_tx_buf[10] <= " ";
r_tx_buf[11] <= f_hex2ascii(i_rd_data[111:108]); // 字 1
r_tx_buf[12] <= f_hex2ascii(i_rd_data[107:104]);
r_tx_buf[13] <= f_hex2ascii(i_rd_data[103:100]);
r_tx_buf[14] <= f_hex2ascii(i_rd_data[99:96]);
r_tx_buf[15] <= " ";
r_tx_buf[16] <= f_hex2ascii(i_rd_data[95:92]); // 字 2
r_tx_buf[17] <= f_hex2ascii(i_rd_data[91:88]);
r_tx_buf[18] <= f_hex2ascii(i_rd_data[87:84]);
r_tx_buf[19] <= f_hex2ascii(i_rd_data[83:80]);
r_tx_buf[20] <= " ";
r_tx_buf[21] <= f_hex2ascii(i_rd_data[79:76]); // 字 3
r_tx_buf[22] <= f_hex2ascii(i_rd_data[75:72]);
r_tx_buf[23] <= f_hex2ascii(i_rd_data[71:68]);
r_tx_buf[24] <= f_hex2ascii(i_rd_data[67:64]);
r_tx_buf[25] <= " ";
r_tx_buf[26] <= f_hex2ascii(i_rd_data[63:60]); // 字 4
r_tx_buf[27] <= f_hex2ascii(i_rd_data[59:56]);
r_tx_buf[28] <= f_hex2ascii(i_rd_data[55:52]);
r_tx_buf[29] <= f_hex2ascii(i_rd_data[51:48]);
r_tx_buf[30] <= " ";
r_tx_buf[31] <= f_hex2ascii(i_rd_data[47:44]); // 字 5
r_tx_buf[32] <= f_hex2ascii(i_rd_data[43:40]);
r_tx_buf[33] <= f_hex2ascii(i_rd_data[39:36]);
r_tx_buf[34] <= f_hex2ascii(i_rd_data[35:32]);
r_tx_buf[35] <= " ";
r_tx_buf[36] <= f_hex2ascii(i_rd_data[31:28]); // 字 6
r_tx_buf[37] <= f_hex2ascii(i_rd_data[27:24]);
r_tx_buf[38] <= f_hex2ascii(i_rd_data[23:20]);
r_tx_buf[39] <= f_hex2ascii(i_rd_data[19:16]);
r_tx_buf[40] <= " ";
r_tx_buf[41] <= f_hex2ascii(i_rd_data[15:12]); // 字 7
r_tx_buf[42] <= f_hex2ascii(i_rd_data[11:8]);
r_tx_buf[43] <= f_hex2ascii(i_rd_data[7:4]);
r_tx_buf[44] <= f_hex2ascii(i_rd_data[3:0]);
r_tx_buf[45] <= 8'h0D; r_tx_buf[46] <= 8'h0A;
r_msg_len <= 6'd47;
r_tx_data <= "R";
r_tx_en <= 1'b1;
r_ptr <= 6'd1;
r_pending_rdata <= 1'b0;
r_state <= L_S_SEND;
end
end
//---------- 发送中:一个字节发完继续发下一个 ----------
L_S_SEND:
begin
if(w_tx_busy_fall)
begin
if(r_ptr == r_msg_len) // 消息全部发完
begin
r_state <= L_S_IDLE;
end
else
begin
r_tx_data <= r_tx_buf[r_ptr]; // 取下一个字符
r_tx_en <= 1'b1; // 触发发送
r_ptr <= r_ptr + 1'b1;
end
end
end
default: r_state <= L_S_IDLE;
endcase
end
end
endmodule
uart_tx.v
verilog
`timescale 1ns / 1ps
//=============================================================================
// 文件名 : uart_tx.v
// 功能 : UART 串口发送模块(8N1:1 起始位 + 8 数据位 + 1 停止位)
// 说明 : 时钟频率和波特率通过参数配置,默认 50MHz / 115200。
// 在 i_uart_tx_en 上升沿(且未在发送中)锁存数据并开始发送,
// o_uart_tx_busy 为发送中标志,发送完成自动拉低。
//=============================================================================
module uart_tx #(
parameter P_CLK_FREQ = 50_000_000, // 系统时钟频率(Hz)
parameter P_UART_BPS = 115200 // 波特率(bps)
) (
//---------- 系统接口 ----------
input i_clk , // 系统时钟
input i_rst_n , // 复位信号,低电平有效
input i_uart_tx_en , // 发送使能(上升沿触发)
input [7 : 0] i_uart_tx_data, // 待发送的 8 位数据
output reg o_uart_tx_busy, // 发送中标志
//---------- 输出 ----------
output reg o_uart_txd // 串行发送引脚
);
// 波特率分频计数上限:每发送一个 bit 需要 (P_CLK_FREQ / P_UART_BPS) 个时钟
localparam L_BAUD_CNT_MAX = P_CLK_FREQ / P_UART_BPS;
//-----------------------------------------------------------------------------
// 寄存器定义
//-----------------------------------------------------------------------------
reg [3:0] r_bit_cnt; // 位计数器:0=起始位,1~8=数据位,9=停止位
reg [15:0] r_baud_cnt; // 波特率分频计数器
reg [7 :0] r_tx_data_t; // 发送数据锁存寄存器
reg r_uart_tx_en_d; // 发送使能打拍(用于上升沿检测)
// i_uart_tx_en 上升沿标志
wire w_uart_tx_en_posedge;
//-----------------------------------------------------------------------------
// 检测 i_uart_tx_en 上升沿
//-----------------------------------------------------------------------------
always @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n)
r_uart_tx_en_d <= 1'b0;
else
r_uart_tx_en_d <= i_uart_tx_en;
end
assign w_uart_tx_en_posedge = i_uart_tx_en && !r_uart_tx_en_d;
//-----------------------------------------------------------------------------
// 波特率分频计数器:发送期间每个 bit 周期计数 0~L_BAUD_CNT_MAX-1
//-----------------------------------------------------------------------------
always @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n)
r_baud_cnt <= 16'd0;
else if (o_uart_tx_busy) begin
if (r_baud_cnt == L_BAUD_CNT_MAX - 1)
r_baud_cnt <= 16'd0; // 一个 bit 周期结束,清零
else
r_baud_cnt <= r_baud_cnt + 1'b1;
end else begin
r_baud_cnt <= 16'd0; // 空闲时保持清零
end
end
//-----------------------------------------------------------------------------
// 位计数器:每个 bit 周期加 1,共 10 位(起始+8 数据+停止)
//-----------------------------------------------------------------------------
always @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n)
r_bit_cnt <= 4'd0;
else if (o_uart_tx_busy && (r_baud_cnt == L_BAUD_CNT_MAX - 1))
r_bit_cnt <= r_bit_cnt + 1'b1;
else if (!o_uart_tx_busy)
r_bit_cnt <= 4'd0;
end
//-----------------------------------------------------------------------------
// 发送控制:上升沿锁存数据并置忙,10 位发送完清除忙标志
//-----------------------------------------------------------------------------
always @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n) begin
r_tx_data_t <= 8'd0;
o_uart_tx_busy <= 1'b0;
end
else if (w_uart_tx_en_posedge && !o_uart_tx_busy) begin
r_tx_data_t <= i_uart_tx_data; // 锁存待发送数据
o_uart_tx_busy <= 1'b1; // 进入发送状态
end
else if (o_uart_tx_busy && r_bit_cnt == 4'd9 && r_baud_cnt == L_BAUD_CNT_MAX - 1) begin
o_uart_tx_busy <= 1'b0; // 停止位发送完,清除忙标志
end
end
//-----------------------------------------------------------------------------
// 发送引脚输出:按位计数器输出起始位 / 数据位(LSB 在前)/ 停止位
//-----------------------------------------------------------------------------
always @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n)
o_uart_txd <= 1'b1;
else if (o_uart_tx_busy) begin
case(r_bit_cnt)
4'd0 : o_uart_txd <= 1'b0; // 起始位(低电平)
4'd1 : o_uart_txd <= r_tx_data_t[0]; // 数据位 0(LSB)
4'd2 : o_uart_txd <= r_tx_data_t[1];
4'd3 : o_uart_txd <= r_tx_data_t[2];
4'd4 : o_uart_txd <= r_tx_data_t[3];
4'd5 : o_uart_txd <= r_tx_data_t[4];
4'd6 : o_uart_txd <= r_tx_data_t[5];
4'd7 : o_uart_txd <= r_tx_data_t[6];
4'd8 : o_uart_txd <= r_tx_data_t[7];
4'd9 : o_uart_txd <= 1'b1; // 停止位(高电平)
default : o_uart_txd <= 1'b1;
endcase
end
else
o_uart_txd <= 1'b1; // 空闲时为高电平
end
endmodule
sdram_para.v
verilog
//=============================================================================
// 文件名 : sdram_para.v
// 功能 : SDRAM 控制器公共定义文件
// 说明 : 本文件通过 `include 被 sdram_ctrl / sdram_cmd / sdram_wr_data
// 三个模块引用,集中定义 SDRAM 的工作状态、初始化状态、命令字
// 以及各种时序结束条件的宏。这样三个模块之间共享同一套定义,
// 修改时序参数时只需要改一处。
//=============================================================================
//-----------------------------------------------------------------------------
// SDRAM 读写工作状态编码
//-----------------------------------------------------------------------------
`define W_IDLE 4'd0 // 空闲状态:等待写请求 / 读请求 / 刷新请求
`define W_ACTIVE 4'd1 // 行激活状态(ACTIVE 命令)
`define W_TRCD 4'd2 // 行激活等待(tRCD 时间)
`define W_READ 4'd3 // 读命令状态(READ 命令)
`define W_CL 4'd4 // 等待 CAS 潜伏期(CL 个时钟)
`define W_RD 4'd5 // 读数据状态(连续输出读突发数据)
`define W_RWAIT 4'd6 // 读后预充电等待(本设计未使用)
`define W_WRITE 4'd7 // 写命令状态(WRITE 命令)
`define W_WD 4'd8 // 写数据状态(连续写入写突发数据)
`define W_TDAL 4'd9 // 写后数据等待(tDAL 时间)
`define W_AR 4'd10 // 自动刷新状态(AUTO REFRESH 命令)
`define W_TRFC 4'd11 // 自动刷新等待(tRFC 时间)
//-----------------------------------------------------------------------------
// SDRAM 初始化状态编码
//-----------------------------------------------------------------------------
`define I_NOP 4'd0 // 等待上电 200us 稳定
`define I_PRE 4'd1 // 发送预充电命令(PRECHARGE)
`define I_TRP 4'd2 // 预充电等待(tRP)
`define I_AR1 4'd3 // 第一次自动刷新
`define I_TRF1 4'd4 // 第一次刷新等待(tRFC)
`define I_AR2 4'd5 // 第二次自动刷新
`define I_TRF2 4'd6 // 第二次刷新等待(tRFC)
`define I_MRS 4'd7 // 模式寄存器设置(LMR)
`define I_TMRD 4'd8 // 模式寄存器设置等待(tMRD)
`define I_DONE 4'd9 // 初始化完成
//-----------------------------------------------------------------------------
// 时序结束条件宏(配合 sdram_ctrl 中的参数和计数器使用)
// r_cnt_clk : sdram_ctrl 内部的 SDRAM 时序计数器
// i_cnt_clk : sdram_cmd 的输入计数(来自 sdram_ctrl 输出)
// i_sdwr_byte / i_sdrd_byte : 写 / 读突发长度(16bit 字个数)
//-----------------------------------------------------------------------------
`define end_trp r_cnt_clk == P_TRP_CLK // 预充电等待结束
`define end_trfc r_cnt_clk == P_TRFC_CLK // 刷新等待结束
`define end_tmrd r_cnt_clk == P_TMRD_CLK // 模式寄存器等待结束
`define end_trcd r_cnt_clk == P_TRCD_CLK-1 // 行激活等待结束
`define end_tcl r_cnt_clk == P_TCL_CLK-1 // CAS 潜伏期结束
`define end_rdburst i_cnt_clk == i_sdrd_byte-4 // 读突发终止点(发 B_STOP)
`define end_tread r_cnt_clk == i_sdrd_byte+2 // 读数据阶段结束
`define end_wrburst i_cnt_clk == i_sdwr_byte-1 // 写突发终止点(发 B_STOP)
`define end_twrite r_cnt_clk == i_sdwr_byte-1 // 写数据阶段结束
`define end_tdal r_cnt_clk == P_TDAL_CLK // 写后数据等待结束
`define end_trwait r_cnt_clk == P_TRP_CLK // 读后等待结束
//-----------------------------------------------------------------------------
// SDRAM 命令编码 {CKE, CS#, RAS#, CAS#, WE#}
//-----------------------------------------------------------------------------
`define CMD_INIT 5'b01111 // 上电初始化(全部无效电平)
`define CMD_NOP 5'b10111 // 空操作
`define CMD_ACTIVE 5'b10011 // 行激活
`define CMD_READ 5'b10101 // 读命令
`define CMD_WRITE 5'b10100 // 写命令
`define CMD_B_STOP 5'b10110 // 突发停止
`define CMD_PRGE 5'b10010 // 预充电
`define CMD_A_REF 5'b10001 // 自动刷新
`define CMD_LMR 5'b10000 // 模式寄存器设置
sdram_test.v
verilog
`timescale 1ns / 1ps
//=============================================================================
// 文件名 : sdram_test.v
// 功能 : SDRAM 一次性读写测试控制器(直连 sdram_top,数据通路无 FIFO)
// 流程 : 按键触发(i_trigger)后:
// 1) 向 SDRAM 地址 0 写入 8 个 16bit 字(共 16 字节),
// 写数据按 i_sdram_wr_ack 节拍依次送出;
// 2) 从地址 0 读回 8 个字,按 i_sdram_rd_ack 节拍依次捕获;
// 3) 与期望数据比对,输出测试结果。
// 结果 : o_test_pass / o_test_fail 电平保持到下次触发;
// o_fail_idx / o_fail_exp / o_fail_got 给出第一个出错字的
// 位置、期望值和实际值(8=写超时,9=读超时);
// o_rd_data 打包输出全部 8 个读回字供串口显示。
//
// 数据对齐说明(已按真实板卡 SDRAM 时钟与控制器时钟相差 180° 校准):
// - 写 : SDRAM 采样 WRITE 命令时,数据总线上的字就是 D0,
// 因此第一个字必须在 W_WRITE 周期(第一个写应答拍)送出,
// 之后每个写应答拍送下一个字;
// - 读 : CAS=3 时 D0 在 W_RD 计数 0 期间有效,读应答从计数 1 开始,
// 每个读应答拍捕获一个字,恰好得到 D0..D7。
//=============================================================================
module sdram_test (
input i_clk, // SDRAM 控制器时钟(clk_ref)
input i_rst_n, // 复位信号,低电平有效
input i_trigger, // 按键触发脉冲(已同步到 i_clk 域)
input i_sdram_init_done, // SDRAM 初始化完成标志
//---------- 直连 sdram_top 接口(无 FIFO)----------
output reg o_sdram_wr_req, // 写请求
output reg o_sdram_rd_req, // 读请求
input i_sdram_wr_ack, // 写应答(按拍送数据)
input i_sdram_rd_ack, // 读应答(按拍取数据)
output reg [23:0] o_sys_wraddr, // 写地址
output reg [23:0] o_sys_rdaddr, // 读地址
output reg [15:0] o_sys_data_in, // 写数据
input [15:0] i_sys_data_out, // 读数据
output reg [9:0] o_sdwr_byte, // 写突发长度(固定 8)
output reg [9:0] o_sdrd_byte, // 读突发长度(固定 8)
//---------- 测试结果输出 ----------
output reg o_test_pass, // 测试通过(保持到下次触发)
output reg o_test_fail, // 测试失败(保持到下次触发)
output reg o_test_done, // 测试完成脉冲(1 拍)
output reg [3:0] o_fail_idx, // 第一个出错字序号(0~7)
output reg [15:0] o_fail_exp, // 期望值
output reg [15:0] o_fail_got, // 实际值
// 打包读回数据:{w0,w1,w2,w3,w4,w5,w6,w7},w0 在 [127:112]
output reg [127:0] o_rd_data
);
//-----------------------------------------------------------------------------
// 状态定义
//-----------------------------------------------------------------------------
localparam L_S_IDLE = 3'd0, // 空闲:等待按键触发
L_S_WR = 3'd1, // 写数据:按写应答拍送 8 个字
L_S_WRWAIT = 3'd2, // 等待写突发结束(应答下降)
L_S_RD = 3'd3, // 读数据:按读应答拍收 8 个字
L_S_RDWAIT = 3'd4, // 等待读突发结束(应答下降)
L_S_DONE = 3'd5; // 比对并输出结果
// 超时时间:约 7.5ms @133MHz(正常读写只需几十个周期,防止死等)
localparam [19:0] L_TIMEOUT = 20'd1_000_000;
//-----------------------------------------------------------------------------
// 测试数据模式:8 个 16bit 字 = 16 字节
// 0000/FFFF 检查全 0 全 1;5A5A/A5A5 检查相邻位;其余为特征数据
//-----------------------------------------------------------------------------
function [15:0] f_test_data;
input [3:0] idx; // 字序号 0~7
begin
case (idx)
4'd0: f_test_data = 16'h0000;
4'd1: f_test_data = 16'hFFFF;
4'd2: f_test_data = 16'h5A5A;
4'd3: f_test_data = 16'hA5A5;
4'd4: f_test_data = 16'h1234;
4'd5: f_test_data = 16'h5678;
4'd6: f_test_data = 16'h9ABC;
4'd7: f_test_data = 16'hDEF0;
default: f_test_data = 16'h0000;
endcase
end
endfunction
//-----------------------------------------------------------------------------
// 寄存器定义
//-----------------------------------------------------------------------------
reg [2:0] r_state; // 状态寄存器
reg [3:0] r_idx; // 当前字序号(0~7)
reg [15:0] r_rd_buf [0:7]; // 读回数据缓存
reg [19:0] r_wait_cnt; // 超时计数
//-----------------------------------------------------------------------------
// 主状态机
//-----------------------------------------------------------------------------
always @(posedge i_clk or negedge i_rst_n)
begin
if(!i_rst_n)
begin
r_state <= L_S_IDLE;
r_idx <= 4'd0;
o_sdram_wr_req <= 1'b0;
o_sdram_rd_req <= 1'b0;
o_sys_wraddr <= 24'd0;
o_sys_rdaddr <= 24'd0;
o_sys_data_in <= 16'd0;
o_sdwr_byte <= 10'd8;
o_sdrd_byte <= 10'd8;
o_test_pass <= 1'b0;
o_test_fail <= 1'b0;
o_test_done <= 1'b0;
o_fail_idx <= 4'd0;
o_fail_exp <= 16'd0;
o_fail_got <= 16'd0;
o_rd_data <= 128'd0;
r_wait_cnt <= 20'd0;
end
else
begin
// 默认值:请求 / 完成脉冲默认拉低
o_sdram_wr_req <= 1'b0;
o_sdram_rd_req <= 1'b0;
o_test_done <= 1'b0;
case(r_state)
//---------- 空闲:按键触发且初始化完成后开始测试 ----------
L_S_IDLE:
begin
if(i_trigger && i_sdram_init_done)
begin
o_test_pass <= 1'b0; // 清除上次结果
o_test_fail <= 1'b0;
r_idx <= 4'd0;
o_sys_wraddr <= 24'd0; // 写地址 0
o_sys_rdaddr <= 24'd0; // 读地址 0
o_sys_data_in <= f_test_data(4'd0); // 先送第一个字
o_sdram_wr_req <= 1'b1; // 发起写请求
r_wait_cnt <= 20'd0;
r_state <= L_S_WR;
end
end
//---------- 写数据:每个写应答拍送一个字 ----------
L_S_WR:
begin
o_sdram_wr_req <= 1'b1;
if(i_sdram_wr_ack)
begin
if(r_idx == 4'd7) // 第 8 个字送完
begin
o_sdram_wr_req <= 1'b0;
r_state <= L_S_WRWAIT;
end
else
begin
r_idx <= r_idx + 1'b1;
o_sys_data_in <= f_test_data(r_idx + 1'b1); // 准备下一个字
end
end
else if(r_wait_cnt == L_TIMEOUT - 1) // 超时判写失败
begin
o_fail_idx <= 4'd8; // 8 表示写超时
o_fail_exp <= 16'h0000;
o_fail_got <= 16'hFFFF;
o_test_fail <= 1'b1;
o_test_pass <= 1'b0;
o_test_done <= 1'b1;
r_state <= L_S_IDLE;
end
else
r_wait_cnt <= r_wait_cnt + 1'b1;
end
//---------- 等待写突发完全结束(应答下降沿)----------
L_S_WRWAIT:
begin
if(!i_sdram_wr_ack)
begin
r_idx <= 4'd0;
o_sdram_rd_req <= 1'b1; // 立即发起读请求
r_wait_cnt <= 20'd0;
r_state <= L_S_RD;
end
else if(r_wait_cnt == L_TIMEOUT - 1)
begin
o_fail_idx <= 4'd8;
o_fail_exp <= 16'h0000;
o_fail_got <= 16'hFFFF;
o_test_fail <= 1'b1;
o_test_pass <= 1'b0;
o_test_done <= 1'b1;
r_state <= L_S_IDLE;
end
else
r_wait_cnt <= r_wait_cnt + 1'b1;
end
//---------- 读数据:每个读应答拍捕获一个字 ----------
L_S_RD:
begin
o_sdram_rd_req <= 1'b1;
if(i_sdram_rd_ack)
begin
r_rd_buf[r_idx] <= i_sys_data_out; // 捕获读回数据
if(r_idx == 4'd7) // 8 个字收完
begin
o_sdram_rd_req <= 1'b0;
r_state <= L_S_RDWAIT;
end
else
r_idx <= r_idx + 1'b1;
end
else if(r_wait_cnt == L_TIMEOUT - 1) // 超时判读失败
begin
o_fail_idx <= 4'd9; // 9 表示读超时
o_fail_exp <= 16'h0000;
o_fail_got <= 16'hFFFF;
o_test_fail <= 1'b1;
o_test_pass <= 1'b0;
o_test_done <= 1'b1;
r_state <= L_S_IDLE;
end
else
r_wait_cnt <= r_wait_cnt + 1'b1;
end
//---------- 等待读突发完全结束(应答下降沿)----------
L_S_RDWAIT:
begin
if(!i_sdram_rd_ack)
begin
r_state <= L_S_DONE;
end
else if(r_wait_cnt == L_TIMEOUT - 1)
begin
o_fail_idx <= 4'd9;
o_fail_exp <= 16'h0000;
o_fail_got <= 16'hFFFF;
o_test_fail <= 1'b1;
o_test_pass <= 1'b0;
o_test_done <= 1'b1;
r_state <= L_S_IDLE;
end
else
r_wait_cnt <= r_wait_cnt + 1'b1;
end
//---------- 比对并输出结果 ----------
L_S_DONE:
begin
// 打包全部读回数据供串口显示
o_rd_data <= {r_rd_buf[0], r_rd_buf[1], r_rd_buf[2], r_rd_buf[3],
r_rd_buf[4], r_rd_buf[5], r_rd_buf[6], r_rd_buf[7]};
// 全部 8 个字一致则通过
if(r_rd_buf[0] == f_test_data(4'd0) &&
r_rd_buf[1] == f_test_data(4'd1) &&
r_rd_buf[2] == f_test_data(4'd2) &&
r_rd_buf[3] == f_test_data(4'd3) &&
r_rd_buf[4] == f_test_data(4'd4) &&
r_rd_buf[5] == f_test_data(4'd5) &&
r_rd_buf[6] == f_test_data(4'd6) &&
r_rd_buf[7] == f_test_data(4'd7))
begin
o_test_pass <= 1'b1;
o_test_fail <= 1'b0;
end
else
begin
// 失败:记录第一个出错字的位置、期望值、实际值
o_test_fail <= 1'b1;
o_test_pass <= 1'b0;
if(r_rd_buf[0] != f_test_data(4'd0))
begin o_fail_idx <= 4'd0; o_fail_exp <= f_test_data(4'd0); o_fail_got <= r_rd_buf[0]; end
else if(r_rd_buf[1] != f_test_data(4'd1))
begin o_fail_idx <= 4'd1; o_fail_exp <= f_test_data(4'd1); o_fail_got <= r_rd_buf[1]; end
else if(r_rd_buf[2] != f_test_data(4'd2))
begin o_fail_idx <= 4'd2; o_fail_exp <= f_test_data(4'd2); o_fail_got <= r_rd_buf[2]; end
else if(r_rd_buf[3] != f_test_data(4'd3))
begin o_fail_idx <= 4'd3; o_fail_exp <= f_test_data(4'd3); o_fail_got <= r_rd_buf[3]; end
else if(r_rd_buf[4] != f_test_data(4'd4))
begin o_fail_idx <= 4'd4; o_fail_exp <= f_test_data(4'd4); o_fail_got <= r_rd_buf[4]; end
else if(r_rd_buf[5] != f_test_data(4'd5))
begin o_fail_idx <= 4'd5; o_fail_exp <= f_test_data(4'd5); o_fail_got <= r_rd_buf[5]; end
else if(r_rd_buf[6] != f_test_data(4'd6))
begin o_fail_idx <= 4'd6; o_fail_exp <= f_test_data(4'd6); o_fail_got <= r_rd_buf[6]; end
else
begin o_fail_idx <= 4'd7; o_fail_exp <= f_test_data(4'd7); o_fail_got <= r_rd_buf[7]; end
end
o_test_done <= 1'b1; // 完成脉冲
r_state <= L_S_IDLE; // 回到空闲等待下次触发
end
default: r_state <= L_S_IDLE;
endcase
end
end
endmodule
sdram_top.v
verilog
`timescale 1ns / 1ps
//=============================================================================
// 文件名 : sdram_top.v
// 功能 : SDRAM 控制器顶层封装模块
// 说明 : 将三个子模块封装成统一接口:
// sdram_ctrl ------ 状态机:初始化 / 刷新 / 读写调度
// sdram_cmd ------ 命令生成(把状态翻译成 SDRAM 引脚电平)
// sdram_wr_data------ 数据总线驱动与读数据捕获
//
// 直接接口使用方式(本工程测试路径不使用 FIFO):
// 写操作 : 拉高 i_sdram_wr_req,给出 i_sys_wraddr 和 i_sys_data_in,
// 每个 i_sdram_wr_ack 周期送入一个 16bit 字;
// 读操作 : 拉高 i_sdram_rd_req,给出 i_sys_rdaddr,
// 每个 i_sdram_rd_ack 周期从 o_sys_data_out 取回一个字。
// 注意 : sdwr_byte / sdrd_byte 表示突发长度(16bit 字个数)。
//=============================================================================
module sdram_top (
//---------- 系统接口 ----------
input i_clk, // SDRAM 控制器时钟(133MHz 相移时钟)
input i_rst_n, // 复位信号,低电平有效
//---------- 用户封装接口 ----------
input i_sdram_wr_req, // 写 SDRAM 请求(高有效)
input i_sdram_rd_req, // 读 SDRAM 请求(高有效)
output o_sdram_wr_ack, // 写应答:高电平期间按节拍送数据
output o_sdram_rd_ack, // 读应答:高电平期间按节拍取数据
input [23:0] i_sys_wraddr, // 写地址 {Bank[1:0],Row[12:0],Col[8:0]}
input [23:0] i_sys_rdaddr, // 读地址 {Bank[1:0],Row[12:0],Col[8:0]}
input [15:0] i_sys_data_in, // 写数据输入(16bit)
output [15:0] o_sys_data_out, // 读数据输出(16bit)
input [9:0] i_sdwr_byte, // 写突发长度(1~512 个字)
input [9:0] i_sdrd_byte, // 读突发长度(1~512 个字)
output o_sdram_init_done, // SDRAM 初始化完成标志(高有效)
//---------- SDRAM 硬件引脚接口 ----------
output o_sdram_cke, // 时钟使能
output o_sdram_cs_n, // 片选(低有效)
output o_sdram_ras_n, // 行地址选通(低有效)
output o_sdram_cas_n, // 列地址选通(低有效)
output o_sdram_we_n, // 写使能(低有效)
output [1:0] o_sdram_ba, // Bank 地址
output [12:0] o_sdram_addr, // 地址总线(行/列复用)
inout [15:0] io_sdram_data // 数据总线(双向)
);
//-----------------------------------------------------------------------------
// 内部互连信号
//-----------------------------------------------------------------------------
wire [3:0] w_init_state; // 初始化状态(连到 sdram_ctrl 输出、sdram_cmd 输入)
wire [3:0] w_work_state; // 工作状态(连到 sdram_ctrl 输出、sdram_cmd/sdram_wr_data 输入)
wire [9:0] w_cnt_clk; // SDRAM 时序计数器(连到 sdram_cmd 和 sdram_wr_data)
wire w_sys_r_wn; // 读写方向标志:1=读,0=写
//-----------------------------------------------------------------------------
// 1. SDRAM 控制状态机(初始化 / 刷新 / 读写调度 / 应答产生)
//-----------------------------------------------------------------------------
sdram_ctrl u_sdram_ctrl (
.i_clk (i_clk), // 控制器时钟
.i_rst_n (i_rst_n), // 复位
.i_sdram_wr_req (i_sdram_wr_req), // 写请求
.i_sdram_rd_req (i_sdram_rd_req), // 读请求
.i_sdwr_byte (i_sdwr_byte), // 写突发长度
.i_sdrd_byte (i_sdrd_byte), // 读突发长度
.o_sdram_wr_ack (o_sdram_wr_ack), // 写应答
.o_sdram_rd_ack (o_sdram_rd_ack), // 读应答
.o_sdram_init_done (o_sdram_init_done), // 初始化完成
.o_init_state (w_init_state), // 初始化状态输出
.o_work_state (w_work_state), // 工作状态输出
.o_cnt_clk (w_cnt_clk), // 时序计数器输出
.o_sys_r_wn (w_sys_r_wn) // 读写方向输出
);
//-----------------------------------------------------------------------------
// 2. SDRAM 命令生成(把状态机状态翻译为命令引脚电平)
//-----------------------------------------------------------------------------
sdram_cmd u_sdram_cmd (
.i_clk (i_clk),
.i_rst_n (i_rst_n),
.o_sdram_cke (o_sdram_cke), // 时钟使能
.o_sdram_cs_n (o_sdram_cs_n), // 片选
.o_sdram_ras_n (o_sdram_ras_n), // 行选通
.o_sdram_cas_n (o_sdram_cas_n), // 列选通
.o_sdram_we_n (o_sdram_we_n), // 写使能
.o_sdram_ba (o_sdram_ba), // Bank 地址
.o_sdram_addr (o_sdram_addr), // 地址总线
.i_sys_wraddr (i_sys_wraddr), // 写地址
.i_sys_rdaddr (i_sys_rdaddr), // 读地址
.i_sdwr_byte (i_sdwr_byte), // 写突发长度
.i_sdrd_byte (i_sdrd_byte), // 读突发长度
.i_init_state (w_init_state), // 初始化状态
.i_work_state (w_work_state), // 工作状态
.i_sys_r_wn (w_sys_r_wn), // 读写方向
.i_cnt_clk (w_cnt_clk) // 时序计数器
);
//-----------------------------------------------------------------------------
// 3. SDRAM 数据通路(写数据驱动总线、读数据捕获)
//-----------------------------------------------------------------------------
sdram_wr_data u_sdram_wr_data (
.i_clk (i_clk),
.i_rst_n (i_rst_n),
.io_sdram_data (io_sdram_data), // 双向数据总线
.i_sys_data_in (i_sys_data_in), // 写数据输入
.o_sys_data_out (o_sys_data_out), // 读数据输出
.i_work_state (w_work_state), // 工作状态
.i_cnt_clk (w_cnt_clk) // 时序计数器(本模块未使用)
);
endmodule
sdram_ctrl.v
verilog
`timescale 1ns / 1ps
//=============================================================================
// 文件名 : sdram_ctrl.v
// 功能 : SDRAM 控制器核心状态机
// 说明 : 完成三件事
// 1) 上电初始化:等待 200us 稳定后,依次执行
// 预充电 -> 两次自动刷新 -> 模式寄存器设置(LMR),最终初始化完成;
// 2) 定时刷新:每 15us 请求一次自动刷新,防止数据丢失;
// 3) 读写调度:根据写/读请求调度 ACTIVE -> READ/WRITE -> 数据
// 突发(含 BURST STOP 终止、写后等待 tDAL),并产生读写应答
// 信号 o_sdram_wr_ack / o_sdram_rd_ack 供外部按节拍送/取数据。
//
// 应答时序说明(本工程已按真实板卡相位时序校准):
// - 写应答在 W_WRITE 和 W_WD 阶段有效,持续 sdwr_byte 个时钟,
// 外部在每个应答周期送入一个字;
// - 读应答从 W_RD 计数 1 开始,持续 sdrd_byte 个时钟,
// 外部在每个应答周期取回一个字。
//=============================================================================
module sdram_ctrl (
//---------- 系统接口 ----------
input i_clk, // SDRAM 控制器时钟(133MHz 相移时钟)
input i_rst_n, // 复位信号,低电平有效
//---------- 用户封装接口 ----------
input i_sdram_wr_req, // 写 SDRAM 请求
input i_sdram_rd_req, // 读 SDRAM 请求
input [9:0] i_sdwr_byte, // 写突发长度(16bit 字个数)
input [9:0] i_sdrd_byte, // 读突发长度(16bit 字个数)
output o_sdram_wr_ack, // 写应答(高电平期间送数据)
output o_sdram_rd_ack, // 读应答(高电平期间取数据)
output o_sdram_init_done, // 初始化完成标志
//---------- 内部接口(供 sdram_cmd / sdram_wr_data 使用)----------
output [3:0] o_init_state, // 初始化状态
output [3:0] o_work_state, // 读写工作状态
output [9:0] o_cnt_clk, // SDRAM 时序计数器
output reg o_sys_r_wn // 读写方向:1=读,0=写
);
//-----------------------------------------------------------------------------
// 参数定义:SDRAM 各时序参数(以控制器时钟周期为单位)
//-----------------------------------------------------------------------------
parameter P_TRP_CLK = 9'd4, // 预充电等待 tRP(约 18ns)
P_TRFC_CLK = 9'd6, // 刷新等待 tRFC(约 45ns)
P_TMRD_CLK = 9'd6, // 模式寄存器设置等待 tMRD
P_TRCD_CLK = 9'd2, // 行激活等待 tRCD(约 15ns)
P_TCL_CLK = 9'd3, // CAS 潜伏期(与模式寄存器 CL=3 对应)
P_TDAL_CLK = 9'd3; // 写数据后等待 tDAL(tWR+tRP)
`include "sdram_para.v" // 引入状态 / 命令 / 时序宏定义
//-----------------------------------------------------------------------------
// 寄存器 / 线网定义
//-----------------------------------------------------------------------------
reg [14:0] r_cnt_200us; // 上电 200us 等待计数器(约 20000 个时钟)
reg [3:0] r_init_state; // 初始化状态寄存器
reg [10:0] r_cnt_15us; // 刷新周期计数器(约 1500 个时钟 = 11.3us)
reg r_sdram_ref_req; // 刷新请求标志
reg [3:0] r_work_state; // 读写工作状态寄存器
reg [9:0] r_cnt_clk; // SDRAM 时序计数器(各状态共用的节拍计数)
reg r_cnt_rst_n; // 时序计数器复位(低有效,组合逻辑产生)
wire w_done_200us; // 200us 等待完成标志
wire w_sdram_ref_ack; // 刷新应答(进入 W_AR 状态即应答)
//-----------------------------------------------------------------------------
// 上电 200us 等待:计数到 20000 后允许开始初始化
//-----------------------------------------------------------------------------
always @(posedge i_clk or negedge i_rst_n)
if(!i_rst_n) r_cnt_200us <= 15'd0;
else if(r_cnt_200us < 15'd20_000) r_cnt_200us <= r_cnt_200us + 1'b1;
assign w_done_200us = (r_cnt_200us == 15'd20_000);
//-----------------------------------------------------------------------------
// SDRAM 初始化状态机
// 顺序:等待 200us -> 预充电 -> 等待 tRP -> 刷新1 -> 等待 tRFC
// -> 刷新2 -> 等待 tRFC -> 模式寄存器设置 -> 等待 tMRD -> 完成
//-----------------------------------------------------------------------------
always @(posedge i_clk or negedge i_rst_n)
if(!i_rst_n) r_init_state <= `I_NOP;
else
case(r_init_state)
`I_NOP: r_init_state <= w_done_200us ? `I_PRE : `I_NOP;
`I_PRE: r_init_state <= `I_TRP;
`I_TRP: r_init_state <= (`end_trp) ? `I_AR1 : `I_TRP;
`I_AR1: r_init_state <= `I_TRF1;
`I_TRF1: r_init_state <= (`end_trfc) ? `I_AR2 : `I_TRF1;
`I_AR2: r_init_state <= `I_TRF2;
`I_TRF2: r_init_state <= (`end_trfc) ? `I_MRS : `I_TRF2;
`I_MRS: r_init_state <= `I_TMRD;
`I_TMRD: r_init_state <= (`end_tmrd) ? `I_DONE : `I_TMRD;
`I_DONE: r_init_state <= `I_DONE;
default: r_init_state <= `I_NOP;
endcase
assign o_init_state = r_init_state;
assign o_sdram_init_done = (r_init_state == `I_DONE); // 初始化完成标志
//-----------------------------------------------------------------------------
// 刷新周期计数器:每 15us 产生一次刷新请求(防止 SDRAM 数据丢失)
//-----------------------------------------------------------------------------
always @(posedge i_clk or negedge i_rst_n)
if(!i_rst_n) r_cnt_15us <= 11'd0;
else if(r_cnt_15us < 11'd1499) r_cnt_15us <= r_cnt_15us + 1'b1;
else r_cnt_15us <= 11'd0;
always @(posedge i_clk or negedge i_rst_n)
if(!i_rst_n) r_sdram_ref_req <= 1'b0;
else if(r_cnt_15us == 11'd1498) r_sdram_ref_req <= 1'b1; // 请求刷新
else if(w_sdram_ref_ack) r_sdram_ref_req <= 1'b0; // 刷新完成清除
//-----------------------------------------------------------------------------
// 读写 / 刷新工作状态机
// 空闲时按优先级处理:刷新请求 > 写请求 > 读请求。
// 读写流程:ACTIVE(行激活) -> tRCD 等待 -> READ/WRITE 命令
// -> CL 等待(读) -> 数据阶段(含 B_STOP 突发终止)
// -> 写后等待 tDAL(写)。
//-----------------------------------------------------------------------------
always @(posedge i_clk or negedge i_rst_n) begin
if(!i_rst_n)
r_work_state <= `W_IDLE;
else begin
case(r_work_state)
`W_IDLE:
if(r_sdram_ref_req & o_sdram_init_done) begin
r_work_state <= `W_AR; // 优先执行刷新
o_sys_r_wn <= 1'b1;
end
else if(i_sdram_wr_req & o_sdram_init_done) begin
r_work_state <= `W_ACTIVE; // 执行写操作
o_sys_r_wn <= 1'b0;
end
else if(i_sdram_rd_req & o_sdram_init_done) begin
r_work_state <= `W_ACTIVE; // 执行读操作
o_sys_r_wn <= 1'b1;
end
else begin
r_work_state <= `W_IDLE;
o_sys_r_wn <= 1'b1;
end
`W_ACTIVE:
if(P_TRCD_CLK == 0) // 若 tRCD=0 则跳过等待
if(o_sys_r_wn) r_work_state <= `W_READ;
else r_work_state <= `W_WRITE;
else r_work_state <= `W_TRCD;
`W_TRCD:
if(`end_trcd) // tRCD 等待结束
if(o_sys_r_wn) r_work_state <= `W_READ;
else r_work_state <= `W_WRITE;
else r_work_state <= `W_TRCD;
`W_READ: r_work_state <= `W_CL; // 读命令后进入 CL 等待
`W_CL: r_work_state <= (`end_tcl) ? `W_RD : `W_CL;
`W_RD: r_work_state <= (`end_tread) ? `W_IDLE : `W_RD;
`W_RWAIT: r_work_state <= (`end_trwait)? `W_IDLE : `W_RWAIT;
`W_WRITE: r_work_state <= `W_WD; // 写命令后进入写数据
`W_WD: r_work_state <= (`end_twrite)? `W_TDAL : `W_WD;
`W_TDAL: r_work_state <= (`end_tdal) ? `W_IDLE : `W_TDAL;
`W_AR: r_work_state <= `W_TRFC; // 刷新命令后进入等待
`W_TRFC: r_work_state <= (`end_trfc) ? `W_IDLE : `W_TRFC;
default: r_work_state <= `W_IDLE;
endcase
end
end
assign o_work_state = r_work_state;
assign w_sdram_ref_ack = (r_work_state == `W_AR); // 进入刷新状态即应答
//-----------------------------------------------------------------------------
// 读写应答信号
// 写应答 : W_WRITE 状态 1 拍 + W_WD 状态前 (sdwr_byte-1) 拍,
// 共 sdwr_byte 拍,外部按拍送数据;
// 读应答 : W_RD 状态计数 1 开始共 sdrd_byte 拍,外部按拍取数据。
// 说明 : 应答拍数与数据字一一对应,配合 sdram_test 直接驱动使用。
//-----------------------------------------------------------------------------
assign o_sdram_wr_ack = (r_work_state == `W_WRITE) |
((r_work_state == `W_WD) & (r_cnt_clk < i_sdwr_byte - 2'd1));
assign o_sdram_rd_ack = (r_work_state == `W_RD) &
(r_cnt_clk >= 10'd1) & (r_cnt_clk < i_sdrd_byte + 2'd1);
//-----------------------------------------------------------------------------
// SDRAM 时序计数器:在各状态中持续计数,供时序宏判断结束条件
//-----------------------------------------------------------------------------
always @(posedge i_clk or negedge i_rst_n)
if(!i_rst_n) r_cnt_clk <= 10'd0;
else if(!r_cnt_rst_n) r_cnt_clk <= 10'd0;
else r_cnt_clk <= r_cnt_clk + 1'b1;
assign o_cnt_clk = r_cnt_clk;
//-----------------------------------------------------------------------------
// 时序计数器复位逻辑(组合逻辑):
// 状态进入/切换时复位计数,计数到指定值后保持,从而产生各时序结束标志
//-----------------------------------------------------------------------------
always @(r_init_state or r_work_state or r_cnt_clk or i_sdwr_byte or i_sdrd_byte) begin
case(r_init_state)
`I_NOP: r_cnt_rst_n = 1'b0;
`I_PRE: r_cnt_rst_n = 1'b1;
`I_TRP: r_cnt_rst_n = (`end_trp) ? 1'b0 : 1'b1;
`I_AR1, `I_AR2: r_cnt_rst_n = 1'b1;
`I_TRF1, `I_TRF2: r_cnt_rst_n = (`end_trfc) ? 1'b0 : 1'b1;
`I_MRS: r_cnt_rst_n = 1'b1;
`I_TMRD: r_cnt_rst_n = (`end_tmrd) ? 1'b0 : 1'b1;
`I_DONE:
case(r_work_state)
`W_IDLE: r_cnt_rst_n = 1'b0;
`W_ACTIVE: r_cnt_rst_n = 1'b1;
`W_TRCD: r_cnt_rst_n = (`end_trcd) ? 1'b0 : 1'b1;
`W_CL: r_cnt_rst_n = (`end_tcl) ? 1'b0 : 1'b1;
`W_RD: r_cnt_rst_n = (`end_tread) ? 1'b0 : 1'b1;
`W_RWAIT: r_cnt_rst_n = (`end_trwait)? 1'b0 : 1'b1;
`W_WD: r_cnt_rst_n = (`end_twrite)? 1'b0 : 1'b1;
`W_TDAL: r_cnt_rst_n = (`end_tdal) ? 1'b0 : 1'b1;
`W_TRFC: r_cnt_rst_n = (`end_trfc) ? 1'b0 : 1'b1;
default: r_cnt_rst_n = 1'b0;
endcase
default: r_cnt_rst_n = 1'b0;
endcase
end
endmodule
sdram_cmd.v
verilog
`timescale 1ns / 1ps
//=============================================================================
// 文件名 : sdram_cmd.v
// 功能 : SDRAM 命令生成模块
// 说明 : 根据 sdram_ctrl 给出的初始化状态 / 工作状态,把对应的 SDRAM
// 命令(NOP、PRECHARGE、AUTO REFRESH、LMR、ACTIVE、READ、WRITE、
// BURST STOP)翻译成引脚电平 {CKE, CS#, RAS#, CAS#, WE#},
// 并输出 Bank 地址和地址总线。
// 注意:命令在状态寄存器更新后的下一个时钟周期出现在引脚上,
// 与 sdram_ctrl 的状态机一拍对应。
//=============================================================================
module sdram_cmd (
//---------- 系统接口 ----------
input i_clk, // SDRAM 控制器时钟
input i_rst_n, // 复位信号,低电平有效
//---------- SDRAM 硬件引脚接口 ----------
output o_sdram_cke, // 时钟使能(命令字 bit4)
output o_sdram_cs_n, // 片选(命令字 bit3,低有效)
output o_sdram_ras_n, // 行地址选通(命令字 bit2,低有效)
output o_sdram_cas_n, // 列地址选通(命令字 bit1,低有效)
output o_sdram_we_n, // 写使能(命令字 bit0,低有效)
output [1:0] o_sdram_ba, // Bank 地址
output [12:0] o_sdram_addr, // 地址总线(行地址 / 列地址 / 模式寄存器复用)
//---------- 用户封装接口 ----------
input [23:0] i_sys_wraddr, // 写地址 {Bank,Row,Col}
input [23:0] i_sys_rdaddr, // 读地址 {Bank,Row,Col}
input [9:0] i_sdwr_byte, // 写突发长度(决定 B_STOP 时机)
input [9:0] i_sdrd_byte, // 读突发长度(决定 B_STOP 时机)
//---------- 内部接口(来自 sdram_ctrl)----------
input [3:0] i_init_state, // 初始化状态
input [3:0] i_work_state, // 读写工作状态
input i_sys_r_wn, // 读写方向:1=读,0=写
input [9:0] i_cnt_clk // 时序计数器(判断突发终止点)
);
`include "sdram_para.v" // 引入状态 / 命令宏定义
//-----------------------------------------------------------------------------
// 寄存器 / 线网定义
//-----------------------------------------------------------------------------
reg [4:0] r_sdram_cmd; // SDRAM 命令寄存器 {CKE,CS#,RAS#,CAS#,WE#}
reg [1:0] r_sdram_ba; // Bank 地址寄存器
reg [12:0] r_sdram_addr; // 地址寄存器
wire [23:0] w_sys_addr; // 读写地址选择结果
// 命令寄存器输出到引脚
assign {o_sdram_cke, o_sdram_cs_n, o_sdram_ras_n, o_sdram_cas_n, o_sdram_we_n} = r_sdram_cmd;
assign o_sdram_ba = r_sdram_ba;
assign o_sdram_addr = r_sdram_addr;
// 读写地址选择:读操作使用读地址,写操作使用写地址
assign w_sys_addr = i_sys_r_wn ? i_sys_rdaddr : i_sys_wraddr;
//-----------------------------------------------------------------------------
// 命令生成主逻辑(每个时钟周期根据状态更新命令字)
//-----------------------------------------------------------------------------
always @(posedge i_clk or negedge i_rst_n) begin
if(!i_rst_n) begin
r_sdram_cmd <= `CMD_INIT; // 复位时输出无效电平
r_sdram_ba <= 2'b11;
r_sdram_addr <= 13'h1fff;
end
else
case(i_init_state)
//---------- 初始化阶段:空操作 ----------
`I_NOP, `I_TRP, `I_TRF1, `I_TRF2, `I_TMRD: begin
r_sdram_cmd <= `CMD_NOP;
r_sdram_ba <= 2'b11;
r_sdram_addr <= 13'h1fff;
end
//---------- 预充电命令 ----------
`I_PRE: begin
r_sdram_cmd <= `CMD_PRGE;
r_sdram_ba <= 2'b11; // 所有 Bank 预充电
r_sdram_addr <= 13'h1fff;
end
//---------- 自动刷新命令 ----------
`I_AR1, `I_AR2: begin
r_sdram_cmd <= `CMD_A_REF;
r_sdram_ba <= 2'b11;
r_sdram_addr <= 13'h1fff;
end
//---------- 模式寄存器设置(LMR)----------
`I_MRS: begin
r_sdram_cmd <= `CMD_LMR;
r_sdram_ba <= 2'b00; // 模式寄存器 Bank=00
r_sdram_addr <= {
3'b000, // 工作模式:标准模式
1'b0, // 突发读 / 突发写
2'b00, // 标准模式寄存器
3'b011, // CAS 潜伏期 = 3
1'b0, // 顺序突发
3'b111 // 突发长度 256(由 B_STOP 提前终止)
};
end
//---------- 初始化完成:按工作状态生成命令 ----------
`I_DONE:
case(i_work_state)
// 空闲 / 等待状态输出空操作
`W_IDLE, `W_TRCD, `W_CL, `W_TRFC, `W_TDAL: begin
r_sdram_cmd <= `CMD_NOP;
r_sdram_ba <= 2'b11;
r_sdram_addr <= 13'h1fff;
end
// 行激活:输出 Bank 和行地址
`W_ACTIVE: begin
r_sdram_cmd <= `CMD_ACTIVE;
r_sdram_ba <= w_sys_addr[23:22]; // Bank 地址
r_sdram_addr <= w_sys_addr[21:9]; // 行地址
end
// 读命令:输出列地址,A10=1 表示自动预充电
`W_READ: begin
r_sdram_cmd <= `CMD_READ;
r_sdram_ba <= w_sys_addr[23:22];
r_sdram_addr <= {4'b0010, w_sys_addr[8:0]}; // {A10=1, 列地址}
end
// 读数据阶段:到突发终点时发送 BURST STOP 提前终止
`W_RD: begin
if(`end_rdburst) r_sdram_cmd <= `CMD_B_STOP;
else begin
r_sdram_cmd <= `CMD_NOP;
r_sdram_ba <= 2'b11;
r_sdram_addr <= 13'h1fff;
end
end
// 写命令:输出列地址,A10=1 表示自动预充电
`W_WRITE: begin
r_sdram_cmd <= `CMD_WRITE;
r_sdram_ba <= w_sys_addr[23:22];
r_sdram_addr <= {4'b0010, w_sys_addr[8:0]}; // {A10=1, 列地址}
end
// 写数据阶段:到突发终点时发送 BURST STOP 提前终止
`W_WD: begin
if(`end_wrburst) r_sdram_cmd <= `CMD_B_STOP;
else begin
r_sdram_cmd <= `CMD_NOP;
r_sdram_ba <= 2'b11;
r_sdram_addr <= 13'h1fff;
end
end
// 刷新命令
`W_AR: begin
r_sdram_cmd <= `CMD_A_REF;
r_sdram_ba <= 2'b11;
r_sdram_addr <= 13'h1fff;
end
default: begin
r_sdram_cmd <= `CMD_NOP;
r_sdram_ba <= 2'b11;
r_sdram_addr <= 13'h1fff;
end
endcase
default: begin
r_sdram_cmd <= `CMD_NOP;
r_sdram_ba <= 2'b11;
r_sdram_addr <= 13'h1fff;
end
endcase
end
endmodule
sdram_wr_data.v
verilog
`timescale 1ns / 1ps
//=============================================================================
// 文件名 : sdram_wr_data.v
// 功能 : SDRAM 数据通路模块
// 说明 : 1) 写操作时,把用户写数据 i_sys_data_in 寄存到 r_sdr_din,
// 并通过三态门驱动双向数据总线 io_sdram_data;
// 2) 读操作时,在 W_RD 状态下每个时钟捕获总线数据到 r_sdr_dout,
// 输出给用户 o_sys_data_out。
// 总线驱动使能 r_sdr_dlink 仅在 W_WRITE / W_WD 状态有效,
// 其余时间总线为高阻,避免与 SDRAM 输出冲突。
//=============================================================================
module sdram_wr_data (
//---------- 系统接口 ----------
input i_clk, // SDRAM 控制器时钟
input i_rst_n, // 复位信号,低电平有效
//---------- SDRAM 硬件引脚接口 ----------
inout [15:0] io_sdram_data, // 双向数据总线
//---------- 用户封装接口 ----------
input [15:0] i_sys_data_in, // 写数据输入
output [15:0] o_sys_data_out, // 读数据输出
//---------- 内部接口(来自 sdram_ctrl)----------
input [3:0] i_work_state, // 读写工作状态
input [9:0] i_cnt_clk // 时序计数器(本模块未使用,保留接口)
);
`include "sdram_para.v" // 引入工作状态宏定义
//-----------------------------------------------------------------------------
// 寄存器定义
//-----------------------------------------------------------------------------
reg [15:0] r_sdr_din; // 写数据寄存器(驱动数据总线)
reg r_sdr_dlink; // 数据总线驱动使能(1=驱动,0=高阻)
reg [15:0] r_sdr_dout; // 读数据捕获寄存器
//-----------------------------------------------------------------------------
// 写数据寄存:在 W_WRITE 和 W_WD 状态每个时钟捕获用户写数据
// 说明:第一个字在 W_WRITE 期间送入,恰好与 SDRAM 采样写命令的
// 周期对齐(详见 sdram_test 的时序说明)。
//-----------------------------------------------------------------------------
always @(posedge i_clk or negedge i_rst_n)
if(!i_rst_n) r_sdr_din <= 16'd0;
else if((i_work_state == `W_WRITE) | (i_work_state == `W_WD))
r_sdr_din <= i_sys_data_in;
//-----------------------------------------------------------------------------
// 数据总线驱动使能:仅写数据阶段驱动总线,其余时间高阻
//-----------------------------------------------------------------------------
always @(posedge i_clk or negedge i_rst_n)
if(!i_rst_n) r_sdr_dlink <= 1'b0;
else if((i_work_state == `W_WRITE) | (i_work_state == `W_WD))
r_sdr_dlink <= 1'b1;
else r_sdr_dlink <= 1'b0;
// 双向总线输出:使能时输出写数据,否则高阻(读时由 SDRAM 驱动)
assign io_sdram_data = r_sdr_dlink ? r_sdr_din : 16'hzzzz;
//-----------------------------------------------------------------------------
// 读数据捕获:W_RD 状态每个时钟捕获总线数据
// 说明:真实板卡上第一个读数据在 W_RD 计数 0 期间有效,因此
// 读应答从计数 1 开始时,o_sys_data_out 恰好是 D0..D7。
//-----------------------------------------------------------------------------
always @(posedge i_clk or negedge i_rst_n)
if(!i_rst_n) r_sdr_dout <= 16'd0;
else if(i_work_state == `W_RD)
r_sdr_dout <= io_sdram_data;
assign o_sys_data_out = r_sdr_dout;
endmodule
HC_FPGA_Tcl.tcl
bash
#时钟引脚 50M
set_location_assignment PIN_E1 -to CLOCK_XTAL_50MHz
#复位引脚
set_location_assignment PIN_E15 -to RESET
#LED对应的引脚
set_location_assignment PIN_G15 -to LED0
set_location_assignment PIN_F16 -to LED1
set_location_assignment PIN_F15 -to LED2
set_location_assignment PIN_D16 -to LED3
#按键对应的引脚 KEY1已作为复位按键
#set_location_assignment PIN_E15 -to KEY1
set_location_assignment PIN_E16 -to KEY2
set_location_assignment PIN_M16 -to KEY3
set_location_assignment PIN_M15 -to KEY4
#串口对应的引脚
set_location_assignment PIN_M2 -to RXD
set_location_assignment PIN_G1 -to TXD
#IIC(24LC04)对应的引脚
set_location_assignment PIN_L2 -to SDA
set_location_assignment PIN_L1 -to SCL
#VGA对应的引脚
#VGAB[0..4]
set_location_assignment PIN_C15 -to VGAD[0]
set_location_assignment PIN_B16 -to VGAD[1]
set_location_assignment PIN_A15 -to VGAD[2]
set_location_assignment PIN_B14 -to VGAD[3]
set_location_assignment PIN_A14 -to VGAD[4]
#VGAG[0..5]
set_location_assignment PIN_B13 -to VGAD[5]
set_location_assignment PIN_A13 -to VGAD[6]
set_location_assignment PIN_B12 -to VGAD[7]
set_location_assignment PIN_A12 -to VGAD[8]
set_location_assignment PIN_B11 -to VGAD[9]
set_location_assignment PIN_A11 -to VGAD[10]
#VGAR[0..4]
set_location_assignment PIN_B10 -to VGAD[11]
set_location_assignment PIN_A10 -to VGAD[12]
set_location_assignment PIN_B9 -to VGAD[13]
set_location_assignment PIN_A9 -to VGAD[14]
set_location_assignment PIN_C8 -to VGAD[15]
#SYNC
set_location_assignment PIN_C16 -to VGA_HSYNC
set_location_assignment PIN_D15 -to VGA_VSYNC
#OV7670对应的引脚
set_location_assignment PIN_G5 -to CMOS_DB[0]
set_location_assignment PIN_F2 -to CMOS_DB[1]
set_location_assignment PIN_F3 -to CMOS_DB[2]
set_location_assignment PIN_F5 -to CMOS_DB[3]
set_location_assignment PIN_D1 -to CMOS_DB[4]
set_location_assignment PIN_D3 -to CMOS_DB[5]
set_location_assignment PIN_E5 -to CMOS_DB[6]
set_location_assignment PIN_C3 -to CMOS_DB[7]
set_location_assignment PIN_D4 -to CMOS_XCLK
set_location_assignment PIN_M1 -to CMOS_PCLK
set_location_assignment PIN_D5 -to CMOS_VSYNC
set_location_assignment PIN_F6 -to CMOS_HREF
set_location_assignment PIN_C6 -to CMOS_SCLK
set_location_assignment PIN_D6 -to CMOS_SDAT
#SDRAM引脚
set_location_assignment PIN_R5 -to S_DB[0]
set_location_assignment PIN_T4 -to S_DB[1]
set_location_assignment PIN_T3 -to S_DB[2]
set_location_assignment PIN_R3 -to S_DB[3]
set_location_assignment PIN_T2 -to S_DB[4]
set_location_assignment PIN_R1 -to S_DB[5]
set_location_assignment PIN_P2 -to S_DB[6]
set_location_assignment PIN_P1 -to S_DB[7]
set_location_assignment PIN_R13 -to S_DB[8]
set_location_assignment PIN_T13 -to S_DB[9]
set_location_assignment PIN_R12 -to S_DB[10]
set_location_assignment PIN_T12 -to S_DB[11]
set_location_assignment PIN_T10 -to S_DB[12]
set_location_assignment PIN_R10 -to S_DB[13]
set_location_assignment PIN_T11 -to S_DB[14]
set_location_assignment PIN_R11 -to S_DB[15]
set_location_assignment PIN_T8 -to S_A[0]
set_location_assignment PIN_P9 -to S_A[1]
set_location_assignment PIN_T9 -to S_A[2]
set_location_assignment PIN_R9 -to S_A[3]
set_location_assignment PIN_L16 -to S_A[4]
set_location_assignment PIN_L15 -to S_A[5]
set_location_assignment PIN_N16 -to S_A[6]
set_location_assignment PIN_N15 -to S_A[7]
set_location_assignment PIN_P16 -to S_A[8]
set_location_assignment PIN_P15 -to S_A[9]
set_location_assignment PIN_R8 -to S_A[10]
set_location_assignment PIN_R16 -to S_A[11]
set_location_assignment PIN_T15 -to S_A[12]
set_location_assignment PIN_R4 -to S_CLK
set_location_assignment PIN_R7 -to S_BA[0]
set_location_assignment PIN_T7 -to S_BA[1]
set_location_assignment PIN_T5 -to S_NCAS
set_location_assignment PIN_R14 -to S_CKE
set_location_assignment PIN_R6 -to S_NRAS
set_location_assignment PIN_N1 -to S_NWE
set_location_assignment PIN_T6 -to S_NCS
set_location_assignment PIN_T14 -to S_DQM[1]
set_location_assignment PIN_N2 -to S_DQM[0]
#LCD
set_location_assignment PIN_J13 -to lcd_out_hs
set_location_assignment PIN_J14 -to lcd_out_vs
set_location_assignment PIN_K11 -to lcd_out_de
set_location_assignment PIN_J12 -to lcd_out_clk
set_location_assignment PIN_E7 -to lcd_out_rgb_r[0]
set_location_assignment PIN_D8 -to lcd_out_rgb_r[1]
set_location_assignment PIN_E8 -to lcd_out_rgb_r[2]
set_location_assignment PIN_F7 -to lcd_out_rgb_r[3]
set_location_assignment PIN_F9 -to lcd_out_rgb_r[4]
set_location_assignment PIN_E9 -to lcd_out_rgb_r[5]
set_location_assignment PIN_C9 -to lcd_out_rgb_r[6]
set_location_assignment PIN_D9 -to lcd_out_rgb_r[7]
set_location_assignment PIN_E10 -to lcd_out_rgb_g[0]
set_location_assignment PIN_C11 -to lcd_out_rgb_g[1]
set_location_assignment PIN_D11 -to lcd_out_rgb_g[2]
set_location_assignment PIN_D12 -to lcd_out_rgb_g[3]
set_location_assignment PIN_E11 -to lcd_out_rgb_g[4]
set_location_assignment PIN_C14 -to lcd_out_rgb_g[5]
set_location_assignment PIN_F10 -to lcd_out_rgb_g[6]
set_location_assignment PIN_D14 -to lcd_out_rgb_g[7]
set_location_assignment PIN_F11 -to lcd_out_rgb_b[0]
set_location_assignment PIN_F13 -to lcd_out_rgb_b[1]
set_location_assignment PIN_F14 -to lcd_out_rgb_b[2]
set_location_assignment PIN_G11 -to lcd_out_rgb_b[3]
set_location_assignment PIN_K9 -to lcd_out_rgb_b[4]
set_location_assignment PIN_K10 -to lcd_out_rgb_b[5]
set_location_assignment PIN_G16 -to lcd_out_rgb_b[6]
set_location_assignment PIN_J11 -to lcd_out_rgb_b[7]
测试
bash
[20:54:05.174]收←◆TEST START
PASS
RDATA 0000 FFFF 5A5A A5A5 1234 5678 9ABC DEF0

