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