The reason why no one is using I2C (or any communication bus) Hard IP of iCE40

Right now, I’m working on project that use iCE40UL1K-SWG16 as a PWM waveform generator to control BLDC motor. On of the feature of this thing is Motor control over I2C. Since I2C is easy to use. This is go to choice of Host-FPGA communication bus.

I know that out there. There’re tons of I2C Soft IP core that work really well and well documented. But I want to give a try on Hard IP core of the iCE40 itself. So I did a bit digging and found that iCE40UL1K has 2 I2C Hard IP cores :

According to the datasheet of iCE40UL1K. There’re 2 I2C Hard IP cores baked into the silicon.

So, without using soft core. I can save some (or may be a lot of) Logic cells and made them available for other purpose.

First thing I did is search up on Google for any project or example that use I2C Hard IP. Frankly speaking, NO ONE IS USING THESE HARD IP CORE! There’s absolutely no one using I2C/SPI Hard IP core in their project (They not even use iCE40UL1K lol). This left me with several questions. Why no one is using it and how can I use it.

Answering first question : “Why no one is using it?“. Simple answer, lack of documentation. Lattice Semi themself provided 2 documentations. One is called “FPGA-TN-02010-1-7-iCE40-I2C-and-SPI-Hardened-IP-Usage-Guide” and another one called “FPGA-TN-02011-1-7-Advanced-iCE40-I2C-and-SPI-Hardened-IP-User-Guide”.

The first Doc just tell you how to generate the IP (Configs stuffs, Blablabla) but not telling you how to “Use” it. Another one is a bit deeper, taking about I2C registers, timing diagram and waveform. But again, no “How to use” the IP core.

Answering 2nd question : “How can I use it?”.

At first, I want to give up on using this Hard IP core and just use the Soft IP. But I remembered that Lattice Semi have example code that they use I2C (and SPI as well) as a communication bus to interface with host device.

I took the “iCE40UltraRGBLEDControllerDesignFiles” as a guide to use I2C. With more explored, I finally found the I2C code :

folder path : iCE40UltraRGBLEDControllerDesignFiles\UG75\hardware\source\i2c

I did study the code : IT’S HORRIBLY COMPLICATED! There’s separated Verilog code specifically only for the primitive instantiation of i2ac Hard IP. And there’s another separated Verilog code for the State Machine in order to use the I2C Hard IP core CORRECTLY. Plus that code is for iCE40 Ultra not the Ultralite. Ultra has primitive name : SB_I2C but Ultralite has primitive name : SB_I2C_FIFO. With totally different address width(Utra 8 bit vs UL 4bit) and I/O width(Ultra 8 bit vs UL 10 bit). This left me confused of what to do next.

I went back and read two Docs I mentioned earlier for several times. At lease there’s the clue of how can I use the IP. With trail and error. I be able to came up with several Important things you need to (know) do if you really want to use the IP.

First, I disable the FIFO. Since I didn’t need to exchange load of bytes between host and FPGA. It’s just 2 bytes at most! But if you wanted to, just enable them.

Second, The GPIO that need to be inout open drain NEED to be configured with SB_IO_OD. using assign and 1’bz is making iCECube 2 unhappy.

Third, In order to use this I2C Hard IP core PROPERLY. You need to write the State machine. The State machine is everything. From TX/RX, I2C configuration and more.

Forth, Write data once then wait for Ack(nowledgement) from the IP. Once the IP sent Ack. It’s good to read data from IP or the data is well written to IP.

After couple days of work, I came up with the code :

// iCEcube 2 seems to not quite happy with using assign and Hi-z with UL1K
// So SB_IO_OD primitive is required.
//assign io_i2c_sda = sdaoe_i ? 1'bZ : sdaout_i;
//assign sdain_i = io_i2c_sda;

// IO config for I2C Tri-state.
SB_IO_OD #(
    .PIN_TYPE(6'b 1010_01)   // Simple input + Tri state output open-drain. 
) sbio_sda (
    .PACKAGEPIN(io_i2c_sda),
    .OUTPUTENABLE(sdaoe_i),
    .DOUT0(sdaout_i),
    .DIN0(sdain_i)
);
// IO config for I2C SCL
SB_IO_OD #(
    .PIN_TYPE(6'b 0000_01) // Simple input only   
) sbio_scl (
    .PACKAGEPIN(io_i2c_scl),
    //.OUTPUTENABLE(sdaoe_i),
    //.DOUT0(sclin_i),
	.DIN0(sclin_i)
);

// Instantiate I2C primitive.
SB_I2C_FIFO #(
	.I2C_SLAVE_ADDR("0b1000001")// set 7 bit address to 0x41.

        // Note : the bit 1 and 0 is fixed to select the I2C1 or I2C2, I2C1 = 01 I2C2 = 10
) I2C_1 /* synthesis I2C_CLK_DIVIDER=120 SDA_INPUT_DELAYED=1 SCL_INPUT_FILTERED=1 I2C_FIFO_ENB=DISABLED*/  (
				
	// Controlling part
	.CLKI(sysclk), // Input clock for IP
	.CSI(hard_SBCSi), // 
	.STBI(hard_SBSTBi),// Strobe (similar to latch)
	.WEI(hard_SBWRi), // Write(1)/Read(0) select 
	
	// Register address Part
	.ADRI3(hard_SBADRi[3]), 
	.ADRI2(hard_SBADRi[2]), 
	.ADRI1(hard_SBADRi[1]), 
	.ADRI0(hard_SBADRi[0]), 
	

        // Data coming into the IP
	.DATI9(hard_SBDATi[9]), 
	.DATI8(hard_SBDATi[8]), 
	.DATI7(hard_SBDATi[7]), 
	.DATI6(hard_SBDATi[6]), 
	.DATI5(hard_SBDATi[5]), 
	.DATI4(hard_SBDATi[4]), 
	.DATI3(hard_SBDATi[3]), 
	.DATI2(hard_SBDATi[2]), 
	.DATI1(hard_SBDATi[1]), 
	.DATI0(hard_SBDATi[0]),
	
	// I2C input 
	.SCLI(sclin_i), 
	.SDAI(sdain_i), 
	
	// I2C output and control
	//.SCLO(I2C1_SCLo), 
	//.SCLOE(I2C1_SCLoe), 
	.SDAO(sdaout_i), 
	.SDAOE(sdaoe_i), 

	// Data coming out from IP
	.DATO9(hard0_SBDATo[9]), 
	.DATO8(hard0_SBDATo[8]), 
	.DATO7(hard0_SBDATo[7]), 
	.DATO6(hard0_SBDATo[6]), 
	.DATO5(hard0_SBDATo[5]), 
	.DATO4(hard0_SBDATo[4]), 
	.DATO3(hard0_SBDATo[3]), 
	.DATO2(hard0_SBDATo[2]), 
	.DATO1(hard0_SBDATo[1]), 
	.DATO0(hard0_SBDATo[0]), 
	
	.ACKO(hard0_SBACKo), 
	.SRWO(hard0_SBSRWo)
) ; 

