2

enter image description here

As you can see in the waveforms and the code after the start (busy line goes high) condition occurs, I start sending the slave address bit by bit in the testbench through the SDA line, and this needs to get stored in the address register. And in the Addr Ackn state, it checks whether the address matches, and depending on the last bit of the address, it goes into read or write state as written in the code.

But, here for reasons unknown, the address register isn't able to capture the address from the SDA line accurately and hence the FSM is going to a HOLD state.

Can anyone provide suggestions on the issue?

And this is the relevant code snippet.

module I2C_SLAVE #(parameter SLAVE_ADDR = 7'b1101010)    // Slave Address for this module is 0x6A
    (
     input Reset_L,    // (WIRE) Active Low Reset to clear the data within slave and reset the registers
     input SCL,        // (WIRE) Serial Clock 
     inout SDA         // (WIRE) Serial Data
    );

// STATES OF STATE MACHINE BETWEEN START AND STOP CONDITION
localparam ADDRESS   = 3'b000;     // Address from the master is read and checked with slave address
localparam ADDR_ACKN = 3'b001;     // Acknowledgement from slave to master is sent from if address matches
localparam RECEIVE   = 3'b010;     // If Address last bit is 0 then data is received from master to slave (WRITE OPERATION)
localparam RX_ACKN   = 3'b011;     // Sends an Acknowledgement to master after the data is received
localparam TRANSMIT  = 3'b100;     // If Address last bit is 1 then data is transmitted from slave to master (READ OPERATION)
localparam TX_ACKN   = 3'b101;     // Waits for Acknowledgement from master after the data is transmitted
localparam HOLD      = 3'b110;     // It is the rest state which implies slave is not active

// STATE MACHINE VARIABLE
reg [2:0] state_machine;

// REGISTERS FOR INTERNAL OPERATIONS
reg       busy;                    // Used to activate and deactivate FSM (SLAVE)

reg [7:0] address;                 // Used to hold the input serial data
reg [2:0] bit_index;               // Used in accessing each bit of address and data register

reg [7:0] slave_data;              // Data held by the slave

// CONTROL REGISTERS TO ACTIVATE OUTPUT FUNCTION OF SDA
reg       SDA_EN;                  // Controls when the SDA should act as output
reg       SDA_OUT;                 // Holds the serial data to be transmitted from slave

// SDA INPUT AND OUTPUT TRANSITION
assign SDA = (SDA_EN) ? SDA_OUT : 'bz;    // [SDA_EN == 1] => O/P pin and [SDA_EN == 0] => I/P pin

// START CONDITION
always @(negedge SDA)              // If SDA goes low when SCL is high then start condition occurs
begin
    if (SCL == 1)                  // If busy is already high then repeated start condition occurs
    begin
        address       <= 8'b0;     // Address reg is reset to store the new address transmitted from master
        bit_index     <= 3'b111;   // Bit Index reg is preset as the first data transmitted will be MSB
        state_machine <= ADDRESS;  // SM variable is set to first state where address is obtained
        if (busy == 0)
            busy <= 1'b1;          // Busy reg is made high indicating the slave FSM is active
    end
end

// FSM for state operations

always @(posedge SCL)              
    begin
        if (~Reset_L)                  
        begin
            SDA_EN     <= 1'b0;
            SDA_OUT    <= 1'b0;
            slave_data <= 8'b0;
        end
    
    if (busy == 1)
    begin
        case(state_machine)
            ADDRESS:
            begin
                if (bit_index > 0)
                begin
                    address[bit_index] <= SDA;
                    bit_index <= bit_index - 1;  // Bit index reg is decremented until last bit is received 
                 end
                 else
                   state_machine <= ADDR_ACKN;               
            end
            
            

ADDR_ACKN:
                begin
                    if (address[7:1] == SLAVE_ADDR)          // Compares the address transmitted by master with the slave's address
                    begin
//                      SDA_OUT <= 1'b1;                     // If address matched, based on operation to be performed the FSM moves to next state
                        bit_index <= 3'b111;                 // Presets the bit index reg
                        if(address[0] == 0)                  // If Address Bit[0] == 0 then slave becomes data receiver
                            state_machine <= RECEIVE;        // WRITE OPERATION
                        else                                 // If Address Bit[0] == 1 then slave becomes data transmitter
                            state_machine <= TRANSMIT;       // READ OPERATION
                    end
                    else
                        state_machine <= HOLD;               // If address does not match slave moves to HOLD state
                end
toolic
  • 5,637
  • 5
  • 20
  • 33
  • We have no idea what hardware -or software- OP is talking about. And I somehow missed the verilog tag. Whoops. Note that in classic I²C both I/Os should be used in open drain mode. – Turbo J Aug 20 '23 at 09:49

1 Answers1

2

You set the slave address to 7'b1101010 (7'h6a) in the design, but your design received 8'b01010100 in address[7:0] according to your waveforms. This means address[7:1] is 7'b0101010, which is 7'h2a. The received address7'h2a does not match the expected address 7'h6a in the design. This is why the state machine goes to the HOLD state unexpectedly.

The problem is that you have a simulation race condition because SDA and SCL change at the same time. You should change the testbench so that they are changing at different times. Refer to an I2C timing diagram. Here is one way to code the testbench to demonstrate the signals changing at different times:

module tb;
reg sda_tb, scl_tb;
parameter PERIOD = 20;

initial begin
    sda_tb = 1;
    #(PERIOD/4) sda_tb = 0;
    set_address(7'h6a);
    #(PERIOD) $finish;
end

initial begin
    scl_tb = 1;
    forever #(PERIOD/2) scl_tb = ~scl_tb;
end

task set_address (input [6:0] addr);
    integer i;
    for (i=6; i>=0; i=i-1) begin
        #(PERIOD) sda_tb <= addr[i];
    end
endtask
endmodule

waves

toolic
  • 5,637
  • 5
  • 20
  • 33