digital logic – Why is the waveform not matching? (2 clock delay in FSM code)

Context : I have been tasked with testing a HC-04 Ultrasonic sensor with Verilog, and below is the Verilog code, the testbench and the waveform that I am getting,

/*
Module HC_SR04 Ultrasonic Sensor

This module will detect objects present in front of the range, and give the distance in mm.

Input:  clk_50M - 50 MHz clock
        reset   - reset input signal (Use negative reset)
        echo_rx - receive echo from the sensor

Output: trig    - trigger sensor for the sensor
        op     -  output signal to indicate object is present.
        distance_out - distance in mm, if object is present.
*/

// module Declaration
module t1b_ultrasonic(
    input clk_50M, reset, echo_rx,
    output reg trig,
    output reg op,
    output reg [15:0] distance_out
);

initial begin
    trig = 0;
end
//////////////////DO NOT MAKE ANY CHANGES ABOVE THIS LINE //////////////////

// FSM states
localparam IDLE      = 3'd0,
           WAIT_1US  = 3'd1,
           TRIG_HIGH = 3'd2,
           WAIT_ECHO = 3'd3,
           MEASURE   = 3'd4,
           DONE      = 3'd5,
           WAIT_LONG = 3'd6,
           FINAL     = 3'd7;

reg [2:0] state, next_state;
reg [2:0] measurement_count;
reg [31:0] counter;        // General-purpose counter for delays
reg [31:0] echo_counter;   // Counter for echo pulse duration

// Timing parameters for 50MHz clock
localparam ONE_US     = 32'd49;   // Adjusted for exact timing match
localparam TRIG_PULSE = 32'd500;  // 10 us = 500 cycles @ 50MHz
localparam W1         = 32'd550002;
localparam W2         = 32'd520002;
localparam W3         = 32'd500002;
localparam W4         = 32'd570002;
// Divisor for distance based on 340 m/s
localparam integer DIST_DIV = 32'd295;
localparam integer ROUND_OFFSET = 32'd58;

// Initialize registers for simulation safety
initial begin
    state             = IDLE;
    next_state        = IDLE;
    measurement_count = 3'd0;
    counter           = 32'd0;
    echo_counter      = 32'd0;
    op                = 1'b0;
    distance_out      = 16'd0;
end

