287 lines
12 KiB
Systemverilog
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
|