Forum Discussion

Knug's avatar
Knug
Icon for Contributor rankContributor
4 years ago

Ways to create an I2C Slave in a CPLD ? Like your comments here please

There are 2 ways to create an I2C Slave in a CPLD:

1) Using directly the SCL line as a clock signal inside the CPLD (but SCHMITT trigger may be required)

2) Using a fast clock to oversample the SDA and SCL signals

But if 1) is used (ie using the SCL line as clock for I2C state machine), need to enable schmitt trigger on I2C lines SDA & SCL. Slow SCL changes could make noise and invalid data reception. Without a Schmitt trigger, any noise or ringing on SCL line may introduce extra clock cycles, which would break the functionality. Isn't this the case?

Looks like MAXV has a SCHMITT TRIGGER function wrt following post seen :

"Do Altera device input buffers have built-in Schmitt triggers?

In cases where a digital device input comes from a noisy source (e.g., a switch) or a slow edge rate, a Schmitt trigger is used to introduce hysteresis to the input signal. However, such hysteresis causes slower performance, higher power consumption, and higher silicon cost.

The device families that offer optional Schmitt triggers on the input buffers for user I/O pins are the MAX® II and MAX V device families. For other device families, if a Schmitt trigger is necessary, Altera® recommends using an external Schmitt trigger device."

Could the enabling of SCHMITT TRIGGER for MAX V be done in VHDL as follows if by default condition is disabled ?

-- SCHMITT TRIGGER activation
attribute SCHMITT_TRIGGER: string;
attribute SCHMITT_TRIGGER of SCL: signal is "true";
attribute SCHMITT_TRIGGER of SDA: signal is "true";

---------
Alternatively, could use (2) a fast receiver clock to oversample the SDA and SCL signals (eg 1bit period corresponds to 8 receiver clock cycles) and able to detect then easily the START and STOP conditions (establishing a sampling point that is near the middle of the bit period)
Any opinions on this (eg is using the 2nd method better?) will be appreciated.

3 Replies

  • Ash_R_Intel's avatar
    Ash_R_Intel
    Icon for Regular Contributor rankRegular Contributor

    Hi,


    Schmitt trigger can be enabled on the Pin Planner page. Select "2.5V Schmitt Trigger Input" or "3.3V Schmitt Trigger Input", or can be assigned in the .qsf file.


    For the design strategy for I2C Slave, if you want to use method 2, the minimum frequency to be used should be at least twice the incoming SCL frequency. I would personally prefer method 1.

    There are reference designs available on Intel design store: Design Store for Intel® FPGAs.

    Regards.


  • Ash_R_Intel's avatar
    Ash_R_Intel
    Icon for Regular Contributor rankRegular Contributor

    This thread will be transitioned to community support. If you have a new question, feel free to open a new thread to get the support from Intel experts. Otherwise, the community users will continue to help you on this thread. Thank you


  • ak6dn's avatar
    ak6dn
    Icon for Regular Contributor rankRegular Contributor

    I'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