Parcourir la source

add axis travel monitor FPGA verification

botao il y a 2 semaines
Parent
commit
37f7e3cbb6
2 fichiers modifiés avec 1153 ajouts et 0 suppressions
  1. 213 0
      rtl/axis_travel_monitor.v
  2. 940 0
      sim/axis_travel_monitor_tb.v

+ 213 - 0
rtl/axis_travel_monitor.v

@@ -0,0 +1,213 @@
+`timescale 1ns/1ps
+
+module axis_travel_monitor (
+    input  wire                    clk,
+    input  wire                    reset_n,
+
+    // Start a new travel segment.
+    // On start, current_position becomes the new start_position.
+    input  wire                    start,
+
+    // Current position, for example encoder pulse count.
+    input  wire signed [31:0]      current_position,
+
+    // Relative travel command.
+    // +500 = move positive by 500
+    // -500 = move negative by 500
+    input  wire signed [31:0]      target_travel,
+
+    // Allowed target-position error.
+    input  wire [31:0]             tolerance,
+
+    // Physical limit inputs.
+    input  wire                    limit_pos,
+    input  wire                    limit_neg,
+
+    // Latched position where this travel started.
+    output reg  signed [31:0]      start_position,
+
+    // start_position + target_travel
+    output reg  signed [31:0]      target_position,
+
+    // current_position - start_position
+    output wire signed [31:0]      travel,
+
+    // Travel state
+    output reg                     busy,
+
+    // Current position is inside target tolerance window.
+    output wire                    in_position,
+
+    // Latched completion flag.
+    // Cleared by next start or reset.
+    output reg                     done,
+
+    // Latched limit status.
+    output reg                     limit_hit,
+    output reg                     limit_pos_hit,
+    output reg                     limit_neg_hit
+);
+
+    // Keep the direction of the current travel command.
+    reg signed [31:0] latched_target_travel;
+
+    // ------------------------------------------------------------
+    // Actual travelled distance
+    // ------------------------------------------------------------
+
+    assign travel =
+        current_position - start_position;
+
+    // ------------------------------------------------------------
+    // Position error
+    //
+    // Use 33 bits so subtraction itself does not overflow when
+    // comparing two signed 32-bit positions.
+    // ------------------------------------------------------------
+
+    wire signed [32:0] current_ext;
+    wire signed [32:0] target_ext;
+    wire signed [32:0] position_error;
+    wire        [32:0] abs_position_error;
+
+    assign current_ext =
+        {current_position[31], current_position};
+
+    assign target_ext =
+        {target_position[31], target_position};
+
+    assign position_error =
+        current_ext - target_ext;
+
+    assign abs_position_error =
+        position_error[32]
+        ? -position_error
+        : position_error;
+
+    // ------------------------------------------------------------
+    // In-position window
+    //
+    // Example:
+    //
+    // target = 1500
+    // tolerance = 5
+    //
+    // 1495 ... 1505 => in_position = 1
+    // ------------------------------------------------------------
+
+    assign in_position =
+        (busy || done) &&
+        (abs_position_error <= {1'b0, tolerance});
+
+    // ------------------------------------------------------------
+    // Main travel state logic
+    // ------------------------------------------------------------
+
+    always @(posedge clk or negedge reset_n) begin
+
+        if (!reset_n) begin
+
+            start_position        <= 32'sd0;
+            target_position       <= 32'sd0;
+            latched_target_travel <= 32'sd0;
+
+            busy                  <= 1'b0;
+            done                  <= 1'b0;
+
+            limit_hit             <= 1'b0;
+            limit_pos_hit         <= 1'b0;
+            limit_neg_hit         <= 1'b0;
+
+        end else begin
+
+            // ----------------------------------------------------
+            // New travel command
+            //
+            // A new start always creates a new measurement segment.
+            // ----------------------------------------------------
+
+            if (start) begin
+
+                start_position <= current_position;
+
+                target_position <=
+                    current_position + target_travel;
+
+                latched_target_travel <=
+                    target_travel;
+
+                done          <= 1'b0;
+
+                limit_hit     <= 1'b0;
+                limit_pos_hit <= 1'b0;
+                limit_neg_hit <= 1'b0;
+
+
+                // Zero-distance request is already complete.
+                if (target_travel == 0) begin
+
+                    busy <= 1'b0;
+                    done <= 1'b1;
+
+                end else begin
+
+                    busy <= 1'b1;
+
+                end
+
+            end
+
+            // ----------------------------------------------------
+            // Monitor active travel
+            // ----------------------------------------------------
+
+            else if (busy) begin
+
+                // Positive motion:
+                // positive limit is relevant.
+                if (
+                    (latched_target_travel > 0) &&
+                    limit_pos
+                ) begin
+
+                    busy          <= 1'b0;
+                    done          <= 1'b0;
+
+                    limit_hit     <= 1'b1;
+                    limit_pos_hit <= 1'b1;
+
+                end
+
+                // Negative motion:
+                // negative limit is relevant.
+                else if (
+                    (latched_target_travel < 0) &&
+                    limit_neg
+                ) begin
+
+                    busy          <= 1'b0;
+                    done          <= 1'b0;
+
+                    limit_hit     <= 1'b1;
+                    limit_neg_hit <= 1'b1;
+
+                end
+
+                // Reached requested position.
+                else if (
+                    abs_position_error <=
+                    {1'b0, tolerance}
+                ) begin
+
+                    busy <= 1'b0;
+                    done <= 1'b1;
+
+                end
+
+            end
+
+        end
+
+    end
+
+endmodule

+ 940 - 0
sim/axis_travel_monitor_tb.v

@@ -0,0 +1,940 @@
+`timescale 1ns/1ps
+
+module axis_travel_monitor_tb;
+
+    reg clk;
+    reg reset_n;
+    reg start;
+
+    reg signed [31:0] current_position;
+    reg signed [31:0] target_travel;
+
+    reg [31:0] tolerance;
+
+    reg limit_pos;
+    reg limit_neg;
+
+    wire signed [31:0] start_position;
+    wire signed [31:0] target_position;
+    wire signed [31:0] travel;
+
+    wire busy;
+    wire in_position;
+    wire done;
+
+    wire limit_hit;
+    wire limit_pos_hit;
+    wire limit_neg_hit;
+
+    integer errors;
+    integer checks;
+
+    integer i;
+
+    integer random_start;
+    integer random_distance;
+    integer random_tolerance;
+    integer random_target;
+    integer random_middle;
+
+    reg [31:0] lfsr;
+
+    axis_travel_monitor dut (
+        .clk(clk),
+        .reset_n(reset_n),
+
+        .start(start),
+
+        .current_position(current_position),
+        .target_travel(target_travel),
+
+        .tolerance(tolerance),
+
+        .limit_pos(limit_pos),
+        .limit_neg(limit_neg),
+
+        .start_position(start_position),
+        .target_position(target_position),
+        .travel(travel),
+
+        .busy(busy),
+        .in_position(in_position),
+        .done(done),
+
+        .limit_hit(limit_hit),
+        .limit_pos_hit(limit_pos_hit),
+        .limit_neg_hit(limit_neg_hit)
+    );
+
+    // ------------------------------------------------------------
+    // Clock
+    // ------------------------------------------------------------
+
+    initial clk = 0;
+    always #5 clk = ~clk;
+
+    // ------------------------------------------------------------
+    // Reset
+    // ------------------------------------------------------------
+
+    task reset_dut;
+        begin
+
+            reset_n = 0;
+            start = 0;
+
+            current_position = 0;
+            target_travel = 0;
+            tolerance = 0;
+
+            limit_pos = 0;
+            limit_neg = 0;
+
+            repeat (3)
+                @(posedge clk);
+
+            #1;
+
+            reset_n = 1;
+
+            @(posedge clk);
+            #1;
+
+        end
+    endtask
+
+    // ------------------------------------------------------------
+    // Generate one-cycle START pulse
+    // ------------------------------------------------------------
+
+    task start_move;
+
+        input integer position;
+        input integer distance;
+        input integer tolerance_value;
+
+        begin
+
+            current_position = position;
+            target_travel = distance;
+            tolerance = tolerance_value;
+
+            limit_pos = 0;
+            limit_neg = 0;
+
+            @(negedge clk);
+
+            start = 1;
+
+            @(posedge clk);
+            #1;
+
+            start = 0;
+
+        end
+    endtask
+
+    // ------------------------------------------------------------
+    // Check helper
+    // ------------------------------------------------------------
+
+    task expect_value;
+
+        input integer condition;
+        input [8*80-1:0] message;
+
+        begin
+
+            checks = checks + 1;
+
+            if (!condition) begin
+
+                $display(
+                    "FAIL: %0s",
+                    message
+                );
+
+                errors = errors + 1;
+
+            end
+
+        end
+    endtask
+
+    // ------------------------------------------------------------
+    // MAIN
+    // ------------------------------------------------------------
+
+    initial begin
+
+        errors = 0;
+        checks = 0;
+
+        lfsr = 32'h1ACE_B00C;
+
+        reset_dut();
+
+        $display("");
+        $display("============================================================");
+        $display("       AXIS TRAVEL MONITOR AUTOMATED VERIFICATION");
+        $display("============================================================");
+
+
+        // ========================================================
+        // TEST A
+        // Positive travel
+        // ========================================================
+
+        $display("");
+        $display("[A] Positive travel");
+
+        start_move(
+            1000,
+            500,
+            5
+        );
+
+        expect_value(
+            start_position == 1000,
+            "start_position should be 1000"
+        );
+
+        expect_value(
+            target_position == 1500,
+            "target_position should be 1500"
+        );
+
+        expect_value(
+            busy == 1,
+            "positive move should be busy"
+        );
+
+
+        current_position = 1200;
+        #1;
+
+        expect_value(
+            travel == 200,
+            "travel should be 200"
+        );
+
+        expect_value(
+            in_position == 0,
+            "1200 must not be in target window"
+        );
+
+
+        current_position = 1494;
+        #1;
+
+        expect_value(
+            in_position == 0,
+            "1494 must be outside tolerance"
+        );
+
+
+        current_position = 1495;
+        #1;
+
+        expect_value(
+            in_position == 1,
+            "1495 must be inside tolerance"
+        );
+
+
+        @(posedge clk);
+        #1;
+
+        expect_value(
+            done == 1,
+            "positive travel should complete"
+        );
+
+        expect_value(
+            busy == 0,
+            "busy should clear after completion"
+        );
+
+        $display("PASS [A] Positive travel");
+
+
+        // ========================================================
+        // TEST B
+        // Negative travel
+        // ========================================================
+
+        $display("");
+        $display("[B] Negative travel");
+
+        start_move(
+            3000,
+            -500,
+            5
+        );
+
+        expect_value(
+            start_position == 3000,
+            "negative start_position"
+        );
+
+        expect_value(
+            target_position == 2500,
+            "negative target_position"
+        );
+
+
+        current_position = 2506;
+        #1;
+
+        expect_value(
+            in_position == 0,
+            "2506 must be outside tolerance"
+        );
+
+
+        current_position = 2505;
+        #1;
+
+        expect_value(
+            in_position == 1,
+            "2505 must be inside tolerance"
+        );
+
+
+        @(posedge clk);
+        #1;
+
+        expect_value(
+            done == 1,
+            "negative travel should complete"
+        );
+
+        expect_value(
+            travel == -495,
+            "negative travel calculation"
+        );
+
+        $display("PASS [B] Negative travel");
+
+
+        // ========================================================
+        // TEST C
+        // Exact tolerance boundaries
+        // ========================================================
+
+        $display("");
+        $display("[C] Position tolerance window");
+
+        start_move(
+            1000,
+            500,
+            5
+        );
+
+
+        current_position = 1494;
+        #1;
+
+        expect_value(
+            in_position == 0,
+            "target-6 must fail"
+        );
+
+
+        current_position = 1495;
+        #1;
+
+        expect_value(
+            in_position == 1,
+            "target-5 must pass"
+        );
+
+
+        current_position = 1505;
+        #1;
+
+        expect_value(
+            in_position == 1,
+            "target+5 must pass"
+        );
+
+
+        current_position = 1506;
+        #1;
+
+        expect_value(
+            in_position == 0,
+            "target+6 must fail"
+        );
+
+        $display("PASS [C] Tolerance window");
+
+
+        // ========================================================
+        // TEST D
+        // Positive limit during positive travel
+        // ========================================================
+
+        $display("");
+        $display("[D] Positive limit");
+
+        start_move(
+            1000,
+            2000,
+            5
+        );
+
+
+        current_position = 1300;
+
+        limit_pos = 1;
+
+        @(posedge clk);
+        #1;
+
+
+        expect_value(
+            busy == 0,
+            "positive limit must terminate travel"
+        );
+
+        expect_value(
+            done == 0,
+            "limit termination must not be normal DONE"
+        );
+
+        expect_value(
+            limit_hit == 1,
+            "limit_hit must latch"
+        );
+
+        expect_value(
+            limit_pos_hit == 1,
+            "positive limit flag must latch"
+        );
+
+        expect_value(
+            limit_neg_hit == 0,
+            "negative limit flag must remain clear"
+        );
+
+        limit_pos = 0;
+
+        $display("PASS [D] Positive limit");
+
+
+        // ========================================================
+        // TEST E
+        // Negative limit during negative travel
+        // ========================================================
+
+        $display("");
+        $display("[E] Negative limit");
+
+        start_move(
+            3000,
+            -2000,
+            5
+        );
+
+
+        current_position = 2700;
+
+        limit_neg = 1;
+
+        @(posedge clk);
+        #1;
+
+
+        expect_value(
+            busy == 0,
+            "negative limit must terminate travel"
+        );
+
+        expect_value(
+            done == 0,
+            "negative limit must not generate DONE"
+        );
+
+        expect_value(
+            limit_hit == 1,
+            "limit_hit must latch on negative limit"
+        );
+
+        expect_value(
+            limit_neg_hit == 1,
+            "negative limit flag must latch"
+        );
+
+        limit_neg = 0;
+
+        $display("PASS [E] Negative limit");
+
+
+        // ========================================================
+        // TEST F
+        // Wrong-direction limit must not terminate movement
+        // ========================================================
+
+        $display("");
+        $display("[F] Opposite limit immunity");
+
+        start_move(
+            1000,
+            1000,
+            5
+        );
+
+
+        limit_neg = 1;
+
+        @(posedge clk);
+        #1;
+
+
+        expect_value(
+            busy == 1,
+            "negative limit should not stop positive travel"
+        );
+
+        expect_value(
+            limit_hit == 0,
+            "wrong-direction limit must not latch"
+        );
+
+        limit_neg = 0;
+
+        $display("PASS [F] Opposite limit immunity");
+
+
+        // ========================================================
+        // TEST G
+        // Reset during travel
+        // ========================================================
+
+        $display("");
+        $display("[G] Reset during active travel");
+
+        start_move(
+            5000,
+            1000,
+            5
+        );
+
+        current_position = 5300;
+
+
+        reset_n = 0;
+
+        #1;
+
+
+        expect_value(
+            busy == 0,
+            "reset must clear busy"
+        );
+
+        expect_value(
+            done == 0,
+            "reset must clear done"
+        );
+
+        expect_value(
+            limit_hit == 0,
+            "reset must clear limit_hit"
+        );
+
+
+        reset_n = 1;
+
+        @(posedge clk);
+        #1;
+
+        $display("PASS [G] Reset");
+
+
+        // ========================================================
+        // TEST H
+        // New start while previous travel is still active
+        // ========================================================
+
+        $display("");
+        $display("[H] Restart / new segment");
+
+        start_move(
+            1000,
+            1000,
+            5
+        );
+
+
+        current_position = 1300;
+
+
+        // New travel begins from 1300.
+        start_move(
+            1300,
+            200,
+            2
+        );
+
+
+        expect_value(
+            start_position == 1300,
+            "restart must capture new start position"
+        );
+
+        expect_value(
+            target_position == 1500,
+            "restart target should be 1500"
+        );
+
+        expect_value(
+            travel == 0,
+            "new segment travel must restart from zero"
+        );
+
+        expect_value(
+            busy == 1,
+            "new segment should be busy"
+        );
+
+
+        current_position = 1500;
+
+        @(posedge clk);
+        #1;
+
+
+        expect_value(
+            done == 1,
+            "restarted travel should complete"
+        );
+
+        $display("PASS [H] Restart");
+
+
+        // ========================================================
+        // TEST I
+        // Zero travel
+        // ========================================================
+
+        $display("");
+        $display("[I] Zero-distance travel");
+
+        start_move(
+            12345,
+            0,
+            0
+        );
+
+
+        expect_value(
+            start_position == 12345,
+            "zero travel start position"
+        );
+
+        expect_value(
+            target_position == 12345,
+            "zero travel target position"
+        );
+
+        expect_value(
+            busy == 0,
+            "zero travel must not remain busy"
+        );
+
+        expect_value(
+            done == 1,
+            "zero travel should complete immediately"
+        );
+
+        expect_value(
+            in_position == 1,
+            "zero travel should be in position"
+        );
+
+        $display("PASS [I] Zero-distance travel");
+
+
+        // ========================================================
+        // TEST J
+        // Negative absolute positions
+        // ========================================================
+
+        $display("");
+        $display("[J] Negative absolute positions");
+
+        start_move(
+            -5000,
+            750,
+            3
+        );
+
+
+        expect_value(
+            target_position == -4250,
+            "negative-coordinate target"
+        );
+
+
+        current_position = -4250;
+
+        @(posedge clk);
+        #1;
+
+
+        expect_value(
+            done == 1,
+            "negative-coordinate move should complete"
+        );
+
+        expect_value(
+            travel == 750,
+            "travel across negative coordinates"
+        );
+
+        $display("PASS [J] Negative absolute positions");
+
+
+        // ========================================================
+        // TEST K
+        // 500 deterministic pseudo-random travel segments
+        // ========================================================
+
+        $display("");
+        $display("[K] 500 randomized travel segments");
+
+
+        for (i = 0; i < 500; i = i + 1) begin
+
+            // LFSR
+            lfsr = {
+                lfsr[30:0],
+                lfsr[31] ^
+                lfsr[21] ^
+                lfsr[1] ^
+                lfsr[0]
+            };
+
+
+            random_start =
+                $signed(lfsr[15:0]);
+
+
+            // Make distance between 50 and 1049.
+            random_distance =
+                50 + (lfsr[25:16] % 1000);
+
+
+            // Random direction.
+            if (lfsr[26])
+                random_distance = -random_distance;
+
+
+            random_tolerance =
+                lfsr[30:27] % 6;
+
+
+            random_target =
+                random_start +
+                random_distance;
+
+
+            random_middle =
+                random_start +
+                random_distance / 2;
+
+
+            start_move(
+                random_start,
+                random_distance,
+                random_tolerance
+            );
+
+
+            checks = checks + 3;
+
+
+            if (start_position != random_start) begin
+
+                $display(
+                    "FAIL RANDOM START: case=%0d expected=%0d actual=%0d",
+                    i,
+                    random_start,
+                    start_position
+                );
+
+                errors = errors + 1;
+
+            end
+
+
+            if (target_position != random_target) begin
+
+                $display(
+                    "FAIL RANDOM TARGET: case=%0d expected=%0d actual=%0d",
+                    i,
+                    random_target,
+                    target_position
+                );
+
+                errors = errors + 1;
+
+            end
+
+
+            if (!busy) begin
+
+                $display(
+                    "FAIL RANDOM BUSY: case=%0d",
+                    i
+                );
+
+                errors = errors + 1;
+
+            end
+
+
+            // Halfway should still be active.
+            current_position = random_middle;
+
+            @(posedge clk);
+            #1;
+
+
+            checks = checks + 1;
+
+
+            if (!busy) begin
+
+                $display(
+                    "FAIL RANDOM EARLY DONE: case=%0d",
+                    i
+                );
+
+                errors = errors + 1;
+
+            end
+
+
+            // Move exactly to target.
+            current_position = random_target;
+
+            @(posedge clk);
+            #1;
+
+
+            checks = checks + 3;
+
+
+            if (!done) begin
+
+                $display(
+                    "FAIL RANDOM DONE: case=%0d start=%0d distance=%0d target=%0d current=%0d",
+                    i,
+                    random_start,
+                    random_distance,
+                    random_target,
+                    current_position
+                );
+
+                errors = errors + 1;
+
+            end
+
+
+            if (busy) begin
+
+                $display(
+                    "FAIL RANDOM BUSY CLEAR: case=%0d",
+                    i
+                );
+
+                errors = errors + 1;
+
+            end
+
+
+            if (!in_position) begin
+
+                $display(
+                    "FAIL RANDOM IN_POSITION: case=%0d",
+                    i
+                );
+
+                errors = errors + 1;
+
+            end
+
+
+            if ((i % 100) == 0) begin
+
+                $display(
+                    "Random progress: %0d / 500",
+                    i
+                );
+
+            end
+
+        end
+
+
+        $display("PASS [K] 500 randomized travel segments");
+
+
+        // ========================================================
+        // FINAL RESULT
+        // ========================================================
+
+        $display("");
+        $display("============================================================");
+        $display("TOTAL CHECKS : %0d", checks);
+        $display("TOTAL ERRORS : %0d", errors);
+        $display("============================================================");
+
+
+        if (errors == 0) begin
+
+            $display("");
+            $display("ALL AXIS TRAVEL TESTS PASSED");
+
+            $display("");
+            $display("Verified:");
+            $display("  - positive travel");
+            $display("  - negative travel");
+            $display("  - start-position capture");
+            $display("  - relative travel calculation");
+            $display("  - target-position calculation");
+            $display("  - tolerance / in-position window");
+            $display("  - positive limit");
+            $display("  - negative limit");
+            $display("  - opposite-limit handling");
+            $display("  - reset");
+            $display("  - restart during travel");
+            $display("  - zero-distance travel");
+            $display("  - negative coordinates");
+            $display("  - 500 randomized travel segments");
+
+            $display("");
+            $display("============================================================");
+
+            $finish;
+
+        end else begin
+
+            $display("");
+            $display(
+                "AXIS TRAVEL TEST FAILED: %0d error(s)",
+                errors
+            );
+
+            $display("============================================================");
+
+            $fatal(1);
+
+        end
+
+    end
+
+endmodule