strix/fpga/rtl/stats_block.v

148 lines
6.1 KiB
Verilog

// Live statistics: back-to-back trials of `trial_ms` milliseconds. During each trial every
// channel counts its bits, ones, and agreements (a bit equal to the bit before it, which may
// be the last bit of the previous trial, so a trial has as many comparisons as bits). At the
// end of a trial the counts are latched into a snapshot and the counters restart in the same
// cycle, so no bit is lost between trials. Bias, z-scores, random walk and lag-1
// autocorrelation all follow from the three counts; rolling and cumulative windows are built
// from consecutive snapshots outside the FPGA.
//
// With von Neumann enabled, each channel's bits pass through a von Neumann extractor (pairs
// 01 -> 0, 10 -> 1, 00 and 11 dropped) before counting.
//
// Snapshot layout (big-endian), read one byte at a time by index:
// 0-3 trial sequence number
// 4 healthy generators during the whole trial (AND over the trial)
// 5 bit 0: von Neumann was enabled for the whole trial
// 6-7 trial length in ms
// 8+12c channel c: bits (4), ones (4), agreements (4)
// 8+12C trial sequence number again; a mismatch with bytes 0-3 means a torn read
module stats_block #(
parameter CHANNELS = 5,
parameter TICK_CYCLES = 120000 // clk cycles per millisecond
) (
input wire clk,
input wire reset,
input wire [CHANNELS-1:0] bits,
input wire valid, // new bits on all channels this cycle
input wire von_neumann,
input wire [15:0] trial_ms,
input wire restart, // start a new trial now, sequence back to 0
input wire [3:0] healthy,
input wire [7:0] read_index,
output reg [7:0] read_byte, // one cycle after read_index
output reg trial_end // pulse: a new snapshot is available
);
localparam SNAPSHOT_BYTES = 12 + 12 * CHANNELS;
// Trial timing
reg [16:0] tick_count;
reg [15:0] ms_count;
wire tick = (tick_count == TICK_CYCLES - 1);
wire trial_due = tick && (ms_count + 16'd1 >= trial_ms);
always @(posedge clk) begin
trial_end <= 1'b0;
if (reset || restart) begin
tick_count <= 17'd0;
ms_count <= 16'd0;
end else begin
tick_count <= tick ? 17'd0 : tick_count + 17'd1;
if (trial_due) begin
ms_count <= 16'd0;
trial_end <= 1'b1;
end else if (tick) begin
ms_count <= ms_count + 16'd1;
end
end
end
// Von Neumann extraction per channel
reg [CHANNELS-1:0] pair_first, have_first;
wire [CHANNELS-1:0] vn_valid = have_first & (bits ^ pair_first);
wire [CHANNELS-1:0] count_valid = von_neumann ? (valid ? vn_valid : {CHANNELS{1'b0}})
: {CHANNELS{valid}};
wire [CHANNELS-1:0] count_bit = von_neumann ? pair_first : bits;
always @(posedge clk) begin
if (reset || !von_neumann) begin
have_first <= {CHANNELS{1'b0}};
end else if (valid) begin
have_first <= ~have_first;
pair_first <= bits;
end
end
// Counters
reg [31:0] bit_count [0:CHANNELS-1];
reg [31:0] one_count [0:CHANNELS-1];
reg [31:0] agree_count [0:CHANNELS-1];
reg [CHANNELS-1:0] previous_bit, has_previous;
reg [31:0] sequence_number;
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;
integer k;
always @(*) begin
snapshot_next = {SNAPSHOT_BYTES{8'h00}};
snapshot_next[8*SNAPSHOT_BYTES-1 -: 64] = {sequence_number, 4'd0, healthy_during & healthy,
7'd0, von_neumann_during & von_neumann, trial_ms};
for (k = 0; k < CHANNELS; k = k + 1)
snapshot_next[8*SNAPSHOT_BYTES-1-64-96*k -: 96] = {bit_count[k], one_count[k], agree_count[k]};
snapshot_next[31:0] = sequence_number;
end
integer c;
always @(posedge clk) begin
if (reset || restart) begin
sequence_number <= 32'd0;
healthy_during <= 4'hF;
von_neumann_during <= von_neumann;
for (c = 0; c < CHANNELS; c = c + 1) begin
bit_count[c] <= 32'd0;
one_count[c] <= 32'd0;
agree_count[c] <= 32'd0;
end
has_previous <= {CHANNELS{1'b0}};
end else begin
for (c = 0; c < CHANNELS; c = c + 1) begin
// The bit arriving in the trial's last cycle belongs to the next trial.
if (count_valid[c]) begin
bit_count[c] <= (trial_end ? 32'd0 : bit_count[c]) + 32'd1;
one_count[c] <= (trial_end ? 32'd0 : one_count[c]) + count_bit[c];
agree_count[c] <= (trial_end ? 32'd0 : agree_count[c])
+ (has_previous[c] && count_bit[c] == previous_bit[c]);
previous_bit[c] <= count_bit[c];
has_previous[c] <= 1'b1;
end else if (trial_end) begin
bit_count[c] <= 32'd0;
one_count[c] <= 32'd0;
agree_count[c] <= 32'd0;
end
end
if (trial_end) begin
snapshot <= snapshot_next;
sequence_number <= sequence_number + 32'd1;
healthy_during <= healthy;
von_neumann_during <= von_neumann;
end else begin
healthy_during <= healthy_during & healthy;
von_neumann_during <= von_neumann_during & von_neumann;
end
end
end
// 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;
endmodule