Skip to main content

logicaffeine_compile/codegen_sva/
arbiter_rtl.rs

1//! arbiter_rtl — the deterministic NoC arbiter's entropy core in silicon.
2//!
3//! The handout claims a "deterministic (seeded, reproducible) interconnect" — the same
4//! seeded scheduler our concurrency runtime already ships
5//! (`logicaffeine_system::concurrency::seeded_pick`, a SplitMix64 draw). This module
6//! emits that draw source as synthesizable RTL and proves, under iverilog, that the
7//! hardware draw sequence is **byte-identical** to the software scheduler's for the same
8//! seed — so a message-passing schedule replays identically whether it ran on the CPU
9//! runtime or the chip's NoC.
10//!
11//! The `below(n)` reduction (`draw % n`, with the runtime's "n<=1 ⇒ 0 without drawing"
12//! rule) is a thin wrapper over this core; the core IS the reproducible entropy, so it
13//! is what the differential pins.
14
15/// The SplitMix64 golden constants — IDENTICAL to
16/// `logicaffeine_system::concurrency::SplitMix64`.
17pub const SM64_GAMMA: u64 = 0x9E37_79B9_7F4A_7C15;
18pub const SM64_MIX1: u64 = 0xBF58_476D_1CE4_E5B9;
19pub const SM64_MIX2: u64 = 0x94D0_49BB_1331_11EB;
20
21/// The software SplitMix64 draw — mirrors the runtime, the oracle the RTL is checked
22/// against.
23pub fn sm64_next(state: &mut u64) -> u64 {
24    *state = state.wrapping_add(SM64_GAMMA);
25    let mut z = *state;
26    z = (z ^ (z >> 30)).wrapping_mul(SM64_MIX1);
27    z = (z ^ (z >> 27)).wrapping_mul(SM64_MIX2);
28    z ^ (z >> 31)
29}
30
31/// Emit the clocked SplitMix64 draw core: on each `step`, advance the state and present
32/// the mixed 64-bit draw on `draw` with `draw_valid`. Combinational mixing, registered
33/// state — one draw per requested step, deterministic from the loaded `seed`.
34pub fn emit_seeded_arbiter_verilog() -> String {
35    format!(
36        r#"// seeded_arbiter_prng — the NoC arbiter's deterministic entropy core.
37// SplitMix64, bit-identical to logicaffeine_system::concurrency::SplitMix64.
38module seeded_arbiter_prng(
39  input clk,
40  input rst,
41  input [63:0] seed,
42  input load,
43  input step,
44  output reg [63:0] draw,
45  output reg draw_valid
46);
47  reg [63:0] state;
48  // Combinational mix of the NEXT state (state + gamma).
49  wire [63:0] s1 = state + 64'h{gamma:016x};
50  wire [63:0] z1 = (s1 ^ (s1 >> 30)) * 64'h{mix1:016x};
51  wire [63:0] z2 = (z1 ^ (z1 >> 27)) * 64'h{mix2:016x};
52  wire [63:0] mixed = z2 ^ (z2 >> 31);
53  always @(posedge clk) begin
54    if (rst) begin
55      state <= 64'd0; draw <= 64'd0; draw_valid <= 1'b0;
56    end else begin
57      draw_valid <= 1'b0;
58      if (load) begin
59        state <= seed;
60      end else if (step) begin
61        state <= s1;
62        draw <= mixed;
63        draw_valid <= 1'b1;
64      end
65    end
66  end
67endmodule
68"#,
69        gamma = SM64_GAMMA,
70        mix1 = SM64_MIX1,
71        mix2 = SM64_MIX2,
72    )
73}
74
75/// A self-checking testbench: load a seed, step N times, and compare each hardware draw
76/// against the software oracle sequence from `arbiter_seq.hex`. Prints PASS/FAIL.
77pub fn emit_seeded_arbiter_testbench(n: usize) -> String {
78    format!(
79        r#"// tb_seeded_arbiter — HW SplitMix64 draws vs the software scheduler oracle.
80`timescale 1ns/1ps
81module tb_seeded_arbiter;
82  reg clk = 0; always #5 clk = ~clk;
83  reg rst = 1, load = 0, step = 0;
84  reg [63:0] seed;
85  reg [63:0] oracle [0:{last}];
86  wire [63:0] draw; wire draw_valid;
87  integer got = 0, errors = 0;
88  seeded_arbiter_prng dut(.clk(clk), .rst(rst), .seed(seed), .load(load), .step(step),
89                          .draw(draw), .draw_valid(draw_valid));
90  always @(posedge clk) if (draw_valid) begin
91    if (draw !== oracle[got]) begin errors = errors + 1; $display("FAIL draw %0d", got); end
92    got = got + 1;
93  end
94  initial begin
95    $readmemh("arbiter_seq.hex", oracle);
96    seed = 64'h{seed:016x};
97    rst = 1; repeat (3) @(posedge clk); #1 rst = 0;
98    @(posedge clk); #1 load = 1;
99    @(posedge clk); #1 load = 0;
100    repeat ({n}) begin @(posedge clk); #1 step = 1; @(posedge clk); #1 step = 0; end
101    repeat (4) @(posedge clk);
102    if (got !== {n}) $display("FAIL: got %0d of {n} draws", got);
103    else if (errors == 0) $display("PASS: {n} HW SplitMix64 draws match the software scheduler bit-exact");
104    $finish;
105  end
106endmodule
107"#,
108        last = n - 1,
109        n = n,
110        seed = ARBITER_TEST_SEED,
111    )
112}
113
114/// The fixed seed the arbiter differential uses (a stand-in for `LOGOS_SEED`).
115pub const ARBITER_TEST_SEED: u64 = 0x1234_5678_9ABC_DEF0;
116
117#[cfg(test)]
118#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_sva/arbiter_rtl/tests.rs"]
119mod tests;