2009-06-15
/*
modified I2C salve design from
http://www.fpga4fun.com/I2C_2.html
1. Asynchronous design: ASIC or FPGA design option
2. 8 bits CSR RW interface: 0~15, address and control
3. PAD not included
4. Altera CPLD verified
RTL model
Module: i2c_salve, a I2C slave device which only supports sdt mode
Ref spec: Rev 03, 2007
This module is from www.fpga4fun.com
SDA HOLD TIME: ref NXP's spec.
A device must internally provide a hold time of at least 300 ns for the SDA signal (with respect to the
VIH(min) of the SCL signal) to bridge the undefined region of the falling edge of SCL.
*/
`timescale 1ns/10ps
`define D #1
module i2c_slave (porn, SCL, SDAin, SDA_OUT, i2c_writing, i2c_debug, csr_din, csr_dout, i2c_data_ptr, i2c_wr, rstn);
parameter I2C_ADR = 7'h27;
input porn;
input SCL;
input SDAin;
input [7:0] csr_dout;
input rstn;
output SDA_OUT;
output i2c_writing;
output [7:0] i2c_debug;
output [7:0] csr_din;
output [4:0] i2c_data_ptr;
output i2c_wr;
reg [4:0] i2c_data_ptr; // address pointer
reg incycle;
wire I2C_rstn, incycle_rstn, i2c_writing_i;
// Delay Line Here, this has to be taken care by manual
`ifdef FPGA // FPGA
wire SDA_in_d;
assign #300 SDA_in_d = SDAin;
/*
I2C start and stop conditions detection logic
That's the "black magic" part of this design...
We use two wires with a combinatorial loop
to detect the start and stop conditions
making sure these two wires don't get optimized away
*/
wire SDA_shadow /* synthesis keep = 1 */; // FPGA directive
wire start_or_stop /* synthesis keep = 1 */; // FPGA directive
assign #5 SDA_shadow =
(!porn) ? 1'b1 : (~SCL | start_or_stop) ? SDA_in_d : SDA_shadow;
assign start_or_stop =
~SCL? 1'b0 : (SDA_in_d ^ SDA_shadow);
`else // ASIC
i2c_line_det U_i2c_line_det
(.porn(porn), .SDAin(SDAin), .SDA_in_d(SDA_in_d), .SCL(SCL), .start_or_stop(start_or_stop));
`endif
// I2C reset, pure async, note: I2C is not a always-running clock.
assign I2C_rstn = porn & rstn & ~start_or_stop;
// incycle: starting from 1st neg SCL
always @(negedge SCL or negedge I2C_rstn)
if(!I2C_rstn)
incycle <= `D 1'b0; // either S or P or Sr
else if(~SDA_in_d) // detection bits sequence following start bit
incycle <= `D 1'b1;
// Now we are ready to count the I2C bits coming in
reg [3:0] bitcnt;// counts the I2C bits from 7 downto 0, plus an ACK bit
wire bit_DATA = ~bitcnt[3];// the DATA bits are the first 8 bits sent
wire bit_ACK = bitcnt[3]; // the ACK bit is the 9th bit sent
reg data_phase;
always @(negedge SCL or negedge I2C_rstn)
if(!I2C_rstn)
begin
bitcnt <= `D 4'h7; // the bit 7 is received first
data_phase <= `D 0;
end
else
begin
if(bit_ACK)
begin
bitcnt <= `D 4'h7; // reset to 0111 after 1000
data_phase <= `D 1; // 1st bit_ACK followed by data phase
end
else if (incycle)
bitcnt <= `D bitcnt - 4'h1;
end
// and detect if the I2C address matches our own
wire adr_phase = ~data_phase;
reg adr_match, op_read, got_ACK;
reg SDAr, i2c_writing;
// sample SDA on posedge since the I2C spec
// specifies as low as 0us hold-time on negedge
always @(posedge SCL) SDAr<=SDA_in_d;
reg [7:0] mem;
wire op_write = ~op_read;
always @(negedge SCL or negedge I2C_rstn)
if(!I2C_rstn) begin
got_ACK <= 0;
adr_match <= 1;
op_read <= 0;
i2c_data_ptr <= 0;
i2c_writing <= 0; // to aviod combi glitch
end
else if (incycle) begin // only active while incycle
if((adr_phase & bitcnt==7) && (SDAr!=I2C_ADR[6])) adr_match<=0;
if((adr_phase & bitcnt==6) && (SDAr!=I2C_ADR[5])) adr_match<=0;
if((adr_phase & bitcnt==5) && (SDAr!=I2C_ADR[4])) adr_match<=0;
if((adr_phase & bitcnt==4) && (SDAr!=I2C_ADR[3])) adr_match<=0;
if((adr_phase & bitcnt==3) && (SDAr!=I2C_ADR[2])) adr_match<=0;
if((adr_phase & bitcnt==2) && (SDAr!=I2C_ADR[1])) adr_match<=0;
if((adr_phase & bitcnt==1) && (SDAr!=I2C_ADR[0])) adr_match<=0;
if(adr_phase & bitcnt==0) op_read <= SDAr;
/* we monitor the ACK to be able to free the bus
when the master doesn't ACK during a read operation
HOST will send NACK prior to STOP according to spec.
*/
if(bit_ACK) got_ACK <= ~SDAr;
// shift register write
if((adr_match) & bit_DATA & data_phase & op_write)
mem[bitcnt] <= SDAr;
// inc ptr after add phase
if((adr_match) & bit_ACK & data_phase )
i2c_data_ptr <= i2c_data_ptr + 1;
i2c_writing <= i2c_writing_i;
end
assign i2c_wr = adr_match & data_phase & op_write & bit_ACK;
// changed to SCL sync design
assign csr_din = mem;
// and drive the SDA line when necessary.
wire data_bit_low = ~csr_dout[bitcnt[2:0]];
wire SDA_assert_low =
adr_match & bit_DATA & data_phase & op_read & data_bit_low & got_ACK;
wire SDA_assert_ACK = (adr_match ) & bit_ACK & (adr_phase | op_write);
wire SDA_low = SDA_assert_low | SDA_assert_ACK;
assign SDA_OUT = SDA_low;
// PAD Implementation
// assign SDA = SDA_low ? 1'b0 : 1'bz;
assign i2c_writing_i = adr_match & data_phase & op_write & incycle;
// ref spec section, I2C interface. i2c_writing will go back to standby.
assign i2c_debug = {bitcnt[3:0], 2'b00, op_read, start_or_stop};
endmodule
module i2c_line_det ( SDAin, SDA_in_d, SCL, start_or_stop, porn );
input SDAin, SCL, porn;
output SDA_in_d, start_or_stop;
wire SDA_shadow, n2, n3;
wire n2d, n2d1, n2d2, n2dd;
`ifdef _RTL // for RTL sim only, since w/o delay in gate lib
assign #10 SDA_in_d = SDAin;
assign #5 SDA_shadow = (!porn) ? 1'b1 : (~SCL | start_or_stop) ? SDA_in_d : SDA_shadow;
assign start_or_stop = ~SCL? 1'b0 : (SDA_in_d ^ SDA_shadow);
`else
DEL5 U11 ( .A(SDAin), .Y(SDA_in_d1) );
DEL5 U12 ( .A(SDA_in_d1), .Y(SDA_in_d2));
DEL5 U13 ( .A(SDA_in_d2), .Y(SDA_in_d));
// latch with active low preset
LATCH_1X SDA_shadow_reg ( .PRSTN(porn), .D(SDA_in_d), .EN(n3), .Q(SDA_shadow) );
INV_1X U9 (.A(SCL), .Y(SLCn));
NAND2_1X U10 ( .A(SCLn), .B(n2), .Y(start_or_stop) );
// to keep minimum pulse width
DEL5 U1 (.A(n2), .Y(n2d));
BUF1 U2 (.A(n2d), .Y(n2d1));
BUF1 U3 (.A(n2d1), .Y(n2d2));
BUF1 U4 (.A(n2d2), .Y(n2d3));
BUF1 U5 (.A(n2d3), .Y(n2d4));
BUF1 U6 (.A(n2d4), .Y(n2dd));
//
AND2_1X U7 ( .A(n2dd), .B(SCL), .Y(n3) );
NOR2_1X U8 ( .A(SDA_in_d), .B(SDA_shadow), .Y(n2) );
`endif
endmodule
module LATCH_1X(PRSTN, D, EN, Q);
input PRSTN, D, EN;
output Q;
udp_ldlatch_p0 P1 (.q(Q), .d(D), .en(EN), .clear(1'b1), .preset(PRSTN));
endmodule
primitive udp_ldlatch_p0(q, d, en, clear, preset);
output q;
input d, en, clear, preset;
reg q;
table
// d en clear preset : state : q
? ? 0 ? : ? : 0;// clear to 0
? ? 1 0 : ? : 1;// preset to 1
0 1 1 1 : ? : 0;// on enable, transmit d
1 1 1 1 : ? : 1;// on enable, transmit d
? 0 1 1 : ? : -;// not enable, no change
? ? p 1 : ? : -;// ignore positive edge of clear
? ? 1 p : ? : -;// ignore positive edge of preset
endtable
endprimitive
module NOR2_1X (A, B, Y);
input A, B;
output Y;
assign #1 Y=!(A | B);
endmodule
module NAND2_1X (A, B, Y);
input A, B;
output Y;
assign #1 Y=!(A & B);
endmodule
module AND2_1X (A, B, Y);
input A, B;
output Y;
assign #1 Y=A &B;
endmodule
module INV_1X (A, Y);
input A;
output Y;
assign #1 Y=!A;
endmodule
module BUF1 (A, Y);
input A;
output Y;
assign #1 Y=A;
endmodule
module DEL5(A, Y);
input A;
output Y;
assign #5 Y= A;
endmodule
// CSR example
//
// write enable
assign csr_wr = i2c_wr;
assign csr_w_add = i2c_data_ptr;
assign csr_clk = SCL;
assign wr_csr00 = csr_wr & (csr_w_add == 5'h0);
// CSR00
always @(negedge porn or posedge csr_clk)
if (!porn)
csr00 <= `D cr00_iv;
else if (wr_csr00)
csr00 <= `D csr_din;