Files
gps/hardware/external/gps/rtl/pps/tb/pps_adaptive_tb.sv
T

192 lines
7.4 KiB
Systemverilog

`timescale 1ns/1ps
module pps_adaptive_tb;
import pps_pkg::*;
localparam int unsigned CLK_FREQ_HZ = 1000;
localparam logic [63:0] TICK_Q32 = 64'h0000_0001_0000_0000;
localparam logic [63:0] SECOND_Q32 = CLK_FREQ_HZ * TICK_Q32;
localparam logic [31:0] ACQUIRE_CONFIG = 32'h0540_0800;
localparam logic [31:0] PRECISION_CONFIG = 32'h0340_0800;
localparam logic signed [47:0] ACQUIRE_KP_Q46 = 48'sh0800_0000_0000;
localparam logic signed [47:0] ACQUIRE_KI_Q46 = 48'sh0400_0000_0000;
localparam logic signed [47:0] PRECISION_KP_Q46 = 48'sh0200_0000_0000;
localparam logic signed [47:0] PRECISION_KI_Q46 = 48'sh0100_0000_0000;
logic clk = 1'b0;
logic resetn = 1'b0;
logic enable = 1'b1;
logic track_enable = 1'b1;
logic holdover = 1'b0;
logic clear_status_toggle = 1'b0;
logic commit_toggle = 1'b0;
logic commit_coarse = 1'b0;
logic [63:0] epoch_counter = '0;
logic [63:0] gps_seconds = '0;
logic [31:0] gps_fraction_q32 = '0;
logic [31:0] epoch_sequence = '0;
logic signed [PPS_COEFF_WIDTH-1:0] kp_q46 = ACQUIRE_KP_Q46;
logic signed [PPS_COEFF_WIDTH-1:0] ki_q46 = ACQUIRE_KI_Q46;
logic [31:0] max_slew_q32 = 32'h7fff_ffff;
logic [31:0] loop_config = ACQUIRE_CONFIG;
logic commit_ack_toggle;
logic [63:0] raw_counter;
logic pps_out, aligned, tracking, locked, pps_valid;
logic stale_sequence, stale_age, rate_saturated, phase_saturated;
logic [31:0] active_sequence, pulse_count;
logic [63:0] active_seconds, active_subsec_q32;
logic signed [63:0] phase_error_q32;
logic signed [63:0] integrator_q32, rate_word_q32;
int checks = 0;
always #4 clk = ~clk;
pps_core #(.CLK_FREQ_HZ(CLK_FREQ_HZ)) dut (
.clk(clk), .resetn(resetn), .enable(enable), .track_enable(track_enable),
.holdover(holdover), .clear_status_toggle(clear_status_toggle),
.commit_toggle(commit_toggle), .commit_coarse(commit_coarse),
.epoch_counter(epoch_counter), .gps_seconds(gps_seconds),
.gps_fraction_q32(gps_fraction_q32), .epoch_sequence(epoch_sequence),
.kp_q46(kp_q46), .ki_q46(ki_q46), .max_slew_q32(max_slew_q32),
.loop_config(loop_config),
.commit_ack_toggle(commit_ack_toggle), .raw_counter(raw_counter),
.pps_out(pps_out), .aligned(aligned), .tracking(tracking), .locked(locked),
.pps_valid(pps_valid), .stale_sequence(stale_sequence),
.stale_age(stale_age), .rate_saturated(rate_saturated),
.phase_saturated(phase_saturated), .active_sequence(active_sequence),
.active_seconds(active_seconds), .active_subsec_q32(active_subsec_q32),
.phase_error_q32(phase_error_q32), .integrator_q32(integrator_q32),
.rate_word_q32(rate_word_q32), .pulse_count(pulse_count)
);
task automatic check_adaptive(input logic condition, input string message);
begin
checks = checks + 1;
if (!condition)
$fatal(1, "ADAPTIVE CHECK %0d FAILED: %s", checks, message);
end
endtask
task automatic commit_anchor(
input logic [63:0] counter_value,
input logic [63:0] seconds_value,
input logic [31:0] fraction_value,
input logic [31:0] sequence_id,
input logic coarse);
logic expected_ack;
int timeout;
begin
@(negedge clk);
epoch_counter = counter_value;
gps_seconds = seconds_value;
gps_fraction_q32 = fraction_value;
epoch_sequence = sequence_id;
commit_coarse = coarse;
commit_toggle = ~commit_toggle;
expected_ack = commit_toggle;
timeout = 0;
while (commit_ack_toggle != expected_ack) begin
@(posedge clk);
timeout = timeout + 1;
if (timeout > 40) $fatal(1, "adaptive commit timeout");
end
@(negedge clk);
end
endtask
task automatic commit_current_offset(
input integer signed offset_ticks,
input logic [31:0] sequence_id);
logic [63:0] counter_value;
logic [63:0] seconds_value;
logic signed [64:0] subsec_value;
logic [31:0] fraction_value;
begin
@(negedge clk);
counter_value = raw_counter;
seconds_value = active_seconds;
subsec_value = $signed({1'b0, active_subsec_q32}) +
(offset_ticks * $signed({1'b0, TICK_Q32}));
if (subsec_value >= $signed({1'b0, SECOND_Q32})) begin
subsec_value = subsec_value - $signed({1'b0, SECOND_Q32});
seconds_value = seconds_value + 64'd1;
end else if (subsec_value < 0) begin
subsec_value = subsec_value + $signed({1'b0, SECOND_Q32});
seconds_value = seconds_value - 64'd1;
end
fraction_value = subsec_value / CLK_FREQ_HZ;
commit_anchor(
counter_value, seconds_value, fraction_value, sequence_id, 1'b0);
end
endtask
initial begin
logic [31:0] sequence_before_holdover;
logic signed [63:0] acquire_rate;
logic signed [63:0] acquire_integrator;
repeat (5) @(posedge clk);
resetn = 1'b1;
repeat (5) @(posedge clk);
commit_anchor(raw_counter, 64'd100, 32'd0, 32'd1, 1'b1);
check_adaptive(aligned && !locked && !pps_valid,
"coarse alignment starts invalid and unlocked");
// Lock detection uses the unfiltered PI phase residual. Five coherent
// updates inside the programmable eight-tick window are required.
for (int update = 0; update < 4; update = update + 1) begin
commit_current_offset(0, update + 2);
check_adaptive(!locked, "lock cannot assert before configured count");
end
commit_current_offset(0, 32'd6);
check_adaptive(locked && pps_valid,
"raw coherent residuals acquire lock");
// Exercise the acquisition gains, then replace Kp, Ki and lock policy
// while running. The core captures one coherent set per commit and keeps
// the integrator state across a profile transition.
commit_current_offset(1, 32'd7);
acquire_rate = rate_word_q32;
acquire_integrator = integrator_q32;
check_adaptive((acquire_rate > 0) && (acquire_integrator > 0),
"acquisition Kp and Ki both contribute");
kp_q46 = PRECISION_KP_Q46;
ki_q46 = PRECISION_KI_Q46;
loop_config = PRECISION_CONFIG;
commit_current_offset(1, 32'd8);
check_adaptive(locked && pps_valid,
"on-the-fly PI profile transition preserves lock");
check_adaptive(integrator_q32 > acquire_integrator,
"new Ki continues the preserved integrator");
check_adaptive(rate_word_q32 < acquire_rate,
"lower precision Kp/Ki reduce loop correction");
check_adaptive((dut.kp_q46_work == PRECISION_KP_Q46) &&
(dut.ki_q46_work == PRECISION_KI_Q46),
"commit atomically captures runtime Kp and Ki");
sequence_before_holdover = active_sequence;
holdover = 1'b1;
repeat (3) @(posedge clk);
check_adaptive(locked && !tracking && !pps_valid,
"holdover retains lock state but marks PPS invalid");
commit_current_offset(0, 32'd9);
check_adaptive(active_sequence == sequence_before_holdover,
"holdover rejects control updates");
holdover = 1'b0;
repeat (3) @(posedge clk);
check_adaptive(pps_valid && tracking,
"leaving holdover restores validity without a phase step");
commit_current_offset(200, 32'd10);
check_adaptive(!locked && !pps_valid,
"one raw residual beyond release threshold drops lock");
$display("PPS ADAPTIVE TB PASS: %0d checks phase=%0d rate=%0d",
checks, phase_error_q32, rate_word_q32);
$finish;
end
endmodule