strix/fpga/rtl/health_monitor.v

141 lines
5.3 KiB
Verilog

// 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