`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////// // Testbench: plfm_chirp_controller_enhanced // Tests: A5 fix (multi-driven chirp_counter removed), FSM sequencing, // chirp waveform output, T/R switch timing, beam scanning counters // // NOTE: Uses shortened timing parameters for feasible simulation. // The real module uses T1_SAMPLES=3600, T1_RADAR_LISTENING=16440, etc. // We override to T1=8, LISTEN=4, T2=4, GUARD=4 for fast verification. ////////////////////////////////////////////////////////////////////////////// module tb_chirp_controller; // ========================================================================= // PARAMETERS — shortened for simulation // ========================================================================= parameter T1_SAMPLES = 8; // was 3600 parameter T1_RADAR_LISTENING = 4; // was 16440 parameter T2_SAMPLES = 4; // was 60 parameter T2_RADAR_LISTENING = 4; // was 20940 parameter GUARD_SAMPLES = 4; // was 21048 parameter CHIRP_MAX = 4; // was 32 (use 4: 2 long + 2 short) parameter ELEVATION_MAX = 2; // was 31 parameter AZIMUTH_MAX = 2; // was 50 // ========================================================================= // CLOCK GENERATION // ========================================================================= reg clk_120m, clk_100m; reg reset_n; // 120 MHz: period = 8.333 ns initial clk_120m = 0; always #4.166 clk_120m = ~clk_120m; // 100 MHz: period = 10 ns initial clk_100m = 0; always #5 clk_100m = ~clk_100m; // ========================================================================= // DUT SIGNALS // ========================================================================= reg new_chirp, new_elevation, new_azimuth; reg mixers_enable; wire [7:0] chirp_data; wire chirp_valid; wire new_chirp_frame; wire chirp_done; wire rf_switch_ctrl; wire rx_mixer_en, tx_mixer_en; wire adar_tx_load_1, adar_rx_load_1; wire adar_tx_load_2, adar_rx_load_2; wire adar_tx_load_3, adar_rx_load_3; wire adar_tx_load_4, adar_rx_load_4; wire adar_tr_1, adar_tr_2, adar_tr_3, adar_tr_4; wire [5:0] chirp_counter; wire [5:0] elevation_counter; wire [5:0] azimuth_counter; // ========================================================================= // DUT INSTANTIATION with overridden parameters // ========================================================================= plfm_chirp_controller_enhanced #( .T1_SAMPLES(T1_SAMPLES), .T1_RADAR_LISTENING(T1_RADAR_LISTENING), .T2_SAMPLES(T2_SAMPLES), .T2_RADAR_LISTENING(T2_RADAR_LISTENING), .GUARD_SAMPLES(GUARD_SAMPLES), .CHIRP_MAX(CHIRP_MAX), .ELEVATION_MAX(ELEVATION_MAX), .AZIMUTH_MAX(AZIMUTH_MAX) ) dut ( .clk_120m(clk_120m), .clk_100m(clk_100m), .reset_n(reset_n), .new_chirp(new_chirp), .new_elevation(new_elevation), .new_azimuth(new_azimuth), .mixers_enable(mixers_enable), .chirp_data(chirp_data), .chirp_valid(chirp_valid), .new_chirp_frame(new_chirp_frame), .chirp_done(chirp_done), .rf_switch_ctrl(rf_switch_ctrl), .rx_mixer_en(rx_mixer_en), .tx_mixer_en(tx_mixer_en), .adar_tx_load_1(adar_tx_load_1), .adar_rx_load_1(adar_rx_load_1), .adar_tx_load_2(adar_tx_load_2), .adar_rx_load_2(adar_rx_load_2), .adar_tx_load_3(adar_tx_load_3), .adar_rx_load_3(adar_rx_load_3), .adar_tx_load_4(adar_tx_load_4), .adar_rx_load_4(adar_rx_load_4), .adar_tr_1(adar_tr_1), .adar_tr_2(adar_tr_2), .adar_tr_3(adar_tr_3), .adar_tr_4(adar_tr_4), .chirp_counter(chirp_counter), .elevation_counter(elevation_counter), .azimuth_counter(azimuth_counter) ); // ========================================================================= // TEST INFRASTRUCTURE // ========================================================================= integer test_num; integer pass_count; integer fail_count; integer total_tests; // State name decoder for debug function [95:0] state_name; input [2:0] state; begin case (state) 3'b000: state_name = "IDLE "; 3'b001: state_name = "LONG_CHIRP "; 3'b010: state_name = "LONG_LISTEN "; 3'b011: state_name = "GUARD_TIME "; 3'b100: state_name = "SHORT_CHIRP "; 3'b101: state_name = "SHORT_LISTEN"; 3'b110: state_name = "DONE "; default: state_name = "UNKNOWN "; endcase end endfunction task check; input [255:0] test_name; input condition; begin test_num = test_num + 1; if (condition) begin $display(" [PASS] Test %0d: %0s", test_num, test_name); pass_count = pass_count + 1; end else begin $display(" [FAIL] Test %0d: %0s", test_num, test_name); fail_count = fail_count + 1; end end endtask // Wait for N cycles of clk_120m task wait_120m; input integer n; integer i; begin for (i = 0; i < n; i = i + 1) @(posedge clk_120m); end endtask // Wait until DUT enters a specific state (with timeout) task wait_for_state; input [2:0] target_state; input integer timeout_cycles; integer i; begin for (i = 0; i < timeout_cycles; i = i + 1) begin @(posedge clk_120m); if (dut.current_state == target_state) begin i = timeout_cycles; // exit end end end endtask // ========================================================================= // MAIN TEST SEQUENCE // ========================================================================= initial begin $dumpfile("tb_chirp_controller.vcd"); $dumpvars(0, tb_chirp_controller); test_num = 0; pass_count = 0; fail_count = 0; // Initialize reset_n = 0; new_chirp = 0; new_elevation = 0; new_azimuth = 0; mixers_enable = 0; $display(""); $display("============================================================"); $display(" CHIRP CONTROLLER TESTBENCH"); $display(" Testing A5 fix: single-driver chirp_counter on clk_120m"); $display(" Parameters: CHIRP_MAX=%0d, T1=%0d, T2=%0d", CHIRP_MAX, T1_SAMPLES, T2_SAMPLES); $display("============================================================"); $display(""); // ===================================================================== // TEST GROUP 1: RESET BEHAVIOR // ===================================================================== $display("--- Group 1: Reset Behavior ---"); #100; // T1.1: After reset, should be in IDLE check("Reset: state is IDLE", dut.current_state == 3'b000); // T1.2: chirp_counter should be 0 after reset (was the A5 bug: Driver1 reset to 1, Driver2 to 0) check("Reset: chirp_counter is 0", chirp_counter == 6'd0); // T1.3: chirp_data should be 128 (midpoint) in IDLE check("Reset: chirp_data is 128 (midpoint)", chirp_data == 8'd128); // T1.4: rf_switch should be off check("Reset: rf_switch_ctrl is 0", rf_switch_ctrl == 1'b0); // T1.5: chirp_valid should be 0 check("Reset: chirp_valid is 0", chirp_valid == 1'b0); // T1.6: chirp_done should be 0 check("Reset: chirp_done is 0", chirp_done == 1'b0); // Release reset @(posedge clk_120m); reset_n = 1; @(posedge clk_120m); // ===================================================================== // TEST GROUP 2: IDLE STATE — no transition without mixers_enable // ===================================================================== $display("--- Group 2: IDLE Hold ---"); // T2.1: With new_chirp but no mixers_enable, stay in IDLE new_chirp = 1; wait_120m(5); check("IDLE hold: no transition without mixers_enable", dut.current_state == 3'b000); new_chirp = 0; // ===================================================================== // TEST GROUP 3: FULL FSM SEQUENCE // ===================================================================== $display("--- Group 3: Full FSM Sequence ---"); // Enable mixers and trigger chirp mixers_enable = 1; @(posedge clk_120m); new_chirp = 1; // chirp__toggling is just new_chirp pass-through @(posedge clk_120m); // T3.1: Should transition to LONG_CHIRP wait_for_state(3'b001, 5); // LONG_CHIRP check("FSM: enters LONG_CHIRP", dut.current_state == 3'b001); // T3.2: RF switch should be ON during LONG_CHIRP @(posedge clk_120m); // one cycle for output to settle check("LONG_CHIRP: rf_switch_ctrl is 1", rf_switch_ctrl == 1'b1); // T3.3: ADAR T/R switches should be 1 (transmit mode) check("LONG_CHIRP: adar_tr_1 is 1", adar_tr_1 == 1'b1); // T3.4: chirp_valid should be 1 check("LONG_CHIRP: chirp_valid is 1", chirp_valid == 1'b1); // T3.5: chirp_data should NOT be 128 (should be reading from LUT) // Note: with shortened params, LUT index wraps, but data shouldn't be stuck at 128 // Actually, with T1_SAMPLES=8, it reads long_chirp_lut[0..7] which has real data check("LONG_CHIRP: chirp_data comes from LUT (not midpoint)", chirp_data != 8'd128); // Wait for LONG_CHIRP to finish (T1_SAMPLES = 8 cycles) wait_for_state(3'b010, T1_SAMPLES + 5); // LONG_LISTEN // T3.6: Should reach LONG_LISTEN check("FSM: enters LONG_LISTEN", dut.current_state == 3'b010); // T3.7: RF switch OFF during listen @(posedge clk_120m); check("LONG_LISTEN: rf_switch_ctrl is 0", rf_switch_ctrl == 1'b0); // T3.8: chirp_data should be 128 during listen check("LONG_LISTEN: chirp_data is 128", chirp_data == 8'd128); // T3.9: chirp_counter should have incremented to 1 after first LONG_LISTEN // Wait for listen to finish wait_for_state(3'b001, T1_RADAR_LISTENING + 5); // back to LONG_CHIRP check("chirp_counter: incremented to 1 after first listen", chirp_counter == 6'd1); // Now wait through second LONG_CHIRP + LONG_LISTEN cycle // After CHIRP_MAX/2 = 2 long chirps, should go to GUARD_TIME wait_for_state(3'b010, T1_SAMPLES + 5); // LONG_LISTEN again wait_for_state(3'b011, T1_RADAR_LISTENING + 5); // GUARD_TIME // T3.10: After CHIRP_MAX/2 long chirps, enters GUARD_TIME check("FSM: enters GUARD_TIME after CHIRP_MAX/2 long chirps", dut.current_state == 3'b011); // Wait through guard time wait_for_state(3'b100, GUARD_SAMPLES + 5); // SHORT_CHIRP // T3.11: Enters SHORT_CHIRP check("FSM: enters SHORT_CHIRP", dut.current_state == 3'b100); // T3.12: RF switch ON during SHORT_CHIRP @(posedge clk_120m); check("SHORT_CHIRP: rf_switch_ctrl is 1", rf_switch_ctrl == 1'b1); // Wait through SHORT_CHIRP -> SHORT_LISTEN -> SHORT_CHIRP -> SHORT_LISTEN -> DONE // That's 2 more chirps (chirp_counter goes from 2 to 3, then 3 to CHIRP_MAX-1=3) wait_for_state(3'b101, T2_SAMPLES + 5); // SHORT_LISTEN wait_for_state(3'b100, T2_RADAR_LISTENING + 5); // SHORT_CHIRP again wait_for_state(3'b101, T2_SAMPLES + 5); // SHORT_LISTEN again wait_for_state(3'b110, T2_RADAR_LISTENING + 5); // DONE // T3.13: FSM reaches DONE state check("FSM: reaches DONE state", dut.current_state == 3'b110); // T3.14: chirp_done asserted — check on next clock edge // Also deassert new_chirp NOW (during DONE state) so FSM stays in IDLE // after DONE transitions. If we wait, FSM goes DONE→IDLE→LONG_CHIRP instantly. new_chirp = 0; @(posedge clk_120m); check("DONE: chirp_done is 1", chirp_done == 1'b1); // T3.15: Returns to IDLE // Note: chirp_done check consumed one edge (DONE→IDLE already happened) // With new_chirp=0, FSM should stay in IDLE @(posedge clk_120m); check("FSM: returns to IDLE after DONE", dut.current_state == 3'b000); // ===================================================================== // TEST GROUP 4: SINGLE-DRIVER VERIFICATION (A5 FIX CORE TEST) // ===================================================================== $display("--- Group 4: A5 Fix - Single Driver Verification ---"); // Reset and re-run with both clocks to verify no race condition reset_n = 0; mixers_enable = 0; new_chirp = 0; #100; reset_n = 1; @(posedge clk_120m); // T4.1: After re-reset, chirp_counter is 0 check("Re-reset: chirp_counter is 0", chirp_counter == 6'd0); // T4.2: Toggling new_chirp on clk_100m should NOT change chirp_counter // (The old bug: clk_100m driver would increment it) @(posedge clk_100m); new_chirp = 1; @(posedge clk_100m); @(posedge clk_100m); @(posedge clk_100m); @(posedge clk_100m); check("A5 fix: new_chirp pulses alone don't change chirp_counter", chirp_counter == 6'd0); new_chirp = 0; // T4.3: Only the FSM (clk_120m) should drive chirp_counter // Start a chirp sequence and verify counter increments only at listen end mixers_enable = 1; @(posedge clk_120m); new_chirp = 1; @(posedge clk_120m); // Wait for first LONG_CHIRP wait_for_state(3'b001, 5); check("A5 fix: chirp_counter still 0 at start of LONG_CHIRP", chirp_counter == 6'd0); // Wait for first LONG_LISTEN completion wait_for_state(3'b010, T1_SAMPLES + 5); // During listen, counter hasn't incremented yet check("A5 fix: chirp_counter still 0 during LONG_LISTEN", chirp_counter == 6'd0); // Wait for listen to end and counter to increment wait_for_state(3'b001, T1_RADAR_LISTENING + 5); // back to LONG_CHIRP check("A5 fix: chirp_counter is 1 after first listen completes", chirp_counter == 6'd1); // ===================================================================== // TEST GROUP 5: MIXER DISABLE // ===================================================================== $display("--- Group 5: Mixer Disable ---"); // T5.1: Disabling mixers should reset outputs mixers_enable = 0; wait_120m(3); check("Mixer disable: chirp_data returns to 128", chirp_data == 8'd128); check("Mixer disable: chirp_valid is 0", chirp_valid == 1'b0); check("Mixer disable: rf_switch_ctrl is 0", rf_switch_ctrl == 1'b0); // ===================================================================== // TEST GROUP 6: ELEVATION/AZIMUTH COUNTERS (clk_100m domain, separate) // ===================================================================== $display("--- Group 6: Beam Steering Counters ---"); // Reset reset_n = 0; mixers_enable = 0; new_chirp = 0; new_elevation = 0; new_azimuth = 0; #100; reset_n = 1; @(posedge clk_100m); // T6.1: Elevation counter resets to 1 check("Reset: elevation_counter is 1", elevation_counter == 6'd1); // T6.2: Azimuth counter resets to 1 check("Reset: azimuth_counter is 1", azimuth_counter == 6'd1); // T6.3: Elevation counter increments on new_elevation // Note: elevation__toggling = new_elevation (level-sensitive pass-through) // With ELEVATION_MAX=2, holding high oscillates 1->2->1->... new_elevation = 1; @(posedge clk_100m); @(posedge clk_100m); check("Elevation: increments on new_elevation", elevation_counter == 6'd2 || elevation_counter == 6'd1); // T6.4: Elevation counter wraps at ELEVATION_MAX // Counter toggles between 1 and 2 each cycle when held high @(posedge clk_100m); check("Elevation: wraps at ELEVATION_MAX", (elevation_counter == 6'd1) || (elevation_counter == 6'd2)); new_elevation = 0; @(posedge clk_100m); // T6.5: Azimuth counter increments on new_azimuth new_azimuth = 1; @(posedge clk_100m); @(posedge clk_100m); check("Azimuth: increments on new_azimuth", azimuth_counter == 6'd2 || azimuth_counter == 6'd1); new_azimuth = 0; // ===================================================================== // TEST GROUP 7: MIXER ENABLE SIGNALS // ===================================================================== $display("--- Group 7: Mixer Control Outputs ---"); // T7.1: rx_mixer_en follows mixers_enable mixers_enable = 1; #1; check("rx_mixer_en follows mixers_enable", rx_mixer_en == 1'b1); // T7.2: tx_mixer_en follows mixers_enable check("tx_mixer_en follows mixers_enable", tx_mixer_en == 1'b1); // T7.3: ADAR load pins tied low check("ADAR load pins: adar_tx_load_1 is 0", adar_tx_load_1 == 1'b0); check("ADAR load pins: adar_rx_load_1 is 0", adar_rx_load_1 == 1'b0); // ===================================================================== // SUMMARY // ===================================================================== $display(""); $display("============================================================"); total_tests = pass_count + fail_count; $display(" RESULTS: %0d/%0d tests passed", pass_count, total_tests); if (fail_count == 0) $display(" STATUS: ALL TESTS PASSED"); else $display(" STATUS: %0d TESTS FAILED", fail_count); $display("============================================================"); $display(""); #100; $finish; end // Timeout watchdog initial begin #500000; // 500 us max $display("TIMEOUT: Simulation took too long!"); $finish; end endmodule