1

I'm trying to write an I2C Slave and test it in isolation.

I have a simulation that should be pulling SDA low when write_ack is high (Also highlighted by the red dots). However, you can see that SDA remains the same.

Part of me thinks it's to do with the way I'm testing with the force methods and the delays.

Any help appreciated.

I have found the keyword release which seems to help.

Code below & EDA Playground is here: https://edaplayground.com/x/6snM

Simulation Results

/**
I2C Slave to Read/Write 8 bits of data only
*/

`timescale 1ns / 1ps

module Slave(
    inout wire SDA,
    input wire SCL);

  reg [4:0] IDLE            = 4'b0000;
  reg [4:0] START           = 4'b0001;
  reg [4:0] READ_ADDRESS    = 4'b0010;
  reg [4:0] READ_WRITE      = 4'b0011;
  reg [4:0] DATA            = 4'b0100;
  reg [4:0] DATA_ACK        = 4'b0101;
  reg [4:0] STOP            = 4'b0110;
  reg [4:0] ADDRESS_ACK     = 4'b0111;

  reg [4:0] state           = 4'b0010;

  reg [6:0] slaveAddress    = 7'b0001000;
  reg [7:0] addr;
  reg [6:0] addressCounter  = 7'b0000000;

  reg [7:0] data;
  reg [6:0] dataCounter     = 7'b0000000;

  reg readWrite         = 1'b0;
  reg start = 0;
  reg write_ack         = 0;

  assign SDA = (write_ack == 1) ? 0 : 'b1z;

  always @(negedge SDA) begin
    if ((start == 0) && (SCL == 1)) 
    begin
        start <= 1;
        addressCounter <= 0;
        dataCounter <= 0;
    end
  end

  always @(posedge SDA) begin
    if (state == DATA_ACK && SCL == 1)
      begin
        start <= 0;
        state <= READ_ADDRESS;
      end
    end

  always @(posedge SCL)
    begin
        if (start == 1)
        begin
          case (state)
            READ_ADDRESS: 
              begin
                addr[addressCounter] <= SDA;
                addressCounter <= addressCounter + 1;
                if (addressCounter == 6) 
                    begin
                     state <= READ_WRITE;
                   end
             end
           READ_WRITE:
             begin
                readWrite <= SDA;
                state <= ADDRESS_ACK;
              end
            ADDRESS_ACK:
              begin
                write_ack <= 1;
                state <= DATA;
              end
            DATA:
              begin
                write_ack <= 0;
            
                data[dataCounter] <= SDA;
                dataCounter <= dataCounter + 1;
                if (dataCounter == 8) 
                    begin
                     state <= DATA_ACK;
                      write_ack <= 1;
                   end
              end
            DATA_ACK:
             begin
               write_ack <= 0;
               state <= STOP;
              end
            STOP:
              begin
                start <= 0;
                state <= READ_ADDRESS;
              end
          endcase
        end
    end


endmodule

Test Code

/**
Testing I2C Slace for reading/writing 8 bits of data only
*/

`timescale 1ns / 1ps

module Slave_TB ();

  reg clk;

  wire SDA;
  wire SCL;

  pullup(SDA);
  pullup(SCL);

  reg [6:0] addressToSend   = 7'b0001000;
  reg readWite              = 1'b1;
  reg [7:0] dataToSend      = 8'b01100111;

  integer ii=0;

  initial begin
        clk = 0;
        force SCL = clk;
        forever begin
            clk = #1 ~clk;
            force SCL = clk;
        end     
    end


  Slave #() UUT
    (.SDA(SDA),
     .SCL(SCL));

  initial 
    begin
      $display("Starting Testbench...");
  
      clk = 0;
      force SCL = clk;
  
      #11
  
      // Set SDA Low to start
      force SDA = 0;

      // Write address
      for(ii=0; ii<7; ii=ii+1)
        begin
          $display("Address SDA %h to %h", SDA, addressToSend[ii]);
          #2 force SDA = addressToSend[ii];
        end
  
      // Are we wanting to read or write to/from the device?
      $display("Read/Write %h SDA: %h", readWite, SDA);
      #2 force SDA = readWite;
  
      $display("SDA: %h", SDA);
      #2; // Wait for ACK bit
  
      for(ii=0; ii<8; ii=ii+1)
        begin
          $display("Data SDA %h to %h", SDA, dataToSend[ii]);
          #2 force SDA = dataToSend[ii];
        end
  
      #2; // Wait for ACK bit
  
      // Force SDA high again, we are done
      #2 force SDA = 1;

      #100;
      $finish();
    end

  initial 
  begin
    // Required to dump signals to EPWave
    $dumpfile("dump.vcd");
    $dumpvars(0);
  end

endmodule
toolic
  • 57,801
  • 17
  • 75
  • 117
Chris
  • 2,739
  • 4
  • 29
  • 57
  • 1
    As soon as you force sda to any value, that value will stick till release or till next force. Regular operations cannot change the value. Check use of your forces. Also, you should not drive the same signal like 'state', 'start', ... from different always blocks. You ask for undefined behavior there. – Serge May 16 '21 at 18:31

1 Answers1

1

Instead of using force, a more conventional approach is to add a tristate buffer to the testbench, just like you have in the design.

For SDA, create a buffer control signal (drive_sda) and a testbench data signal (sda_tb). Use a task to drive a byte and wait for the ACK.

Since SCL is not an inout, there is no need for a pullup, and it can be directly driven by clk.

module Slave_TB;
    reg clk;

    wire SDA;
    wire SCL = clk;

    pullup(SDA);

    reg [6:0] addressToSend   = 7'b000_1000;  //8
    reg readWite              = 1'b1;         //write
    reg [7:0] dataToSend      = 8'b0110_0111; //103 = 0x67
    reg sda_tb;
    reg drive_sda;
    assign SDA = (drive_sda) ? sda_tb : 1'bz;

    integer ii=0;

initial begin
    clk = 0;
    forever begin
        clk = #1 ~clk;
    end
end

  Slave UUT
    (.SDA(SDA),
     .SCL(SCL));

initial begin
    $display("Starting Testbench...");

    drive_sda = 0;
    sda_tb = 1;

    #11;

    // Set SDA Low to start
    drive_sda = 1;
    sda_tb = 0;

    write({addressToSend, readWite});
    write(dataToSend);

    // Force SDA high again, we are done
    #2;
    drive_sda = 1;
    sda_tb = 1;

    #50;
    $finish;
end

task write (reg [7:0] data);
    integer ii;
    for (ii=7; ii>=0; ii=ii-1) begin
        $display("Data SDA %h to %h", SDA, data[ii]);
        #2;
        drive_sda = 1;
        sda_tb = data[ii];
    end
    #2 drive_sda = 0;
endtask

initial begin
    // Required to dump signals to EPWave
    $dumpfile("dump.vcd");
    $dumpvars(0);
end
endmodule
toolic
  • 57,801
  • 17
  • 75
  • 117