// Sequential logic: state updates, counters, and outputs
always @(posedge clk_50M or negedge reset) begin
    if (!reset) begin
        // Active-low asynchronous reset
        state             <= IDLE;
        measurement_count <= 3'd0;
        counter           <= 32'd0;
        echo_counter      <= 32'd0;
        trig              <= 1'b0;
        op                <= 1'b0;
        distance_out      <= 16'd0;
    end else begin
        // Update outputs on transitions before state change
        if (state == MEASURE && !echo_rx) begin
            distance_out <= (echo_counter + ROUND_OFFSET) / DIST_DIV;
            op           <= (((echo_counter + ROUND_OFFSET) / DIST_DIV) < 16'd70) ? 1'b1 : 1'b0;
        end else if (state == WAIT_ECHO && !echo_rx) begin
            distance_out <= 16'd0;
            op           <= 1'b0;
        end

        state <= next_state;

        case (state)
            IDLE: begin
                trig         <= 1'b0;
                counter      <= 32'd0;
                echo_counter <= 32'd0;
            end

            WAIT_1US: begin
                trig    <= 1'b0;
                counter <= (next_state == TRIG_HIGH) ? 32'd0 : counter + 1;
            end

            TRIG_HIGH: begin
                trig    <= (counter < TRIG_PULSE) ? 1'b1 : 1'b0;
                counter <= counter + 1;
            end

            WAIT_ECHO: begin
                trig         <= 1'b0;
                counter      <= 32'd0;
                if (echo_rx) begin
                    echo_counter <= 32'd1;
                end else begin
                    echo_counter <= 32'd0;
                end
            end

            MEASURE: begin
                trig <= 1'b0;
                if (echo_rx) begin
                    echo_counter <= echo_counter + 1;
                end else begin
                    echo_counter <= 32'd0;
                end
            end

            DONE: begin
                trig              <= 1'b0;
                measurement_count <= measurement_count + 1;
                counter           <= 32'd0;
            end

            WAIT_LONG: begin
                trig    <= 1'b0;
                counter <= (next_state == IDLE) ? 32'd0 : counter + 1;
            end

            FINAL: begin
                trig <= 1'b0;
            end

            default: begin
                trig <= 1'b0;
            end
        endcase

        // Override counter reset for transition to WAIT_ECHO
        if (next_state == WAIT_ECHO && state != WAIT_ECHO) begin
            counter <= 32'd0;
        end
    end
end

// Combinational: next-state logic
always @(*) begin
    next_state = state;
    case (state)
        IDLE: begin
            next_state = WAIT_1US;
        end

        WAIT_1US: begin
            if (counter >= ONE_US) begin
                next_state = TRIG_HIGH;
            end
        end

        TRIG_HIGH: begin
            if (counter >= TRIG_PULSE) begin
                next_state = WAIT_ECHO;
            end
        end

        WAIT_ECHO: begin
            if (echo_rx) begin
                next_state = MEASURE;
            end else begin
                next_state = DONE;
            end
        end

        MEASURE: begin
            if (!echo_rx) begin
                next_state = DONE;
            end
        end

        DONE: begin
            if (measurement_count == 3'd4) begin
                next_state = FINAL;
            end else begin
                next_state = WAIT_LONG;
            end
        end

        WAIT_LONG: begin
            case (measurement_count)
                3'd1: begin
                    if (counter >= W1) next_state = IDLE;
                end
                3'd2: begin
                    if (counter >= W2) next_state = IDLE;
                end
                3'd3: begin
                    if (counter >= W3) next_state = IDLE;
                end
                3'd4: begin
                    if (counter >= W4) next_state = IDLE;
                end
                default: begin
                    next_state = IDLE;
                end
            endcase
        end

        FINAL: begin
            next_state = FINAL;
        end

        default: begin
            next_state = IDLE;
        end
    endcase
end

//////////////////DO NOT MAKE ANY CHANGES BELOW THIS LINE //////////////////

endmodule

The testbench is;

// This module will test the ultrasonic sensor.
`timescale 1ns/1ns

module tb();

    reg clk_50M;
    reg reset;
    reg echo;
    wire trig;
    wire obstacle;
    wire [15:0] distance_out;

    reg exp_trig;
    reg [15:0] exp_distance_out;

    integer i, fw;
    integer counter, error_count;

    t1b_ultrasonic uut(
        .clk_50M(clk_50M),
        .reset(reset),
        .echo_rx(echo),
        .trig(trig),
        .op(obstacle),
        .distance_out(distance_out)
    );
    
    initial begin
        clk_50M = 0;
        reset = 1; echo = 0;
        i = 0; fw = 0;
        counter = 0; error_count = 0;
        exp_trig = 0; exp_distance_out = 0;
    end
     
    always begin
      #10 clk_50M = ~clk_50M;             
    end

    always @(posedge clk_50M) begin
        #100; reset = 1; #920;
        exp_trig = 1;
        i = i + 1;
        #10000;
        exp_trig = 0;
        echo = 1;
        repeat(50000)@(posedge clk_50M);
        echo = 0;
        @(posedge clk_50M);
        exp_distance_out = 169;
        repeat(49999)@(posedge clk_50M);
        #10001100;
        exp_trig = 1;
        i = i + 1;
        #10000;
        exp_trig = 0;
        echo = 1;
        repeat(80000)@(posedge clk_50M);
        echo = 0;
        @(posedge clk_50M);
        exp_distance_out = 271;
        repeat(19999)@(posedge clk_50M);
        #10001100;
        exp_trig = 1;
        i = i + 1;
        #10000;
        exp_trig = 0;
        echo = 1;
        repeat(100000)@(posedge clk_50M);
        echo = 0;
        @(posedge clk_50M);
        exp_distance_out = 339;
        #10001080;
        exp_trig = 1;
        i = i + 1;
        #10000;
        exp_trig = 0;
        echo = 1;
        repeat(30000)@(posedge clk_50M);
        echo = 0;
        @(posedge clk_50M);
        exp_distance_out = 101;
        repeat(69999)@(posedge clk_50M);
        echo = 0;
        #10001100;
        exp_trig = 1;
        i = i + 1;
        #10000;
        exp_trig = 0;
        @(posedge clk_50M);
        exp_distance_out = 0;
        exp_trig = 0;
        repeat(500)@(posedge clk_50M);
        //$stop();
    end

    always@(posedge clk_50M) begin
        #1;
        if(trig !== exp_trig) error_count = error_count + 1;
        if(distance_out !== exp_distance_out) error_count = error_count + 1;

        if(i >= 4 ) begin
            if(error_count !== 0) begin
            fw = $fopen("result.txt", "w");
            $fdisplay(fw, "%02h","Errors");
            $display("Error(s) encountered, please check your design!");
            $fclose(fw);
        end else begin
            fw = $fopen("result.txt", "w");
            $fdisplay(fw, "%02h","No Errors");
            $display("No errors encountered, congratulations!");
            $fclose(fw);
        end
        i = 0;
        end
    end

endmodule

The waveforms are;

enter image description here

The waveform when first error is flagged;

enter image description here

How do I fix this issue between the trig and exp_trig signal which is 2 clocks delayed and this propagates to give error_count = 19. What change in the Verilog code can I make to fix this?

Read more here: Source link