TestBench I2C Slave SDA 不会变低

TestBench I2C Slave SDA won't go low

我正在尝试编写一个 I2C 从设备并对其进行隔离测试。

我有一个模拟应该在 write_ack 为高时将 SDA 拉低(也由红点突出显示)。但是,您可以看到 SDA 保持不变。

部分我认为这与我使用 force 方法和延迟进行测试的方式有关。

感谢任何帮助。

我找到了似乎有帮助的关键字 release

下面的代码和 EDA 游乐场在这里:https://edaplayground.com/x/6snM

/**
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

测试代码

/**
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

不使用 force,更传统的方法是向测试台添加三态缓冲区,就像您在设计中所做的那样。

对于SDA,创建一个缓冲控制信号(drive_sda)和一个测试平台数据信号(sda_tb)。使用一个task驱动一个字节并等待ACK。

因为SCL不是inout,所以不需要上拉,可以直接用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