.
.
.
.
.
// FSM of I2C.
always@(posedge sysclk)begin

	case(i2c_steps)
	0: begin // Init step.
		hard_SBCSi <= 1;// Activates IP.
		i2c_steps <= 1;// move to next step.
	end
	
	1: begin // Enable I2C via I2CCR1 register
		hard_SBWRi <= 1;// write enable
		hard_SBSTBi <= 1;// Strobe == 1
		soft_SBADRi[3:0] <= 4'b0001;// Write I2CCR1 (i2c Control register) to register.
		hard_SBDATi <= 8'b1000_0000;// Enable I2C system and SDA delay as a default of 300ns.
		
		if(hard0_SBACKo)begin// wait until i2c core ack. Then proceed to next task.
			hard_SBWRi <= 0;
			hard_SBSTBi <= 0;
			soft_SBADRi <= 4'b0000;
			hard_SBDATi <= 10'b0000000000;
			i2c_steps <= 2;
		end
		else
			i2c_steps <= i2c_steps;
		
	end
		
	2: begin // set Slave address.
		hard_SBWRi <= 1;// write enable
		hard_SBSTBi <= 1;// Strobe == 1
		soft_SBADRi[3:0] <= 4'b0100;// Write I2CSADDR (i2c Slave address register) to register.
		hard_SBDATi <= 8'b0001_0000;// i2c Address from Bit 6 to bit 2. Bit 1 and 0 are omitted since it's fixed at 01.

		if(hard0_SBACKo)begin// wait until i2c core ack. Then proceed to next task.
			hard_SBWRi <= 0;
			hard_SBSTBi <= 0;
			soft_SBADRi <= 4'b0000;
			hard_SBDATi <= 10'b0000000000;
			i2c_steps <= 3;
		end
		else
			i2c_steps <= i2c_steps;
		
	end
		
	3: begin // Enable clock stretching
		hard_SBWRi <= 1;// write enable
		hard_SBSTBi <= 1;// Strobe == 1
		soft_SBADRi[3:0] <= 4'b0111;// Write I2CCMDR (i2c COmmand register) to register.
		hard_SBDATi <= 8'b0000_0100;// enable clock stretching.
		
		if(hard0_SBACKo)begin// wait until i2c core ack. Then proceed to next task.
			hard_SBWRi <= 0;
			hard_SBSTBi <= 0;
			soft_SBADRi <= 4'b0000;
			hard_SBDATi <= 10'b0000000000;
			i2c_steps <= 4;
		end
		else
			i2c_steps <= i2c_steps;
	
	end
	
	4: begin // Keep reading register until not busy.
		hard_SBWRi <= 0;// read status require this to set to 0
		hard_SBSTBi <= 1;// Strobe == 1
		soft_SBADRi[3:0] <= 4'b1011;// Write I2CSR (i2c status) to register.
		hard_SBDATi <= 8'b0;
		
		if(hard0_SBACKo) begin// wait until i2c core ack. Then proceed to next task.
			hard_SBWRi <= 0;
			hard_SBSTBi <= 0;
			soft_SBADRi <= 4'b0000;
			hard_SBDATi <= 10'b0000000000;
			i2c_stat[7:0] <= hard0_SBDATo[7:0];
			if(i2c_stat[6] == 1) // make sure that It's not busy.
				i2c_steps <= 5;// move to next step.
			else
				i2c_steps <= i2c_steps;
		end
	end
	
	5: begin // perform 2 dummy read from RXDR (according to Advanced Hardened IP user guide - FPGA-TN-02011-1-7).
		hard_SBWRi <= 0;// read data require this to set to 0.
		hard_SBSTBi <= 1;// Strobe == 1
		soft_SBADRi[3:0] <= 4'b1001; // Write I2CRXDR (received data) to register.
		hard_SBDATi <= 8'b0;
		
		if(hard0_SBACKo) begin// wait until i2c core ack. Then proceed to next task.
			hard_SBWRi <= 0;
			hard_SBSTBi <= 0;
			soft_SBADRi <= 4'b0000;
			hard_SBDATi <= 10'b0000000000;
			i2c_dmrd <= hard0_SBDATo[7:0];
			i2c_cnt <= i2c_cnt + 1;
			if(i2c_cnt == 2)begin
				i2c_cnt <= 0;
				i2c_steps <= 6;
			end
		end
		else
			i2c_steps <= i2c_steps;
	
	end
	
	6: begin // wait until RX register is ready to read.
		hard_SBWRi <= 0;// read status require this to set to 0
		hard_SBSTBi <= 1;// Strobe == 1
		soft_SBADRi[3:0] <= 4'b1011;// Write I2CSR (i2c status) to register.
		hard_SBDATi <= 8'b0;
		
		
		if(hard0_SBACKo) begin// wait until i2c core ack. Then proceed to next task.
			hard_SBWRi <= 0;
			hard_SBSTBi <= 0;
			soft_SBADRi <= 4'b0000;
			hard_SBDATi <= 10'b0000000000;
			i2c_stat[7:0] <= hard0_SBDATo[7:0];
			if(i2c_stat[2] == 1) // make sure that It's not busy.
				i2c_steps <= 7;// move to next step.
			else
				i2c_steps <= i2c_steps;
		end
	
	end
	
	7: begin // receive data from Master device.
		hard_SBWRi <= 0;// read data require this to set to 0.
		hard_SBSTBi <= 1;// Strobe == 1
		soft_SBADRi[3:0] <= 4'b1001; // Write I2CRXDR (received data) to register.
		hard_SBDATi <= 8'b0;
		
		if(hard0_SBACKo) begin// wait until i2c core ack. Then proceed to next task.
			hard_SBWRi <= 0;
			hard_SBSTBi <= 0;
			soft_SBADRi <= 4'b0000;
			hard_SBDATi <= 10'b0000000000;
			i2c_cmd[i2c_cmd_cnt] <= hard0_SBDATo[7:0];
			if(cmd_decoded) begin
				i2c_cmd_cnt <= 0;
			end
			else
				i2c_cmd_cnt <= i2c_cmd_cnt + 1;			
			
			i2c_steps <= 8;
		end
		else
			i2c_steps <= i2c_steps;
		
	end
	
	8: begin// back to step 6.
		i2c_steps <= 6;
	end
	
	endcase
	
end

The I2C registers can be found in “FPGA-TN-02011-1-7-Advanced-iCE40-I2C-and-SPI-Hardened-IP-User-Guide”.

FPGA-TN-02011-1-7 page 19

If I have some more spare time. I will write the example code of using I2C Hard IP core

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s