feat: add two-path bias amplifier with random walk and majority vote stages (FPGA v6)

This commit is contained in:
randogoth 2026-10-25 14:59:27 +02:00
parent 5305d66102
commit 8541cce59c
11 changed files with 741 additions and 58 deletions

View file

@ -1,5 +1,6 @@
// Board-independent STRIX logic: entropy generators with health monitors, live statistics,
// source selection, raw capture, frequency counter and the SPI register interface to the SAMD21.
// bias amplifier, 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
@ -18,7 +19,8 @@
//
// Statistics channels 0-3 are the generators' processed outputs, channel 4 is the XOR of the
// healthy generators' outputs. Reading STATS returns the latest trial snapshot (layout in
// stats_block.v) byte by byte, starting at byte 0 in every transaction.
// stats_block.v) byte by byte, starting at byte 0 in every transaction. AMP does the same for
// the bias amplifier's trial snapshot (layout in bias_amplifier.v).
module strix_core #(
parameter CAPTURE_ADDR_WIDTH = 15,
parameter FREQ_GATE_BITS = 24,
@ -35,7 +37,7 @@ module strix_core #(
output wire capture_done
);
localparam [7:0] VERSION = 8'h05;
localparam [7:0] VERSION = 8'h06;
localparam GENERATORS = 4;
localparam [6:0] REG_ID0 = 7'h00, // "STRX"
@ -74,7 +76,15 @@ module strix_core #(
REG_TRIAL_LOW = 7'h27,
REG_STATS_CTRL = 7'h28, // bit 0 von Neumann on the statistics path;
// write bit 1: restart trials, sequence 0
REG_STATS = 7'h40; // burst read of the trial snapshot
REG_STATS = 7'h40, // burst read of the statistics trial snapshot
REG_AMP = 7'h42, // burst read of the amplifier trial snapshot
REG_AMP_CTRL = 7'h50, // bit 0 von Neumann on the amplifier path;
// write bit 1: restart the amplifier, sequence 0
REG_AMP_SOURCES = 7'h51, // generators feeding the amplifier (healthy only)
REG_AMP_SUB_MS = 7'h52, // sub-trial length in ms
REG_AMP_SUBS = 7'h53, // sub-trials per trial, 1-15
REG_AMP_STAGE = 7'h54; // stage s at 0x54 + 3s: mode (0 random walk,
// 1 majority vote), parameter high, parameter low
// 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,
@ -146,6 +156,26 @@ module strix_core #(
stats_valid <= out_valid[0];
end
// Bias amplifier, fed from the same registered generator outputs
reg [GENERATORS-1:0] amp_sources;
reg amp_von_neumann, amp_restart;
reg [7:0] amp_sub_ms;
reg [3:0] amp_subtrials;
reg [2:0] amp_stage_mode;
reg [47:0] amp_stage_param;
reg [7:0] amp_index;
wire [7:0] amp_byte;
wire amp_trial_end;
bias_amplifier #(.GENERATORS(GENERATORS), .TICK_CYCLES(TICK_CYCLES)) amplifier (
.clk(clk), .reset(reset), .gen_bit(stats_bits[GENERATORS-1:0]), .gen_valid(stats_valid),
.sources(amp_sources & stats_healthy), .von_neumann(amp_von_neumann),
.stage_mode(amp_stage_mode), .stage_param(amp_stage_param),
.sub_ms(amp_sub_ms), .subtrials(amp_subtrials), .restart(amp_restart),
.read_index(amp_index), .read_byte(amp_byte), .trial_end(amp_trial_end)
);
stats_block #(.CHANNELS(GENERATORS + 1), .TICK_CYCLES(TICK_CYCLES)) stats (
.clk(clk), .reset(reset), .bits(stats_bits), .valid(stats_valid),
.von_neumann(stats_von_neumann), .trial_ms(trial_ms), .restart(stats_restart),
@ -196,7 +226,7 @@ module strix_core #(
wire selected, rx_valid, rx_is_first, tx_loaded;
wire [7:0] rx_byte;
reg [7:0] tx_byte;
reg [7:0] tx_byte, tx_byte_next;
spi_slave spi (
.clk(clk), .reset(reset),
@ -233,47 +263,67 @@ module strix_core #(
always @(*) begin
case (reg_addr)
REG_ID0: tx_byte = "S";
REG_ID1: tx_byte = "T";
REG_ID2: tx_byte = "R";
REG_ID3: tx_byte = "X";
REG_VERSION: tx_byte = VERSION;
REG_FREQ_GATE: tx_byte = FREQ_GATE_BITS;
REG_GENERATORS: tx_byte = GENERATORS;
REG_RINGS: tx_byte = RINGS_PER_GEN;
REG_SOURCE_A: tx_byte = {4'd0, source_a};
REG_DECIM_HIGH: tx_byte = decimation[15:8];
REG_DECIM_LOW: tx_byte = decimation[7:0];
REG_STATUS: tx_byte = {4'd0, freq_valid, freq_busy, capture_done, capture_busy};
REG_SOURCE_B: tx_byte = {4'd0, source_b};
REG_MODE: tx_byte = {7'd0, pair_mode};
REG_DATA: tx_byte = capture_byte;
REG_RING_SELECT: tx_byte = ring_select;
REG_FREQ_COUNT3: tx_byte = freq_count[31:24];
REG_FREQ_COUNT2: tx_byte = freq_count[23:16];
REG_FREQ_COUNT1: tx_byte = freq_count[15:8];
REG_FREQ_COUNT0: tx_byte = freq_count[7:0];
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};
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;
REG_TRIAL_HIGH: tx_byte = trial_ms[15:8];
REG_TRIAL_LOW: tx_byte = trial_ms[7:0];
REG_STATS_CTRL: tx_byte = {7'd0, stats_von_neumann};
REG_STATS: tx_byte = stats_byte;
default: tx_byte = gen_register(reg_addr);
REG_ID0: tx_byte_next = "S";
REG_ID1: tx_byte_next = "T";
REG_ID2: tx_byte_next = "R";
REG_ID3: tx_byte_next = "X";
REG_VERSION: tx_byte_next = VERSION;
REG_FREQ_GATE: tx_byte_next = FREQ_GATE_BITS;
REG_GENERATORS: tx_byte_next = GENERATORS;
REG_RINGS: tx_byte_next = RINGS_PER_GEN;
REG_SOURCE_A: tx_byte_next = {4'd0, source_a};
REG_DECIM_HIGH: tx_byte_next = decimation[15:8];
REG_DECIM_LOW: tx_byte_next = decimation[7:0];
REG_STATUS: tx_byte_next = {4'd0, freq_valid, freq_busy, capture_done, capture_busy};
REG_SOURCE_B: tx_byte_next = {4'd0, source_b};
REG_MODE: tx_byte_next = {7'd0, pair_mode};
REG_DATA: tx_byte_next = capture_byte;
REG_RING_SELECT: tx_byte_next = ring_select;
REG_FREQ_COUNT3: tx_byte_next = freq_count[31:24];
REG_FREQ_COUNT2: tx_byte_next = freq_count[23:16];
REG_FREQ_COUNT1: tx_byte_next = freq_count[15:8];
REG_FREQ_COUNT0: tx_byte_next = freq_count[7:0];
REG_GEN_ENABLE: tx_byte_next = {4'd0, gen_enable};
REG_TEMPORAL: tx_byte_next = {4'd0, temporal};
REG_FREQ_GEN: tx_byte_next = {6'd0, freq_gen};
REG_HEALTHY: tx_byte_next = {4'd0, healthy};
REG_RCT_CUTOFF: tx_byte_next = rct_cutoff;
REG_APT_HIGH: tx_byte_next = {5'd0, apt_cutoff[10:8]};
REG_APT_LOW: tx_byte_next = apt_cutoff[7:0];
REG_MAX_DEAD: tx_byte_next = max_dead;
REG_TRIAL_HIGH: tx_byte_next = trial_ms[15:8];
REG_TRIAL_LOW: tx_byte_next = trial_ms[7:0];
REG_STATS_CTRL: tx_byte_next = {7'd0, stats_von_neumann};
REG_STATS: tx_byte_next = stats_byte;
REG_AMP: tx_byte_next = amp_byte;
REG_AMP_CTRL: tx_byte_next = {7'd0, amp_von_neumann};
REG_AMP_SOURCES: tx_byte_next = {4'd0, amp_sources};
REG_AMP_SUB_MS: tx_byte_next = amp_sub_ms;
REG_AMP_SUBS: tx_byte_next = {4'd0, amp_subtrials};
7'h54: tx_byte_next = {7'd0, amp_stage_mode[0]};
7'h55: tx_byte_next = amp_stage_param[15:8];
7'h56: tx_byte_next = amp_stage_param[7:0];
7'h57: tx_byte_next = {7'd0, amp_stage_mode[1]};
7'h58: tx_byte_next = amp_stage_param[31:24];
7'h59: tx_byte_next = amp_stage_param[23:16];
7'h5A: tx_byte_next = {7'd0, amp_stage_mode[2]};
7'h5B: tx_byte_next = amp_stage_param[47:40];
7'h5C: tx_byte_next = amp_stage_param[39:32];
default: tx_byte_next = gen_register(reg_addr);
endcase
end
// Registered read-back: the next byte is needed only after half an SCK period, long after
// the command byte has set reg_addr, so a cycle of latency keeps this wide multiplexer off
// the SPI timing path.
always @(posedge clk) tx_byte <= tx_byte_next;
always @(posedge clk) begin
capture_start <= 1'b0;
freq_start <= 1'b0;
health_clear <= 1'b0;
stats_restart <= 1'b0;
amp_restart <= 1'b0;
if (reset) begin
gen_enable <= {GENERATORS{1'b1}};
temporal <= 4'd5;
@ -290,11 +340,23 @@ module strix_core #(
trial_ms <= 16'd200;
stats_von_neumann <= 1'b0;
stats_index <= 8'd0;
amp_sources <= {GENERATORS{1'b1}};
amp_von_neumann <= 1'b0;
amp_sub_ms <= 8'd40;
amp_subtrials <= 4'd5;
amp_stage_mode <= 3'b000;
amp_stage_param <= {16'd4, 16'd8, 16'd8}; // random walk 8 x 8 x 4 = bound 256
amp_index <= 8'd0;
is_read <= 1'b0;
reg_addr <= 7'd0;
bytes_read <= {CAPTURE_ADDR_WIDTH{1'b0}};
prefetch_addr <= {CAPTURE_ADDR_WIDTH{1'b0}};
end else begin
if (!selected)
amp_index <= 8'd0;
else if (tx_loaded && is_read && reg_addr == REG_AMP)
amp_index <= amp_index + 8'd1;
if (!selected)
stats_index <= 8'd0;
else if (tx_loaded && is_read && reg_addr == REG_STATS)
@ -329,6 +391,22 @@ module strix_core #(
REG_MAX_DEAD: max_dead <= rx_byte;
REG_TRIAL_HIGH: trial_ms[15:8] <= rx_byte;
REG_TRIAL_LOW: trial_ms[7:0] <= rx_byte;
REG_AMP_CTRL: begin
amp_von_neumann <= rx_byte[0];
amp_restart <= rx_byte[1];
end
REG_AMP_SOURCES: amp_sources <= rx_byte[GENERATORS-1:0];
REG_AMP_SUB_MS: if (rx_byte != 8'd0) amp_sub_ms <= rx_byte;
REG_AMP_SUBS: if (rx_byte >= 8'd1 && rx_byte <= 8'd15) amp_subtrials <= rx_byte[3:0];
7'h54: amp_stage_mode[0] <= rx_byte[0];
7'h55: amp_stage_param[15:8] <= rx_byte;
7'h56: amp_stage_param[7:0] <= rx_byte;
7'h57: amp_stage_mode[1] <= rx_byte[0];
7'h58: amp_stage_param[31:24] <= rx_byte;
7'h59: amp_stage_param[23:16] <= rx_byte;
7'h5A: amp_stage_mode[2] <= rx_byte[0];
7'h5B: amp_stage_param[47:40] <= rx_byte;
7'h5C: amp_stage_param[39:32] <= rx_byte;
REG_STATS_CTRL: begin
stats_von_neumann <= rx_byte[0];
stats_restart <= rx_byte[1];