Files
templates/hdl/scope-acquisition/tests/tb_trigger_engine.sv
T

287 lines
12 KiB
Systemverilog

`timescale 1ns/1ps
module tb_trigger_engine;
reg clk = 0, rst = 1, arm = 0, stop = 0, session_fault = 0;
reg [2:0] cfg_source = 0;
reg [1:0] cfg_mode = 1;
reg cfg_falling = 0;
reg [9:0] cfg_level = 512, cfg_hysteresis = 0;
reg [31:0] cfg_auto_timeout = 0;
reg [63:0] sample_data = 0, sample_index = 0;
reg sample_valid = 0, ext_level = 0, eligible = 1;
wire trigger_hit, auto_hit, armed, config_error;
wire [63:0] trigger_index;
reg [63:0] next_sample_index = 64'h0000000100000000;
integer checks = 0, hits = 0, fallback_hits = 0;
integer source;
always #5 clk = ~clk;
trigger_engine dut(.*);
function automatic [63:0] channels(input [9:0] a, b, c, d);
channels = {6'b0, d, 6'b0, c, 6'b0, b, 6'b0, a};
endfunction
function automatic [63:0] selected(input integer channel, input [9:0] value);
begin
case (channel)
0: selected = channels(value, 900, 900, 900);
1: selected = channels(900, value, 900, 900);
2: selected = channels(900, 900, value, 900);
3: selected = channels(900, 900, 900, value);
default: selected = channels(900, 900, 900, 900);
endcase
end
endfunction
task automatic arm_config(input integer source_value, mode_value,
input bit falling_value, input [9:0] level_value, hysteresis_value,
input [31:0] timeout_value);
begin
@(negedge clk);
cfg_source = source_value;
cfg_mode = mode_value;
cfg_falling = falling_value;
cfg_level = level_value;
cfg_hysteresis = hysteresis_value;
cfg_auto_timeout = timeout_value;
arm = 1;
sample_valid = 1; // ARM itself must never consume or trigger.
eligible = 1;
#0.3;
if (trigger_hit !== 1'b0 || auto_hit !== 1'b0)
$fatal(1, "trigger asserted on ARM");
@(posedge clk);
#0.3;
if (armed !== ((source_value <= 4) && (mode_value <= 2)))
$fatal(1, "ARM/config validity mismatch for source=%0d mode=%0d", source_value, mode_value);
if (trigger_index !== 64'd0)
$fatal(1, "ARM did not reset trigger index");
if ((source_value > 4 || mode_value > 2) && config_error !== 1'b1)
$fatal(1, "invalid configuration did not set sticky error");
@(negedge clk);
arm = 0;
sample_valid = 0;
end
endtask
task automatic step(input [63:0] word_value,
input bit valid_value, ext_value, eligible_value, expected_hit, expected_auto);
reg [63:0] presented_index;
begin
@(negedge clk);
arm = 0; stop = 0; session_fault = 0;
sample_data = word_value;
sample_valid = valid_value;
ext_level = ext_value;
eligible = eligible_value;
sample_index = next_sample_index;
presented_index = sample_index;
if (valid_value) next_sample_index = next_sample_index + 1'b1;
#0.3;
if (trigger_hit !== expected_hit || auto_hit !== expected_auto)
$fatal(1, "trigger decision check%0d index=%h data=%h ext=%b eligible=%b valid=%b: got hit/auto=%b/%b expected=%b/%b",
checks, sample_index, sample_data, ext_level, eligible, sample_valid,
trigger_hit, auto_hit, expected_hit, expected_auto);
// Decisions must still describe the sample presented at this edge.
@(posedge clk);
if (trigger_hit !== expected_hit || auto_hit !== expected_auto)
$fatal(1, "same-edge trigger contract failed at check%0d", checks);
#0.3;
if (expected_hit) begin
if (armed !== 1'b0 || trigger_index !== presented_index)
$fatal(1, "hit did not latch exact 64-bit sample index/disarm");
hits = hits + 1;
if (expected_auto) fallback_hits = fallback_hits + 1;
end
checks = checks + 1;
end
endtask
task automatic cancel(input bit fault_value, simultaneous_arm);
begin
@(negedge clk);
stop = !fault_value;
session_fault = fault_value;
arm = simultaneous_arm;
sample_valid = 1;
eligible = 1;
sample_data = channels(1023, 1023, 1023, 1023);
#0.3;
if (trigger_hit !== 1'b0 || auto_hit !== 1'b0)
$fatal(1, "STOP/fault allowed trigger");
@(posedge clk);
#0.3;
if (armed !== 1'b0)
$fatal(1, "STOP/fault did not outrank ARM/disarm");
@(negedge clk);
stop = 0; session_fault = 0; arm = 0; sample_valid = 0;
end
endtask
initial begin
#21 rst = 0;
// Every analog source, both polarities, equality and invalid gaps.
for (source = 0; source < 4; source = source + 1) begin
arm_config(source, 1, 0, 512, 0, 1);
step(selected(source, 512), 1, 0, 1, 0, 0);
step(selected(source, 511), 1, 0, 1, 0, 0);
step(selected(source, 1023), 0, 0, 1, 0, 0);
step(selected(source, 512), 1, 0, 1, 1, 0);
step(selected(source, 0), 1, 0, 1, 0, 0);
step(selected(source, 1023), 1, 0, 1, 0, 0);
arm_config(source, 2, 1, 512, 0, 1);
step(selected(source, 512), 1, 0, 1, 0, 0);
step(selected(source, 513), 1, 0, 1, 0, 0);
step(selected(source, 0), 0, 0, 1, 0, 0);
step(selected(source, 512), 1, 0, 1, 1, 0);
end
// Nonzero hysteresis is inclusive at both arm/fire thresholds.
arm_config(0, 1, 0, 500, 20, 0);
step(selected(0, 500), 1, 0, 1, 0, 0);
step(selected(0, 480), 1, 0, 1, 0, 0);
step(selected(0, 510), 1, 0, 1, 0, 0);
step(selected(0, 519), 1, 0, 1, 0, 0);
step(selected(0, 520), 1, 0, 1, 1, 0);
arm_config(1, 1, 1, 500, 20, 0);
step(selected(1, 519), 1, 0, 1, 0, 0);
step(selected(1, 520), 1, 0, 1, 0, 0);
step(selected(1, 500), 1, 0, 1, 0, 0);
step(selected(1, 481), 1, 0, 1, 0, 0);
step(selected(1, 480), 1, 0, 1, 1, 0);
// Threshold arithmetic must clamp rather than wrap at the ADC limits.
arm_config(2, 1, 0, 5, 10, 0);
step(selected(2, 1), 1, 0, 1, 0, 0);
step(selected(2, 0), 1, 0, 1, 0, 0);
step(selected(2, 14), 1, 0, 1, 0, 0);
step(selected(2, 15), 1, 0, 1, 1, 0);
arm_config(3, 1, 1, 1018, 10, 0);
step(selected(3, 1022), 1, 0, 1, 0, 0);
step(selected(3, 1023), 1, 0, 1, 0, 0);
step(selected(3, 1009), 1, 0, 1, 0, 0);
step(selected(3, 1008), 1, 0, 1, 1, 0);
arm_config(0, 1, 0, 512, 1023, 0);
step(selected(0, 0), 1, 0, 1, 0, 0);
step(selected(0, 512), 1, 0, 1, 0, 0);
step(selected(0, 1023), 1, 0, 1, 1, 0);
arm_config(0, 1, 1, 512, 1023, 0);
step(selected(0, 1023), 1, 0, 1, 0, 0);
step(selected(0, 512), 1, 0, 1, 0, 0);
step(selected(0, 0), 1, 0, 1, 1, 0);
arm_config(0, 1, 0, 0, 0, 0);
step(selected(0, 0), 1, 0, 1, 0, 0);
step(selected(0, 1023), 1, 0, 1, 0, 0);
cancel(0, 0);
arm_config(0, 1, 1, 1023, 0, 0);
step(selected(0, 1023), 1, 0, 1, 0, 0);
step(selected(0, 0), 1, 0, 1, 0, 0);
cancel(0, 0);
arm_config(0, 1, 0, 1023, 0, 0);
step(selected(0, 1022), 1, 0, 1, 0, 0);
step(selected(0, 1023), 1, 0, 1, 1, 0);
arm_config(0, 1, 1, 0, 0, 0);
step(selected(0, 1), 1, 0, 1, 0, 0);
step(selected(0, 0), 1, 0, 1, 1, 0);
// A prehistory crossing is consumed, not replayed when eligible rises.
arm_config(0, 1, 0, 500, 0, 0);
step(selected(0, 400), 1, 0, 0, 0, 0);
step(selected(0, 600), 1, 0, 0, 0, 0);
step(selected(0, 600), 1, 0, 1, 0, 0);
step(selected(0, 400), 1, 0, 1, 0, 0);
step(selected(0, 500), 1, 0, 1, 1, 0);
// External level history starts with an observation, not a fake edge.
arm_config(4, 1, 0, 512, 0, 0);
step(0, 1, 1, 1, 0, 0);
step(0, 1, 0, 1, 0, 0);
step(0, 0, 1, 1, 0, 0);
step(0, 1, 1, 1, 1, 0);
arm_config(4, 2, 1, 512, 0, 0);
step(0, 1, 0, 1, 0, 0);
step(0, 1, 1, 1, 0, 0);
step(0, 1, 0, 1, 1, 0);
arm_config(4, 1, 0, 512, 0, 0);
step(0, 1, 0, 0, 0, 0);
step(0, 1, 1, 0, 0, 0);
step(0, 1, 1, 1, 0, 0);
step(0, 1, 0, 1, 0, 0);
step(0, 1, 1, 1, 1, 0);
// Live config edits must not alter the armed session.
arm_config(0, 1, 0, 400, 0, 99);
cfg_source = 3; cfg_mode = 0; cfg_falling = 1;
cfg_level = 1; cfg_hysteresis = 1023; cfg_auto_timeout = 0;
step(channels(399, 0, 0, 0), 1, 0, 1, 0, 0);
step(channels(399, 0, 0, 1023), 1, 0, 1, 0, 0);
step(channels(400, 0, 0, 0), 1, 0, 1, 1, 0);
// AUTO counts accepted samples, never idle clock/valid gaps.
arm_config(0, 0, 0, 500, 0, 3);
step(selected(0, 600), 1, 0, 1, 0, 0);
step(selected(0, 400), 0, 0, 1, 0, 0);
step(selected(0, 600), 1, 0, 1, 0, 0);
step(selected(0, 600), 0, 0, 1, 0, 0);
step(selected(0, 600), 1, 0, 1, 1, 1);
arm_config(0, 0, 0, 500, 0, 2);
step(selected(0, 600), 1, 0, 0, 0, 0);
step(selected(0, 600), 1, 0, 0, 0, 0);
step(selected(0, 600), 1, 0, 0, 0, 0);
step(selected(0, 600), 1, 0, 1, 1, 1);
arm_config(4, 0, 0, 500, 0, 0);
step(0, 1, 1, 0, 0, 0);
step(0, 0, 0, 1, 0, 0);
step(0, 1, 1, 1, 1, 1);
arm_config(0, 0, 0, 500, 0, 2);
step(selected(0, 400), 1, 0, 1, 0, 0);
step(selected(0, 500), 1, 0, 1, 1, 0); // real edge outranks timeout
// NORMAL/SINGLE never time out; both fire once per explicit ARM.
arm_config(0, 1, 0, 500, 0, 1);
step(selected(0, 600), 1, 0, 1, 0, 0);
step(selected(0, 600), 1, 0, 1, 0, 0);
cancel(0, 0);
arm_config(0, 2, 0, 500, 0, 1);
step(selected(0, 600), 1, 0, 1, 0, 0);
step(selected(0, 400), 1, 0, 1, 0, 0);
step(selected(0, 500), 1, 0, 1, 1, 0);
step(selected(0, 400), 1, 0, 1, 0, 0);
step(selected(0, 500), 1, 0, 1, 0, 0);
arm_config(0, 1, 0, 500, 0, 0);
step(selected(0, 400), 1, 0, 1, 0, 0);
cancel(0, 0); // STOP on prospective crossing
arm_config(0, 0, 0, 500, 0, 0);
cancel(1, 0); // fault also suppresses immediate AUTO fallback
arm_config(0, 1, 0, 500, 0, 0);
cancel(0, 1); // STOP wins over simultaneous ARM
arm_config(0, 1, 0, 500, 0, 0);
cancel(1, 1); // fault wins over simultaneous ARM
for (source = 5; source < 8; source = source + 1) begin
arm_config(source, 1, 0, 500, 0, 0);
step(selected(0, 0), 1, 0, 1, 0, 0);
step(selected(0, 1023), 1, 1, 1, 0, 0);
end
arm_config(0, 3, 0, 500, 0, 0);
step(selected(0, 0), 1, 0, 1, 0, 0);
arm_config(0, 1, 0, 500, 0, 0);
if (config_error !== 1'b1) $fatal(1, "valid ARM cleared sticky config error");
step(selected(0, 400), 1, 0, 1, 0, 0);
step(selected(0, 500), 1, 0, 1, 1, 0);
#1.7 rst = 1;
#25;
if (armed !== 1'b0 || config_error !== 1'b0 || trigger_index !== 64'd0 ||
trigger_hit !== 1'b0 || auto_hit !== 1'b0)
$fatal(1, "reset did not clear trigger/config state");
rst = 0;
if (checks < 100 || hits < 20 || fallback_hits != 3)
$fatal(1, "insufficient trigger coverage checks=%0d hits=%0d AUTO=%0d", checks, hits, fallback_hits);
$display("PASS tb_trigger_engine checks=%0d hits=%0d auto=%0d", checks, hits, fallback_hits);
$finish;
end
initial begin
#30000;
$fatal(1, "tb_trigger_engine timeout");
end
endmodule