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
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -25,3 +25,6 @@ fpga/**/simulation/
|
||||||
arduino/strix_capture/bitstream.h
|
arduino/strix_capture/bitstream.h
|
||||||
arduino/strix_capture/signature.h
|
arduino/strix_capture/signature.h
|
||||||
captures/
|
captures/
|
||||||
|
|
||||||
|
# Local builds of external tools (onod3000)
|
||||||
|
.build/
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@
|
||||||
"python312@latest",
|
"python312@latest",
|
||||||
"python312Packages.pyserial@latest",
|
"python312Packages.pyserial@latest",
|
||||||
"python312Packages.numpy@latest",
|
"python312Packages.numpy@latest",
|
||||||
"iverilog@latest"
|
"iverilog@latest",
|
||||||
|
"rustc@latest",
|
||||||
|
"cargo@latest"
|
||||||
],
|
],
|
||||||
"shell": {
|
"shell": {
|
||||||
"init_hook": [
|
"init_hook": [
|
||||||
|
|
@ -23,7 +25,7 @@
|
||||||
],
|
],
|
||||||
"sim": [
|
"sim": [
|
||||||
"mkdir -p /tmp/strix-sim",
|
"mkdir -p /tmp/strix-sim",
|
||||||
"iverilog -g2005 -DSIM -o /tmp/strix-sim/tb_strix_core fpga/sim/tb_strix_core.v fpga/rtl/strix_core.v fpga/rtl/entropy_generator.v fpga/rtl/freq_counter.v fpga/rtl/ring_osc.v fpga/rtl/raw_capture.v fpga/rtl/spi_slave.v",
|
"iverilog -g2005 -DSIM -o /tmp/strix-sim/tb_strix_core fpga/sim/tb_strix_core.v fpga/rtl/strix_core.v fpga/rtl/entropy_generator.v fpga/rtl/health_monitor.v fpga/rtl/freq_counter.v fpga/rtl/ring_osc.v fpga/rtl/raw_capture.v fpga/rtl/spi_slave.v",
|
||||||
"vvp -n /tmp/strix-sim/tb_strix_core"
|
"vvp -n /tmp/strix-sim/tb_strix_core"
|
||||||
],
|
],
|
||||||
"fpga": [
|
"fpga": [
|
||||||
|
|
@ -38,6 +40,9 @@
|
||||||
"upload-capture": [
|
"upload-capture": [
|
||||||
"arduino-cli upload --fqbn arduino:samd:mkrvidor4000 -p ${PORT:-/dev/ttyACM0} arduino/strix_capture"
|
"arduino-cli upload --fqbn arduino:samd:mkrvidor4000 -p ${PORT:-/dev/ttyACM0} arduino/strix_capture"
|
||||||
],
|
],
|
||||||
|
"onod-build": [
|
||||||
|
"cargo build --release --manifest-path ${ONOD3000_DIR:-$HOME/Projects/randonautica/code/onod3000}/Cargo.toml --target-dir .build/onod3000"
|
||||||
|
],
|
||||||
"jitter": [
|
"jitter": [
|
||||||
"python3 tools/jitter.py --port ${PORT:-/dev/ttyACM0} --out captures \"$@\""
|
"python3 tools/jitter.py --port ${PORT:-/dev/ttyACM0} --out captures \"$@\""
|
||||||
]
|
]
|
||||||
|
|
|
||||||
104
devbox.lock
104
devbox.lock
|
|
@ -49,6 +49,44 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"cargo@latest": {
|
||||||
|
"last_modified": "2026-10-02T22:55:44Z",
|
||||||
|
"resolved": "github:NixOS/nixpkgs/55ba7f49ef2962b42cbd126522b7df5f95037679#cargo",
|
||||||
|
"source": "devbox-search",
|
||||||
|
"version": "1.98.1",
|
||||||
|
"systems": {
|
||||||
|
"aarch64-darwin": {
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "out",
|
||||||
|
"path": "/nix/store/z76zwygvlbbapfasr22i7xxrsi9w2wm6-cargo-1.98.1",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"store_path": "/nix/store/z76zwygvlbbapfasr22i7xxrsi9w2wm6-cargo-1.98.1"
|
||||||
|
},
|
||||||
|
"aarch64-linux": {
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "out",
|
||||||
|
"path": "/nix/store/4xg2np6a2m3ff0camgca44rb5h8g71nl-cargo-1.98.1",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"store_path": "/nix/store/4xg2np6a2m3ff0camgca44rb5h8g71nl-cargo-1.98.1"
|
||||||
|
},
|
||||||
|
"x86_64-linux": {
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "out",
|
||||||
|
"path": "/nix/store/gmgfpzc0w2pgby0lmq9r88m39albiyfw-cargo-1.98.1",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"store_path": "/nix/store/gmgfpzc0w2pgby0lmq9r88m39albiyfw-cargo-1.98.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"github:NixOS/nixpkgs/nixpkgs-unstable": {
|
"github:NixOS/nixpkgs/nixpkgs-unstable": {
|
||||||
"last_modified": "2026-09-29T03:29:29Z",
|
"last_modified": "2026-09-29T03:29:29Z",
|
||||||
"resolved": "github:NixOS/nixpkgs/b6c8664de9b6cc07fe5666a29f91884ba81197c4?lastModified=1790652569&narHash=sha256-641r7xrlSOHYoktL9%2FaArPIYNgZJ9eM5URzSEOtPJDY%3D"
|
"resolved": "github:NixOS/nixpkgs/b6c8664de9b6cc07fe5666a29f91884ba81197c4?lastModified=1790652569&narHash=sha256-641r7xrlSOHYoktL9%2FaArPIYNgZJ9eM5URzSEOtPJDY%3D"
|
||||||
|
|
@ -265,6 +303,72 @@
|
||||||
"store_path": "/nix/store/j90zi1mxv9dvqmsjqicnaafm22bp3qca-python3.12-pyserial-3.5"
|
"store_path": "/nix/store/j90zi1mxv9dvqmsjqicnaafm22bp3qca-python3.12-pyserial-3.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"rustc@latest": {
|
||||||
|
"last_modified": "2026-10-02T22:55:44Z",
|
||||||
|
"plugin_version": "0.0.1",
|
||||||
|
"resolved": "github:NixOS/nixpkgs/55ba7f49ef2962b42cbd126522b7df5f95037679#rustc",
|
||||||
|
"source": "devbox-search",
|
||||||
|
"version": "1.98.1",
|
||||||
|
"systems": {
|
||||||
|
"aarch64-darwin": {
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "out",
|
||||||
|
"path": "/nix/store/yb6wa0sk67mynp3k90lw9jgcdjk9msrs-rustc-wrapper-1.98.1",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "man",
|
||||||
|
"path": "/nix/store/52a882rxhl50v46qm10820aa8n5n3xcv-rustc-wrapper-1.98.1-man",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "doc",
|
||||||
|
"path": "/nix/store/yhfspxrjqm5ysi79f2y6srsd6y4r1602-rustc-wrapper-1.98.1-doc"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"store_path": "/nix/store/yb6wa0sk67mynp3k90lw9jgcdjk9msrs-rustc-wrapper-1.98.1"
|
||||||
|
},
|
||||||
|
"aarch64-linux": {
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "out",
|
||||||
|
"path": "/nix/store/8dpmmiq8hdj1z4fvig4ch1mfid0vzrxm-rustc-wrapper-1.98.1",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "man",
|
||||||
|
"path": "/nix/store/r8fchqs0dq0d6hw96jl3n37p0vpkp603-rustc-wrapper-1.98.1-man",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "doc",
|
||||||
|
"path": "/nix/store/i2iwz1pzzsy8wr9ijq4p3pclqfmzdvkf-rustc-wrapper-1.98.1-doc"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"store_path": "/nix/store/8dpmmiq8hdj1z4fvig4ch1mfid0vzrxm-rustc-wrapper-1.98.1"
|
||||||
|
},
|
||||||
|
"x86_64-linux": {
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "out",
|
||||||
|
"path": "/nix/store/hr3npzrq9l029s5hf948by203m6qz3lw-rustc-wrapper-1.98.1",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "man",
|
||||||
|
"path": "/nix/store/0y1vday672aw7dp36ms72bcdpdsxdwri-rustc-wrapper-1.98.1-man",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "doc",
|
||||||
|
"path": "/nix/store/rf1fpdx7xg9wzvi7fkw9qwnk565n5yci-rustc-wrapper-1.98.1-doc"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"store_path": "/nix/store/hr3npzrq9l029s5hf948by203m6qz3lw-rustc-wrapper-1.98.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ module entropy_generator #(
|
||||||
input wire [7:0] ring_select, // ring exposed on the characterization outputs
|
input wire [7:0] ring_select, // ring exposed on the characterization outputs
|
||||||
output reg out_bit, // processed output, held for `temporal` clocks
|
output reg out_bit, // processed output, held for `temporal` clocks
|
||||||
output wire spatial_bit, // XOR of all rings, one per clock
|
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_enhanced,
|
||||||
output reg ring_tap,
|
output reg ring_tap,
|
||||||
output wire ring_raw_tap // unsynchronized, for frequency counting
|
output wire ring_raw_tap // unsynchronized, for frequency counting
|
||||||
|
|
@ -62,7 +63,8 @@ module entropy_generator #(
|
||||||
level4 <= ^level3;
|
level4 <= ^level3;
|
||||||
end
|
end
|
||||||
|
|
||||||
assign spatial_bit = level4;
|
assign spatial_bit = level4;
|
||||||
|
assign ring_samples = sample_sync;
|
||||||
|
|
||||||
// Temporal XOR over `temporal` consecutive spatial bits.
|
// Temporal XOR over `temporal` consecutive spatial bits.
|
||||||
reg [3:0] phase = 4'd0;
|
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,
|
// Board-independent STRIX logic: entropy generators with health monitors, source selection,
|
||||||
// frequency counter and the SPI register interface to the SAMD21.
|
// 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,
|
// 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
|
// 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
|
// 0x04+g spatial XOR of all rings, every clock
|
||||||
// 0x08+g enhanced output of ring RING_SELECT
|
// 0x08+g enhanced output of ring RING_SELECT
|
||||||
// 0x0C+g single tap 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 #(
|
module strix_core #(
|
||||||
parameter CAPTURE_ADDR_WIDTH = 15,
|
parameter CAPTURE_ADDR_WIDTH = 15,
|
||||||
parameter FREQ_GATE_BITS = 24,
|
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 clk,
|
||||||
input wire reset,
|
input wire reset,
|
||||||
|
|
@ -25,7 +30,7 @@ module strix_core #(
|
||||||
output wire capture_done
|
output wire capture_done
|
||||||
);
|
);
|
||||||
|
|
||||||
localparam [7:0] VERSION = 8'h03;
|
localparam [7:0] VERSION = 8'h04;
|
||||||
localparam GENERATORS = 4;
|
localparam GENERATORS = 4;
|
||||||
|
|
||||||
localparam [6:0] REG_ID0 = 7'h00, // "STRX"
|
localparam [6:0] REG_ID0 = 7'h00, // "STRX"
|
||||||
|
|
@ -53,7 +58,20 @@ module strix_core #(
|
||||||
REG_FREQ_COUNT0 = 7'h15,
|
REG_FREQ_COUNT0 = 7'h15,
|
||||||
REG_GEN_ENABLE = 7'h16, // bit g enables generator g
|
REG_GEN_ENABLE = 7'h16, // bit g enables generator g
|
||||||
REG_TEMPORAL = 7'h17, // consecutive bits XORed per output bit, 1-8
|
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
|
// Generators
|
||||||
|
|
||||||
|
|
@ -62,15 +80,39 @@ module strix_core #(
|
||||||
reg [7:0] ring_select;
|
reg [7:0] ring_select;
|
||||||
wire [GENERATORS-1:0] out_bit, spatial_bit, ring_enhanced, ring_tap, ring_raw_tap;
|
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;
|
genvar g;
|
||||||
generate
|
generate
|
||||||
for (g = 0; g < GENERATORS; g = g + 1) begin : gen
|
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 (
|
entropy_generator #(.RINGS(RINGS_PER_GEN)) generator (
|
||||||
.clk(clk), .enable(gen_enable[g]), .temporal(temporal), .ring_select(ring_select),
|
.clk(clk), .enable(gen_enable[g]), .temporal(temporal), .ring_select(ring_select),
|
||||||
.out_bit(out_bit[g]), .spatial_bit(spatial_bit[g]),
|
.out_bit(out_bit[g]), .spatial_bit(spatial_bit[g]),
|
||||||
.ring_enhanced(ring_enhanced[g]), .ring_tap(ring_tap[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
|
end
|
||||||
endgenerate
|
endgenerate
|
||||||
|
|
||||||
|
|
@ -136,6 +178,22 @@ module strix_core #(
|
||||||
|
|
||||||
wire reading_data = is_read && reg_addr == REG_DATA;
|
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
|
always @(*) begin
|
||||||
case (reg_addr)
|
case (reg_addr)
|
||||||
REG_ID0: tx_byte = "S";
|
REG_ID0: tx_byte = "S";
|
||||||
|
|
@ -161,13 +219,19 @@ module strix_core #(
|
||||||
REG_GEN_ENABLE: tx_byte = {4'd0, gen_enable};
|
REG_GEN_ENABLE: tx_byte = {4'd0, gen_enable};
|
||||||
REG_TEMPORAL: tx_byte = {4'd0, temporal};
|
REG_TEMPORAL: tx_byte = {4'd0, temporal};
|
||||||
REG_FREQ_GEN: tx_byte = {6'd0, freq_gen};
|
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
|
endcase
|
||||||
end
|
end
|
||||||
|
|
||||||
always @(posedge clk) begin
|
always @(posedge clk) begin
|
||||||
capture_start <= 1'b0;
|
capture_start <= 1'b0;
|
||||||
freq_start <= 1'b0;
|
freq_start <= 1'b0;
|
||||||
|
health_clear <= 1'b0;
|
||||||
if (reset) begin
|
if (reset) begin
|
||||||
gen_enable <= {GENERATORS{1'b1}};
|
gen_enable <= {GENERATORS{1'b1}};
|
||||||
temporal <= 4'd5;
|
temporal <= 4'd5;
|
||||||
|
|
@ -177,6 +241,10 @@ module strix_core #(
|
||||||
pair_mode <= 1'b0;
|
pair_mode <= 1'b0;
|
||||||
decimation <= 16'd5;
|
decimation <= 16'd5;
|
||||||
freq_gen <= 2'd0;
|
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;
|
is_read <= 1'b0;
|
||||||
reg_addr <= 7'd0;
|
reg_addr <= 7'd0;
|
||||||
bytes_read <= {CAPTURE_ADDR_WIDTH{1'b0}};
|
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_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_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_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
|
REG_CONTROL: begin
|
||||||
if (rx_byte[0]) capture_start <= 1'b1;
|
if (rx_byte[0]) capture_start <= 1'b1;
|
||||||
if (rx_byte[0] || rx_byte[1]) bytes_read <= {CAPTURE_ADDR_WIDTH{1'b0}};
|
if (rx_byte[0] || rx_byte[1]) bytes_read <= {CAPTURE_ADDR_WIDTH{1'b0}};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// Testbench for strix_core: register access, generator XOR logic checked against the sampled
|
// Testbench for strix_core: register access, generator XOR logic checked against the sampled
|
||||||
// ring outputs, captures read back across transactions, generator enable, and the frequency
|
// ring outputs, captures read back across transactions, generator enable, the frequency
|
||||||
// counter. Rings are replaced by the SIM model in ring_osc.v.
|
// counter, and the health monitors with injected faults. Rings are replaced by the SIM model in ring_osc.v.
|
||||||
`timescale 1ns / 1ps
|
`timescale 1ns / 1ps
|
||||||
|
|
||||||
module tb_strix_core;
|
module tb_strix_core;
|
||||||
|
|
@ -14,7 +14,14 @@ module tb_strix_core;
|
||||||
REG_GENERATORS = 7'h06, REG_RINGS = 7'h07, REG_SOURCE_A = 7'h08,
|
REG_GENERATORS = 7'h06, REG_RINGS = 7'h07, REG_SOURCE_A = 7'h08,
|
||||||
REG_DECIM_LOW = 7'h0A, REG_CONTROL = 7'h0B, REG_STATUS = 7'h0C,
|
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_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_GEN_ENABLE = 7'h16, REG_TEMPORAL = 7'h17, REG_FREQ_GEN = 7'h18,
|
||||||
|
REG_HEALTHY = 7'h20, REG_HEALTH_CTRL = 7'h21;
|
||||||
|
|
||||||
|
// Per-generator health registers
|
||||||
|
function [6:0] gen_reg(input integer generator, input integer index);
|
||||||
|
gen_reg = 7'h30 + 4 * generator + index;
|
||||||
|
endfunction
|
||||||
|
localparam FLAGS = 0, RCT_EVENTS = 1, APT_EVENTS = 2, DEAD_RINGS = 3;
|
||||||
|
|
||||||
reg clk = 1'b0;
|
reg clk = 1'b0;
|
||||||
always #4.1667 clk = ~clk; // 120 MHz
|
always #4.1667 clk = ~clk; // 120 MHz
|
||||||
|
|
@ -23,7 +30,8 @@ module tb_strix_core;
|
||||||
reg sck = 1'b0, mosi = 1'b0, ss_n = 1'b1;
|
reg sck = 1'b0, mosi = 1'b0, ss_n = 1'b1;
|
||||||
wire miso, done;
|
wire miso, done;
|
||||||
|
|
||||||
strix_core #(.CAPTURE_ADDR_WIDTH(CAPTURE_ADDR_WIDTH), .FREQ_GATE_BITS(14), .RINGS_PER_GEN(RINGS)) dut (
|
strix_core #(.CAPTURE_ADDR_WIDTH(CAPTURE_ADDR_WIDTH), .FREQ_GATE_BITS(14), .RINGS_PER_GEN(RINGS),
|
||||||
|
.DEAD_WINDOW_BITS(6)) dut (
|
||||||
.clk(clk), .reset(reset),
|
.clk(clk), .reset(reset),
|
||||||
.spi_sck(sck), .spi_mosi(mosi), .spi_ss_n(ss_n), .spi_miso(miso),
|
.spi_sck(sck), .spi_mosi(mosi), .spi_ss_n(ss_n), .spi_miso(miso),
|
||||||
.capture_done(done)
|
.capture_done(done)
|
||||||
|
|
@ -171,7 +179,7 @@ module tb_strix_core;
|
||||||
// Identification
|
// Identification
|
||||||
read_reg(REG_ID0, v); expect(v, "S", "ID0");
|
read_reg(REG_ID0, v); expect(v, "S", "ID0");
|
||||||
read_reg(REG_ID3, v); expect(v, "X", "ID3");
|
read_reg(REG_ID3, v); expect(v, "X", "ID3");
|
||||||
read_reg(REG_VERSION, v); expect(v, 8'h03, "VERSION");
|
read_reg(REG_VERSION, v); expect(v, 8'h04, "VERSION");
|
||||||
read_reg(REG_GENERATORS, v); expect(v, 8'd4, "GENERATORS");
|
read_reg(REG_GENERATORS, v); expect(v, 8'd4, "GENERATORS");
|
||||||
read_reg(REG_RINGS, v); expect(v, RINGS, "RINGS");
|
read_reg(REG_RINGS, v); expect(v, RINGS, "RINGS");
|
||||||
read_reg(REG_TEMPORAL, v); expect(v, 8'd5, "TEMPORAL");
|
read_reg(REG_TEMPORAL, v); expect(v, 8'd5, "TEMPORAL");
|
||||||
|
|
@ -216,6 +224,41 @@ module tb_strix_core;
|
||||||
read_reg(REG_FREQ_COUNT0, v);
|
read_reg(REG_FREQ_COUNT0, v);
|
||||||
if (v == 8'd0) begin $display("FAIL frequency count is zero"); errors = errors + 1; end
|
if (v == 8'd0) begin $display("FAIL frequency count is zero"); errors = errors + 1; end
|
||||||
|
|
||||||
|
// Health: all generators healthy once a dead-ring sweep (90 x 64 cycles) has completed
|
||||||
|
#100000;
|
||||||
|
read_reg(REG_HEALTHY, v); expect(v, 8'h0F, "HEALTHY");
|
||||||
|
read_reg(gen_reg(0, FLAGS), v); expect(v, 8'h38, "FLAGS g0"); // healthy, swept, active
|
||||||
|
read_reg(gen_reg(0, DEAD_RINGS), v); expect(v, 8'd0, "DEAD g0");
|
||||||
|
|
||||||
|
// A dead ring is counted but tolerated
|
||||||
|
force dut.gen[1].generator.raw_enhanced[7] = 1'b0;
|
||||||
|
#120000;
|
||||||
|
read_reg(gen_reg(1, DEAD_RINGS), v); expect(v, 8'd1, "DEAD g1");
|
||||||
|
read_reg(REG_HEALTHY, v); expect(v, 8'h0F, "HEALTHY dead ring");
|
||||||
|
release dut.gen[1].generator.raw_enhanced[7];
|
||||||
|
|
||||||
|
// A stuck spatial stream fails both NIST tests
|
||||||
|
force dut.gen[2].generator.level4 = 1'b0;
|
||||||
|
#20000;
|
||||||
|
release dut.gen[2].generator.level4;
|
||||||
|
read_reg(REG_HEALTHY, v); expect(v, 8'h0B, "HEALTHY stuck");
|
||||||
|
read_reg(gen_reg(2, FLAGS), v); expect(v & 8'h03, 8'h03, "FLAGS g2 failed");
|
||||||
|
read_reg(gen_reg(2, RCT_EVENTS), v);
|
||||||
|
if (v == 8'd0) begin $display("FAIL no RCT events"); errors = errors + 1; end
|
||||||
|
read_reg(gen_reg(2, APT_EVENTS), v);
|
||||||
|
if (v == 8'd0) begin $display("FAIL no APT events"); errors = errors + 1; end
|
||||||
|
|
||||||
|
// Clear restores health without restarting the sweep
|
||||||
|
write_reg(REG_HEALTH_CTRL, 8'h01);
|
||||||
|
read_reg(REG_HEALTHY, v); expect(v, 8'h0F, "HEALTHY cleared");
|
||||||
|
read_reg(gen_reg(2, RCT_EVENTS), v); expect(v, 8'd0, "RCT events cleared");
|
||||||
|
|
||||||
|
// A disabled generator is not healthy
|
||||||
|
write_reg(REG_GEN_ENABLE, 8'h07);
|
||||||
|
read_reg(REG_HEALTHY, v); expect(v, 8'h07, "HEALTHY disabled");
|
||||||
|
read_reg(gen_reg(3, FLAGS), v); expect(v, 8'h00, "FLAGS g3 disabled");
|
||||||
|
write_reg(REG_GEN_ENABLE, 8'h0F);
|
||||||
|
|
||||||
if (xor_checks < 100) begin
|
if (xor_checks < 100) begin
|
||||||
$display("FAIL only %0d temporal XOR checks ran", xor_checks);
|
$display("FAIL only %0d temporal XOR checks ran", xor_checks);
|
||||||
errors = errors + 1;
|
errors = errors + 1;
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ set_global_assignment -name VERILOG_FILE ../rtl/strix.v
|
||||||
set_global_assignment -name VERILOG_FILE ../rtl/sys_pll.v
|
set_global_assignment -name VERILOG_FILE ../rtl/sys_pll.v
|
||||||
set_global_assignment -name VERILOG_FILE ../rtl/strix_core.v
|
set_global_assignment -name VERILOG_FILE ../rtl/strix_core.v
|
||||||
set_global_assignment -name VERILOG_FILE ../rtl/entropy_generator.v
|
set_global_assignment -name VERILOG_FILE ../rtl/entropy_generator.v
|
||||||
|
set_global_assignment -name VERILOG_FILE ../rtl/health_monitor.v
|
||||||
set_global_assignment -name VERILOG_FILE ../rtl/freq_counter.v
|
set_global_assignment -name VERILOG_FILE ../rtl/freq_counter.v
|
||||||
set_global_assignment -name VERILOG_FILE ../rtl/ring_osc.v
|
set_global_assignment -name VERILOG_FILE ../rtl/ring_osc.v
|
||||||
set_global_assignment -name VERILOG_FILE ../rtl/raw_capture.v
|
set_global_assignment -name VERILOG_FILE ../rtl/raw_capture.v
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ Subcommands:
|
||||||
freq measure ring frequencies in every generator
|
freq measure ring frequencies in every generator
|
||||||
rings estimate each ring's relative predictability at k=1 (sizing input)
|
rings estimate each ring's relative predictability at k=1 (sizing input)
|
||||||
sweep capture sources at increasing decimation k and fit the accumulated jitter
|
sweep capture sources at increasing decimation k and fit the accumulated jitter
|
||||||
output check every generator's spatial XOR and processed output for bias and correlation
|
output run the onod3000 test suite on every generator's processed output (256 KB each)
|
||||||
|
health show the health monitors (NIST repetition count / adaptive proportion, dead rings)
|
||||||
pairs capture the same ring index in two generators simultaneously and test for correlation
|
pairs capture the same ring index in two generators simultaneously and test for correlation
|
||||||
|
|
||||||
Entropy per bit is estimated from conditional block entropies and converted to a
|
Entropy per bit is estimated from conditional block entropies and converted to a
|
||||||
|
|
@ -21,6 +22,7 @@ deviation grows with sqrt(k). On synthetic captures it overestimates by a factor
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import math
|
import math
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from itertools import combinations
|
from itertools import combinations
|
||||||
|
|
@ -39,6 +41,8 @@ REG_SOURCE_A, REG_DECIM_HIGH, REG_DECIM_LOW = 0x08, 0x09, 0x0A
|
||||||
REG_CONTROL, REG_STATUS, REG_SOURCE_B, REG_MODE = 0x0B, 0x0C, 0x0D, 0x0E
|
REG_CONTROL, REG_STATUS, REG_SOURCE_B, REG_MODE = 0x0B, 0x0C, 0x0D, 0x0E
|
||||||
REG_RING_SELECT, REG_FREQ_COUNT3 = 0x11, 0x12
|
REG_RING_SELECT, REG_FREQ_COUNT3 = 0x11, 0x12
|
||||||
REG_GEN_ENABLE, REG_TEMPORAL, REG_FREQ_GEN = 0x16, 0x17, 0x18
|
REG_GEN_ENABLE, REG_TEMPORAL, REG_FREQ_GEN = 0x16, 0x17, 0x18
|
||||||
|
REG_HEALTHY, REG_HEALTH_CTRL, REG_RCT_CUTOFF, REG_APT_HIGH, REG_APT_LOW, REG_MAX_DEAD = 0x20, 0x21, 0x22, 0x23, 0x24, 0x25
|
||||||
|
REG_GEN_HEALTH = 0x30 # + 4g: flags, RCT events, APT events, dead rings
|
||||||
CONTROL_START_FREQ = 0x04
|
CONTROL_START_FREQ = 0x04
|
||||||
STATUS_FREQ_VALID = 0x08
|
STATUS_FREQ_VALID = 0x08
|
||||||
|
|
||||||
|
|
@ -72,8 +76,8 @@ class Board:
|
||||||
while self.serial.readline():
|
while self.serial.readline():
|
||||||
pass
|
pass
|
||||||
self.serial.timeout = 200
|
self.serial.timeout = 200
|
||||||
if not identity.startswith("STRX v3"):
|
if not identity.startswith("STRX v4"):
|
||||||
sys.exit(f"unexpected board identity: {identity!r} (needs the v3 FPGA image)")
|
sys.exit(f"unexpected board identity: {identity!r} (needs the v4 FPGA image)")
|
||||||
self.generators = self.read(REG_GENERATORS)
|
self.generators = self.read(REG_GENERATORS)
|
||||||
self.rings = self.read(REG_RINGS)
|
self.rings = self.read(REG_RINGS)
|
||||||
print(f"board: {identity}, {self.generators} generators x {self.rings} rings")
|
print(f"board: {identity}, {self.generators} generators x {self.rings} rings")
|
||||||
|
|
@ -272,18 +276,45 @@ def run_sweep(board: Board | None, args) -> None:
|
||||||
|
|
||||||
|
|
||||||
def run_output(board: Board | None, args) -> None:
|
def run_output(board: Board | None, args) -> None:
|
||||||
# Longer contexts are undersampled at 256 Kbit and read falsely low on random data.
|
|
||||||
order = min(args.order, 8)
|
|
||||||
temporal = board.read(REG_TEMPORAL) if board else args.temporal
|
temporal = board.read(REG_TEMPORAL) if board else args.temporal
|
||||||
generators = board.generators if board else 4
|
generators = board.generators if board else 4
|
||||||
print(f"temporal XOR = {temporal}; output captured with decimation {temporal}")
|
if not args.onod.exists():
|
||||||
print("ones should be within about +-0.003 of 0.5 (3 sigma for 256 Kbit)\n")
|
sys.exit(f"{args.onod} not found; build it with `devbox run onod-build`")
|
||||||
print_header()
|
print(f"temporal XOR = {temporal}; {args.bursts} bursts of 32 KB per generator\n")
|
||||||
for g in range(generators):
|
for g in range(generators):
|
||||||
for source, k in [(SOURCE_SPATIAL + g, 1), (SOURCE_OUTPUT + g, temporal)]:
|
source = SOURCE_OUTPUT + g
|
||||||
data = cached_capture(board, args.out / f"output_s{source:02x}_k{k}.bin", source, k)
|
bursts = [cached_capture(board, args.out / f"output_g{g}_t{temporal}_b{b}.bin", source, temporal)
|
||||||
if data is not None:
|
for b in range(args.bursts)]
|
||||||
print_row(source_name(source, 0), k, analyze(to_bits(data), order))
|
if any(b is None for b in bursts):
|
||||||
|
continue
|
||||||
|
path = args.out / f"output_g{g}_t{temporal}.bin"
|
||||||
|
path.write_bytes(b"".join(bursts))
|
||||||
|
report = subprocess.run([str(args.onod), "-f", str(path)], capture_output=True, text=True).stdout
|
||||||
|
lines = report.splitlines()
|
||||||
|
if args.verbose:
|
||||||
|
print(f"generator {g}:\n{report}")
|
||||||
|
else:
|
||||||
|
summary = next((l for l in lines if "tests passed" in l), "no summary")
|
||||||
|
failed = [l.split()[0] for l in lines if l.rstrip().endswith("❌")]
|
||||||
|
print(f"generator {g}: {summary}" + (f" failed: {', '.join(failed)}" if failed else ""))
|
||||||
|
|
||||||
|
|
||||||
|
def run_health(board: Board | None, args) -> None:
|
||||||
|
if board is None:
|
||||||
|
sys.exit("health needs the board")
|
||||||
|
if args.clear:
|
||||||
|
board.write(REG_HEALTH_CTRL, 1)
|
||||||
|
apt = board.read(REG_APT_HIGH) << 8 | board.read(REG_APT_LOW)
|
||||||
|
print(f"cutoffs: repetition {board.read(REG_RCT_CUTOFF)}, adaptive proportion {apt}/1024, "
|
||||||
|
f"dead rings tolerated {board.read(REG_MAX_DEAD)}")
|
||||||
|
healthy = board.read(REG_HEALTHY)
|
||||||
|
print(f"{'gen':>3} {'healthy':>7} {'active':>6} {'swept':>5} {'RCT':>4} {'APT':>4} {'dead':>4}")
|
||||||
|
for g in range(board.generators):
|
||||||
|
flags = board.read(REG_GEN_HEALTH + 4 * g)
|
||||||
|
rct, apt_events, dead = (board.read(REG_GEN_HEALTH + 4 * g + i) for i in (1, 2, 3))
|
||||||
|
yes = lambda bit: "yes" if flags >> bit & 1 else "no"
|
||||||
|
print(f"{g:>3} {('yes' if healthy >> g & 1 else 'NO'):>7} {yes(3):>6} {yes(4):>5} "
|
||||||
|
f"{rct:>4} {apt_events:>4} {dead:>4}")
|
||||||
|
|
||||||
|
|
||||||
def run_pairs(board: Board | None, args) -> None:
|
def run_pairs(board: Board | None, args) -> None:
|
||||||
|
|
@ -332,6 +363,11 @@ def main() -> None:
|
||||||
default=[1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096])
|
default=[1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096])
|
||||||
output = sub.add_parser("output")
|
output = sub.add_parser("output")
|
||||||
output.add_argument("--temporal", type=int, default=5, help="for --analyze-only")
|
output.add_argument("--temporal", type=int, default=5, help="for --analyze-only")
|
||||||
|
output.add_argument("--bursts", type=int, default=8, help="32 KB captures per generator")
|
||||||
|
output.add_argument("--onod", type=Path, default=Path(".build/onod3000/release/onod3000"))
|
||||||
|
output.add_argument("--verbose", action="store_true", help="print the full onod3000 report")
|
||||||
|
health = sub.add_parser("health")
|
||||||
|
health.add_argument("--clear", action="store_true", help="clear failures and event counters first")
|
||||||
pairs = sub.add_parser("pairs")
|
pairs = sub.add_parser("pairs")
|
||||||
pairs.add_argument("--rings", type=int, nargs="+", default=list(range(0, 90, 9)))
|
pairs.add_argument("--rings", type=int, nargs="+", default=list(range(0, 90, 9)))
|
||||||
pairs.add_argument("-k", type=int, default=64, help="decimation")
|
pairs.add_argument("-k", type=int, default=64, help="decimation")
|
||||||
|
|
@ -340,7 +376,7 @@ def main() -> None:
|
||||||
args.out.mkdir(parents=True, exist_ok=True)
|
args.out.mkdir(parents=True, exist_ok=True)
|
||||||
board = None if args.analyze_only else Board(args.port)
|
board = None if args.analyze_only else Board(args.port)
|
||||||
handlers = {"freq": run_freq, "rings": run_rings, "sweep": run_sweep,
|
handlers = {"freq": run_freq, "rings": run_rings, "sweep": run_sweep,
|
||||||
"output": run_output, "pairs": run_pairs}
|
"output": run_output, "health": run_health, "pairs": run_pairs}
|
||||||
handlers[args.command](board, args)
|
handlers[args.command](board, args)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue