Search code examples
verilogi2c

I2C slave module in Verilog does not acknowledge


I wrote this I2C slave module in Verilog:

module I2CSlave(
input iSCL,
input iI2C_CLK,
inout bSDA,
output reg [7:0] odata,
output reg oread,
output wire oactive
);    

reg incycle = 1'b0;
reg pSDA;
reg pSCL;
always @(posedge iI2C_CLK) begin
    if ((pSCL) && (iSCL) && (pSDA) && (~bSDA)) begin
        incycle <= 1;
    end
    if ((pSCL) && (iSCL) && (~pSDA) && (bSDA)) begin
        incycle <= 0;
    end
    pSDA <= bSDA;
    pSCL <= iSCL;
end

assign oactive = incycle;

localparam STATE_IDLE = 0;
localparam STATE_ADDR = 1;
localparam STATE_RW = 2;
localparam STATE_ACK = 3;
localparam STATE_DATA = 4;
localparam STATE_ACK2 = 5;

reg [7:0] i = 0;
reg [7:0] state = STATE_IDLE;
reg [6:0] addr = 7'h03;
reg addr_match = 1;
reg rw;

reg lSDA;
always @(posedge iSCL) lSDA <= bSDA;

assign bSDA = ((state == STATE_ACK) || (state == STATE_ACK2)) ? 0 : 1'bz;
assign oread = (state == STATE_ACK2);
assign ostate = i;

always @(negedge iSCL or negedge incycle) begin
    if (~incycle) begin
        state <= STATE_IDLE;
        addr_match <= 1;
    end
    else if (addr_match) begin
        case (state)
            STATE_IDLE: begin               
                state <= STATE_ADDR;
                i <= 7;
            end
            STATE_ADDR: begin
                if (addr[i-1] != lSDA) addr_match <= 0;
                if (i == 1) begin
                    state <= STATE_RW;
                    i <= i - 1;
                end
                else i <= i - 1;
            end
            STATE_RW: begin
                rw <= lSDA;
                state <= STATE_ACK;
            end
            STATE_ACK: begin
                state <= STATE_DATA;
                i = 7;
            end
            STATE_DATA : begin
                odata[i] <= lSDA;
                if (i == 0) state <= STATE_ACK2;
                else i <= i - 1;
            end
            STATE_ACK2: begin
                state <= STATE_DATA;
                i = 7;
            end
        endcase
    end
end

endmodule

As of now it should just read the data sent by master. It seems to work well in simulation, but when I upload it into the FPGA, sometimes everything is OK, however sometimes it does not acknowledge the data sent by master and it just seems to ignore them. I am newbie in Verilog, so I hope, this is not a silly question.


Solution

  • One possible cause for random fails while running on real hardware is that you didn't synchronize inputs.

    You are sampling slowly changing signals (i2c bus will have a long slopes) that are truly asynchronous to your design's clock. Depending on your luck, you will have random violations for setup/hold times of your fpga's d-flops, which results in metastability issues. The same value in the register might be treated differently in multiple parts of the chip. That will wreak havoc in your i2c slave's logic.

    You must synchronize asynchronous inputs, in the simplest case passing it through a couple of registers before feeding it to the module's fsm.