黑金AX301的EEPROM 24LC04

参考

Zynq上UART/IIC/SPI的27个实验-第14课:PL 逻辑 IIC 主机访问 AT24C256.csdn

FPGA-SDcard-Reader.github

黑金AX301的EEPROM 24LC04.csdn

黑金AX301的SDRAM.csdn

黑金AX301的spi驱动SdCard.csdn

开发板板载了一片 EEPROM,型号为 24LC04,容量为:4Kbit(2256 8bit),

由 2 个 256byte 的 block 组成,通过 IIC 总线进行通信。板载 EEPROM 就是为

了学习 IIC 总线的通信方式。EEPROM 一般用在仪器仪表等设计上,用作一些

参数的存储,掉电不丢失。这种芯片操作简单,具有极高的性价比,所以虽然容

量比高,但价格非常便宜,对于那些对成本要求很高的产品来说,是个不错的选

择。

目标

点击KEY2, 向地址 0 ~ 15 写入16字节数据

然后读出, 如果读写一致则 LED1 亮, 否则LED1闪

HC_FPGA_Demo_Top.v

verilog 复制代码
`timescale 1ns / 1ps
//=============================================================================
// 文件名 : HC_FPGA_Demo_Top.v
// 功能   : EEPROM(24C02) 读写测试顶层(不使用 PLL / sys / sdram)
// 说明   : 1) 直接用板载 50MHz 时钟,RESET 按键低有效复位;
//          2) 上电后串口打印 "EEPROM TEST READY";
//          3) 按下 KEY2,向地址 0~15 写入 16 字节测试数据,再读回比对;
//          4) 读写一致 -> LED1 常亮,串口打印 "PASS";
//             不一致/无应答 -> LED1 闪烁,串口打印 "FAIL ..."。
//          串口 : 115200, 8N1。I2C : 250KHz(实际约 125KHz)。
//=============================================================================
module HC_FPGA_Demo_Top
(
    input         CLOCK_XTAL_50MHz,   // 50MHz 系统时钟
    input         RESET,              // 复位按键(低有效)
    input         KEY2,               // 测试触发按键
    output        TXD,                // 串口发送(115200, 8N1)
    output        LED1,               // 测试结果指示(亮=通过, 闪=失败)
    output        SCL,                // EEPROM(24C02) I2C 时钟
    inout         SDA                 // EEPROM(24C02) I2C 数据(双向)
);

//-----------------------------------------------------------------------------
// 复位同步:RESET 低有效,异步置位、同步释放(50MHz 域)
//-----------------------------------------------------------------------------
reg rst_r1;
reg rst_r2;
always @(posedge CLOCK_XTAL_50MHz or negedge RESET)
begin
    if(!RESET)
    begin
        rst_r1 <= 1'b0;
        rst_r2 <= 1'b0;
    end
    else
    begin
        rst_r1 <= 1'b1;
        rst_r2 <= rst_r1;
    end
end
wire RST_N = rst_r2;

//-----------------------------------------------------------------------------
// 按键消抖:KEY2 按下输出 1 拍脉冲(50MHz 域)
//-----------------------------------------------------------------------------
wire key_pulse;
key_debounce key_debounce_inst
(
    .i_clk      (CLOCK_XTAL_50MHz),
    .i_rst_n    (RST_N),
    .i_key_n    (KEY2),
    .o_key_pulse(key_pulse)
);

//-----------------------------------------------------------------------------
// 脉冲展宽:key_pulse(20ns) -> 约 100us 电平。
//   I2C 驱动时钟 dri_clk 约 1MHz(1us 周期),20ns 脉冲会漏采,
//   展宽到 100us 后可安全同步到 dri_clk 域。
//-----------------------------------------------------------------------------
reg [15:0] r_ext_cnt;
reg        r_ext_pulse;
always @(posedge CLOCK_XTAL_50MHz or negedge RST_N)
begin
    if(!RST_N)
    begin
        r_ext_cnt   <= 16'd0;
        r_ext_pulse <= 1'b0;
    end
    else if(key_pulse)
    begin
        r_ext_cnt   <= 16'd5000;   // 100us @50MHz
        r_ext_pulse <= 1'b1;
    end
    else if(r_ext_cnt != 16'd0)
        r_ext_cnt <= r_ext_cnt - 1'b1;
    else
        r_ext_pulse <= 1'b0;
end

//-----------------------------------------------------------------------------
// EEPROM 测试核心(全部工作在 dri_clk 域)
//-----------------------------------------------------------------------------
wire        w_dri_clk;      // I2C 操作驱动时钟(由 i2c_master 分频产生)
wire        w_test_trigger; // 按键触发(同步到 dri_clk)
wire        w_i2c_exec;     // I2C 触发执行
wire        w_i2c_rh_wl;    // 0:写 1:读
wire [15:0] w_i2c_addr;     // 字地址
wire [7:0]  w_i2c_data_w;   // 写数据
wire [7:0]  w_i2c_data_r;   // 读数据
wire        w_i2c_done;     // 单次操作完成
wire        w_i2c_ack;      // 应答标志(0:应答 1:无应答)
wire        w_rw_done;      // 测试完成
wire        w_rw_result;    // 测试通过(电平)
wire        w_rw_fail;      // 测试失败(电平)
wire [4:0]  w_fail_idx;     // 失败位置
wire [7:0]  w_fail_exp;     // 期望值
wire [7:0]  w_fail_got;     // 实际值
wire [127:0] w_rd_data;     // 读回 16 字节

// 触发同步:50MHz 展宽脉冲 -> dri_clk 域 1 拍脉冲
reg t_s1;
reg t_s2;
reg t_s2_d;
always @(posedge w_dri_clk or negedge RST_N)
begin
    if(!RST_N)
    begin
        t_s1   <= 1'b0;
        t_s2   <= 1'b0;
        t_s2_d <= 1'b0;
    end
    else
    begin
        t_s1   <= r_ext_pulse;
        t_s2   <= t_s1;
        t_s2_d <= t_s2;
    end
end
assign w_test_trigger = t_s2 & ~t_s2_d;

// 24C02 读写测试控制器
eeprom_rw #(
    .P_WR_WAIT_TIME(14'd6000),   // 约 6ms 写周期等待
    .P_MAX_BYTE    (16'd16)      // 16 字节
) u_eeprom_rw
(
    .i_clk        (w_dri_clk),
    .i_rst_n      (RST_N),
    .i_trigger    (w_test_trigger),

    .o_i2c_rh_wl  (w_i2c_rh_wl),
    .o_i2c_exec   (w_i2c_exec),
    .o_i2c_addr   (w_i2c_addr),
    .o_i2c_data_w (w_i2c_data_w),

    .i_i2c_data_r (w_i2c_data_r),
    .i_i2c_done   (w_i2c_done),
    .i_i2c_ack    (w_i2c_ack),

    .o_rw_done    (w_rw_done),
    .o_rw_result  (w_rw_result),
    .o_rw_fail    (w_rw_fail),
    .o_fail_idx   (w_fail_idx),
    .o_fail_exp   (w_fail_exp),
    .o_fail_got   (w_fail_got),
    .o_rd_data    (w_rd_data)
);

// I2C 主机驱动(24C02 : 8 位字地址, 从机地址 0x50)
i2c_master #(
    .P_SLAVE_ADDR(7'b1010000),   // 0x50
    .P_CLK_FREQ  (26'd50_000_000),
    .P_I2C_FREQ  (18'd250_000)
) u_i2c_master
(
    .i_clk        (CLOCK_XTAL_50MHz),
    .i_rst_n      (RST_N),
    .i_i2c_exec   (w_i2c_exec),
    .i_bit_ctrl   (1'b0),        // 24C02 : 8 位字地址
    .i_i2c_rh_wl  (w_i2c_rh_wl),
    .i_i2c_addr   (w_i2c_addr),
    .i_i2c_data_w (w_i2c_data_w),
    .o_i2c_data_r (w_i2c_data_r),
    .o_i2c_done   (w_i2c_done),
    .o_i2c_ack    (w_i2c_ack),
    .o_scl        (SCL),
    .io_sda       (SDA),
    .o_dri_clk    (w_dri_clk)
);

//-----------------------------------------------------------------------------
// 测试结果同步到 50MHz 域(两级同步器)
//-----------------------------------------------------------------------------
reg p_s1;
reg p_s2;
always @(posedge CLOCK_XTAL_50MHz or negedge RST_N)
begin
    if(!RST_N)
    begin
        p_s1 <= 1'b0;
        p_s2 <= 1'b0;
    end
    else
    begin
        p_s1 <= w_rw_result;
        p_s2 <= p_s1;
    end
end
wire test_pass_s = p_s2;

reg f_s1;
reg f_s2;
always @(posedge CLOCK_XTAL_50MHz or negedge RST_N)
begin
    if(!RST_N)
    begin
        f_s1 <= 1'b0;
        f_s2 <= 1'b0;
    end
    else
    begin
        f_s1 <= w_rw_fail;
        f_s2 <= f_s1;
    end
end
wire test_fail_s = f_s2;

//-----------------------------------------------------------------------------
// LED1:通过=常亮,失败=闪烁(0.5s 翻转),空闲=熄灭
//-----------------------------------------------------------------------------
reg [25:0] blink_cnt;
reg        led1_blink;
always @(posedge CLOCK_XTAL_50MHz or negedge RST_N)
begin
    if(!RST_N)
    begin
        blink_cnt  <= 26'd0;
        led1_blink <= 1'b0;
    end
    else if(blink_cnt == 26'd25_000_000 - 1)
    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);

//-----------------------------------------------------------------------------
// 串口打印控制
//-----------------------------------------------------------------------------
// 上电约 20ms 后打印一次 "EEPROM TEST READY"
reg [19:0] r_ready_cnt;
reg        r_ready_flag;
reg        r_ready_flag_d;
always @(posedge CLOCK_XTAL_50MHz or negedge RST_N)
begin
    if(!RST_N)
    begin
        r_ready_cnt  <= 20'd0;
        r_ready_flag <= 1'b0;
    end
    else if(!r_ready_flag)
    begin
        if(r_ready_cnt == 20'd1_000_000 - 1)
        begin
            r_ready_cnt  <= 20'd0;
            r_ready_flag <= 1'b1;
        end
        else
            r_ready_cnt <= r_ready_cnt + 1'b1;
    end
end
always @(posedge CLOCK_XTAL_50MHz or negedge RST_N)
begin
    if(!RST_N)
        r_ready_flag_d <= 1'b0;
    else
        r_ready_flag_d <= r_ready_flag;
end
wire ready_pulse = r_ready_flag & ~r_ready_flag_d;

// 结果电平 0->1 沿(50MHz 域),用于锁存失败信息
wire result_any = test_pass_s | test_fail_s;
reg  result_any_d;
always @(posedge CLOCK_XTAL_50MHz or negedge RST_N)
begin
    if(!RST_N)
        result_any_d <= 1'b0;
    else
        result_any_d <= result_any;
end
wire result_pulse = result_any & ~result_any_d;

// 锁存失败信息(在结果沿到达时采样)
reg [4:0]  fail_idx_50;
reg [7:0]  fail_exp_50;
reg [7:0]  fail_got_50;
reg [127:0] rd_data_50;
always @(posedge CLOCK_XTAL_50MHz or negedge RST_N)
begin
    if(!RST_N)
    begin
        fail_idx_50 <= 5'd0;
        fail_exp_50 <= 8'd0;
        fail_got_50 <= 8'd0;
        rd_data_50  <= 128'd0;
    end
    else if(result_pulse)
    begin
        fail_idx_50 <= w_fail_idx;
        fail_exp_50 <= w_fail_exp;
        fail_got_50 <= w_fail_got;
        rd_data_50  <= w_rd_data;
    end
end

wire trig_pass = result_pulse & test_pass_s;
wire trig_fail = result_pulse & test_fail_s;

uart_tx_eeprom u_uart_tx_eeprom
(
    .i_clk        (CLOCK_XTAL_50MHz),
    .i_rst_n      (RST_N),
    .i_trig_ready (ready_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

uart_tx_eeprom.v

verilog 复制代码
`timescale 1ns / 1ps
//=============================================================================
// 文件名 : uart_tx_eeprom.v
// 功能   : 通过底层 uart_tx 模块发送若干 ASCII 字符串(115200, 8N1)
// 消息   :
//   i_trig_ready : "EEPROM TEST READY\r\n"
//   i_trig_start : "TEST START\r\n"
//   i_trig_pass  : "PASS\r\n"
//   i_trig_fail  : "FAIL n=XX exp=HH got=HH\r\n"(n 为字节序号/16写/17读)
//   测试结束后   : "RDATA b00 b01 ... b15\r\n"(全部 16 字节)
// 说明   : 发送忙时触发的信号会保存在 pending 标志里,不会丢失。
//=============================================================================
module uart_tx_eeprom (
    //---------- 系统接口 ----------
    input                  i_clk,          // 系统时钟(50MHz)
    input                  i_rst_n,        // 复位(低有效)
    //---------- 触发信号(1 拍脉冲) ----------
    input                  i_trig_ready,   // 上电就绪
    input                  i_trig_start,   // 测试开始
    input                  i_trig_pass,    // 测试通过
    input                  i_trig_fail,    // 测试失败
    //---------- 失败信息 ----------
    input          [4 :0]  i_fail_idx,     // 失败位置(0~15/16/17)
    input          [7 :0]  i_fail_exp,     // 期望值
    input          [7 :0]  i_fail_got,     // 实际值
    //---------- 读回数据(b00 在 [127:120]) ----------
    input          [127:0] i_rd_data,
    //---------- 输出 ----------
    output                 o_txd
);

