本設計用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