1

I am trying to write Verilog code for I2C master, and there are a couple of problems I am facing. I was able to compile and run its testbench on Quartus and modelsim, respectively. However, I am trying to have it switch back to reset mode on (reset = 1) once the state reaches STATE_STOP (Basically have it stay in STATE_IDLE).

What can I add to either the code or the testbench to make it that way? The simulation continues after STATE_STOP, which I am not able to understand, and so I have to, at an arbitrary time, add either $finish or reset = 1 in the testbench.

`timescale 1ns/1ps

module I2C_Master(
    input wire clk,
    input wire reset,
    input wire [7:0]data,
    input wire [6:0]add,
    input wire readorwrite,
    output reg i2c_sda,
    output reg i2c_scl
    );
    
    localparam STATE_IDLE = 0;
    localparam STATE_START = 1;
    localparam STATE_ADDR = 2;
    localparam STATE_RW = 3;
    localparam STATE_WACK = 4;
    localparam STATE_DATA = 5;
    localparam STATE_STOP = 6;
    localparam STATE_WACK2 = 7;
    
    
    reg [7:0] state;
    reg [7:0] count;
    
    always @(posedge clk) begin
        if (reset == 1)
            i2c_scl <= 1;
        else begin
            if (state == STATE_START)
                i2c_scl <= 0;
            else if (state == STATE_IDLE || state == STATE_STOP)
                i2c_scl <= 1;
            else 
                i2c_scl <= ~i2c_scl;
        end
    end
    
    always @(posedge clk) begin
        if (reset == 1) begin
            state <= 0;
            i2c_sda <= 1;
            count <= 8'd0;
        end
        else begin
            case(state)
            
                STATE_IDLE: begin
                    i2c_sda <= 1;
                    state <= STATE_START;
                end
                
                STATE_START: begin
                    i2c_sda <= 0;
                    state <= STATE_ADDR;
                    count <= 6;
                end
                
                STATE_ADDR: begin
                    i2c_sda <= add[count];
                    if (count == 0) state <= STATE_RW;
                    else count <= count - 1;
                end
                
                STATE_RW: begin
                    i2c_sda <= readorwrite;
                    state <= STATE_WACK;
                end
                
                STATE_WACK: begin
                    i2c_sda <= 0;
                    state <= STATE_DATA;
                    count <= 7;
                end
                
                STATE_DATA: begin
                    i2c_sda <= data[count];
                    if (count == 0) state <= STATE_WACK;
                    else count <= count - 1;
                end
                
                STATE_WACK2: begin
                    i2c_sda <= readorwrite;
                    state <= STATE_STOP;
                end
                
                STATE_STOP: begin
                    i2c_sda <= 0;
                end
            endcase
        end

end
endmodule

The testbench:

module I2C_MasterTB;

    reg clk;
    reg reset;
    reg [7:0]data;
    reg [6:0]add;
    reg readorwrite;
    
    wire i2c_sda;
    wire i2c_scl;
    
I2C_Master uut (
    .clk(clk),
    .reset(reset),
    .data(data),
    .add(add),
    .readorwrite(readorwrite),
    .i2c_sda(i2c_sda),
    .i2c_scl(i2c_scl)
);

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

initial begin
    reset = 1;
    add <= 7'h50;
    data <= 8'haa;
    readorwrite <= 0;
    
    #10;
    
    reset = 0;
    
    #100;
    
    reset = 1;
    
end

endmodule

As I already mentioned, I only arbitrarily chose a time period to finish the simulation, but I can't seem to think of the way to the end the simulation as soon as it reaches the last state and have it stay in idle state. Here is the simulation:

enter image description here

toolic
  • 57,801
  • 17
  • 75
  • 117

2 Answers2

1

The problem is in the design, not the testbench.

The state machine can never enter the STATE_WACK2, and therefore it also can never enter STATE_STOP. I think the bug is in STATE_DATA, where you probably want to transition to WACK2, not WACK:

            STATE_DATA: begin
                i2c_sda <= data[count];
                if (count == 0) state <= STATE_WACK2;
                else count <= count - 1;
            end

After that is fixed, another bug is that you always stay in STATE_STOP. You probably want to transition into IDLE:

            STATE_STOP: begin
                i2c_sda <= 0;
                state <= STATE_IDLE;
            end

You also need a control signal to decide when you want to start an I2C transfer, otherwise, the state machine will start up again. You should add an input signal to the design for that, like start.

            STATE_IDLE: begin
                i2c_sda <= 1;
                state <= (start) ? STATE_START : STATE_IDLE;
            end
toolic
  • 57,801
  • 17
  • 75
  • 117
-1

In terms of just timing the testbench to stop after a certain value is reached, you should be able to access the module local value using hierarchical reference and then use wait() with an equality check.

To get the value it would be something like uut.state in your testbench file and in place of a set time (i.e. replacing #100) use wait(uut.state == STATE_STOP). The testbench should pause there until the condition is met and proceed afterwards.