Browse Source

add axis overshoot and travel overrun verification

botao 2 weeks ago
parent
commit
f14d79c560
2 changed files with 1004 additions and 72 deletions
  1. 189 72
      rtl/axis_travel_monitor.v
  2. 815 0
      sim/axis_travel_overrun_tb.v

+ 189 - 72
rtl/axis_travel_monitor.v

@@ -1,29 +1,31 @@
 `timescale 1ns/1ps
 
-module axis_travel_monitor (
+module axis_travel_monitor #(
+    // How far beyond the target is considered a serious overrun.
+    parameter integer OVERRUN_LIMIT = 20
+)(
     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.
+    // Current measured position.
     input  wire signed [31:0]      current_position,
 
-    // Relative travel command.
-    // +500 = move positive by 500
-    // -500 = move negative by 500
+    // Relative requested travel.
+    // +500 = positive travel
+    // -500 = negative travel
     input  wire signed [31:0]      target_travel,
 
-    // Allowed target-position error.
+    // Allowed target error.
     input  wire [31:0]             tolerance,
 
-    // Physical limit inputs.
+    // Physical limits.
     input  wire                    limit_pos,
     input  wire                    limit_neg,
 
-    // Latched position where this travel started.
+    // Position captured when START occurs.
     output reg  signed [31:0]      start_position,
 
     // start_position + target_travel
@@ -32,41 +34,45 @@ module axis_travel_monitor (
     // current_position - start_position
     output wire signed [31:0]      travel,
 
-    // Travel state
+    // Travel state.
     output reg                     busy,
 
-    // Current position is inside target tolerance window.
+    // Current position is inside target tolerance.
     output wire                    in_position,
 
-    // Latched completion flag.
-    // Cleared by next start or reset.
+    // Normal completion.
     output reg                     done,
 
-    // Latched limit status.
+    // Limit status.
     output reg                     limit_hit,
     output reg                     limit_pos_hit,
-    output reg                     limit_neg_hit
+    output reg                     limit_neg_hit,
+
+    // Target was crossed without entering tolerance window.
+    output reg                     overshoot,
+
+    // Position went too far beyond target.
+    output reg                     travel_overrun
 );
 
-    // Keep the direction of the current travel command.
     reg signed [31:0] latched_target_travel;
 
     // ------------------------------------------------------------
-    // Actual travelled distance
+    // Travel calculation
     // ------------------------------------------------------------
 
     assign travel =
         current_position - start_position;
 
     // ------------------------------------------------------------
-    // Position error
-    //
-    // Use 33 bits so subtraction itself does not overflow when
-    // comparing two signed 32-bit positions.
+    // Extend positions to 33 bits before subtraction/comparison.
     // ------------------------------------------------------------
 
     wire signed [32:0] current_ext;
     wire signed [32:0] target_ext;
+    wire signed [32:0] tolerance_ext;
+    wire signed [32:0] overrun_limit_ext;
+
     wire signed [32:0] position_error;
     wire        [32:0] abs_position_error;
 
@@ -76,6 +82,12 @@ module axis_travel_monitor (
     assign target_ext =
         {target_position[31], target_position};
 
+    assign tolerance_ext =
+        {1'b0, tolerance};
+
+    assign overrun_limit_ext =
+        OVERRUN_LIMIT;
+
     assign position_error =
         current_ext - target_ext;
 
@@ -85,22 +97,64 @@ module axis_travel_monitor (
         : position_error;
 
     // ------------------------------------------------------------
-    // In-position window
+    // Current position is inside allowed target window.
+    // ------------------------------------------------------------
+
+    assign in_position =
+        (busy || done) &&
+        (abs_position_error <= tolerance_ext);
+
+    // ------------------------------------------------------------
+    // Overshoot detection
     //
-    // Example:
+    // Positive movement:
     //
     // target = 1500
     // tolerance = 5
     //
-    // 1495 ... 1505 => in_position = 1
+    // >1505 means target window was passed.
+    //
+    // Negative movement is the opposite.
     // ------------------------------------------------------------
 
-    assign in_position =
-        (busy || done) &&
-        (abs_position_error <= {1'b0, tolerance});
+    wire positive_overshoot_now;
+    wire negative_overshoot_now;
+
+    assign positive_overshoot_now =
+        (latched_target_travel > 0) &&
+        (current_ext > (target_ext + tolerance_ext));
+
+    assign negative_overshoot_now =
+        (latched_target_travel < 0) &&
+        (current_ext < (target_ext - tolerance_ext));
+
+    // ------------------------------------------------------------
+    // Serious travel-overrun detection.
+    //
+    // Default OVERRUN_LIMIT = 20.
+    //
+    // target=1500:
+    // positive overrun at >=1520
+    //
+    // target=2500:
+    // negative overrun at <=2480
+    // ------------------------------------------------------------
+
+    wire positive_overrun_now;
+    wire negative_overrun_now;
+
+    assign positive_overrun_now =
+        (latched_target_travel > 0) &&
+        (current_ext >=
+            (target_ext + overrun_limit_ext));
+
+    assign negative_overrun_now =
+        (latched_target_travel < 0) &&
+        (current_ext <=
+            (target_ext - overrun_limit_ext));
 
     // ------------------------------------------------------------
-    // Main travel state logic
+    // Main state logic
     // ------------------------------------------------------------
 
     always @(posedge clk or negedge reset_n) begin
@@ -118,17 +172,19 @@ module axis_travel_monitor (
             limit_pos_hit         <= 1'b0;
             limit_neg_hit         <= 1'b0;
 
+            overshoot             <= 1'b0;
+            travel_overrun        <= 1'b0;
+
         end else begin
 
-            // ----------------------------------------------------
-            // New travel command
-            //
-            // A new start always creates a new measurement segment.
-            // ----------------------------------------------------
+            // ====================================================
+            // New travel segment
+            // ====================================================
 
             if (start) begin
 
-                start_position <= current_position;
+                start_position <=
+                    current_position;
 
                 target_position <=
                     current_position + target_travel;
@@ -136,14 +192,16 @@ module axis_travel_monitor (
                 latched_target_travel <=
                     target_travel;
 
-                done          <= 1'b0;
+                done           <= 1'b0;
 
-                limit_hit     <= 1'b0;
-                limit_pos_hit <= 1'b0;
-                limit_neg_hit <= 1'b0;
+                limit_hit      <= 1'b0;
+                limit_pos_hit  <= 1'b0;
+                limit_neg_hit  <= 1'b0;
 
+                overshoot      <= 1'b0;
+                travel_overrun <= 1'b0;
 
-                // Zero-distance request is already complete.
+                // Zero-distance command is complete immediately.
                 if (target_travel == 0) begin
 
                     busy <= 1'b0;
@@ -155,52 +213,111 @@ module axis_travel_monitor (
 
                 end
 
-            end
-
-            // ----------------------------------------------------
-            // Monitor active travel
-            // ----------------------------------------------------
+            end else begin
+
+                // =================================================
+                // Continue monitoring overrun even after the normal
+                // travel state has stopped.
+                //
+                // This allows:
+                //
+                // reach target normally
+                //      ↓
+                // external mechanics keep moving
+                //      ↓
+                // travel_overrun becomes 1
+                // =================================================
 
-            else if (busy) begin
-
-                // Positive motion:
-                // positive limit is relevant.
                 if (
-                    (latched_target_travel > 0) &&
-                    limit_pos
+                    (
+                        busy       ||
+                        done       ||
+                        overshoot  ||
+                        limit_hit
+                    ) &&
+                    !travel_overrun
                 ) begin
 
-                    busy          <= 1'b0;
-                    done          <= 1'b0;
+                    if (
+                        positive_overrun_now ||
+                        negative_overrun_now
+                    ) begin
+
+                        travel_overrun <= 1'b1;
 
-                    limit_hit     <= 1'b1;
-                    limit_pos_hit <= 1'b1;
+                    end
 
                 end
 
-                // Negative motion:
-                // negative limit is relevant.
-                else if (
-                    (latched_target_travel < 0) &&
-                    limit_neg
-                ) begin
+                // =================================================
+                // Active travel monitoring
+                // =================================================
 
-                    busy          <= 1'b0;
-                    done          <= 1'b0;
+                if (busy) begin
 
-                    limit_hit     <= 1'b1;
-                    limit_neg_hit <= 1'b1;
+                    // ---------------------------------------------
+                    // Positive physical limit has highest priority.
+                    // ---------------------------------------------
 
-                end
+                    if (
+                        (latched_target_travel > 0) &&
+                        limit_pos
+                    ) begin
 
-                // Reached requested position.
-                else if (
-                    abs_position_error <=
-                    {1'b0, tolerance}
-                ) begin
+                        busy          <= 1'b0;
+                        done          <= 1'b0;
 
-                    busy <= 1'b0;
-                    done <= 1'b1;
+                        limit_hit     <= 1'b1;
+                        limit_pos_hit <= 1'b1;
+
+                    end
+
+                    // ---------------------------------------------
+                    // Negative physical limit.
+                    // ---------------------------------------------
+
+                    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
+
+                    // ---------------------------------------------
+                    // Normal arrival inside tolerance window.
+                    // ---------------------------------------------
+
+                    else if (
+                        abs_position_error <= tolerance_ext
+                    ) begin
+
+                        busy <= 1'b0;
+                        done <= 1'b1;
+
+                    end
+
+                    // ---------------------------------------------
+                    // Target was skipped.
+                    //
+                    // This is NOT a normal completion.
+                    // ---------------------------------------------
+
+                    else if (
+                        positive_overshoot_now ||
+                        negative_overshoot_now
+                    ) begin
+
+                        busy      <= 1'b0;
+                        done      <= 1'b0;
+                        overshoot <= 1'b1;
+
+                    end
 
                 end
 

+ 815 - 0
sim/axis_travel_overrun_tb.v

@@ -0,0 +1,815 @@
+`timescale 1ns/1ps
+
+module axis_travel_overrun_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;
+
+    wire overshoot;
+    wire travel_overrun;
+
+    integer errors;
+    integer checks;
+
+    integer i;
+
+    integer random_start;
+    integer random_distance;
+    integer random_target;
+    integer random_tolerance;
+    integer random_excess;
+
+    reg [31:0] lfsr;
+
+    axis_travel_monitor #(
+        .OVERRUN_LIMIT(20)
+    ) 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),
+
+        .overshoot(overshoot),
+        .travel_overrun(travel_overrun)
+    );
+
+    // ------------------------------------------------------------
+    // 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
+
+    // ------------------------------------------------------------
+    // Start one travel
+    // ------------------------------------------------------------
+
+    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
+
+    // ------------------------------------------------------------
+    // Generic expectation
+    // ------------------------------------------------------------
+
+    task expect_value;
+
+        input integer condition;
+        input [8*100-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'hA55A_1234;
+
+        reset_dut();
+
+        $display("");
+        $display("============================================================");
+        $display("       AXIS OVERSHOOT / OVERRUN VERIFICATION");
+        $display("============================================================");
+
+
+        // ========================================================
+        // TEST A
+        //
+        // Positive overshoot:
+        //
+        // target    = 1500
+        // tolerance = 5
+        //
+        // 1490 -> 1510
+        //
+        // skips the valid 1495..1505 target window.
+        // ========================================================
+
+        $display("");
+        $display("[A] Positive target overshoot");
+
+        start_move(
+            1000,
+            500,
+            5
+        );
+
+        current_position = 1490;
+
+        @(posedge clk);
+        #1;
+
+        expect_value(
+            busy == 1,
+            "1490 should still be active"
+        );
+
+        expect_value(
+            overshoot == 0,
+            "1490 must not be overshoot"
+        );
+
+
+        current_position = 1510;
+
+        @(posedge clk);
+        #1;
+
+        expect_value(
+            busy == 0,
+            "positive overshoot must terminate travel"
+        );
+
+        expect_value(
+            done == 0,
+            "overshoot must not generate normal DONE"
+        );
+
+        expect_value(
+            overshoot == 1,
+            "positive overshoot flag must latch"
+        );
+
+        expect_value(
+            in_position == 0,
+            "1510 must not be in position"
+        );
+
+        expect_value(
+            travel_overrun == 0,
+            "10-count overshoot must not trigger 20-count overrun"
+        );
+
+        $display("PASS [A] Positive overshoot detected");
+
+
+        // ========================================================
+        // TEST B
+        //
+        // Negative overshoot.
+        //
+        // target=2500
+        //
+        // 2510 -> 2490
+        // ========================================================
+
+        $display("");
+        $display("[B] Negative target overshoot");
+
+        start_move(
+            3000,
+            -500,
+            5
+        );
+
+        current_position = 2510;
+
+        @(posedge clk);
+        #1;
+
+        expect_value(
+            busy == 1,
+            "2510 should still be active"
+        );
+
+
+        current_position = 2490;
+
+        @(posedge clk);
+        #1;
+
+        expect_value(
+            busy == 0,
+            "negative overshoot must terminate travel"
+        );
+
+        expect_value(
+            done == 0,
+            "negative overshoot must not generate DONE"
+        );
+
+        expect_value(
+            overshoot == 1,
+            "negative overshoot flag must latch"
+        );
+
+        expect_value(
+            travel_overrun == 0,
+            "10-count negative overshoot is below overrun threshold"
+        );
+
+        $display("PASS [B] Negative overshoot detected");
+
+
+        // ========================================================
+        // TEST C
+        //
+        // Serious positive overrun.
+        //
+        // target = 1500
+        // current = 1525
+        //
+        // exceeds target by 25.
+        // ========================================================
+
+        $display("");
+        $display("[C] Positive travel overrun");
+
+        start_move(
+            1000,
+            500,
+            5
+        );
+
+        current_position = 1525;
+
+        @(posedge clk);
+        #1;
+
+        expect_value(
+            overshoot == 1,
+            "1525 must trigger overshoot"
+        );
+
+        expect_value(
+            travel_overrun == 1,
+            "1525 must trigger 20-count travel overrun"
+        );
+
+        expect_value(
+            done == 0,
+            "serious overrun must not be normal DONE"
+        );
+
+        $display("PASS [C] Positive travel overrun detected");
+
+
+        // ========================================================
+        // TEST D
+        //
+        // Serious negative overrun.
+        // ========================================================
+
+        $display("");
+        $display("[D] Negative travel overrun");
+
+        start_move(
+            3000,
+            -500,
+            5
+        );
+
+        current_position = 2475;
+
+        @(posedge clk);
+        #1;
+
+        expect_value(
+            overshoot == 1,
+            "2475 must trigger negative overshoot"
+        );
+
+        expect_value(
+            travel_overrun == 1,
+            "2475 must trigger negative travel overrun"
+        );
+
+        expect_value(
+            done == 0,
+            "negative overrun must not be normal DONE"
+        );
+
+        $display("PASS [D] Negative travel overrun detected");
+
+
+        // ========================================================
+        // TEST E
+        //
+        // Reach target normally first.
+        //
+        // Then mechanics continue moving beyond target.
+        //
+        // FPGA must still detect travel_overrun.
+        // ========================================================
+
+        $display("");
+        $display("[E] Post-completion mechanical drift");
+
+        start_move(
+            1000,
+            500,
+            5
+        );
+
+        current_position = 1500;
+
+        @(posedge clk);
+        #1;
+
+        expect_value(
+            done == 1,
+            "1500 must complete normally"
+        );
+
+        expect_value(
+            overshoot == 0,
+            "normal arrival must not overshoot"
+        );
+
+        expect_value(
+            travel_overrun == 0,
+            "normal arrival must not overrun"
+        );
+
+
+        // Simulate mechanics continuing after command completion.
+        current_position = 1525;
+
+        @(posedge clk);
+        #1;
+
+        expect_value(
+            done == 1,
+            "original normal completion remains latched"
+        );
+
+        expect_value(
+            overshoot == 0,
+            "post-completion drift is not skipped-target overshoot"
+        );
+
+        expect_value(
+            travel_overrun == 1,
+            "continued movement must trigger overrun"
+        );
+
+        $display("PASS [E] Post-completion overrun detected");
+
+
+        // ========================================================
+        // TEST F
+        //
+        // New START must clear old error flags.
+        // ========================================================
+
+        $display("");
+        $display("[F] New segment clears previous alarms");
+
+        start_move(
+            2000,
+            -300,
+            5
+        );
+
+        expect_value(
+            busy == 1,
+            "new segment must become active"
+        );
+
+        expect_value(
+            done == 0,
+            "new segment clears old DONE"
+        );
+
+        expect_value(
+            overshoot == 0,
+            "new segment clears overshoot"
+        );
+
+        expect_value(
+            travel_overrun == 0,
+            "new segment clears travel_overrun"
+        );
+
+        expect_value(
+            limit_hit == 0,
+            "new segment clears limit_hit"
+        );
+
+        $display("PASS [F] Alarm reset on new START");
+
+
+        // ========================================================
+        // TEST G
+        //
+        // Limit has priority over target overshoot.
+        //
+        // Position is already too far,
+        // but physical positive limit is active simultaneously.
+        //
+        // Result:
+        //
+        // limit_hit = 1
+        // overshoot = 0
+        // ========================================================
+
+        $display("");
+        $display("[G] Physical limit priority");
+
+        start_move(
+            1000,
+            500,
+            5
+        );
+
+        current_position = 1525;
+        limit_pos = 1;
+
+        @(posedge clk);
+        #1;
+
+        expect_value(
+            limit_hit == 1,
+            "physical limit must latch"
+        );
+
+        expect_value(
+            limit_pos_hit == 1,
+            "positive limit flag must latch"
+        );
+
+        expect_value(
+            overshoot == 0,
+            "limit must take priority over overshoot state"
+        );
+
+        expect_value(
+            done == 0,
+            "limit event is not normal completion"
+        );
+
+        limit_pos = 0;
+
+        $display("PASS [G] Limit priority");
+
+
+        // ========================================================
+        // TEST H
+        //
+        // 300 pseudo-random target-crossing tests.
+        //
+        // Every case deliberately jumps past target.
+        // ========================================================
+
+        $display("");
+        $display("[H] 300 randomized target-crossing tests");
+
+        for (i = 0; i < 300; i = i + 1) begin
+
+            // LFSR
+            lfsr = {
+                lfsr[30:0],
+                lfsr[31] ^
+                lfsr[21] ^
+                lfsr[1] ^
+                lfsr[0]
+            };
+
+
+            random_start =
+                $signed(lfsr[15:0]);
+
+
+            random_distance =
+                100 + (lfsr[25:16] % 900);
+
+
+            if (lfsr[26])
+                random_distance = -random_distance;
+
+
+            random_tolerance =
+                lfsr[29:27] % 6;
+
+
+            random_target =
+                random_start +
+                random_distance;
+
+
+            // Always jump beyond tolerance.
+            random_excess =
+                random_tolerance +
+                1 +
+                (lfsr[15:8] % 30);
+
+
+            start_move(
+                random_start,
+                random_distance,
+                random_tolerance
+            );
+
+
+            if (random_distance > 0)
+                current_position =
+                    random_target + random_excess;
+            else
+                current_position =
+                    random_target - random_excess;
+
+
+            @(posedge clk);
+            #1;
+
+
+            checks = checks + 4;
+
+
+            if (!overshoot) begin
+
+                $display(
+                    "FAIL RANDOM OVERSHOOT case=%0d start=%0d distance=%0d target=%0d current=%0d tolerance=%0d",
+                    i,
+                    random_start,
+                    random_distance,
+                    random_target,
+                    current_position,
+                    random_tolerance
+                );
+
+                errors = errors + 1;
+
+            end
+
+
+            if (busy) begin
+
+                $display(
+                    "FAIL RANDOM BUSY case=%0d",
+                    i
+                );
+
+                errors = errors + 1;
+
+            end
+
+
+            if (done) begin
+
+                $display(
+                    "FAIL RANDOM DONE case=%0d",
+                    i
+                );
+
+                errors = errors + 1;
+
+            end
+
+
+            if (random_excess >= 20) begin
+
+                if (!travel_overrun) begin
+
+                    $display(
+                        "FAIL RANDOM OVERRUN case=%0d excess=%0d",
+                        i,
+                        random_excess
+                    );
+
+                    errors = errors + 1;
+
+                end
+
+            end else begin
+
+                if (travel_overrun) begin
+
+                    $display(
+                        "FAIL RANDOM FALSE OVERRUN case=%0d excess=%0d",
+                        i,
+                        random_excess
+                    );
+
+                    errors = errors + 1;
+
+                end
+
+            end
+
+
+            if ((i % 50) == 0) begin
+
+                $display(
+                    "Random progress: %0d / 300",
+                    i
+                );
+
+            end
+
+        end
+
+        $display("PASS [H] 300 randomized target-crossing tests");
+
+
+        // ========================================================
+        // TEST I
+        //
+        // RESET clears all alarms.
+        // ========================================================
+
+        $display("");
+        $display("[I] Reset clears error state");
+
+        reset_n = 0;
+
+        #1;
+
+        expect_value(
+            busy == 0,
+            "reset clears busy"
+        );
+
+        expect_value(
+            done == 0,
+            "reset clears done"
+        );
+
+        expect_value(
+            overshoot == 0,
+            "reset clears overshoot"
+        );
+
+        expect_value(
+            travel_overrun == 0,
+            "reset clears travel_overrun"
+        );
+
+        expect_value(
+            limit_hit == 0,
+            "reset clears limit_hit"
+        );
+
+        reset_n = 1;
+
+        $display("PASS [I] Reset clears state");
+
+
+        // ========================================================
+        // FINAL RESULT
+        // ========================================================
+
+        $display("");
+        $display("============================================================");
+        $display("TOTAL CHECKS : %0d", checks);
+        $display("TOTAL ERRORS : %0d", errors);
+        $display("============================================================");
+
+
+        if (errors == 0) begin
+
+            $display("");
+            $display("ALL AXIS OVERSHOOT TESTS PASSED");
+
+            $display("");
+            $display("Verified:");
+            $display("  - positive overshoot");
+            $display("  - negative overshoot");
+            $display("  - positive travel overrun");
+            $display("  - negative travel overrun");
+            $display("  - post-completion drift");
+            $display("  - alarm clearing on new START");
+            $display("  - physical-limit priority");
+            $display("  - 300 randomized target crossings");
+            $display("  - reset alarm clearing");
+
+            $display("");
+            $display("============================================================");
+
+            $finish;
+
+        end else begin
+
+            $display("");
+            $display(
+                "AXIS OVERSHOOT TEST FAILED: %0d error(s)",
+                errors
+            );
+
+            $display("============================================================");
+
+            $fatal(1);
+
+        end
+
+    end
+
+endmodule