feat: add two-path bias amplifier with random walk and majority vote stages (FPGA v6)
This commit is contained in:
parent
5305d66102
commit
8541cce59c
11 changed files with 741 additions and 58 deletions
97
fpga/sim/tb_amp_stage.v
Normal file
97
fpga/sim/tb_amp_stage.v
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
// Testbench for amp_stage: random input bits against a reference model of both modes, plus a
|
||||
// statistical check that a biased input comes out more biased.
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
module tb_amp_stage;
|
||||
|
||||
reg clk = 1'b0;
|
||||
always #5 clk = ~clk;
|
||||
|
||||
reg restart = 1'b1, mode = 1'b0, in_valid = 1'b0, in_bit = 1'b0;
|
||||
reg [15:0] param = 16'd1;
|
||||
wire out_valid, out_bit;
|
||||
|
||||
amp_stage dut (
|
||||
.clk(clk), .restart(restart), .mode(mode), .param(param),
|
||||
.in_valid(in_valid), .in_bit(in_bit), .out_valid(out_valid), .out_bit(out_bit)
|
||||
);
|
||||
|
||||
integer errors = 0;
|
||||
|
||||
// Reference model, advanced on the same edges as the stage
|
||||
integer model_position, model_seen, model_ones, model_bound, dut_outputs, ones_out;
|
||||
reg model_valid, model_bit;
|
||||
|
||||
always @(posedge clk) begin
|
||||
model_valid = 1'b0;
|
||||
if (restart) begin
|
||||
model_position = 0; model_seen = 0; model_ones = 0;
|
||||
end else if (in_valid) begin
|
||||
if (mode == 1'b0) begin
|
||||
model_bound = param; // signed copy: -param would wrap as unsigned
|
||||
model_position = model_position + (in_bit ? 1 : -1);
|
||||
if (model_position >= model_bound || model_position <= -model_bound) begin
|
||||
model_valid = 1'b1; model_bit = model_position > 0; model_position = 0;
|
||||
end
|
||||
end else begin
|
||||
model_seen = model_seen + 1; model_ones = model_ones + in_bit;
|
||||
if (model_seen >= param) begin
|
||||
model_valid = 1'b1; model_bit = 2 * model_ones > model_seen;
|
||||
model_seen = 0; model_ones = 0;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// The model and the stage both update on the rising edge; compare in between.
|
||||
always @(negedge clk) if (!restart) begin
|
||||
if (out_valid !== model_valid || (out_valid && out_bit !== model_bit)) begin
|
||||
if (errors < 5) $display("FAIL at %0t: out %b/%b, model %b/%b", $time, out_valid, out_bit,
|
||||
model_valid, model_bit);
|
||||
errors = errors + 1;
|
||||
end
|
||||
if (out_valid) begin
|
||||
dut_outputs = dut_outputs + 1;
|
||||
ones_out = ones_out + out_bit;
|
||||
end
|
||||
end
|
||||
|
||||
task run(input m, input [15:0] p, input integer cycles, input integer probability_permille);
|
||||
integer i;
|
||||
begin
|
||||
@(negedge clk);
|
||||
mode = m; param = p; restart = 1'b1;
|
||||
@(negedge clk);
|
||||
restart = 1'b0; dut_outputs = 0; ones_out = 0;
|
||||
for (i = 0; i < cycles; i = i + 1) begin
|
||||
in_valid = ({$random} % 3) != 0;
|
||||
in_bit = ({$random} % 1000) < probability_permille;
|
||||
@(negedge clk);
|
||||
end
|
||||
in_valid = 1'b0;
|
||||
@(negedge clk);
|
||||
end
|
||||
endtask
|
||||
|
||||
initial begin
|
||||
run(1'b0, 16'd1, 2000, 500);
|
||||
run(1'b0, 16'd5, 20000, 500);
|
||||
run(1'b0, 16'd16, 50000, 500);
|
||||
run(1'b1, 16'd1, 2000, 500);
|
||||
run(1'b1, 16'd7, 20000, 500);
|
||||
run(1'b1, 16'd8, 20000, 500); // even window: ties output 0
|
||||
|
||||
// Amplification: 55% ones in, random walk bound 10 -> about 88% ones out
|
||||
// (P = 1 / (1 + ((1-p)/p)^n) = 1 / (1 + (0.45/0.55)^10) = 0.88)
|
||||
run(1'b0, 16'd10, 400000, 550);
|
||||
if (dut_outputs < 500 || ones_out * 100 < dut_outputs * 83 || ones_out * 100 > dut_outputs * 93) begin
|
||||
$display("FAIL amplification: %0d of %0d outputs are 1", ones_out, dut_outputs);
|
||||
errors = errors + 1;
|
||||
end
|
||||
|
||||
if (errors == 0) $display("PASS (amplified %0d/%0d ones)", ones_out, dut_outputs);
|
||||
else $display("%0d error(s)", errors);
|
||||
$finish;
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
// 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, and the statistics snapshots against a
|
||||
// reference model. Rings are replaced by the SIM model in ring_osc.v.
|
||||
// counter, the health monitors with injected faults, the statistics snapshots against a
|
||||
// reference model, and the bias amplifier's bit counts in several configurations. Rings are replaced by the SIM model in ring_osc.v.
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
module tb_strix_core;
|
||||
|
|
@ -17,10 +17,16 @@ module tb_strix_core;
|
|||
REG_DATA = 7'h10, REG_RING_SELECT = 7'h11, REG_FREQ_COUNT0 = 7'h15,
|
||||
REG_GEN_ENABLE = 7'h16, REG_TEMPORAL = 7'h17, REG_FREQ_GEN = 7'h18,
|
||||
REG_HEALTHY = 7'h20, REG_HEALTH_CTRL = 7'h21, REG_TRIAL_LOW = 7'h27,
|
||||
REG_STATS_CTRL = 7'h28, REG_STATS = 7'h40;
|
||||
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;
|
||||
localparam AMP_SNAPSHOT_BYTES = 44;
|
||||
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;
|
||||
|
||||
// Per-generator health registers
|
||||
function [6:0] gen_reg(input integer generator, input integer index);
|
||||
|
|
@ -219,6 +225,43 @@ module tb_strix_core;
|
|||
end
|
||||
endtask
|
||||
|
||||
reg [7:0] amp [0:AMP_SNAPSHOT_BYTES-1];
|
||||
|
||||
function [31:0] amp_word(input integer index);
|
||||
amp_word = {amp[index], amp[index+1], amp[index+2], amp[index+3]};
|
||||
endfunction
|
||||
|
||||
// Configures the amplifier (stage bounds or windows, random walk), restarts it, waits for its
|
||||
// second trial and reads that trial's snapshot.
|
||||
task amp_trial(input [3:0] sources, input von_neumann, input [15:0] p0, input [15:0] p1, input [15:0] p2);
|
||||
reg [7:0] dummy;
|
||||
integer i;
|
||||
begin
|
||||
write_reg(REG_AMP_SOURCES, sources);
|
||||
write_reg(REG_AMP_STAGE + 1, p0 >> 8); write_reg(REG_AMP_STAGE + 2, p0);
|
||||
write_reg(REG_AMP_STAGE + 4, p1 >> 8); write_reg(REG_AMP_STAGE + 5, p1);
|
||||
write_reg(REG_AMP_STAGE + 7, p2 >> 8); write_reg(REG_AMP_STAGE + 8, p2);
|
||||
write_reg(REG_AMP_CTRL, {6'd0, 1'b1, von_neumann}); // restart
|
||||
@(posedge dut.amp_trial_end); @(posedge dut.amp_trial_end); #200;
|
||||
ss_n = 1'b0; #SCK_HALF;
|
||||
spi_byte({1'b1, REG_AMP}, dummy);
|
||||
for (i = 0; i < AMP_SNAPSHOT_BYTES; i = i + 1) spi_byte(8'h00, amp[i]);
|
||||
#SCK_HALF ss_n = 1'b1; #(4 * SCK_HALF);
|
||||
if (amp_word(0) !== amp_word(AMP_SNAPSHOT_BYTES - 4) || amp_word(0) !== 32'd1) begin
|
||||
$display("FAIL amplifier snapshot sequence %0d/%0d", amp_word(0), amp_word(AMP_SNAPSHOT_BYTES - 4));
|
||||
errors = errors + 1;
|
||||
end
|
||||
if (amp[4][5]) begin $display("FAIL amplifier merge overflow"); errors = errors + 1; end
|
||||
end
|
||||
endtask
|
||||
|
||||
task expect_range(input integer got, input integer low, input integer high, input [8*24-1:0] what);
|
||||
if (got < low || got > high) begin
|
||||
$display("FAIL %0s: %0d not in %0d..%0d", what, got, low, high);
|
||||
errors = errors + 1;
|
||||
end
|
||||
endtask
|
||||
|
||||
task wait_trial_end;
|
||||
begin
|
||||
@(posedge dut.trial_end);
|
||||
|
|
@ -237,7 +280,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'h05, "VERSION");
|
||||
read_reg(REG_VERSION, v); expect(v, 8'h06, "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");
|
||||
|
|
@ -358,6 +401,34 @@ module tb_strix_core;
|
|||
end
|
||||
write_reg(REG_STATS_CTRL, 8'h00);
|
||||
|
||||
// Bias amplifier
|
||||
write_reg(REG_AMP_SUB_MS, 8'd2);
|
||||
write_reg(REG_AMP_SUBS, 8'd3);
|
||||
|
||||
// Passthrough (all bounds 1): every generator bit appears once on the bias path
|
||||
amp_trial(4'hF, 1'b0, 16'd1, 16'd1, 16'd1);
|
||||
expect(amp[4] & 8'h0F, 8'h0F, "amp sources");
|
||||
expect(amp[5], 8'd3, "amp sub-trials");
|
||||
expect_range(amp_word(8), 4 * AMP_GEN_BITS - 8, 4 * AMP_GEN_BITS + 8, "amp passthrough bits");
|
||||
expect_range(amp_word(24), 4 * AMP_GEN_BITS - 8, 4 * AMP_GEN_BITS + 8, "amp autocorr bits");
|
||||
expect_range(amp_word(12), 4 * AMP_GEN_BITS / 3, 4 * AMP_GEN_BITS * 2 / 3, "amp passthrough ones");
|
||||
if (amp[20] == 0) begin $display("FAIL amplifier cast no votes"); errors = errors + 1; end
|
||||
|
||||
// One source only
|
||||
amp_trial(4'h1, 1'b0, 16'd1, 16'd1, 16'd1);
|
||||
expect(amp[4] & 8'h0F, 8'h01, "amp single source");
|
||||
expect_range(amp_word(8), AMP_GEN_BITS - 2, AMP_GEN_BITS + 2, "amp single-source bits");
|
||||
|
||||
// Random walk bound 2 in stage 1: about a quarter of the bits (4 steps per output)
|
||||
amp_trial(4'hF, 1'b0, 16'd2, 16'd1, 16'd1);
|
||||
expect_range(amp_word(8), 4 * AMP_GEN_BITS / 6, 4 * AMP_GEN_BITS / 3, "amp bound-2 bits");
|
||||
|
||||
// Von Neumann keeps about a quarter of the bits
|
||||
amp_trial(4'hF, 1'b1, 16'd1, 16'd1, 16'd1);
|
||||
expect(amp[4] & 8'h10, 8'h10, "amp von Neumann flag");
|
||||
expect_range(amp_word(8), 4 * AMP_GEN_BITS / 6, 4 * AMP_GEN_BITS / 3, "amp von Neumann bits");
|
||||
write_reg(REG_AMP_CTRL, 8'h00);
|
||||
|
||||
if (xor_checks < 100) begin
|
||||
$display("FAIL only %0d temporal XOR checks ran", xor_checks);
|
||||
errors = errors + 1;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue