feat: add live statistics with per-trial snapshots and von Neumann option (FPGA v5)

This commit is contained in:
randogoth 2026-10-25 14:27:45 +02:00
parent 67bd7eec58
commit 5305d66102
8 changed files with 427 additions and 17 deletions

View file

@ -1,6 +1,7 @@
// Testbench for strix_core: register access, generator XOR logic checked against the sampled
// ring outputs, captures read back across transactions, generator enable, the frequency
// counter, and the health monitors with injected faults. Rings are replaced by the SIM model in ring_osc.v.
// counter, the health monitors with injected faults, and the statistics snapshots against a
// reference model. Rings are replaced by the SIM model in ring_osc.v.
`timescale 1ns / 1ps
module tb_strix_core;
@ -15,7 +16,11 @@ module tb_strix_core;
REG_DECIM_LOW = 7'h0A, REG_CONTROL = 7'h0B, REG_STATUS = 7'h0C,
REG_DATA = 7'h10, REG_RING_SELECT = 7'h11, REG_FREQ_COUNT0 = 7'h15,
REG_GEN_ENABLE = 7'h16, REG_TEMPORAL = 7'h17, REG_FREQ_GEN = 7'h18,
REG_HEALTHY = 7'h20, REG_HEALTH_CTRL = 7'h21;
REG_HEALTHY = 7'h20, REG_HEALTH_CTRL = 7'h21, REG_TRIAL_LOW = 7'h27,
REG_STATS_CTRL = 7'h28, REG_STATS = 7'h40;
localparam SNAPSHOT_BYTES = 72;
localparam TICK_CYCLES = 1500; // 1 "ms" = 12.5 us: a 10 "ms" trial outlasts a 72-byte read
localparam TRIAL_BITS = 10 * TICK_CYCLES / 5; // 10 "ms" trials at temporal 5
// Per-generator health registers
function [6:0] gen_reg(input integer generator, input integer index);
@ -31,7 +36,7 @@ module tb_strix_core;
wire miso, done;
strix_core #(.CAPTURE_ADDR_WIDTH(CAPTURE_ADDR_WIDTH), .FREQ_GATE_BITS(14), .RINGS_PER_GEN(RINGS),
.DEAD_WINDOW_BITS(6)) dut (
.DEAD_WINDOW_BITS(6), .TICK_CYCLES(TICK_CYCLES)) dut (
.clk(clk), .reset(reset),
.spi_sck(sck), .spi_mosi(mosi), .spi_ss_n(ss_n), .spi_miso(miso),
.capture_done(done)
@ -80,6 +85,40 @@ module tb_strix_core;
end
end
// Reference model of statistics channel 0, mirroring stats_block without von Neumann:
// counts restart at each trial end, and the bit arriving in that cycle opens the new trial.
reg [31:0] model_bits = 0, model_ones = 0, model_agree = 0;
reg [31:0] model_latched_bits, model_latched_ones, model_latched_agree;
reg model_previous, model_has_previous = 1'b0;
always @(posedge clk) begin
if (dut.stats_restart) begin
model_bits <= 0; model_ones <= 0; model_agree <= 0; model_has_previous <= 1'b0;
end else begin
if (dut.trial_end) begin
model_latched_bits <= model_bits;
model_latched_ones <= model_ones;
model_latched_agree <= model_agree;
end
if (dut.stats.valid) begin
model_bits <= (dut.trial_end ? 0 : model_bits) + 1;
model_ones <= (dut.trial_end ? 0 : model_ones) + dut.stats.bits[0];
model_agree <= (dut.trial_end ? 0 : model_agree)
+ (model_has_previous && dut.stats.bits[0] == model_previous);
model_previous <= dut.stats.bits[0];
model_has_previous <= 1'b1;
end else if (dut.trial_end) begin
model_bits <= 0; model_ones <= 0; model_agree <= 0;
end
end
end
reg [7:0] snapshot [0:SNAPSHOT_BYTES-1];
function [31:0] snapshot_word(input integer index);
snapshot_word = {snapshot[index], snapshot[index+1], snapshot[index+2], snapshot[index+3]};
endfunction
task spi_byte(input [7:0] out, output [7:0] in);
integer i;
begin
@ -169,8 +208,27 @@ module tb_strix_core;
end
endtask
reg [7:0] v;
integer changes, polls;
task read_snapshot;
reg [7:0] dummy;
integer i;
begin
ss_n = 1'b0; #SCK_HALF;
spi_byte({1'b1, REG_STATS}, dummy);
for (i = 0; i < SNAPSHOT_BYTES; i = i + 1) spi_byte(8'h00, snapshot[i]);
#SCK_HALF ss_n = 1'b1; #(4 * SCK_HALF);
end
endtask
task wait_trial_end;
begin
@(posedge dut.trial_end);
#200;
end
endtask
reg [7:0] v;
integer changes, polls;
reg [31:0] sequence_seen;
initial begin
#100 reset = 1'b0;
@ -179,7 +237,7 @@ module tb_strix_core;
// Identification
read_reg(REG_ID0, v); expect(v, "S", "ID0");
read_reg(REG_ID3, v); expect(v, "X", "ID3");
read_reg(REG_VERSION, v); expect(v, 8'h04, "VERSION");
read_reg(REG_VERSION, v); expect(v, 8'h05, "VERSION");
read_reg(REG_GENERATORS, v); expect(v, 8'd4, "GENERATORS");
read_reg(REG_RINGS, v); expect(v, RINGS, "RINGS");
read_reg(REG_TEMPORAL, v); expect(v, 8'd5, "TEMPORAL");
@ -259,6 +317,47 @@ module tb_strix_core;
read_reg(gen_reg(3, FLAGS), v); expect(v, 8'h00, "FLAGS g3 disabled");
write_reg(REG_GEN_ENABLE, 8'h0F);
// Statistics: 10 "ms" trials, temporal 5 -> TRIAL_BITS bits per channel per trial
write_reg(REG_TEMPORAL, 8'd5);
write_reg(REG_TRIAL_LOW, 8'd10);
write_reg(REG_STATS_CTRL, 8'h02); // restart
wait_trial_end;
wait_trial_end;
read_snapshot;
if (snapshot_word(0) !== snapshot_word(SNAPSHOT_BYTES - 4)) begin
$display("FAIL torn snapshot"); errors = errors + 1;
end
expect(snapshot_word(0), 8'd1, "trial sequence");
expect(snapshot[4], 8'h0F, "trial healthy");
expect(snapshot[7], 8'd10, "trial ms");
if (snapshot_word(8) !== model_latched_bits || snapshot_word(12) !== model_latched_ones
|| snapshot_word(16) !== model_latched_agree) begin
$display("FAIL channel 0: %0d/%0d/%0d, model %0d/%0d/%0d", snapshot_word(8), snapshot_word(12),
snapshot_word(16), model_latched_bits, model_latched_ones, model_latched_agree);
errors = errors + 1;
end
if (snapshot_word(8) < TRIAL_BITS - 1 || snapshot_word(8) > TRIAL_BITS + 1) begin
$display("FAIL channel 0 bit count %0d, expected about %0d", snapshot_word(8), TRIAL_BITS);
errors = errors + 1;
end
if (snapshot_word(8 + 48) !== snapshot_word(8)) begin
$display("FAIL combined channel bit count differs"); errors = errors + 1;
end
if (snapshot_word(12) == 0 || snapshot_word(12) == snapshot_word(8)) begin
$display("FAIL channel 0 ones count %0d is degenerate", snapshot_word(12)); errors = errors + 1;
end
// Von Neumann keeps about a quarter of the bits
write_reg(REG_STATS_CTRL, 8'h03);
wait_trial_end;
wait_trial_end;
read_snapshot;
expect(snapshot[5], 8'h01, "trial von Neumann");
if (snapshot_word(8) < TRIAL_BITS / 10 || snapshot_word(8) > TRIAL_BITS * 45 / 100) begin
$display("FAIL von Neumann bit count %0d", snapshot_word(8)); errors = errors + 1;
end
write_reg(REG_STATS_CTRL, 8'h00);
if (xor_checks < 100) begin
$display("FAIL only %0d temporal XOR checks ran", xor_checks);
errors = errors + 1;