//-----------------------------------------------------------------------------
// 底层 uart_tx 例化
//-----------------------------------------------------------------------------
wire       w_tx_busy;
reg        r_tx_en;
reg [7:0]  r_tx_data;

uart_tx #(
    .P_CLK_FREQ (50_000_000),
    .P_UART_BPS (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];   // 消息缓冲(最大 56 字节)
reg  [5:0] r_msg_len;
reg  [5:0] r_ptr;
reg  [1:0] r_state;
reg        r_pending_ready;
reg        r_pending_start;
reg        r_pending_pass;
reg        r_pending_fail;
reg        r_pending_rdata;
reg        r_tx_busy_d;

// busy 下降沿 = 一个字节发送完毕
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;

integer n;
initial
    for (n = 0; n < 64; n = n + 1)
        r_tx_buf[n] = 8'h00;

// 4bit 十六进制 -> ASCII
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
        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;
        if (i_trig_pass | i_trig_fail)
            r_pending_rdata <= 1'b1;   // 测试结束后打印读回数据

        case (r_state)

        L_S_IDLE: begin
            if (r_pending_ready) begin
                // "EEPROM TEST READY\r\n" : 19 字节
                r_tx_buf[0]  <= "E"; r_tx_buf[1]  <= "E"; r_tx_buf[2]  <= "P";
                r_tx_buf[3]  <= "R"; r_tx_buf[4]  <= "O"; r_tx_buf[5]  <= "M";
                r_tx_buf[6]  <= " "; r_tx_buf[7]  <= "T"; r_tx_buf[8]  <= "E";
                r_tx_buf[9]  <= "S"; r_tx_buf[10] <= "T"; r_tx_buf[11] <= " ";
                r_tx_buf[12] <= "R"; r_tx_buf[13] <= "E"; r_tx_buf[14] <= "A";
                r_tx_buf[15] <= "D"; r_tx_buf[16] <= "Y";
                r_tx_buf[17] <= 8'h0D; r_tx_buf[18] <= 8'h0A;
                r_msg_len <= 6'd19;
                r_tx_data <= "E";
                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=XX exp=HH got=HH\r\n" : 25 字节
                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 / 4'd10;        // 十位
                r_tx_buf[8]  <= 8'h30 + i_fail_idx % 4'd10;        // 个位
                r_tx_buf[9]  <= " ";
                r_tx_buf[10] <= "e"; r_tx_buf[11] <= "x"; r_tx_buf[12] <= "p"; r_tx_buf[13] <= "=";
                r_tx_buf[14] <= f_hex2ascii(i_fail_exp[7:4]);
                r_tx_buf[15] <= f_hex2ascii(i_fail_exp[3:0]);
                r_tx_buf[16] <= " ";
                r_tx_buf[17] <= "g"; r_tx_buf[18] <= "o"; r_tx_buf[19] <= "t"; r_tx_buf[20] <= "=";
                r_tx_buf[21] <= f_hex2ascii(i_fail_got[7:4]);
                r_tx_buf[22] <= f_hex2ascii(i_fail_got[3:0]);
                r_tx_buf[23] <= 8'h0D; r_tx_buf[24] <= 8'h0A;
                r_msg_len <= 6'd25;
                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 b00 b01 ... b15\r\n" : 56 字节
                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]);   // b00
                r_tx_buf[7]  <= f_hex2ascii(i_rd_data[123:120]);
                r_tx_buf[8]  <= " ";
                r_tx_buf[9]  <= f_hex2ascii(i_rd_data[119:116]);   // b01
                r_tx_buf[10] <= f_hex2ascii(i_rd_data[115:112]);
                r_tx_buf[11] <= " ";
                r_tx_buf[12] <= f_hex2ascii(i_rd_data[111:108]);   // b02
                r_tx_buf[13] <= f_hex2ascii(i_rd_data[107:104]);
                r_tx_buf[14] <= " ";
                r_tx_buf[15] <= f_hex2ascii(i_rd_data[103:100]);   // b03
                r_tx_buf[16] <= f_hex2ascii(i_rd_data[99:96]);
                r_tx_buf[17] <= " ";
                r_tx_buf[18] <= f_hex2ascii(i_rd_data[95:92]);     // b04
                r_tx_buf[19] <= f_hex2ascii(i_rd_data[91:88]);
                r_tx_buf[20] <= " ";
                r_tx_buf[21] <= f_hex2ascii(i_rd_data[87:84]);     // b05
                r_tx_buf[22] <= f_hex2ascii(i_rd_data[83:80]);
                r_tx_buf[23] <= " ";
                r_tx_buf[24] <= f_hex2ascii(i_rd_data[79:76]);     // b06
                r_tx_buf[25] <= f_hex2ascii(i_rd_data[75:72]);
                r_tx_buf[26] <= " ";
                r_tx_buf[27] <= f_hex2ascii(i_rd_data[71:68]);     // b07
                r_tx_buf[28] <= f_hex2ascii(i_rd_data[67:64]);
                r_tx_buf[29] <= " ";
                r_tx_buf[30] <= f_hex2ascii(i_rd_data[63:60]);     // b08
                r_tx_buf[31] <= f_hex2ascii(i_rd_data[59:56]);
                r_tx_buf[32] <= " ";
                r_tx_buf[33] <= f_hex2ascii(i_rd_data[55:52]);     // b09
                r_tx_buf[34] <= f_hex2ascii(i_rd_data[51:48]);
                r_tx_buf[35] <= " ";
                r_tx_buf[36] <= f_hex2ascii(i_rd_data[47:44]);     // b10
                r_tx_buf[37] <= f_hex2ascii(i_rd_data[43:40]);
                r_tx_buf[38] <= " ";
                r_tx_buf[39] <= f_hex2ascii(i_rd_data[39:36]);     // b11
                r_tx_buf[40] <= f_hex2ascii(i_rd_data[35:32]);
                r_tx_buf[41] <= " ";
                r_tx_buf[42] <= f_hex2ascii(i_rd_data[31:28]);     // b12
                r_tx_buf[43] <= f_hex2ascii(i_rd_data[27:24]);
                r_tx_buf[44] <= " ";
                r_tx_buf[45] <= f_hex2ascii(i_rd_data[23:20]);     // b13
                r_tx_buf[46] <= f_hex2ascii(i_rd_data[19:16]);
                r_tx_buf[47] <= " ";
                r_tx_buf[48] <= f_hex2ascii(i_rd_data[15:12]);     // b14
                r_tx_buf[49] <= f_hex2ascii(i_rd_data[11:8]);
                r_tx_buf[50] <= " ";
                r_tx_buf[51] <= f_hex2ascii(i_rd_data[7:4]);       // b15
                r_tx_buf[52] <= f_hex2ascii(i_rd_data[3:0]);
                r_tx_buf[53] <= 8'h0D; r_tx_buf[54] <= 8'h0A;
                r_msg_len <= 6'd55;
                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)
                    r_state <= L_S_IDLE;
                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_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

