feat: add per-generator NIST health tests and dead-ring monitor, onod3000 output test (FPGA v4)

This commit is contained in:
randogoth 2026-10-25 13:36:55 +02:00
parent 585e755c66
commit 67bd7eec58
9 changed files with 436 additions and 28 deletions

View file

@ -1,5 +1,5 @@
// Board-independent STRIX logic: entropy generators, source selection, raw capture,
// frequency counter and the SPI register interface to the SAMD21.
// Board-independent STRIX logic: entropy generators with health monitors, source selection,
// raw capture, frequency counter and the SPI register interface to the SAMD21.
//
// SPI protocol (mode 0, one transaction per ss_n low period): the first byte is a command,
// bit 7 = read (1) or write (0), bits 6:0 = register address. A write stores the following
@ -11,10 +11,15 @@
// 0x04+g spatial XOR of all rings, every clock
// 0x08+g enhanced output of ring RING_SELECT
// 0x0C+g single tap of ring RING_SELECT
//
// Health: a generator is healthy when it is enabled, its monitor has completed a dead-ring
// sweep, neither NIST test has failed since the last clear, and at most MAX_DEAD_RINGS rings
// are dead. Per-generator registers at 0x30 + 4g: FLAGS, RCT_EVENTS, APT_EVENTS, DEAD_RINGS.
module strix_core #(
parameter CAPTURE_ADDR_WIDTH = 15,
parameter FREQ_GATE_BITS = 24,
parameter RINGS_PER_GEN = 90
parameter RINGS_PER_GEN = 90,
parameter DEAD_WINDOW_BITS = 16
) (
input wire clk,
input wire reset,
@ -25,7 +30,7 @@ module strix_core #(
output wire capture_done
);
localparam [7:0] VERSION = 8'h03;
localparam [7:0] VERSION = 8'h04;
localparam GENERATORS = 4;
localparam [6:0] REG_ID0 = 7'h00, // "STRX"
@ -53,7 +58,20 @@ module strix_core #(
REG_FREQ_COUNT0 = 7'h15,
REG_GEN_ENABLE = 7'h16, // bit g enables generator g
REG_TEMPORAL = 7'h17, // consecutive bits XORed per output bit, 1-8
REG_FREQ_GEN = 7'h18; // generator for the frequency measurement
REG_FREQ_GEN = 7'h18, // generator for the frequency measurement
REG_HEALTHY = 7'h20, // read-only: bit g = generator g healthy
REG_HEALTH_CTRL = 7'h21, // write: bit 0 clears failures and event counters
REG_RCT_CUTOFF = 7'h22, // repetition count test cutoff (run length)
REG_APT_HIGH = 7'h23, // adaptive proportion test cutoff (per 1024 bits)
REG_APT_LOW = 7'h24,
REG_MAX_DEAD = 7'h25; // dead rings tolerated per generator
// Per-generator health registers at 0x30 + 4g + index
localparam [1:0] GEN_FLAGS = 2'd0, // bit 0 RCT failed, 1 APT failed, 2 too many dead rings,
// 3 active, 4 sweep done, 5 healthy
GEN_RCT_EVENTS = 2'd1,
GEN_APT_EVENTS = 2'd2,
GEN_DEAD_RINGS = 2'd3;
// Generators
@ -62,15 +80,39 @@ module strix_core #(
reg [7:0] ring_select;
wire [GENERATORS-1:0] out_bit, spatial_bit, ring_enhanced, ring_tap, ring_raw_tap;
reg health_clear;
reg [7:0] rct_cutoff, max_dead;
reg [10:0] apt_cutoff;
wire [GENERATORS-1:0] rct_failed, apt_failed, sweep_done, too_many_dead, healthy;
wire [7:0] rct_events [0:GENERATORS-1];
wire [7:0] apt_events [0:GENERATORS-1];
wire [7:0] dead_rings [0:GENERATORS-1];
genvar g;
generate
for (g = 0; g < GENERATORS; g = g + 1) begin : gen
wire [RINGS_PER_GEN-1:0] ring_samples;
wire active = gen_enable[g] && !reset;
entropy_generator #(.RINGS(RINGS_PER_GEN)) generator (
.clk(clk), .enable(gen_enable[g]), .temporal(temporal), .ring_select(ring_select),
.out_bit(out_bit[g]), .spatial_bit(spatial_bit[g]),
.ring_enhanced(ring_enhanced[g]), .ring_tap(ring_tap[g]),
.ring_raw_tap(ring_raw_tap[g])
.ring_raw_tap(ring_raw_tap[g]), .ring_samples(ring_samples)
);
health_monitor #(.RINGS(RINGS_PER_GEN), .DEAD_WINDOW_BITS(DEAD_WINDOW_BITS)) monitor (
.clk(clk), .active(active), .clear(health_clear),
.sample_bit(spatial_bit[g]), .ring_samples(ring_samples),
.rct_cutoff(rct_cutoff), .apt_cutoff(apt_cutoff),
.rct_failed(rct_failed[g]), .apt_failed(apt_failed[g]),
.rct_events(rct_events[g]), .apt_events(apt_events[g]),
.dead_rings(dead_rings[g]), .sweep_done(sweep_done[g])
);
assign too_many_dead[g] = sweep_done[g] && dead_rings[g] > max_dead;
assign healthy[g] = active && sweep_done[g] && !rct_failed[g] && !apt_failed[g]
&& !too_many_dead[g];
end
endgenerate
@ -136,6 +178,22 @@ module strix_core #(
wire reading_data = is_read && reg_addr == REG_DATA;
function [7:0] gen_register(input [6:0] addr);
reg [1:0] n;
begin
n = addr[3:2];
if (addr[6:4] != 3'b011)
gen_register = 8'h00;
else case (addr[1:0])
GEN_FLAGS: gen_register = {2'd0, healthy[n], sweep_done[n], gen_enable[n],
too_many_dead[n], apt_failed[n], rct_failed[n]};
GEN_RCT_EVENTS: gen_register = rct_events[n];
GEN_APT_EVENTS: gen_register = apt_events[n];
default: gen_register = sweep_done[n] ? dead_rings[n] : 8'h00;
endcase
end
endfunction
always @(*) begin
case (reg_addr)
REG_ID0: tx_byte = "S";
@ -161,13 +219,19 @@ module strix_core #(
REG_GEN_ENABLE: tx_byte = {4'd0, gen_enable};
REG_TEMPORAL: tx_byte = {4'd0, temporal};
REG_FREQ_GEN: tx_byte = {6'd0, freq_gen};
default: tx_byte = 8'h00;
REG_HEALTHY: tx_byte = {4'd0, healthy};
REG_RCT_CUTOFF: tx_byte = rct_cutoff;
REG_APT_HIGH: tx_byte = {5'd0, apt_cutoff[10:8]};
REG_APT_LOW: tx_byte = apt_cutoff[7:0];
REG_MAX_DEAD: tx_byte = max_dead;
default: tx_byte = gen_register(reg_addr);
endcase
end
always @(posedge clk) begin
capture_start <= 1'b0;
freq_start <= 1'b0;
health_clear <= 1'b0;
if (reset) begin
gen_enable <= {GENERATORS{1'b1}};
temporal <= 4'd5;
@ -177,6 +241,10 @@ module strix_core #(
pair_mode <= 1'b0;
decimation <= 16'd5;
freq_gen <= 2'd0;
rct_cutoff <= 8'd81; // 0.5 bits/bit claimed, alpha 2^-40
apt_cutoff <= 11'd824; // same, 1024-bit window
max_dead <= 8'd8; // measured sizing tolerates about 17
health_clear <= 1'b1;
is_read <= 1'b0;
reg_addr <= 7'd0;
bytes_read <= {CAPTURE_ADDR_WIDTH{1'b0}};
@ -204,6 +272,11 @@ module strix_core #(
REG_GEN_ENABLE: gen_enable <= rx_byte[GENERATORS-1:0];
REG_TEMPORAL: if (rx_byte >= 8'd1 && rx_byte <= 8'd8) temporal <= rx_byte[3:0];
REG_FREQ_GEN: freq_gen <= rx_byte[1:0];
REG_HEALTH_CTRL: health_clear <= rx_byte[0];
REG_RCT_CUTOFF: if (rx_byte >= 8'd2) rct_cutoff <= rx_byte;
REG_APT_HIGH: apt_cutoff[10:8] <= rx_byte[2:0];
REG_APT_LOW: apt_cutoff[7:0] <= rx_byte;
REG_MAX_DEAD: max_dead <= rx_byte;
REG_CONTROL: begin
if (rx_byte[0]) capture_start <= 1'b1;
if (rx_byte[0] || rx_byte[1]) bytes_read <= {CAPTURE_ADDR_WIDTH{1'b0}};