feat: add per-generator NIST health tests and dead-ring monitor, onod3000 output test (FPGA v4)
This commit is contained in:
parent
585e755c66
commit
67bd7eec58
9 changed files with 436 additions and 28 deletions
|
|
@ -16,6 +16,7 @@ module entropy_generator #(
|
|||
input wire [7:0] ring_select, // ring exposed on the characterization outputs
|
||||
output reg out_bit, // processed output, held for `temporal` clocks
|
||||
output wire spatial_bit, // XOR of all rings, one per clock
|
||||
output wire [RINGS-1:0] ring_samples, // synchronized enhanced outputs, for health tests
|
||||
output reg ring_enhanced,
|
||||
output reg ring_tap,
|
||||
output wire ring_raw_tap // unsynchronized, for frequency counting
|
||||
|
|
@ -62,7 +63,8 @@ module entropy_generator #(
|
|||
level4 <= ^level3;
|
||||
end
|
||||
|
||||
assign spatial_bit = level4;
|
||||
assign spatial_bit = level4;
|
||||
assign ring_samples = sample_sync;
|
||||
|
||||
// Temporal XOR over `temporal` consecutive spatial bits.
|
||||
reg [3:0] phase = 4'd0;
|
||||
|
|
|
|||
141
fpga/rtl/health_monitor.v
Normal file
141
fpga/rtl/health_monitor.v
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
// Continuous health tests for one entropy generator.
|
||||
//
|
||||
// - Repetition count test and adaptive proportion test (NIST SP 800-90B, section 4.4) on the
|
||||
// generator's spatial XOR stream. The default cutoffs in strix_core assume 0.5 bits of
|
||||
// min-entropy per bit and a false-alarm probability of 2^-40, which is far below the
|
||||
// measured entropy, so they only catch real failures.
|
||||
// - Dead-ring monitor: visits the rings one at a time and counts those whose sampled output
|
||||
// does not change during a whole window.
|
||||
//
|
||||
// Failures are sticky until `clear`, which leaves the dead-ring sweep running. Monitoring
|
||||
// pauses while the generator is disabled and for a short settling period after it is enabled.
|
||||
module health_monitor #(
|
||||
parameter RINGS = 90,
|
||||
parameter DEAD_WINDOW_BITS = 16, // 2^16 clk cycles per ring (0.55 ms at 120 MHz)
|
||||
parameter SETTLE_BITS = 10 // 1024 cycles ignored after enable
|
||||
) (
|
||||
input wire clk,
|
||||
input wire active, // generator enabled and system out of reset
|
||||
input wire clear, // clears failures and event counters
|
||||
input wire sample_bit, // spatial XOR stream, one bit per clock
|
||||
input wire [RINGS-1:0] ring_samples, // synchronized ring outputs
|
||||
input wire [7:0] rct_cutoff,
|
||||
input wire [10:0] apt_cutoff,
|
||||
output reg rct_failed,
|
||||
output reg apt_failed,
|
||||
output reg [7:0] rct_events, // saturating
|
||||
output reg [7:0] apt_events, // saturating
|
||||
output reg [7:0] dead_rings, // result of the last complete sweep
|
||||
output reg sweep_done // at least one sweep completed since activation
|
||||
);
|
||||
|
||||
// Settling
|
||||
|
||||
reg [SETTLE_BITS-1:0] settle_count = {SETTLE_BITS{1'b0}};
|
||||
wire settled = &settle_count;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (!active) settle_count <= {SETTLE_BITS{1'b0}};
|
||||
else if (!settled) settle_count <= settle_count + 1'b1;
|
||||
end
|
||||
|
||||
wire testing = active && settled;
|
||||
|
||||
// Repetition count test: a run of rct_cutoff identical bits is a failure.
|
||||
|
||||
reg previous_bit;
|
||||
reg [7:0] run_length;
|
||||
|
||||
always @(posedge clk) begin
|
||||
previous_bit <= sample_bit;
|
||||
if (!testing || sample_bit != previous_bit)
|
||||
run_length <= 8'd1;
|
||||
else if (run_length != 8'hFF)
|
||||
run_length <= run_length + 8'd1;
|
||||
end
|
||||
|
||||
wire rct_event = testing && sample_bit == previous_bit && run_length + 8'd1 == rct_cutoff;
|
||||
|
||||
// Adaptive proportion test: within each 1024-bit window, the first bit's value occurring
|
||||
// apt_cutoff times or more is a failure.
|
||||
|
||||
reg [9:0] window_position;
|
||||
reg reference_bit;
|
||||
reg [10:0] matches;
|
||||
reg window_flagged;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (!testing) begin
|
||||
window_position <= 10'd0;
|
||||
end else begin
|
||||
window_position <= window_position + 10'd1;
|
||||
if (window_position == 10'd0) begin
|
||||
reference_bit <= sample_bit;
|
||||
matches <= 11'd1;
|
||||
window_flagged <= 1'b0;
|
||||
end else if (sample_bit == reference_bit) begin
|
||||
matches <= matches + 11'd1;
|
||||
if (matches + 11'd1 == apt_cutoff) window_flagged <= 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
wire apt_event = testing && window_position != 10'd0 && sample_bit == reference_bit
|
||||
&& matches + 11'd1 == apt_cutoff && !window_flagged;
|
||||
|
||||
// Sticky results
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (clear) begin
|
||||
rct_failed <= 1'b0;
|
||||
apt_failed <= 1'b0;
|
||||
rct_events <= 8'd0;
|
||||
apt_events <= 8'd0;
|
||||
end else begin
|
||||
if (rct_event) begin
|
||||
rct_failed <= 1'b1;
|
||||
if (rct_events != 8'hFF) rct_events <= rct_events + 8'd1;
|
||||
end
|
||||
if (apt_event) begin
|
||||
apt_failed <= 1'b1;
|
||||
if (apt_events != 8'hFF) apt_events <= apt_events + 8'd1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// Dead-ring monitor
|
||||
|
||||
reg [7:0] ring_index;
|
||||
reg [DEAD_WINDOW_BITS-1:0] dead_window;
|
||||
reg ring_bit, ring_bit_previous, ring_toggled;
|
||||
reg [7:0] dead_so_far;
|
||||
|
||||
always @(posedge clk) begin
|
||||
ring_bit <= ring_samples[ring_index];
|
||||
ring_bit_previous <= ring_bit;
|
||||
if (!testing) begin
|
||||
ring_index <= 8'd0;
|
||||
dead_window <= {DEAD_WINDOW_BITS{1'b0}};
|
||||
ring_toggled <= 1'b0;
|
||||
dead_so_far <= 8'd0;
|
||||
sweep_done <= 1'b0;
|
||||
end else begin
|
||||
dead_window <= dead_window + 1'b1;
|
||||
// The first two cycles of a window still carry the previous ring's bits.
|
||||
if (dead_window > 2 && ring_bit != ring_bit_previous) ring_toggled <= 1'b1;
|
||||
if (&dead_window) begin
|
||||
ring_toggled <= 1'b0;
|
||||
if (ring_index == RINGS - 1) begin
|
||||
ring_index <= 8'd0;
|
||||
dead_rings <= dead_so_far + !ring_toggled;
|
||||
dead_so_far <= 8'd0;
|
||||
sweep_done <= 1'b1;
|
||||
end else begin
|
||||
ring_index <= ring_index + 8'd1;
|
||||
dead_so_far <= dead_so_far + !ring_toggled;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
@ -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}};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue