黑金AX301的SDRAM

参考

FPGA-SDcard-Reader.github

黑金AX301的EEPROM 24LC04.csdn

黑金AX301的SDRAM.csdn

黑金AX301的spi驱动SdCard.csdn

黑金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
相关推荐
hahaha60162 小时前
HLS高层次综合设计技巧--高层次综合设计探讨
fpga开发
minglie15 小时前
黑金AX301的EEPROM 24LC04
fpga开发
Hello-FPGA5 小时前
量子计算滨松c15550-22up单光子相机与PCIe1004采集卡
人工智能·计算机视觉·fpga开发
9527华安7 小时前
FPGA实现CameraLink转SFP光口,基于GT Transceivers Wizard Aurora8B10B编解码架构,提供4套工程源码和技术支持
fpga开发·sfp·cameralink·aurora8b10b·transceivers
ALINX技术博客1 天前
【期刊共读】如何应对 FPGA 短缺常态?
嵌入式硬件·fpga开发·fpga
156082072191 天前
短波接收机灵敏度测试
fpga开发
Riwuarua2 天前
从近似0基础开始FPGA开发 -- part.6 逻辑设计与状态机
fpga开发
LCG元2 天前
【实战】Vivado FPGA 从零实现 UART 串口收发:波特率生成、状态机设计与仿真调试
fpga开发
郭郭的柳柳在学FPGA2 天前
紫光DDR3工程仿真遇到的坑
fpga开发