feat: add per-second GCP-format trials with history, watch and HTTP access (FPGA v10)
This commit is contained in:
parent
d821b77d3b
commit
6bee626b7f
12 changed files with 475 additions and 29 deletions
131
fpga/rtl/gcp_block.v
Normal file
131
fpga/rtl/gcp_block.v
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
// Per-second trials in the format of Global Consciousness Project (GCP) style network analyses,
|
||||
// running alongside the other outputs. Every TRIAL_MS milliseconds (one second) each generator
|
||||
// yields:
|
||||
// - the sum of the first SAMPLE_BITS bits of the second, the GCP trial (expected 100, sd 7.07);
|
||||
// - the sum of all its bits during the second, for a full-rate z-score.
|
||||
// Both use XOR-masked bits: bit i of the second is inverted when i is odd, so a constant bias
|
||||
// cancels. All generators deliver in lockstep, so one bit count serves them all; generators that
|
||||
// were not healthy for the whole second are flagged in the snapshot, not excluded from counting.
|
||||
//
|
||||
// Snapshot layout (big-endian), latched at the end of each second:
|
||||
// 0-3 sequence number, starting at 1 (0: no second completed yet)
|
||||
// 4 generators healthy during the whole second (bit g)
|
||||
// 5 SAMPLE_BITS
|
||||
// 6-7 0
|
||||
// 8-11 bits per generator during the second
|
||||
// 12+5g generator g: sample sum (1), masked ones during the second (4)
|
||||
// 32-35 sequence number again; a mismatch with bytes 0-3 means a torn read
|
||||
module gcp_block #(
|
||||
parameter GENERATORS = 4,
|
||||
parameter SAMPLE_BITS = 200,
|
||||
parameter TRIAL_MS = 1000,
|
||||
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,
|
||||
input wire [GENERATORS-1: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 + 5 * GENERATORS + 4;
|
||||
localparam [7:0] SAMPLE_SIZE = SAMPLE_BITS;
|
||||
|
||||
// Trial timing
|
||||
|
||||
reg [16:0] tick_count;
|
||||
reg [15:0] ms_count;
|
||||
wire tick = (tick_count == TICK_CYCLES - 1);
|
||||
|
||||
always @(posedge clk) begin
|
||||
trial_end <= 1'b0;
|
||||
if (reset) begin
|
||||
tick_count <= 17'd0;
|
||||
ms_count <= 16'd0;
|
||||
end else begin
|
||||
tick_count <= tick ? 17'd0 : tick_count + 17'd1;
|
||||
if (tick && ms_count == TRIAL_MS - 1) begin
|
||||
ms_count <= 16'd0;
|
||||
trial_end <= 1'b1;
|
||||
end else if (tick) begin
|
||||
ms_count <= ms_count + 16'd1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// Counters. As in stats_block, the bit arriving in the trial_end cycle opens the next second.
|
||||
|
||||
reg [31:0] bit_count;
|
||||
reg [7:0] sampled; // bits taken into the sample sums this second
|
||||
reg odd; // the next bit has an odd index in the second
|
||||
reg [7:0] sample_sum [0:GENERATORS-1];
|
||||
reg [31:0] masked_ones [0:GENERATORS-1];
|
||||
reg [31:0] sequence_number;
|
||||
reg [GENERATORS-1:0] healthy_during;
|
||||
|
||||
wire mask = trial_end ? 1'b0 : odd;
|
||||
wire in_sample = trial_end || sampled < SAMPLE_BITS;
|
||||
wire [GENERATORS-1:0] masked = gen_bit ^ {GENERATORS{mask}};
|
||||
|
||||
reg [8*SNAPSHOT_BYTES-1:0] snapshot_next;
|
||||
reg [7:0] snapshot [0:SNAPSHOT_BYTES-1];
|
||||
|
||||
integer k;
|
||||
always @(*) begin
|
||||
snapshot_next = {SNAPSHOT_BYTES{8'h00}};
|
||||
snapshot_next[8*SNAPSHOT_BYTES-1 -: 96] = {sequence_number,
|
||||
{8-GENERATORS{1'b0}}, healthy_during & healthy,
|
||||
SAMPLE_SIZE, 16'd0, bit_count};
|
||||
for (k = 0; k < GENERATORS; k = k + 1)
|
||||
snapshot_next[8*SNAPSHOT_BYTES-1-96-40*k -: 40] = {sample_sum[k], masked_ones[k]};
|
||||
snapshot_next[31:0] = sequence_number;
|
||||
end
|
||||
|
||||
integer g, b;
|
||||
always @(posedge clk) begin
|
||||
if (reset) begin
|
||||
bit_count <= 32'd0;
|
||||
sampled <= 8'd0;
|
||||
odd <= 1'b0;
|
||||
sequence_number <= 32'd1;
|
||||
healthy_during <= {GENERATORS{1'b1}};
|
||||
for (g = 0; g < GENERATORS; g = g + 1) begin
|
||||
sample_sum[g] <= 8'd0;
|
||||
masked_ones[g] <= 32'd0;
|
||||
end
|
||||
end else begin
|
||||
if (gen_valid) begin
|
||||
bit_count <= (trial_end ? 32'd0 : bit_count) + 32'd1;
|
||||
sampled <= in_sample ? (trial_end ? 8'd0 : sampled) + 8'd1 : sampled;
|
||||
odd <= ~mask;
|
||||
for (g = 0; g < GENERATORS; g = g + 1) begin
|
||||
sample_sum[g] <= (trial_end ? 8'd0 : sample_sum[g]) + (in_sample && masked[g]);
|
||||
masked_ones[g] <= (trial_end ? 32'd0 : masked_ones[g]) + masked[g];
|
||||
end
|
||||
end else if (trial_end) begin
|
||||
bit_count <= 32'd0;
|
||||
sampled <= 8'd0;
|
||||
odd <= 1'b0;
|
||||
for (g = 0; g < GENERATORS; g = g + 1) begin
|
||||
sample_sum[g] <= 8'd0;
|
||||
masked_ones[g] <= 32'd0;
|
||||
end
|
||||
end
|
||||
if (trial_end) begin
|
||||
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;
|
||||
end else begin
|
||||
healthy_during <= healthy_during & healthy;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
always @(posedge clk)
|
||||
read_byte <= (read_index < SNAPSHOT_BYTES) ? snapshot[read_index] : 8'h00;
|
||||
|
||||
endmodule
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
// Board-independent STRIX logic: entropy generators with health monitors, entropy byte
|
||||
// stream, live statistics, bias amplifier, source selection, raw capture, frequency counter
|
||||
// stream, live statistics, bias amplifier, per-second GCP-style trials, 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,
|
||||
|
|
@ -20,7 +20,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. AMP does the same for
|
||||
// the bias amplifier's trial snapshot (layout in bias_amplifier.v). Reading STREAM returns
|
||||
// the bias amplifier's trial snapshot (layout in bias_amplifier.v), and GCP for the per-second
|
||||
// trial snapshot (layout in gcp_block.v). Reading STREAM returns
|
||||
// entropy bytes from the stream FIFO, removing each byte once it has been clocked out; read
|
||||
// at most STREAM_LEVEL bytes (an empty FIFO returns 0x00).
|
||||
module strix_core #(
|
||||
|
|
@ -28,7 +29,8 @@ module strix_core #(
|
|||
parameter FREQ_GATE_BITS = 24,
|
||||
parameter RINGS_PER_GEN = 90,
|
||||
parameter DEAD_WINDOW_BITS = 16,
|
||||
parameter TICK_CYCLES = 120000 // clk cycles per millisecond
|
||||
parameter TICK_CYCLES = 120000, // clk cycles per millisecond
|
||||
parameter GCP_TRIAL_MS = 1000
|
||||
) (
|
||||
input wire clk,
|
||||
input wire reset,
|
||||
|
|
@ -41,7 +43,7 @@ module strix_core #(
|
|||
output reg nina_gpio0_in // GPIO0 is driven by the module (to the SAMD21), not to it
|
||||
);
|
||||
|
||||
localparam [7:0] VERSION = 8'h09;
|
||||
localparam [7:0] VERSION = 8'h0A;
|
||||
localparam GENERATORS = 4;
|
||||
|
||||
localparam [6:0] REG_ID0 = 7'h00, // "STRX"
|
||||
|
|
@ -93,6 +95,7 @@ module strix_core #(
|
|||
REG_STATS = 7'h40, // burst read of the statistics trial snapshot
|
||||
REG_STREAM = 7'h44, // burst read of entropy bytes
|
||||
REG_AMP = 7'h42, // burst read of the amplifier trial snapshot
|
||||
REG_GCP = 7'h46, // burst read of the per-second 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)
|
||||
|
|
@ -220,6 +223,18 @@ module strix_core #(
|
|||
.trial_end(trial_end)
|
||||
);
|
||||
|
||||
// Per-second GCP-style trials, fed from the same registered generator outputs
|
||||
|
||||
reg [7:0] gcp_index;
|
||||
wire [7:0] gcp_byte;
|
||||
wire gcp_trial_end;
|
||||
|
||||
gcp_block #(.GENERATORS(GENERATORS), .TRIAL_MS(GCP_TRIAL_MS), .TICK_CYCLES(TICK_CYCLES)) gcp (
|
||||
.clk(clk), .reset(reset), .gen_bit(stats_bits[GENERATORS-1:0]), .gen_valid(stats_valid),
|
||||
.healthy(stats_healthy), .read_index(gcp_index), .read_byte(gcp_byte),
|
||||
.trial_end(gcp_trial_end)
|
||||
);
|
||||
|
||||
// Source selection
|
||||
|
||||
reg [3:0] source_a, source_b;
|
||||
|
|
@ -367,6 +382,7 @@ module strix_core #(
|
|||
REG_DATA: burst_byte = capture_byte;
|
||||
REG_STATS: burst_byte = stats_byte;
|
||||
REG_AMP: burst_byte = amp_byte;
|
||||
REG_GCP: burst_byte = gcp_byte;
|
||||
REG_STREAM: burst_byte = stream_available ? stream_head : 8'h00;
|
||||
default: begin
|
||||
burst_byte = 8'h00;
|
||||
|
|
@ -411,6 +427,7 @@ module strix_core #(
|
|||
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;
|
||||
gcp_index <= 8'd0;
|
||||
stream_source <= 3'd4;
|
||||
nina_enable <= 1'b0;
|
||||
nina_gpio0_in <= 1'b0;
|
||||
|
|
@ -425,6 +442,11 @@ module strix_core #(
|
|||
else if (tx_loaded && is_read && reg_addr == REG_AMP)
|
||||
amp_index <= amp_index + 8'd1;
|
||||
|
||||
if (!selected)
|
||||
gcp_index <= 8'd0;
|
||||
else if (tx_loaded && is_read && reg_addr == REG_GCP)
|
||||
gcp_index <= gcp_index + 8'd1;
|
||||
|
||||
if (!selected)
|
||||
stats_index <= 8'd0;
|
||||
else if (tx_loaded && is_read && reg_addr == REG_STATS)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// Testbench for strix_core: register access, generator XOR logic checked against the sampled
|
||||
// ring outputs, captures read back across transactions, generator enable, the frequency
|
||||
// counter, the health monitors with injected faults, the statistics snapshots against a
|
||||
// reference model, the bias amplifier's bit counts in several configurations, and the entropy
|
||||
// byte stream against the FIFO contents. Rings are replaced by the SIM model in ring_osc.v.
|
||||
// reference model, the bias amplifier's bit counts in several configurations, the entropy
|
||||
// byte stream against the FIFO contents, and the per-second GCP trials against a reference model. Rings are replaced by the SIM model in ring_osc.v.
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
module tb_strix_core;
|
||||
|
|
@ -21,14 +21,18 @@ module tb_strix_core;
|
|||
REG_STATS_CTRL = 7'h28, REG_STATS = 7'h40, REG_AMP = 7'h42,
|
||||
REG_AMP_CTRL = 7'h50, REG_AMP_SOURCES = 7'h51, REG_AMP_SUB_MS = 7'h52,
|
||||
REG_AMP_SUBS = 7'h53, REG_AMP_STAGE = 7'h54, REG_STREAM_CTRL = 7'h29,
|
||||
REG_LEVEL_HIGH = 7'h2A, REG_LEVEL_LOW = 7'h2B, REG_STREAM = 7'h44;
|
||||
REG_LEVEL_HIGH = 7'h2A, REG_LEVEL_LOW = 7'h2B, REG_STREAM = 7'h44,
|
||||
REG_GCP = 7'h46;
|
||||
localparam AMP_SNAPSHOT_BYTES = 44;
|
||||
localparam GCP_SNAPSHOT_BYTES = 36;
|
||||
localparam SNAPSHOT_BYTES = 72;
|
||||
localparam TICK_CYCLES = 1500; // 1 "ms" = 12.5 us: a 10 "ms" trial outlasts a 72-byte read
|
||||
localparam TRIAL_BITS = 10 * TICK_CYCLES / 5; // 10 "ms" trials at temporal 5
|
||||
// Amplifier trials in the tests: 3 sub-trials of 2 "ms" -> 9000 clk cycles -> 1800 output
|
||||
// bits per generator at temporal 5
|
||||
localparam AMP_GEN_BITS = 3 * 2 * TICK_CYCLES / 5;
|
||||
localparam GCP_TRIAL_MS = 10; // shortened "second": GCP_TRIAL_BITS bits per generator
|
||||
localparam GCP_TRIAL_BITS = GCP_TRIAL_MS * TICK_CYCLES / 5;
|
||||
|
||||
// Per-generator health registers
|
||||
function [6:0] gen_reg(input integer generator, input integer index);
|
||||
|
|
@ -44,7 +48,7 @@ module tb_strix_core;
|
|||
wire miso, done;
|
||||
|
||||
strix_core #(.CAPTURE_ADDR_WIDTH(CAPTURE_ADDR_WIDTH), .FREQ_GATE_BITS(14), .RINGS_PER_GEN(RINGS),
|
||||
.DEAD_WINDOW_BITS(6), .TICK_CYCLES(TICK_CYCLES)) dut (
|
||||
.DEAD_WINDOW_BITS(6), .TICK_CYCLES(TICK_CYCLES), .GCP_TRIAL_MS(GCP_TRIAL_MS)) dut (
|
||||
.clk(clk), .reset(reset),
|
||||
.spi_sck(sck), .spi_mosi(mosi), .spi_ss_n(ss_n), .spi_miso(miso),
|
||||
.capture_done(done)
|
||||
|
|
@ -121,6 +125,48 @@ module tb_strix_core;
|
|||
end
|
||||
end
|
||||
|
||||
// Reference model of the GCP trials: bit i of a second is inverted when i is odd, and the
|
||||
// first 200 masked bits form the sample sum.
|
||||
reg [31:0] gcp_model_bits = 0;
|
||||
reg [7:0] gcp_model_sum [0:3];
|
||||
reg [31:0] gcp_model_ones [0:3];
|
||||
reg [31:0] gcp_latched_bits;
|
||||
reg [7:0] gcp_latched_sum [0:3];
|
||||
reg [31:0] gcp_latched_ones [0:3];
|
||||
integer gm;
|
||||
|
||||
initial for (gm = 0; gm < 4; gm = gm + 1) begin
|
||||
gcp_model_sum[gm] = 0;
|
||||
gcp_model_ones[gm] = 0;
|
||||
end
|
||||
|
||||
always @(posedge clk) if (!reset) begin : gcp_model
|
||||
reg [31:0] index;
|
||||
reg masked;
|
||||
if (dut.gcp_trial_end) begin
|
||||
gcp_latched_bits <= gcp_model_bits;
|
||||
for (gm = 0; gm < 4; gm = gm + 1) begin
|
||||
gcp_latched_sum[gm] <= gcp_model_sum[gm];
|
||||
gcp_latched_ones[gm] <= gcp_model_ones[gm];
|
||||
end
|
||||
end
|
||||
if (dut.gcp.gen_valid) begin
|
||||
index = dut.gcp_trial_end ? 0 : gcp_model_bits;
|
||||
gcp_model_bits <= index + 1;
|
||||
for (gm = 0; gm < 4; gm = gm + 1) begin
|
||||
masked = dut.gcp.gen_bit[gm] ^ index[0];
|
||||
gcp_model_ones[gm] <= (dut.gcp_trial_end ? 0 : gcp_model_ones[gm]) + masked;
|
||||
gcp_model_sum[gm] <= (dut.gcp_trial_end ? 0 : gcp_model_sum[gm]) + (index < 200 && masked);
|
||||
end
|
||||
end else if (dut.gcp_trial_end) begin
|
||||
gcp_model_bits <= 0;
|
||||
for (gm = 0; gm < 4; gm = gm + 1) begin
|
||||
gcp_model_sum[gm] <= 0;
|
||||
gcp_model_ones[gm] <= 0;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
reg [7:0] snapshot [0:SNAPSHOT_BYTES-1];
|
||||
|
||||
function [31:0] snapshot_word(input integer index);
|
||||
|
|
@ -282,7 +328,7 @@ module tb_strix_core;
|
|||
// Identification
|
||||
read_reg(REG_ID0, v); expect(v, "S", "ID0");
|
||||
read_reg(REG_ID3, v); expect(v, "X", "ID3");
|
||||
read_reg(REG_VERSION, v); expect(v, 8'h09, "VERSION");
|
||||
read_reg(REG_VERSION, v); expect(v, 8'h0A, "VERSION");
|
||||
read_reg(REG_GENERATORS, v); expect(v, 8'd4, "GENERATORS");
|
||||
read_reg(REG_RINGS, v); expect(v, RINGS, "RINGS");
|
||||
read_reg(REG_TEMPORAL, v); expect(v, 8'd5, "TEMPORAL");
|
||||
|
|
@ -459,6 +505,39 @@ module tb_strix_core;
|
|||
read_reg(REG_LEVEL_LOW, v);
|
||||
expect_range(v, 0, 15, "stream level after flush");
|
||||
|
||||
// GCP trials: a full second after the earlier tests, checked against the model
|
||||
@(posedge dut.gcp_trial_end); #200;
|
||||
begin : gcp_read
|
||||
reg [7:0] gcp [0:GCP_SNAPSHOT_BYTES-1];
|
||||
reg [7:0] dummy;
|
||||
integer i;
|
||||
ss_n = 1'b0; #SCK_HALF;
|
||||
spi_byte({1'b1, REG_GCP}, dummy);
|
||||
for (i = 0; i < GCP_SNAPSHOT_BYTES; i = i + 1) spi_byte(8'h00, gcp[i]);
|
||||
#SCK_HALF ss_n = 1'b1; #(4 * SCK_HALF);
|
||||
if ({gcp[0], gcp[1], gcp[2], gcp[3]} !== {gcp[32], gcp[33], gcp[34], gcp[35]}
|
||||
|| {gcp[0], gcp[1], gcp[2], gcp[3]} < 32'd2) begin
|
||||
$display("FAIL GCP sequence %0d", {gcp[0], gcp[1], gcp[2], gcp[3]}); errors = errors + 1;
|
||||
end
|
||||
expect(gcp[4], 8'h0F, "GCP healthy");
|
||||
expect(gcp[5], 8'd200, "GCP sample bits");
|
||||
if ({gcp[8], gcp[9], gcp[10], gcp[11]} !== gcp_latched_bits) begin
|
||||
$display("FAIL GCP bits %0d, model %0d", {gcp[8], gcp[9], gcp[10], gcp[11]}, gcp_latched_bits);
|
||||
errors = errors + 1;
|
||||
end
|
||||
expect_range({gcp[8], gcp[9], gcp[10], gcp[11]}, GCP_TRIAL_BITS - 1, GCP_TRIAL_BITS + 1, "GCP bits");
|
||||
for (i = 0; i < 4; i = i + 1) begin
|
||||
if (gcp[12 + 5 * i] !== gcp_latched_sum[i]
|
||||
|| {gcp[13 + 5 * i], gcp[14 + 5 * i], gcp[15 + 5 * i], gcp[16 + 5 * i]} !== gcp_latched_ones[i]) begin
|
||||
$display("FAIL GCP generator %0d: sum %0d ones %0d, model %0d / %0d", i, gcp[12 + 5 * i],
|
||||
{gcp[13 + 5 * i], gcp[14 + 5 * i], gcp[15 + 5 * i], gcp[16 + 5 * i]},
|
||||
gcp_latched_sum[i], gcp_latched_ones[i]);
|
||||
errors = errors + 1;
|
||||
end
|
||||
expect_range(gcp[12 + 5 * i], 1, 199, "GCP sample sum");
|
||||
end
|
||||
end
|
||||
|
||||
if (xor_checks < 100) begin
|
||||
$display("FAIL only %0d temporal XOR checks ran", xor_checks);
|
||||
errors = errors + 1;
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ set_global_assignment -name VERILOG_FILE ../rtl/entropy_stream.v
|
|||
set_global_assignment -name VERILOG_FILE ../rtl/bias_amplifier.v
|
||||
set_global_assignment -name VERILOG_FILE ../rtl/amp_path.v
|
||||
set_global_assignment -name VERILOG_FILE ../rtl/amp_stage.v
|
||||
set_global_assignment -name VERILOG_FILE ../rtl/gcp_block.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/raw_capture.v
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue