本设计用verilog实现了一个简单的I2C协议,实现功能为往固定地址先写入一个字节,然后再读出该字节。
涉及到的EEPROM为Atmel家的AT24C04,4Kbit存储空间,8位位宽,需要9位宽的地址,其他细节参见规格书doc0180。
AT24C04支持5种读写模式:字节写,页写,当前地址读,随机读,顺序读。在当前地址读或顺序读时序的最后不发送NAK,则可以继续下一个地址的数据,直到主控给出一个NAK。
另外需要注意一点是,AT24C04需要至多5ms的写时间,即在写操作的结束时序到下一个操作的开始时许之间需要间隔5ms,以保证器件对写入数据的固化。
简化起见,本设计使用了一个1s的从开始写到开始读的时间间隔。
接口程序
`timescale 1ns/1ps // AT24C04接口程序,只支持字节写入和随机读取 // 本接口接收指定地坿的单字节写入和单字节读取 module i2c_intf #( parameter SYS_FREQ = 200_000_000, parameter SCL_FREQ = 100_000 )( input wire clk, nrst, // 写信号 input wire wrreq, input wire [8:0] waddr, input wire [7:0] wdata, input wire rdreq, // 读信号 input wire [8:0] raddr, output reg [7:0] rdata, output reg vld, // 忙线指示信号 output reg rdy, // i2c接口信号 output reg scl, inout sda ); reg sda_out; // 用于观察信号 wire sda_in; assign sda = (sda_out == 0) ? 1'b0 : 1'bz; assign sda_in = sda; // I2C的SCL周期 localparam SCL_T = SYS_FREQ / SCL_FREQ; // 由于AT24C04为4K容量,数据长度8位,需要9bit地址,最高位存于器件地址中 localparam DADDR_6 = 6'b101000; reg [7:0] device_addr; // SCL计数 reg [15:0] cnt_scl; wire add_cnt_scl; wire end_cnt_scl; // bit计数 reg [3:0] cnt_bit; wire add_cnt_bit; wire end_cnt_bit; // cnt计数状态内执行顺序 reg [3:0] cnt_step; wire add_cnt_step; wire end_cnt_step; // 变量对应不同状态需要执行的步骤数 reg [3:0] bit_num, step_num; always @(posedge clk or negedge nrst) begin if(nrst == 0) device_addr <= 0; else if(state_c == S_WR_BYTE && cnt_step == 2 - 1 || state_c == S_RD_RANDOM && cnt_step == 2 - 1) device_addr <= {DADDR_6, waddr[8], 1'b0}; else if(state_c == S_RD_RANDOM && cnt_step == 5 - 1) device_addr <= {DADDR_6, raddr[8], 1'b1}; end // 状态划分为:空闲,写字节,随机写 localparam S_IDLE = 6'b000_001; localparam S_WR_BYTE = 6'b000_010; localparam S_RD_RANDOM = 6'b000_100; reg [5:0] state_c, state_n; wire idle2wr_byte; wire idle2rd_random; wire wr_byte2idle; wire rd_random2idle; always @(posedge clk or negedge nrst) begin if(nrst == 0) state_c <= S_IDLE; else state_c <= state_n; end always @* begin case (state_c) S_IDLE: begin if(idle2wr_byte) state_n = S_WR_BYTE; else if(idle2rd_random) state_n = S_RD_RANDOM; else state_n = state_c; end S_WR_BYTE: begin if(wr_byte2idle) state_n = S_IDLE; else state_n = state_c; end S_RD_RANDOM: begin if(rd_random2idle) state_n = S_IDLE; else state_n = state_c; end default: state_n = state_c; endcase end assign idle2wr_byte = state_c == S_IDLE && wrreq; assign idle2rd_random = state_c == S_IDLE && rdreq; assign wr_byte2idle = state_c == S_WR_BYTE && end_cnt_step; assign rd_random2idle = state_c == S_RD_RANDOM && end_cnt_step; always @(posedge clk or negedge nrst) begin if(nrst == 0) cnt_scl <= 0; else if(add_cnt_scl) begin if(end_cnt_scl) cnt_scl <= 0; else cnt_scl <= cnt_scl + 1'b1; end end assign add_cnt_scl = state_c != S_IDLE; assign end_cnt_scl = add_cnt_scl && cnt_scl == SCL_T - 1; always @(posedge clk or negedge nrst) begin if(nrst == 0) cnt_bit <= 0; else if(add_cnt_bit) begin if(end_cnt_bit) cnt_bit <= 0; else cnt_bit <= cnt_bit + 1'b1; end end assign add_cnt_bit = end_cnt_scl; assign end_cnt_bit = add_cnt_bit && cnt_bit == bit_num - 1; // 状态内以开始,读,写或结束为一个步骤 always @(posedge clk or negedge nrst) begin if(nrst == 0) cnt_step <= 0; else if(add_cnt_step) begin if(end_cnt_step) cnt_step <= 0; else cnt_step <= cnt_step + 1'b1; end end assign add_cnt_step = end_cnt_bit; assign end_cnt_step = add_cnt_step && cnt_step == step_num - 1; // 写单个字节分为以下步骤:开始,写器件地址,写存储地址,写数据,结束 // 读单个字节分为以下步骤:开始,写器件地址,写存储地址,开始时序,写器件地址,读数据,结束 always @* begin if(state_c == S_IDLE) begin step_num = 0; bit_num = 0; end else if(state_c == S_WR_BYTE) begin step_num = 5; if(cnt_step == 1 - 1 || cnt_step == step_num - 1) bit_num = 1; else bit_num = 9; end else if(state_c == S_RD_RANDOM) begin step_num = 7; if(cnt_step == 1 - 1 || cnt_step == 4 - 1 || cnt_step == step_num - 1) bit_num = 1; else bit_num = 9; end else begin step_num = 0; bit_num = 0; end end // scl信号前半个周期为低,后半个周期为高,且第1个半个周期保持高电平 always @(posedge clk or negedge nrst) begin if(nrst == 0) scl <= 1; else if(add_cnt_scl && cnt_scl == SCL_T / 2 - 1) scl <= 1; else if(end_cnt_scl && !end_cnt_step) scl <= 0; end // 重点,SDA信号 always @(posedge clk or negedge nrst) begin if(nrst == 0) sda_out <= 1; else begin // 开始时序 if((cnt_step == 1 - 1 || state_c == S_RD_RANDOM && cnt_step == 4 - 1) && cnt_scl == SCL_T * 3 / 4 - 1) sda_out <= 0; // 结束时序,且在结束时钟周期需要需要事先将SDA拉低 else if(cnt_step == step_num - 1 && cnt_scl == SCL_T / 4 - 1) sda_out <= 0; else if(cnt_step == step_num - 1 && cnt_scl == SCL_T * 3 / 4 - 1) sda_out <= 1; // 在应答周期将SDA拉高 else if(cnt_bit == 9 - 1 && cnt_scl == SCL_T / 4 - 1) sda_out <= 1; // 器件地址 else if((state_c == S_WR_BYTE && cnt_step == 2 - 1 || state_c == S_RD_RANDOM && (cnt_step == 2 - 1 || cnt_step == 5 - 1)) && cnt_scl == SCL_T / 4 - 1 && cnt_bit != 9 - 1) sda_out <= device_addr[7 - cnt_bit]; // 写地址 else if(state_c == S_WR_BYTE && cnt_step == 3 - 1 && cnt_scl == SCL_T / 4 - 1 && cnt_bit != 9 - 1) sda_out <= waddr[7 - cnt_bit]; // 写数据 else if(state_c == S_WR_BYTE && cnt_step == 4 - 1 && cnt_scl == SCL_T / 4 - 1 && cnt_bit != 9 - 1) sda_out <= wdata[7 - cnt_bit]; // 读地址 else if(state_c == S_RD_RANDOM && cnt_step == 3 - 1 && cnt_scl == SCL_T / 4 - 1 && cnt_bit != 9 - 1) sda_out <= raddr[7 - cnt_bit]; end end // 读数据代码 always @(posedge clk or negedge nrst) begin if(nrst == 0) rdata <= 0; else if(state_c == S_RD_RANDOM && cnt_step == 6 - 1 && cnt_scl == SCL_T / 4 * 3 - 1 && cnt_bit != 9 - 1) rdata[7 - cnt_bit] <= sda; end // 读数据有效指示信号 always @(posedge clk or negedge nrst) begin if(nrst == 0) vld <= 0; else if(state_c == S_RD_RANDOM && end_cnt_step) vld <= 1; else vld <= 0; end always @(posedge clk or negedge nrst) begin if(nrst == 0) rdy <= 1; else if(state_c == S_IDLE) rdy <= 1; else rdy <= 0; end // ILA例化代码 ila_read ila_read_u ( .clk (clk ), // input wire clk .probe0 (vld ), // input wire [7:0] probe0 .probe1 (scl ), .probe2 (sda_out), .probe3 (cnt_step), .probe4 (cnt_bit), .probe5 (rdata ), .probe6 (wrreq ), .probe7 (rdreq ), .probe8 (sda_in ) ); endmodule
顶层测试例程
`timescale 1ns / 1ps module i2c_example( input wire clk_p, clk_n, nrst, input wire key_in, output wire scl, inout sda ); parameter SYS_FREQ = 200_000_000; parameter SCL_FREQ = 100_000; // 差分时钟信号转为单端信号 IBUFGDS #( .DIFF_TERM("FALSE"), .IBUF_LOW_PWR("TRUE"), .IOSTANDARD("DEFAULT") ) IBUFGDS_inst( .O(clk), .I(clk_p), .IB(clk_n) ); reg [31:0] cnt; wire add_cnt; wire end_cnt; reg flag; wire [7:0] data; wire vld; wire wrreq; // 加入按键模块用于调试 wire key_out; reg key_out_ff; always @(posedge clk or negedge nrst) begin if(nrst == 0) key_out_ff <= 0; else key_out_ff <= key_out; end assign wrreq = key_out == 0 && key_out_ff == 1; always @(posedge clk or negedge nrst) begin if(nrst == 0) flag <= 0; else if(wrreq) flag <= 1; else if(end_cnt) flag <= 0; end always @(posedge clk or negedge nrst) begin if(nrst == 0) cnt <= 0; else if(add_cnt) begin if(end_cnt) cnt <= 0; else cnt <= cnt + 1'b1; end end assign add_cnt = flag; assign end_cnt = add_cnt && cnt == SYS_FREQ - 1; i2c_intf #(SYS_FREQ, SCL_FREQ) i2c_intf_u( .clk (clk ), .nrst (nrst ), .wrreq (wrreq ), .waddr (9'h03 ), .wdata (8'h55 ), .rdreq (end_cnt), .raddr (9'h03 ), .rdata (data ), .vld (vld ), .rdy ( ), .scl (scl ), .sda (sda ) ); debounce debounce_u( .clk (clk ), .nrst (nrst ), .key_in (key_in ), .key_out(key_out) ); endmodule