Forum Discussion
ak6dn
Regular Contributor
4 years agoI've always used the oversampled high speed clock to treat both the I2C clk and data lines both as data.
This way I can do digital filtering to eliminate glitches on either line, and the state machine runs at the frequency of the internal logic.
Here is an I2C slave module I wrote long ago and shipped in a successful product.
It's in Verilog (you reference VHDL) but it illustrates the concept of using a high speed clock to oversample I2c clk/data.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // This module supports standard I2C bus read and write transactions as a slave target. // // Single and multibyte writes, with an autoincrementing slave register ptr: // <start> <addr+wr> <ack> <ptr> <ack> [<wrdata@ptr++> <ack>]+ <stop> // // Single and multibyte reads, with an autoincrementing slave register ptr: // <start> <addr+wr> <ack> <ptr> <ack> <start> <addr+rd> <ack> [<rddata@ptr++> <ack>]* <rddata@ptr++> <nack> <stop> // // 2006-07-01 donorth - initial version // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// `timescale 1ns / 1ns module i2c_slave ( /*AUTOARG*/ // Outputs sclo, sdao, datao, addr, wr, rd, // Inputs clk, reset, scli, sdai, i2caddr, datai ); // global clock and reset input clk; input reset; // serial i2c clock and data input scli; output reg sclo; input sdai; output reg sdao; // own address input [7:1] i2caddr; // external data/addr bus and control input [7:0] datai; output reg [7:0] datao; output reg [7:0] addr; output reg wr; output reg rd; // slave state machine definitions parameter SS_IDLE = 3'b000, SS_START = 3'b001, SS_SLAVE = 3'b011, SS_SLAVE_ACK = 3'b010, SS_ADDR = 3'b110, SS_ADDR_ACK = 3'b111, SS_DATA = 3'b101, SS_DATA_ACK = 3'b100; // i2c data direction type parameter I2C_WRITE = 1'b0, I2C_READ = 1'b1; // for simulation parameter TPD = 0; // synchronized i2c bus inputs reg [3:0] sdair, sclir; // i2c sda start/stop and scl rising/falling states reg sda_start, sda_stop; reg scl_rising, scl_falling; // i2c serial to parallel data register reg [8:0] datas; // i2c slave selected and r/w mode registers reg selected, mode; // i2c slave state machine reg [2:0] slave_state; // i2c slave state counter reg [3:0] slave_cnt; // indicate count has reached eight wire slave_cnt_eight = (slave_cnt == 4'h3); // 4b LFSR Counter Logic function [3:0] LFSD4; input [3:0] D; begin LFSD4 = { D[2:0], D[3] } ^ { 2'b0, D[3], 1'b0 }; end endfunction // LFSD4; //////////////////////////////////////////////////////////////////////////// // synchronize i2c bus inputs to clock always @(posedge clk or posedge reset) begin if (reset) begin sclir <= #TPD (-1); sdair <= #TPD (-1); end else begin // synchronize i2c sda/scl inputs sclir <= #TPD {sclir[2:0],scli}; sdair <= #TPD {sdair[2:0],sdai}; end end wire sdain = sdair[2]; // serial shift datain wire slaveack = (sdair == 4'b0000); // slave acknowlwdge //////////////////////////////////////////////////////////////////////////// // i2c bus start/stop detect and clock rising/falling detect always @(posedge clk or posedge reset) begin if (reset) begin sda_start <= #TPD 0; sda_stop <= #TPD 0; scl_rising <= #TPD 0; scl_falling <= #TPD 0; end else begin // START is scl H and sda H->L sda_start <= #TPD sclir == 4'b1111 && sdair == 4'b1100; // STOP is scl H and sda L->H sda_stop <= #TPD sclir == 4'b1111 && sdair == 4'b0011; // RISING is scl L->H scl_rising <= #TPD sclir == 4'b0011; // FALLING is scl H->L scl_falling <= #TPD sclir == 4'b1100; end end //////////////////////////////////////////////////////////////////////////// // i2c slave state machine always @(posedge clk or posedge reset) begin if (reset) begin slave_cnt <= #TPD (-1); slave_state <= #TPD SS_IDLE; end else begin if (sda_start) slave_state <= #TPD SS_START; // force START else if (sda_stop) slave_state <= #TPD SS_IDLE; // force STOP else if (scl_falling) // only change when asserted begin case (slave_state) SS_START: begin slave_cnt <= #TPD (-1); slave_state <= #TPD SS_SLAVE; end SS_SLAVE: begin slave_cnt <= #TPD LFSD4(slave_cnt); slave_state <= #TPD slave_cnt_eight ? SS_SLAVE_ACK : SS_SLAVE; end SS_SLAVE_ACK: begin slave_cnt <= #TPD (-1); slave_state <= #TPD selected ? ((mode == I2C_WRITE) ? SS_ADDR : SS_DATA) : SS_IDLE; end SS_ADDR: begin slave_cnt <= #TPD LFSD4(slave_cnt); slave_state <= #TPD slave_cnt_eight ? SS_ADDR_ACK : SS_ADDR; end SS_ADDR_ACK: begin slave_cnt <= #TPD (-1); slave_state <= #TPD SS_DATA; end SS_DATA: begin slave_cnt <= #TPD LFSD4(slave_cnt); slave_state <= #TPD slave_cnt_eight ? SS_DATA_ACK : SS_DATA; end SS_DATA_ACK: begin slave_cnt <= #TPD (-1); slave_state <= #TPD (mode == I2C_WRITE || slaveack) ? SS_DATA : SS_IDLE; end default: begin slave_cnt <= #TPD (-1); slave_state <= #TPD SS_IDLE; end endcase // case(slave_state) end end end // synthesis translate_off reg [5*8:1] slave_state_dpy; always @(slave_state) casez (slave_state) SS_IDLE: slave_state_dpy <= "IDLE "; SS_START: slave_state_dpy <= "START"; SS_SLAVE: slave_state_dpy <= "SLAVE"; SS_SLAVE_ACK: slave_state_dpy <= "S/ACK"; SS_ADDR: slave_state_dpy <= "ADDR "; SS_ADDR_ACK: slave_state_dpy <= "A/ACK"; SS_DATA: slave_state_dpy <= "DATA "; SS_DATA_ACK: slave_state_dpy <= "D/ACK"; default: slave_state_dpy <= "?????"; endcase // casez(slave_state) // synthesis translate_on //////////////////////////////////////////////////////////////////////////// // slave address/command input process always @(posedge clk or posedge reset) begin if (reset) begin mode <= #TPD I2C_WRITE; selected <= #TPD 0; end else begin // compare slave i2c address for match in slave state if (scl_falling && slave_state == SS_SLAVE && slave_cnt_eight) begin mode <= #TPD datas[0] ? I2C_READ : I2C_WRITE; selected <= #TPD (datas[7:1] == i2caddr[7:1]); end else if (scl_falling) begin // selected only stays active for one state selected <= #TPD 0; end else if (sda_stop) begin // if stop seen, deselect immediately mode <= #TPD I2C_WRITE; selected <= #TPD 0; end end end // synthesis translate_off reg [2*8:1] mode_dpy; always @(mode) casez (mode) I2C_READ: mode_dpy <= "RD"; I2C_WRITE: mode_dpy <= "WR"; default: mode_dpy <= "??"; endcase // casez(mode) // synthesis translate_on //////////////////////////////////////////////////////////////////////////// // word address processing always @(posedge clk or posedge reset) begin if (reset) addr <= #TPD 0; else if (scl_falling) begin if (slave_state == SS_ADDR_ACK) // load new address from serial data input addr <= #TPD datas[8:1]; else if (slave_state == SS_SLAVE_ACK && mode == I2C_READ && selected || slave_state == SS_DATA_ACK && mode == I2C_WRITE || slave_state == SS_DATA_ACK && mode == I2C_READ && slaveack) // increment address by one addr <= #TPD addr + 1; end end //////////////////////////////////////////////////////////////////////////// // i2c bus serial data input process always @(posedge clk or posedge reset) begin if (reset) datas <= #TPD 0; else if (scl_rising) // shift i2c data bus in, always datas <= #TPD {datas[7:0],sdain}; end //////////////////////////////////////////////////////////////////////////// // data output processing on client bus always @(posedge clk or posedge reset) begin if (reset) datao <= #TPD 0; else if (scl_falling) // clock out parallel data only on slave write complete if (slave_state == SS_DATA && slave_cnt_eight && mode == I2C_WRITE) datao <= #TPD datas[7:0]; end //////////////////////////////////////////////////////////////////////////// // rd and wr signal generation always @(posedge clk or posedge reset) begin if (reset) begin rd <= #TPD 0; wr <= #TPD 0; end else begin // read pulse comes prior to i2c serial data required rd <= #TPD scl_rising && mode == I2C_READ && (slave_state == SS_SLAVE_ACK && selected || slave_state == SS_DATA_ACK && slaveack); // write pulse comes after i2c serial data is captured wr <= #TPD scl_rising && mode == I2C_WRITE && (slave_state == SS_DATA_ACK); end end //////////////////////////////////////////////////////////////////////////// // serial clock out (wait state) processing always @(posedge clk or posedge reset) begin if (reset) sclo <= #TPD 1; // deassert high else sclo <= #TPD 1; // not implemented yet end //////////////////////////////////////////////////////////////////////////// // serial data out / ack processing always @(posedge clk or posedge reset) begin if (reset) sdao <= #TPD 1; // deassert high else begin if (slave_state == SS_SLAVE_ACK && selected || slave_state == SS_ADDR_ACK || slave_state == SS_DATA_ACK && mode == I2C_WRITE) // assert slave ack response sdao <= #TPD 0; // assert low else if (mode == I2C_READ && slave_state == SS_DATA) // assert read data response case (slave_cnt) // 4b LFSR counter 4'hF: sdao <= #TPD datai[7]; 4'hD: sdao <= #TPD datai[6]; 4'h9: sdao <= #TPD datai[5]; 4'h1: sdao <= #TPD datai[4]; 4'h2: sdao <= #TPD datai[3]; 4'h4: sdao <= #TPD datai[2]; 4'h8: sdao <= #TPD datai[1]; 4'h3: sdao <= #TPD datai[0]; default sdao <= #TPD 1; endcase else sdao <= #TPD 1; // deassert high end end //////////////////////////////////////////////////////////////////////////// endmodule // i2c_slave