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

109
fpga/rtl/amp_path.v Normal file
View file

@ -0,0 +1,109 @@
// One amplifier path: the generator streams are merged into one stream, which then passes
// three stages in series. Merging before amplifying is equivalent to amplifying each stream
// with the same first stage and merging after (the random walk is distributive), and needs a
// third of the stages. Amplified bits are tallied per sub-trial and per trial.
module amp_path #(
parameter GENERATORS = 4
) (
input wire clk,
input wire restart,
input wire [GENERATORS-1:0] in_valid,
input wire [GENERATORS-1:0] in_bit,
input wire [2:0] stage_mode,
input wire [47:0] stage_param, // stage s at [16*s +: 16]
input wire sub_end, // pulse: close the current sub-trial
input wire [3:0] sub_index, // sub-trial being closed
input wire trial_end, // pulse: start a new trial (after the last sub_end)
output reg [31:0] trial_bits, // amplified bits in the current trial
output reg [31:0] trial_ones,
output reg [15:0] votes, // bit i: sub-trial i voted 1
output reg [15:0] voted, // bit i: sub-trial i had a majority
output reg overflow // a merge input arrived before the last was taken
);
// Merge: each generator's latest bit waits in its own slot; one slot is drained per clock,
// lowest index first. Generators deliver at most one bit per `temporal` (>= 1, default 5)
// clocks in lockstep, so with up to 4 generators the slots never overflow at temporal >= 4
// (flagged if they do).
reg [GENERATORS-1:0] pending, pending_bit, take;
reg merged_valid, merged_bit;
integer i;
always @(*) begin
take = {GENERATORS{1'b0}};
for (i = 0; i < GENERATORS; i = i + 1)
if (pending[i] && take == {GENERATORS{1'b0}}) take[i] = 1'b1;
end
always @(posedge clk) begin
merged_valid <= |take && !restart;
for (i = 0; i < GENERATORS; i = i + 1)
if (take[i]) merged_bit <= pending_bit[i];
if (restart) begin
pending <= {GENERATORS{1'b0}};
overflow <= 1'b0;
end else begin
for (i = 0; i < GENERATORS; i = i + 1) begin
if (in_valid[i]) begin
if (pending[i] && !take[i]) overflow <= 1'b1;
pending[i] <= 1'b1;
pending_bit[i] <= in_bit[i];
end else if (take[i]) begin
pending[i] <= 1'b0;
end
end
end
end
// Stages
wire first_valid, first_bit, second_valid, second_bit, out_valid, out_bit;
amp_stage first (
.clk(clk), .restart(restart), .mode(stage_mode[0]), .param(stage_param[15:0]),
.in_valid(merged_valid), .in_bit(merged_bit), .out_valid(first_valid), .out_bit(first_bit)
);
amp_stage second (
.clk(clk), .restart(restart), .mode(stage_mode[1]), .param(stage_param[31:16]),
.in_valid(first_valid), .in_bit(first_bit), .out_valid(second_valid), .out_bit(second_bit)
);
amp_stage third (
.clk(clk), .restart(restart), .mode(stage_mode[2]), .param(stage_param[47:32]),
.in_valid(second_valid), .in_bit(second_bit), .out_valid(out_valid), .out_bit(out_bit)
);
// Tallies. A bit arriving in a sub_end or trial_end cycle counts towards the next one, so
// the trial count and the sum of its sub-trial counts can differ by one bit.
reg [23:0] sub_bits, sub_ones;
always @(posedge clk) begin
if (restart || trial_end) begin
trial_bits <= out_valid ? 32'd1 : 32'd0;
trial_ones <= (out_valid && out_bit) ? 32'd1 : 32'd0;
votes <= 16'd0;
voted <= 16'd0;
end else if (out_valid) begin
trial_bits <= trial_bits + 32'd1;
trial_ones <= trial_ones + out_bit;
end
if (restart || sub_end) begin
sub_bits <= out_valid ? 24'd1 : 24'd0;
sub_ones <= (out_valid && out_bit) ? 24'd1 : 24'd0;
end else if (out_valid) begin
sub_bits <= sub_bits + 24'd1;
sub_ones <= sub_ones + out_bit;
end
if (!restart && sub_end && sub_bits != 0 && {sub_ones, 1'b0} != sub_bits) begin
voted[sub_index] <= 1'b1;
votes[sub_index] <= {sub_ones, 1'b0} > sub_bits;
end
end
endmodule

48
fpga/rtl/amp_stage.v Normal file
View file

@ -0,0 +1,48 @@
// One bias amplifier stage. Both modes track the walk position d = ones - zeros.
//
// Random walk (mode 0): reaching d = +param outputs a 1, d = -param outputs a 0, and the walk
// restarts at 0. Stages compose: bounds n1 then n2 amplify like a single bound n1*n2.
//
// Majority vote (mode 1): every param input bits produce one output bit, 1 if d > 0 (use odd
// windows to avoid ties, which output 0).
//
// param = 1 passes bits through unchanged in both modes.
module amp_stage (
input wire clk,
input wire restart,
input wire mode, // 0 random walk, 1 majority vote
input wire [15:0] param, // bound or window, at least 1
input wire in_valid,
input wire in_bit,
output reg out_valid,
output reg out_bit
);
reg signed [16:0] position;
reg [15:0] seen;
wire signed [16:0] next_position = position + (in_bit ? 17'sd1 : -17'sd1);
wire signed [16:0] bound = {1'b0, param};
wire walk_done = next_position == bound || next_position == -bound;
wire vote_done = seen == param - 16'd1;
wire done = mode ? vote_done : walk_done;
always @(posedge clk) begin
out_valid <= 1'b0;
if (restart) begin
position <= 17'sd0;
seen <= 16'd0;
end else if (in_valid) begin
if (done) begin
out_valid <= 1'b1;
out_bit <= next_position > 0;
position <= 17'sd0;
seen <= 16'd0;
end else begin
position <= next_position;
seen <= seen + 16'd1;
end
end
end
endmodule

186
fpga/rtl/bias_amplifier.v Normal file
View file

@ -0,0 +1,186 @@
// Bias amplifier with two paths fed by the selected healthy generators:
// path 0 (bias): the generator bits themselves;
// path 1 (autocorrelation): each bit XNOR the previous bit of the same generator, so
// positive lag-1 autocorrelation becomes an excess of ones.
// An optional von Neumann extractor per generator runs before both paths.
//
// Trials consist of `subtrials` sub-trials of `sub_ms` milliseconds. Each sub-trial votes with
// the majority of its amplified bits (no vote on a tie or without bits); the trial result is
// the majority of the votes.
//
// Snapshot layout (big-endian), latched at each trial end:
// 0-3 trial sequence number
// 4 bits 3:0 generators used during the whole trial, bit 4 von Neumann during the
// whole trial, bit 5 merge overflow since restart (should never be set)
// 5 sub-trials per trial
// 6 sub-trial length in ms
// 7 0
// 8+16p path p: amplified bits (4), ones (4), votes (2), voted (2), vote count (1),
// result (1: bit 0 trial bit, bit 1 trial valid), 0 (2)
// 40-43 trial sequence number again; a mismatch with bytes 0-3 means a torn read
module bias_amplifier #(
parameter GENERATORS = 4,
parameter TICK_CYCLES = 120000 // clk cycles per millisecond
) (
input wire clk,
input wire reset,
input wire [GENERATORS-1:0] gen_bit,
input wire gen_valid, // all generators deliver in lockstep
input wire [GENERATORS-1:0] sources, // selected and healthy generators
input wire von_neumann,
input wire [2:0] stage_mode,
input wire [47:0] stage_param,
input wire [7:0] sub_ms,
input wire [3:0] subtrials,
input wire restart,
input wire [7:0] read_index,
output reg [7:0] read_byte, // one cycle after read_index
output reg trial_end
);
localparam SNAPSHOT_BYTES = 44;
wire start_over = reset || restart;
// Von Neumann (01 -> 0, 10 -> 1) and autocorrelation conversion per generator
reg [GENERATORS-1:0] pair_first, have_first, previous_bit, has_previous;
wire [GENERATORS-1:0] vn_valid = von_neumann ? (gen_valid ? have_first & (gen_bit ^ pair_first) : {GENERATORS{1'b0}})
: {GENERATORS{gen_valid}};
wire [GENERATORS-1:0] vn_bit = von_neumann ? pair_first : gen_bit;
wire [GENERATORS-1:0] bias_valid = vn_valid & sources;
wire [GENERATORS-1:0] ac_valid = vn_valid & has_previous & sources;
wire [GENERATORS-1:0] ac_bit = ~(vn_bit ^ previous_bit);
always @(posedge clk) begin
if (start_over || !von_neumann) begin
have_first <= {GENERATORS{1'b0}};
end else if (gen_valid) begin
have_first <= ~have_first;
pair_first <= gen_bit;
end
if (start_over) begin
has_previous <= {GENERATORS{1'b0}};
end else begin
has_previous <= has_previous | vn_valid;
previous_bit <= (vn_valid & vn_bit) | (~vn_valid & previous_bit);
end
end
// Sub-trial and trial timing
reg [16:0] tick_count;
reg [7:0] ms_count;
reg [3:0] sub_index;
reg sub_end;
wire tick = (tick_count == TICK_CYCLES - 1);
always @(posedge clk) begin
sub_end <= 1'b0;
trial_end <= 1'b0;
if (start_over) begin
tick_count <= 17'd0;
ms_count <= 8'd0;
sub_index <= 4'd0;
end else begin
tick_count <= tick ? 17'd0 : tick_count + 17'd1;
if (tick) begin
if (ms_count + 8'd1 >= sub_ms) begin
ms_count <= 8'd0;
sub_end <= 1'b1;
end else begin
ms_count <= ms_count + 8'd1;
end
end
// trial_end follows the last sub_end by one cycle, once its vote is registered.
if (sub_end) begin
if (sub_index + 4'd1 >= subtrials) begin
sub_index <= 4'd0;
trial_end <= 1'b1;
end else begin
sub_index <= sub_index + 4'd1;
end
end
end
end
// Paths
wire [31:0] trial_bits [0:1];
wire [31:0] trial_ones [0:1];
wire [15:0] votes [0:1];
wire [15:0] voted [0:1];
wire [1:0] overflow;
amp_path #(.GENERATORS(GENERATORS)) bias_path (
.clk(clk), .restart(start_over), .in_valid(bias_valid), .in_bit(vn_bit),
.stage_mode(stage_mode), .stage_param(stage_param),
.sub_end(sub_end), .sub_index(sub_index), .trial_end(trial_end),
.trial_bits(trial_bits[0]), .trial_ones(trial_ones[0]),
.votes(votes[0]), .voted(voted[0]), .overflow(overflow[0])
);
amp_path #(.GENERATORS(GENERATORS)) autocorrelation_path (
.clk(clk), .restart(start_over), .in_valid(ac_valid), .in_bit(ac_bit),
.stage_mode(stage_mode), .stage_param(stage_param),
.sub_end(sub_end), .sub_index(sub_index), .trial_end(trial_end),
.trial_bits(trial_bits[1]), .trial_ones(trial_ones[1]),
.votes(votes[1]), .voted(voted[1]), .overflow(overflow[1])
);
// Snapshot
function [4:0] count_ones(input [15:0] value);
integer b;
begin
count_ones = 5'd0;
for (b = 0; b < 16; b = b + 1) count_ones = count_ones + value[b];
end
endfunction
function [127:0] path_bytes(input [31:0] bits, input [31:0] ones, input [15:0] vote, input [15:0] cast);
reg [4:0] cast_count, one_votes;
reg valid, result;
begin
cast_count = count_ones(cast);
one_votes = count_ones(vote & cast);
valid = cast_count != 0 && {one_votes, 1'b0} != {1'b0, cast_count};
result = {one_votes, 1'b0} > {1'b0, cast_count};
path_bytes = {bits, ones, vote, cast, 3'd0, cast_count, 6'd0, valid, result, 16'd0};
end
endfunction
reg [31:0] sequence_number;
reg [GENERATORS-1:0] sources_during;
reg von_neumann_during;
reg [7:0] snapshot [0:SNAPSHOT_BYTES-1]; // byte 0 first, indexed directly
wire [8*SNAPSHOT_BYTES-1:0] snapshot_next = {
sequence_number,
2'd0, |overflow, von_neumann_during & von_neumann, sources_during & sources,
4'd0, subtrials, sub_ms, 8'd0,
path_bytes(trial_bits[0], trial_ones[0], votes[0], voted[0]),
path_bytes(trial_bits[1], trial_ones[1], votes[1], voted[1]),
sequence_number
};
integer k;
always @(posedge clk) begin
if (start_over) begin
sequence_number <= 32'd0;
sources_during <= sources;
von_neumann_during <= von_neumann;
end else if (trial_end) begin
for (k = 0; k < SNAPSHOT_BYTES; k = k + 1)
snapshot[k] <= snapshot_next[8*(SNAPSHOT_BYTES-1-k) +: 8];
sequence_number <= sequence_number + 32'd1;
sources_during <= sources;
von_neumann_during <= von_neumann;
end else begin
sources_during <= sources_during & sources;
von_neumann_during <= von_neumann_during & von_neumann;
end
read_byte <= (read_index < SNAPSHOT_BYTES) ? snapshot[read_index] : 8'h00;
end
endmodule

View file

@ -86,9 +86,10 @@ module stats_block #(
reg [3:0] healthy_during;
reg von_neumann_during;
// The snapshot is latched as one flat byte string (byte 0 in the top bits), so reading it
// is a plain byte multiplexer.
reg [8*SNAPSHOT_BYTES-1:0] snapshot, snapshot_next;
// The snapshot is assembled as one flat byte string (byte 0 in the top bits) and latched
// into a byte array, so reading it is a plain byte multiplexer on the index.
reg [8*SNAPSHOT_BYTES-1:0] snapshot_next;
reg [7:0] snapshot [0:SNAPSHOT_BYTES-1]; // byte 0 first, indexed directly
integer k;
always @(*) begin
@ -100,7 +101,7 @@ module stats_block #(
snapshot_next[31:0] = sequence_number;
end
integer c;
integer c, b;
always @(posedge clk) begin
if (reset || restart) begin
sequence_number <= 32'd0;
@ -129,7 +130,8 @@ module stats_block #(
end
end
if (trial_end) begin
snapshot <= snapshot_next;
for (b = 0; b < SNAPSHOT_BYTES; b = b + 1)
snapshot[b] <= snapshot_next[8*(SNAPSHOT_BYTES-1-b) +: 8];
sequence_number <= sequence_number + 32'd1;
healthy_during <= healthy;
von_neumann_during <= von_neumann;
@ -143,6 +145,6 @@ module stats_block #(
// Snapshot read-out, registered: the byte for read_index appears one cycle later.
always @(posedge clk)
read_byte <= (read_index < SNAPSHOT_BYTES) ? snapshot[8*(SNAPSHOT_BYTES-1-read_index) +: 8] : 8'h00;
read_byte <= (read_index < SNAPSHOT_BYTES) ? snapshot[read_index] : 8'h00;
endmodule

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];