Programmable Devices
CPLDs, FPGAs, SoC FPGAs, Configuration, and Transceivers
20677 Discussions

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

Knug
Beginner
929 Views

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.
0 Kudos
3 Replies
Ash_R_Intel
Employee
909 Views

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.


0 Kudos
Ash_R_Intel
Employee
829 Views

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


0 Kudos
ak6dn
Valued Contributor III
817 Views

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

 

0 Kudos
Reply