`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