feat: add per-second GCP-format trials with history, watch and HTTP access (FPGA v10)

This commit is contained in:
randogoth 2026-10-25 18:23:18 +02:00
parent d821b77d3b
commit 6bee626b7f
12 changed files with 475 additions and 29 deletions

View file

@ -1,8 +1,8 @@
// 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, the health monitors with injected faults, the statistics snapshots against a
// reference model, the bias amplifier's bit counts in several configurations, and the entropy
// byte stream against the FIFO contents. Rings are replaced by the SIM model in ring_osc.v.
// reference model, the bias amplifier's bit counts in several configurations, the entropy
// byte stream against the FIFO contents, and the per-second GCP trials against a reference model. Rings are replaced by the SIM model in ring_osc.v.
`timescale 1ns / 1ps
module tb_strix_core;
@ -21,14 +21,18 @@ module tb_strix_core;
REG_STATS_CTRL = 7'h28, REG_STATS = 7'h40, REG_AMP = 7'h42,
REG_AMP_CTRL = 7'h50, REG_AMP_SOURCES = 7'h51, REG_AMP_SUB_MS = 7'h52,
REG_AMP_SUBS = 7'h53, REG_AMP_STAGE = 7'h54, REG_STREAM_CTRL = 7'h29,
REG_LEVEL_HIGH = 7'h2A, REG_LEVEL_LOW = 7'h2B, REG_STREAM = 7'h44;
REG_LEVEL_HIGH = 7'h2A, REG_LEVEL_LOW = 7'h2B, REG_STREAM = 7'h44,
REG_GCP = 7'h46;
localparam AMP_SNAPSHOT_BYTES = 44;
localparam GCP_SNAPSHOT_BYTES = 36;
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
// Amplifier trials in the tests: 3 sub-trials of 2 "ms" -> 9000 clk cycles -> 1800 output
// bits per generator at temporal 5
localparam AMP_GEN_BITS = 3 * 2 * TICK_CYCLES / 5;
localparam GCP_TRIAL_MS = 10; // shortened "second": GCP_TRIAL_BITS bits per generator
localparam GCP_TRIAL_BITS = GCP_TRIAL_MS * TICK_CYCLES / 5;
// Per-generator health registers
function [6:0] gen_reg(input integer generator, input integer index);
@ -44,7 +48,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), .TICK_CYCLES(TICK_CYCLES)) dut (
.DEAD_WINDOW_BITS(6), .TICK_CYCLES(TICK_CYCLES), .GCP_TRIAL_MS(GCP_TRIAL_MS)) dut (
.clk(clk), .reset(reset),
.spi_sck(sck), .spi_mosi(mosi), .spi_ss_n(ss_n), .spi_miso(miso),
.capture_done(done)
@ -121,6 +125,48 @@ module tb_strix_core;
end
end
// Reference model of the GCP trials: bit i of a second is inverted when i is odd, and the
// first 200 masked bits form the sample sum.
reg [31:0] gcp_model_bits = 0;
reg [7:0] gcp_model_sum [0:3];
reg [31:0] gcp_model_ones [0:3];
reg [31:0] gcp_latched_bits;
reg [7:0] gcp_latched_sum [0:3];
reg [31:0] gcp_latched_ones [0:3];
integer gm;
initial for (gm = 0; gm < 4; gm = gm + 1) begin
gcp_model_sum[gm] = 0;
gcp_model_ones[gm] = 0;
end
always @(posedge clk) if (!reset) begin : gcp_model
reg [31:0] index;
reg masked;
if (dut.gcp_trial_end) begin
gcp_latched_bits <= gcp_model_bits;
for (gm = 0; gm < 4; gm = gm + 1) begin
gcp_latched_sum[gm] <= gcp_model_sum[gm];
gcp_latched_ones[gm] <= gcp_model_ones[gm];
end
end
if (dut.gcp.gen_valid) begin
index = dut.gcp_trial_end ? 0 : gcp_model_bits;
gcp_model_bits <= index + 1;
for (gm = 0; gm < 4; gm = gm + 1) begin
masked = dut.gcp.gen_bit[gm] ^ index[0];
gcp_model_ones[gm] <= (dut.gcp_trial_end ? 0 : gcp_model_ones[gm]) + masked;
gcp_model_sum[gm] <= (dut.gcp_trial_end ? 0 : gcp_model_sum[gm]) + (index < 200 && masked);
end
end else if (dut.gcp_trial_end) begin
gcp_model_bits <= 0;
for (gm = 0; gm < 4; gm = gm + 1) begin
gcp_model_sum[gm] <= 0;
gcp_model_ones[gm] <= 0;
end
end
end
reg [7:0] snapshot [0:SNAPSHOT_BYTES-1];
function [31:0] snapshot_word(input integer index);
@ -282,7 +328,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'h09, "VERSION");
read_reg(REG_VERSION, v); expect(v, 8'h0A, "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");
@ -459,6 +505,39 @@ module tb_strix_core;
read_reg(REG_LEVEL_LOW, v);
expect_range(v, 0, 15, "stream level after flush");
// GCP trials: a full second after the earlier tests, checked against the model
@(posedge dut.gcp_trial_end); #200;
begin : gcp_read
reg [7:0] gcp [0:GCP_SNAPSHOT_BYTES-1];
reg [7:0] dummy;
integer i;
ss_n = 1'b0; #SCK_HALF;
spi_byte({1'b1, REG_GCP}, dummy);
for (i = 0; i < GCP_SNAPSHOT_BYTES; i = i + 1) spi_byte(8'h00, gcp[i]);
#SCK_HALF ss_n = 1'b1; #(4 * SCK_HALF);
if ({gcp[0], gcp[1], gcp[2], gcp[3]} !== {gcp[32], gcp[33], gcp[34], gcp[35]}
|| {gcp[0], gcp[1], gcp[2], gcp[3]} < 32'd2) begin
$display("FAIL GCP sequence %0d", {gcp[0], gcp[1], gcp[2], gcp[3]}); errors = errors + 1;
end
expect(gcp[4], 8'h0F, "GCP healthy");
expect(gcp[5], 8'd200, "GCP sample bits");
if ({gcp[8], gcp[9], gcp[10], gcp[11]} !== gcp_latched_bits) begin
$display("FAIL GCP bits %0d, model %0d", {gcp[8], gcp[9], gcp[10], gcp[11]}, gcp_latched_bits);
errors = errors + 1;
end
expect_range({gcp[8], gcp[9], gcp[10], gcp[11]}, GCP_TRIAL_BITS - 1, GCP_TRIAL_BITS + 1, "GCP bits");
for (i = 0; i < 4; i = i + 1) begin
if (gcp[12 + 5 * i] !== gcp_latched_sum[i]
|| {gcp[13 + 5 * i], gcp[14 + 5 * i], gcp[15 + 5 * i], gcp[16 + 5 * i]} !== gcp_latched_ones[i]) begin
$display("FAIL GCP generator %0d: sum %0d ones %0d, model %0d / %0d", i, gcp[12 + 5 * i],
{gcp[13 + 5 * i], gcp[14 + 5 * i], gcp[15 + 5 * i], gcp[16 + 5 * i]},
gcp_latched_sum[i], gcp_latched_ones[i]);
errors = errors + 1;
end
expect_range(gcp[12 + 5 * i], 1, 199, "GCP sample sum");
end
end
if (xor_checks < 100) begin
$display("FAIL only %0d temporal XOR checks ran", xor_checks);
errors = errors + 1;