参考
Zynq上UART/IIC/SPI的27个实验-第14课:PL 逻辑 IIC 主机访问 AT24C256.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