eeprom_rw.v

verilog 复制代码
`timescale 1ns / 1ps
//=============================================================================
// 文件名 : eeprom_rw.v
// 功能   : EEPROM(24C02) 读写测试控制器
// 说明   : 1) 收到 i_trigger(按键同步后的启动脉冲)后,
//            向地址 0~15 依次写入 16 字节测试数据;
//          2) 每写 1 字节后等待 EEPROM 内部写周期(P_WR_WAIT_TIME);
//          3) 再从地址 0~15 依次读回 16 字节;
//          4) 逐一比对,全部一致则 o_rw_result=1(通过),
//             任一字节不一致或无应答则 o_rw_fail=1(失败)。
//          5) o_fail_idx : 0~15 为第一个不一致的字节地址,
//             16=写无应答,17=读无应答。
//          本模块与 i2c_master 同在一个时钟域(dri_clk)工作。
//=============================================================================
module eeprom_rw #(
    parameter P_WR_WAIT_TIME = 14'd6000,   // EEPROM 内部写周期延时(约6ms @1MHz dri_clk)
    parameter P_MAX_BYTE     = 16'd16      // 测试字节数(地址 0~15)
)(
    input               i_clk,             // 时钟(dri_clk)
    input               i_rst_n,           // 复位(低有效)
    input               i_trigger,         // 启动脉冲(已同步到 i_clk)

    //---------- 到 i2c_master 的控制接口 ----------
    output reg          o_i2c_rh_wl,       // 0:写 1:读
    output reg          o_i2c_exec,        // 触发一次 I2C 操作(1拍脉冲)
    output reg [15:0]   o_i2c_addr,        // 字地址(24C02 只用低 8 位)
    output reg [7:0]    o_i2c_data_w,      // 要写入的数据

    //---------- 来自 i2c_master 的状态接口 ----------
    input       [7:0]   i_i2c_data_r,      // 读出的数据
    input               i_i2c_done,        // 单次 I2C 操作完成(1拍脉冲)
    input               i_i2c_ack,         // 应答标志(0:应答 1:无应答)

    //---------- 测试结果 ----------
    output reg          o_rw_done,         // 测试完成标志(完成后保持)
    output reg          o_rw_result,       // 1:读写一致(通过) 0:失败
    output reg          o_rw_fail,         // 1:测试失败(保持到下次触发)
    output reg [4:0]    o_fail_idx,        // 失败位置(见模块头)
    output reg [7:0]    o_fail_exp,        // 期望值
    output reg [7:0]    o_fail_got,        // 实际值
    output reg [127:0]  o_rd_data          // 读回的 16 字节(b0 在 [127:120])
);

//-----------------------------------------------------------------------------
// 状态机
//-----------------------------------------------------------------------------
localparam S_IDLE       = 5'd0;   // 空闲,等待触发
localparam S_WRITE      = 5'd1;   // 发起一次写操作
localparam S_WRITE_WAIT = 5'd2;   // 等待写完成 + EEPROM 内部写周期
localparam S_READ       = 5'd3;   // 发起一次读操作
localparam S_READ_WAIT  = 5'd4;   // 等待读完成
localparam S_CMP        = 5'd5;   // 比对结果
localparam S_DONE       = 5'd6;   // 保持结果,等待下一次触发

//-----------------------------------------------------------------------------
// 测试数据:16 字节固定图案,覆盖全 0/全 1/5A/A5/递增等位型
//-----------------------------------------------------------------------------
function [7:0] f_test_data;
    input [3:0] idx;
    begin
        case (idx)
            4'd0:   f_test_data = 8'h00;
            4'd1:   f_test_data = 8'hFF;
            4'd2:   f_test_data = 8'h55;
            4'd3:   f_test_data = 8'hAA;
            4'd4:   f_test_data = 8'h33;
            4'd5:   f_test_data = 8'hCC;
            4'd6:   f_test_data = 8'h0F;
            4'd7:   f_test_data = 8'hF0;
            4'd8:   f_test_data = 8'h12;
            4'd9:   f_test_data = 8'h34;
            4'd10:  f_test_data = 8'h56;
            4'd11:  f_test_data = 8'h78;
            4'd12:  f_test_data = 8'h9A;
            4'd13:  f_test_data = 8'hBC;
            4'd14:  f_test_data = 8'hDE;
            4'd15:  f_test_data = 8'hF0;
            default:f_test_data = 8'h00;
        endcase
    end
endfunction

//-----------------------------------------------------------------------------
// 寄存器
//-----------------------------------------------------------------------------
reg [4:0]   r_flow_cnt;        // 状态
reg [3:0]   r_idx;             // 当前字节地址(0~15)
reg [13:0]  r_wait_cnt;        // 写周期/等待计数
reg         r_i2c_done_d;      // done 打拍,用于上升沿检测
reg [7:0]   r_rd_buf [0:15];   // 读回数据缓存

// 组合逻辑:比对读回数据,找到第一个不一致的字节
reg         r_has_mismatch;
reg [3:0]   r_mismatch_idx;
integer     k;
always @(*) begin
    r_has_mismatch = 1'b0;
    r_mismatch_idx = 4'd0;
    for (k = 0; k < 16; k = k + 1) begin
        if (!r_has_mismatch && (r_rd_buf[k] != f_test_data(k[3:0]))) begin
            r_has_mismatch = 1'b1;
            r_mismatch_idx = k[3:0];
        end
    end
end

wire w_i2c_done_pos = i_i2c_done & ~r_i2c_done_d;

//-----------------------------------------------------------------------------
// 主状态机
//-----------------------------------------------------------------------------
always @(posedge i_clk or negedge i_rst_n) begin
    if (!i_rst_n) begin
        r_flow_cnt   <= S_IDLE;
        r_idx        <= 4'd0;
        r_wait_cnt   <= 14'd0;
        r_i2c_done_d <= 1'b0;

        o_i2c_rh_wl  <= 1'b0;
        o_i2c_exec   <= 1'b0;
        o_i2c_addr   <= 16'd0;
        o_i2c_data_w <= 8'd0;

        o_rw_done    <= 1'b0;
        o_rw_result  <= 1'b0;
        o_rw_fail    <= 1'b0;
        o_fail_idx   <= 5'd0;
        o_fail_exp   <= 8'd0;
        o_fail_got   <= 8'd0;
        o_rd_data    <= 128'd0;
    end
    else begin
        r_i2c_done_d <= i_i2c_done;
        o_i2c_exec   <= 1'b0;      // exec 每拍默认拉低
        o_rw_done    <= 1'b0;

        case (r_flow_cnt)

        //---------- 空闲:等待按键触发 ----------
        S_IDLE: begin
            if (i_trigger) begin
                o_rw_result <= 1'b0;
                o_rw_fail   <= 1'b0;
                r_idx       <= 4'd0;
                r_flow_cnt  <= S_WRITE;
            end
        end

        //---------- 写 1 字节:地址 = r_idx,数据 = 测试图案 ----------
        S_WRITE: begin
            o_i2c_rh_wl  <= 1'b0;
            o_i2c_addr   <= {12'd0, r_idx};
            o_i2c_data_w <= f_test_data(r_idx);
            o_i2c_exec   <= 1'b1;
            r_flow_cnt   <= S_WRITE_WAIT;
        end

        //---------- 等待写完成,然后等待 EEPROM 内部写周期 ----------
        S_WRITE_WAIT: begin
            if (w_i2c_done_pos) begin
                if (i_i2c_ack) begin          // 无应答:器件不在/忙
                    o_fail_idx  <= 5'd16;
                    o_fail_exp  <= f_test_data(r_idx);
                    o_fail_got  <= 8'h00;
                    o_rw_result <= 1'b0;
                    o_rw_fail   <= 1'b1;
                    o_rw_done   <= 1'b1;
                    r_flow_cnt  <= S_DONE;
                end
                else begin
                    r_wait_cnt <= 14'd0;      // 从写完成开始计时写周期
                end
            end
            else if (r_wait_cnt < P_WR_WAIT_TIME) begin
                r_wait_cnt <= r_wait_cnt + 1'b1;
            end

            if (r_wait_cnt >= P_WR_WAIT_TIME) begin   // 写周期结束
                r_wait_cnt <= 14'd0;
                if (r_idx == P_MAX_BYTE - 16'd1) begin
                    r_idx      <= 4'd0;
                    r_flow_cnt <= S_READ;
                end
                else begin
                    r_idx      <= r_idx + 1'b1;
                    r_flow_cnt <= S_WRITE;
                end
            end
        end

        //---------- 读 1 字节:地址 = r_idx ----------
        S_READ: begin
            o_i2c_rh_wl <= 1'b1;
            o_i2c_addr  <= {12'd0, r_idx};
            o_i2c_exec  <= 1'b1;
            r_flow_cnt  <= S_READ_WAIT;
        end

        //---------- 等待读完成,保存读回数据 ----------
        S_READ_WAIT: begin
            if (w_i2c_done_pos) begin
                if (i_i2c_ack) begin          // 读器件地址无应答
                    o_fail_idx  <= 5'd17;
                    o_fail_exp  <= f_test_data(r_idx);
                    o_fail_got  <= 8'h00;
                    o_rw_result <= 1'b0;
                    o_rw_fail   <= 1'b1;
                    o_rw_done   <= 1'b1;
                    r_flow_cnt  <= S_DONE;
                end
                else begin
                    r_rd_buf[r_idx] <= i_i2c_data_r;
                    if (r_idx == P_MAX_BYTE - 16'd1) begin
                        r_flow_cnt <= S_CMP;
                    end
                    else begin
                        r_idx      <= r_idx + 1'b1;
                        r_flow_cnt <= S_READ;
                    end
                end
            end
        end

        //---------- 比对全部 16 字节,输出结果 ----------
        S_CMP: 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],
                          r_rd_buf[8],  r_rd_buf[9],  r_rd_buf[10], r_rd_buf[11],
                          r_rd_buf[12], r_rd_buf[13], r_rd_buf[14], r_rd_buf[15]};
            if (r_has_mismatch) begin
                o_rw_result <= 1'b0;
                o_rw_fail   <= 1'b1;
                o_fail_idx  <= {1'b0, r_mismatch_idx};
                o_fail_exp  <= f_test_data(r_mismatch_idx);
                o_fail_got  <= r_rd_buf[r_mismatch_idx];
            end
            else begin
                o_rw_result <= 1'b1;
                o_rw_fail   <= 1'b0;
            end
            o_rw_done  <= 1'b1;
            r_flow_cnt <= S_DONE;
        end

        //---------- 保持结果,等待下一次触发 ----------
        S_DONE: begin
            o_rw_done <= 1'b1;
            if (i_trigger) begin
                // 注意: 触发脉冲只有 1 拍, 如果先回 S_IDLE 再判断,
                // 脉冲会被 S_DONE->S_IDLE 的转移消耗掉导致测试不启动。
                // 这里直接清除旧结果并进入写阶段, 保证每次按键都有效。
                o_rw_result <= 1'b0;
                o_rw_fail   <= 1'b0;
                r_idx       <= 4'd0;
                r_flow_cnt  <= S_WRITE;
            end
        end

        default: r_flow_cnt <= S_IDLE;
        endcase
    end
end

endmodule

i2c_master.v

verilog 复制代码
module i2c_master #(
    parameter   P_SLAVE_ADDR = 7'b1010000,    // EEPROM从机地址
    parameter   P_CLK_FREQ   = 26'd50_000_000,// 模块输入时钟频率
    parameter   P_I2C_FREQ   = 18'd250_000    // IIC_SCL时钟频率
) (
    // 系统信号
    input                   i_clk           ,// 系统时钟
    input                   i_rst_n         ,// 系统复位(低有效)
    // I2C控制接口
    input                   i_i2c_exec      ,// I2C触发执行信号
    input                   i_bit_ctrl      ,// 字地址位控制(16b/8b)
    input                   i_i2c_rh_wl     ,// I2C读写控制信号
    input           [15:0]  i_i2c_addr      ,// I2C器件内地址
    input           [7:0]   i_i2c_data_w    ,// I2C要写的数据
    output  reg     [7:0]   o_i2c_data_r    ,// I2C读出的数据
    output  reg             o_i2c_done      ,// I2C一次操作完成标志
    output  reg             o_i2c_ack       ,// I2C应答标志(0:应答 1:未应答)
    // I2C物理接口
    output  reg             o_scl           ,// I2C的SCL时钟信号
    inout                   io_sda          ,// I2C的SDA信号(双向)
    // 用户接口
    output  reg             o_dri_clk           // I2C操作驱动时钟
);

// Local parameter define (状态机/分频参数)
localparam  S_IDLE     = 8'b0000_0001;    // 空闲状态
localparam  S_SLADDR   = 8'b0000_0010;    // 发送器件地址
localparam  S_ADDR16   = 8'b0000_0100;    // 发送16位字地址
localparam  S_ADDR8    = 8'b0000_1000;    // 发送8位字地址
localparam  S_DATA_WR  = 8'b0001_0000;    // 写数据(8bit)
localparam  S_ADDR_RD  = 8'b0010_0000;    // 发送读操作器件地址
localparam  S_DATA_RD  = 8'b0100_0000;    // 读数据(8bit)
localparam  S_STOP     = 8'b1000_0000;    // 结束I2C操作



localparam  L_CLK_DIVIDE  = (P_CLK_FREQ / P_I2C_FREQ) >> 2'd2; // 驱动时钟分频系数

// Reg define
reg             r_sda_dir       ;// SDA方向控制(1:输出 0:输入)
reg             r_sda_out       ;// SDA输出寄存器
reg             r_st_done       ;// 状态完成标志
reg             r_wr_flag       ;// 读写标志(1:读 0:写)
reg     [6:0]   r_cnt           ;// 状态内计数寄存器
reg     [7:0]   r_cur_state     ;// 状态机当前状态
reg     [7:0]   r_next_state    ;// 状态机下一状态
reg     [15:0]  r_addr_t        ;// 地址临时寄存器
reg     [7:0]   r_data_r        ;// 读数据临时寄存器
reg     [7:0]   r_data_wr_t     ;// 写数据临时寄存器
reg     [9:0]   r_clk_cnt       ;// 分频时钟计数寄存器

// Wire define
wire            w_sda_in        ;// SDA输入信号

// *****************************************************
// **                    main code
// *****************************************************

// SDA双向信号控制
assign  io_sda  = r_sda_dir ? r_sda_out : 1'bz;  // 1:输出数据 0:高阻(输入)
assign  w_sda_in = io_sda;                       // 采集SDA输入信号

// 生成I2C操作的驱动时钟(dri_clk = SCL时钟的4倍频率)
always @(posedge i_clk or negedge i_rst_n) begin
    if (!i_rst_n) begin
        o_dri_clk <= 1'b0;
        r_clk_cnt <= 10'd0;
    end
    else if (r_clk_cnt == (L_CLK_DIVIDE[8:1] - 9'd1)) begin
        r_clk_cnt <= 10'd0;
        o_dri_clk <= ~o_dri_clk;
    end
    else begin
        r_clk_cnt <= r_clk_cnt + 10'b1;
    end
end

// 三段式状态机:1.同步时序描述状态转移
always @(posedge o_dri_clk or negedge i_rst_n) begin
    if (!i_rst_n) begin
        r_cur_state <= S_IDLE;
    end
    else begin
        r_cur_state <= r_next_state;
    end
end

// 三段式状态机:2.组合逻辑判断状态转移条件
always @(*) begin
    r_next_state = S_IDLE;
    case (r_cur_state)
        S_IDLE: begin                      // 空闲状态
            if (i_i2c_exec) begin
                r_next_state = S_SLADDR;
            end
            else begin
                r_next_state = S_IDLE;
            end
        end
        S_SLADDR: begin                    // 发送器件地址
            if (r_st_done) begin
                if (i_bit_ctrl) begin         // 16位字地址
                    r_next_state = S_ADDR16;
                end
                else begin                    // 8位字地址
                    r_next_state = S_ADDR8;
                end
            end
            else begin
                r_next_state = S_SLADDR;
            end
        end
        S_ADDR16: begin                    // 发送16位字地址高8位
            if (r_st_done) begin
                r_next_state = S_ADDR8;
            end
            else begin
                r_next_state = S_ADDR16;
            end
        end
        S_ADDR8: begin                     // 发送字地址低8位
            if (r_st_done) begin
                if (r_wr_flag == 1'b0) begin  // 写操作
                    r_next_state = S_DATA_WR;
                end
                else begin                    // 读操作
                    r_next_state = S_ADDR_RD;
                end
            end
            else begin
                r_next_state = S_ADDR8;
            end
        end
        S_DATA_WR: begin                   // 写8位数据
            if (r_st_done) begin
                r_next_state = S_STOP;
            end
            else begin
                r_next_state = S_DATA_WR;
            end
        end
        S_ADDR_RD: begin                   // 发送读操作器件地址
            if (r_st_done) begin
                r_next_state = S_DATA_RD;
            end
            else begin
                r_next_state = S_ADDR_RD;
            end
        end
        S_DATA_RD: begin                   // 读8位数据
            if (r_st_done) begin
                r_next_state = S_STOP;
            end
            else begin
                r_next_state = S_DATA_RD;
            end
        end
        S_STOP: begin                      // 停止I2C操作
            if (r_st_done) begin
                r_next_state = S_IDLE;
            end
            else begin
                r_next_state = S_STOP;
            end
        end
        default: r_next_state = S_IDLE;
    endcase
end

// 三段式状态机:3.时序电路描述状态输出
always @(posedge o_dri_clk or negedge i_rst_n) begin
    // 复位初始化
    if (!i_rst_n) begin
        o_scl        <= 1'b1;
        r_sda_out    <= 1'b1;
        r_sda_dir    <= 1'b1;
        o_i2c_done   <= 1'b0;
        o_i2c_ack    <= 1'b0;
        r_cnt        <= 7'd0;
        r_st_done    <= 1'b0;
        r_data_r     <= 8'd0;
        o_i2c_data_r <= 8'd0;
        r_wr_flag    <= 1'b0;
        r_addr_t     <= 16'd0;
        r_data_wr_t  <= 8'd0;
    end
    else begin
        r_st_done <= 1'b0;
        r_cnt     <= r_cnt + 7'b1;

        case (r_cur_state)
            S_IDLE: begin                  // 空闲状态
                o_scl        <= 1'b1;
                r_sda_out    <= 1'b1;
                r_sda_dir    <= 1'b1;
                o_i2c_done   <= 1'b0;
                r_cnt        <= 7'd0;

                if (i_i2c_exec) begin
                    r_wr_flag   <= i_i2c_rh_wl;
                    r_addr_t    <= i_i2c_addr;
                    r_data_wr_t <= i_i2c_data_w;
                    o_i2c_ack   <= 1'b0;
                end
            end

            S_SLADDR: begin               // 发送器件地址+写标志
                case (r_cnt)
                    7'd1 : r_sda_out <= 1'b0; // 起始信号:SDA拉低
                    7'd3 : o_scl     <= 1'b0; // SCL拉低准备发送数据
                    7'd4 : r_sda_out <= P_SLAVE_ADDR[6];
                    7'd5 : o_scl     <= 1'b1; // SCL拉高,从机采样
                    7'd7 : o_scl     <= 1'b0; // SCL拉低,准备下一位
                    7'd8 : r_sda_out <= P_SLAVE_ADDR[5];
                    7'd9 : o_scl     <= 1'b1;
                    7'd11: o_scl     <= 1'b0;
                    7'd12: r_sda_out <= P_SLAVE_ADDR[4];
                    7'd13: o_scl     <= 1'b1;
                    7'd15: o_scl     <= 1'b0;
                    7'd16: r_sda_out <= P_SLAVE_ADDR[3];
                    7'd17: o_scl     <= 1'b1;
                    7'd19: o_scl     <= 1'b0;
                    7'd20: r_sda_out <= P_SLAVE_ADDR[2];
                    7'd21: o_scl     <= 1'b1;
                    7'd23: o_scl     <= 1'b0;
                    7'd24: r_sda_out <= P_SLAVE_ADDR[1];
                    7'd25: o_scl     <= 1'b1;
                    7'd27: o_scl     <= 1'b0;
                    7'd28: r_sda_out <= P_SLAVE_ADDR[0];
                    7'd29: o_scl     <= 1'b1;
                    7'd31: o_scl     <= 1'b0;
                    7'd32: r_sda_out <= 1'b0; // 写操作标志(0:写)
                    7'd33: o_scl     <= 1'b1;
                    7'd35: o_scl     <= 1'b0;
                    7'd36: begin               // 切换为输入,等待应答
                        r_sda_dir <= 1'b0;
                        r_sda_out <= 1'b1;
                    end
                    7'd37: o_scl     <= 1'b1; // 采样应答信号
                    7'd38: begin               // 检测从机应答
                        r_st_done <= 1'b1;
                        if (w_sda_in == 1'b1) begin // 无应答
                            o_i2c_ack <= 1'b1;
                        end
                    end
                    7'd39: begin               // 复位计数,准备下状态
                        o_scl  <= 1'b0;
                        r_cnt  <= 7'd0;
                    end
                    default: ;
                endcase
            end

            S_ADDR16: begin               // 发送16位字地址高8位
                case (r_cnt)
                    7'd0 : begin              // 切换为输出,发送地址
                        r_sda_dir <= 1'b1;
                        r_sda_out <= r_addr_t[15];
                    end
                    7'd1 : o_scl     <= 1'b1;
                    7'd3 : o_scl     <= 1'b0;
                    7'd4 : r_sda_out <= r_addr_t[14];
                    7'd5 : o_scl     <= 1'b1;
                    7'd7 : o_scl     <= 1'b0;
                    7'd8 : r_sda_out <= r_addr_t[13];
                    7'd9 : o_scl     <= 1'b1;
                    7'd11: o_scl     <= 1'b0;
                    7'd12: r_sda_out <= r_addr_t[12];
                    7'd13: o_scl     <= 1'b1;
                    7'd15: o_scl     <= 1'b0;
                    7'd16: r_sda_out <= r_addr_t[11];
                    7'd17: o_scl     <= 1'b1;
                    7'd19: o_scl     <= 1'b0;
                    7'd20: r_sda_out <= r_addr_t[10];
                    7'd21: o_scl     <= 1'b1;
                    7'd23: o_scl     <= 1'b0;
                    7'd24: r_sda_out <= r_addr_t[9];
                    7'd25: o_scl     <= 1'b1;
                    7'd27: o_scl     <= 1'b0;
                    7'd28: r_sda_out <= r_addr_t[8];
                    7'd29: o_scl     <= 1'b1;
                    7'd31: o_scl     <= 1'b0;
                    7'd32: begin               // 切换为输入,等待应答
                        r_sda_dir <= 1'b0;
                        r_sda_out <= 1'b1;
                    end
                    7'd33: o_scl     <= 1'b1;
                    7'd34: begin               // 检测从机应答
                        r_st_done <= 1'b1;
                        if (w_sda_in == 1'b1) begin
                            o_i2c_ack <= 1'b1;
                        end
                    end
                    7'd35: begin
                        o_scl  <= 1'b0;
                        r_cnt  <= 7'd0;
                    end
                    default: ;
                endcase
            end

            S_ADDR8: begin                // 发送字地址低8位
                case (r_cnt)
                    7'd0 : begin              // 切换为输出,发送地址
                        r_sda_dir <= 1'b1;
                        r_sda_out <= r_addr_t[7];
                    end
                    7'd1 : o_scl     <= 1'b1;
                    7'd3 : o_scl     <= 1'b0;
                    7'd4 : r_sda_out <= r_addr_t[6];
                    7'd5 : o_scl     <= 1'b1;
                    7'd7 : o_scl     <= 1'b0;
                    7'd8 : r_sda_out <= r_addr_t[5];
                    7'd9 : o_scl     <= 1'b1;
                    7'd11: o_scl     <= 1'b0;
                    7'd12: r_sda_out <= r_addr_t[4];
                    7'd13: o_scl     <= 1'b1;
                    7'd15: o_scl     <= 1'b0;
                    7'd16: r_sda_out <= r_addr_t[3];
                    7'd17: o_scl     <= 1'b1;
                    7'd19: o_scl     <= 1'b0;
                    7'd20: r_sda_out <= r_addr_t[2];
                    7'd21: o_scl     <= 1'b1;
                    7'd23: o_scl     <= 1'b0;
                    7'd24: r_sda_out <= r_addr_t[1];
                    7'd25: o_scl     <= 1'b1;
                    7'd27: o_scl     <= 1'b0;
                    7'd28: r_sda_out <= r_addr_t[0];
                    7'd29: o_scl     <= 1'b1;
                    7'd31: o_scl     <= 1'b0;
                    7'd32: begin               // 切换为输入,等待应答
                        r_sda_dir <= 1'b0;
                        r_sda_out <= 1'b1;
                    end
                    7'd33: o_scl     <= 1'b1;
                    7'd34: begin               // 检测从机应答
                        r_st_done <= 1'b1;
                        if (w_sda_in == 1'b1) begin
                            o_i2c_ack <= 1'b1;
                        end
                    end
                    7'd35: begin
                        o_scl  <= 1'b0;
                        r_cnt  <= 7'd0;
                    end
                    default: ;
                endcase
            end

            S_DATA_WR: begin              // 写8位数据
                case (r_cnt)
                    7'd0 : begin              // 切换为输出,发送数据
                        r_sda_dir <= 1'b1;
                        r_sda_out <= r_data_wr_t[7];
                    end
                    7'd1 : o_scl     <= 1'b1;
                    7'd3 : o_scl     <= 1'b0;
                    7'd4 : r_sda_out <= r_data_wr_t[6];
                    7'd5 : o_scl     <= 1'b1;
                    7'd7 : o_scl     <= 1'b0;
                    7'd8 : r_sda_out <= r_data_wr_t[5];
                    7'd9 : o_scl     <= 1'b1;
                    7'd11: o_scl     <= 1'b0;
                    7'd12: r_sda_out <= r_data_wr_t[4];
                    7'd13: o_scl     <= 1'b1;
                    7'd15: o_scl     <= 1'b0;
                    7'd16: r_sda_out <= r_data_wr_t[3];
                    7'd17: o_scl     <= 1'b1;
                    7'd19: o_scl     <= 1'b0;
                    7'd20: r_sda_out <= r_data_wr_t[2];
                    7'd21: o_scl     <= 1'b1;
                    7'd23: o_scl     <= 1'b0;
                    7'd24: r_sda_out <= r_data_wr_t[1];
                    7'd25: o_scl     <= 1'b1;
                    7'd27: o_scl     <= 1'b0;
                    7'd28: r_sda_out <= r_data_wr_t[0];
                    7'd29: o_scl     <= 1'b1;
                    7'd31: o_scl     <= 1'b0;
                    7'd32: begin               // 切换为输入,等待应答
                        r_sda_dir <= 1'b0;
                        r_sda_out <= 1'b1;
                    end
                    7'd33: o_scl     <= 1'b1;
                    7'd34: begin               // 检测从机应答
                        r_st_done <= 1'b1;
                        if (w_sda_in == 1'b1) begin
                            o_i2c_ack <= 1'b1;
                        end
                    end
                    7'd35: begin
                        o_scl  <= 1'b0;
                        r_cnt  <= 7'd0;
                    end
                    default: ;
                endcase
            end

            S_ADDR_RD: begin              // 发送读操作器件地址
                case (r_cnt)
                    7'd0 : begin              // 重新起始
                        r_sda_dir <= 1'b1;
                        r_sda_out <= 1'b1;
                    end
                    7'd1 : o_scl     <= 1'b1;
                    7'd2 : r_sda_out <= 1'b0; // 重复起始信号
                    7'd3 : o_scl     <= 1'b0;
                    7'd4 : r_sda_out <= P_SLAVE_ADDR[6];
                    7'd5 : o_scl     <= 1'b1;
                    7'd7 : o_scl     <= 1'b0;
                    7'd8 : r_sda_out <= P_SLAVE_ADDR[5];
                    7'd9 : o_scl     <= 1'b1;
                    7'd11: o_scl     <= 1'b0;
                    7'd12: r_sda_out <= P_SLAVE_ADDR[4];
                    7'd13: o_scl     <= 1'b1;
                    7'd15: o_scl     <= 1'b0;
                    7'd16: r_sda_out <= P_SLAVE_ADDR[3];
                    7'd17: o_scl     <= 1'b1;
                    7'd19: o_scl     <= 1'b0;
                    7'd20: r_sda_out <= P_SLAVE_ADDR[2];
                    7'd21: o_scl     <= 1'b1;
                    7'd23: o_scl     <= 1'b0;
                    7'd24: r_sda_out <= P_SLAVE_ADDR[1];
                    7'd25: o_scl     <= 1'b1;
                    7'd27: o_scl     <= 1'b0;
                    7'd28: r_sda_out <= P_SLAVE_ADDR[0];
                    7'd29: o_scl     <= 1'b1;
                    7'd31: o_scl     <= 1'b0;
                    7'd32: r_sda_out <= 1'b1; // 读操作标志(1:读)
                    7'd33: o_scl     <= 1'b1;
                    7'd35: o_scl     <= 1'b0;
                    7'd36: begin               // 切换为输入,等待应答
                        r_sda_dir <= 1'b0;
                        r_sda_out <= 1'b1;
                    end
                    7'd37: o_scl     <= 1'b1;
                    7'd38: begin               // 检测从机应答
                        r_st_done <= 1'b1;
                        if (w_sda_in == 1'b1) begin
                            o_i2c_ack <= 1'b1;
                        end
                    end
                    7'd39: begin
                        o_scl  <= 1'b0;
                        r_cnt  <= 7'd0;
                    end
                    default: ;
                endcase
            end

            S_DATA_RD: begin              // 读8位数据
                case (r_cnt)
                    7'd0 : r_sda_dir <= 1'b0; // 切换为输入
                    7'd1 : begin              // 采样第7位
                        r_data_r[7] <= w_sda_in;
                        o_scl       <= 1'b1;
                    end
                    7'd3 : o_scl     <= 1'b0;
                    7'd5 : begin              // 采样第6位
                        r_data_r[6] <= w_sda_in;
                        o_scl       <= 1'b1;
                    end
                    7'd7 : o_scl     <= 1'b0;
                    7'd9 : begin              // 采样第5位
                        r_data_r[5] <= w_sda_in;
                        o_scl       <= 1'b1;
                    end
                    7'd11: o_scl     <= 1'b0;
                    7'd13: begin              // 采样第4位
                        r_data_r[4] <= w_sda_in;
                        o_scl       <= 1'b1;
                    end
                    7'd15: o_scl     <= 1'b0;
                    7'd17: begin              // 采样第3位
                        r_data_r[3] <= w_sda_in;
                        o_scl       <= 1'b1;
                    end
                    7'd19: o_scl     <= 1'b0;
                    7'd21: begin              // 采样第2位
                        r_data_r[2] <= w_sda_in;
                        o_scl       <= 1'b1;
                    end
                    7'd23: o_scl     <= 1'b0;
                    7'd25: begin              // 采样第1位
                        r_data_r[1] <= w_sda_in;
                        o_scl       <= 1'b1;
                    end
                    7'd27: o_scl     <= 1'b0;
                    7'd29: begin              // 采样第0位
                        r_data_r[0] <= w_sda_in;
                        o_scl       <= 1'b1;
                    end
                    7'd31: o_scl     <= 1'b0;
                    7'd32: begin               // 主机发送非应答
                        r_sda_dir <= 1'b1;
                        r_sda_out <= 1'b1;
                    end
                    7'd33: o_scl     <= 1'b1;
                    7'd34: r_st_done <= 1'b1;  // 读数据完成
                    7'd35: begin               // 保存读数据
                        o_scl        <= 1'b0;
                        r_cnt        <= 7'd0;
                        o_i2c_data_r <= r_data_r;
                    end
                    default: ;
                endcase
            end

            S_STOP: begin                 // 停止I2C操作
                case (r_cnt)
                    7'd0 : begin              // 停止信号起始
                        r_sda_dir <= 1'b1;
                        r_sda_out <= 1'b0;
                    end
                    7'd1 : o_scl     <= 1'b1; // SCL拉高
                    7'd3 : r_sda_out <= 1'b1; // SDA拉高,停止信号
                    7'd15: r_st_done <= 1'b1; // 状态完成
                    7'd16: begin               // 操作完成标志
                        r_cnt        <= 7'd0;
                        o_i2c_done   <= 1'b1;
                    end
                    default: ;
                endcase
            end
        endcase
    end
end
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 复制代码
[22:04:32.216]收←◆TEST START
[22:04:32.316]收←◆PASS
RDATA 00 FF 55 AA 33 CC 0F F0 12 34 56 78 9A BC DE F0
相关推荐
Hello-FPGA37 分钟前
量子计算滨松c15550-22up单光子相机与PCIe1004采集卡
人工智能·计算机视觉·fpga开发
9527华安2 小时前
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开发
国科安芯2 天前
ASC8T245S:把 8 位并行总线稳稳“跨过“电压域的双电源收发器
网络·分布式·单片机·嵌入式硬件·fpga开发·架构
HUSTCH20212 天前
DW IP详解
fpga开发