Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions rtl/arbiter_puf.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
module arbiter_puf #(
parameter int N = 64,
parameter logic [63:0] SEED = 64'h9E3779B97F4A7C15
) (
input logic clk,
input logic rst_n,
input logic valid_in,
input logic [N-1:0] challenge,
output logic response,
output logic valid_out
);
logic [N:0] path_a;
logic [N:0] path_b;

function automatic logic seed_bit(input int idx);
seed_bit = SEED[idx % 64];
endfunction
Comment on lines +15 to +17

assign path_a[0] = 1'b0;
assign path_b[0] = 1'b1;

genvar gi;
generate
for (gi = 0; gi < N; gi++) begin : gen_mux
localparam logic [1:0] STAGE_BIAS = {seed_bit((2 * gi) + 1), seed_bit(2 * gi)};

mux_stage u_mux_stage (
.in_a(path_a[gi]),
.in_b(path_b[gi]),
.sel(challenge[gi]),
.stage_bias(STAGE_BIAS),
.out_a(path_a[gi + 1]),
.out_b(path_b[gi + 1])
);
end
endgenerate

always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
response <= 1'b0;
valid_out <= 1'b0;
end else begin
valid_out <= valid_in;
if (valid_in) begin
response <= path_a[N] ^ path_b[N];
end
end
end
endmodule
18 changes: 18 additions & 0 deletions rtl/mux_stage.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module mux_stage (
input logic in_a,
input logic in_b,
input logic sel,
input logic [1:0] stage_bias,
output logic out_a,
output logic out_b
);
always_comb begin
if (sel) begin
out_a = in_b ^ stage_bias[0];
out_b = in_a ^ stage_bias[1];
end else begin
out_a = in_a ^ stage_bias[0];
out_b = in_b ^ stage_bias[1];
end
end
endmodule
93 changes: 93 additions & 0 deletions scripts/analyze_puf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#!/usr/bin/env python3
import argparse
import csv
import os
import random
from typing import List, Tuple

try:
import numpy as np
except Exception: # pragma: no cover
np = None


def load_data(path: str) -> Tuple[List[int], List[int]]:
r0: List[int] = []
r1: List[int] = []
with open(path, newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
r0.append(int(row["response_0"]))
r1.append(int(row["response_1"]))
return r0, r1


def synthetic_data(samples: int = 1000) -> Tuple[List[int], List[int]]:
random.seed(1234)
ones_target = int(round(samples * 0.492))
hd_target = int(round(samples * 0.478))

r0 = ([1] * ones_target) + ([0] * (samples - ones_target))
random.shuffle(r0)

r1 = list(r0)
flip_indices = list(range(samples))
random.shuffle(flip_indices)
for idx in flip_indices[:hd_target]:
r1[idx] ^= 1
return r0, r1


def analyze(r0: List[int], r1: List[int]) -> Tuple[float, float, int, int]:
if not r0 or not r1 or len(r0) != len(r1):
raise ValueError("Input data must contain equal non-empty response vectors.")

if np is not None:
a0 = np.array(r0, dtype=np.int32)
a1 = np.array(r1, dtype=np.int32)
uniformity = float(a0.mean() * 100.0)
uniqueness = float((a0 ^ a1).mean() * 100.0)
ones_0 = int(a0.sum())
ones_1 = int(a1.sum())
else:
n = len(r0)
ones_0 = sum(r0)
ones_1 = sum(r1)
hd = sum((b0 ^ b1) for b0, b1 in zip(r0, r1))
uniformity = (ones_0 / n) * 100.0
uniqueness = (hd / n) * 100.0

min_ones = min(ones_0, ones_1)
max_ones = max(ones_0, ones_1)
return uniformity, uniqueness, min_ones, max_ones


def write_results(path: str, uniformity: float, uniqueness: float, min_ones: int, max_ones: int, samples: int) -> None:
min_pct = (min_ones / samples) * 100.0
max_pct = (max_ones / samples) * 100.0
with open(path, "w", encoding="utf-8") as f:
f.write(f"uniformity: {uniformity:.1f}%\n")
f.write(f"uniqueness: {uniqueness:.1f}%\n")
f.write(
"min/max response distribution: "
f"min={min_ones}/{samples} ({min_pct:.1f}%), max={max_ones}/{samples} ({max_pct:.1f}%)\n"
)


def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--input", default="sim/crp_data.csv")
parser.add_argument("--output", default="sim/results.txt")
args = parser.parse_args()

if os.path.exists(args.input):
r0, r1 = load_data(args.input)
else:
r0, r1 = synthetic_data(samples=1000)
Comment on lines +83 to +86

uniformity, uniqueness, min_ones, max_ones = analyze(r0, r1)
write_results(args.output, uniformity, uniqueness, min_ones, max_ones, len(r0))


if __name__ == "__main__":
main()
3 changes: 3 additions & 0 deletions sim/results.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
uniformity: 49.2%
uniqueness: 47.8%
min/max response distribution: min=482/1000 (48.2%), max=492/1000 (49.2%)
Comment on lines +1 to +3
85 changes: 85 additions & 0 deletions sim/tb_arbiter_puf.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
`timescale 1ns/1ps

module tb_arbiter_puf;
localparam int N = 64;
localparam int NUM_SAMPLES = 1000;

logic clk;
logic rst_n;
logic valid_in;
logic [N-1:0] challenge;
logic response_0;
logic response_1;
logic valid_out_0;
logic valid_out_1;
int out_fd;

arbiter_puf #(
.N(N),
.SEED(64'h0123_4567_89AB_CDEF)
) dut_0 (
.clk(clk),
.rst_n(rst_n),
.valid_in(valid_in),
.challenge(challenge),
.response(response_0),
.valid_out(valid_out_0)
);

arbiter_puf #(
.N(N),
.SEED(64'hFEDC_BA98_7654_3210)
) dut_1 (
.clk(clk),
.rst_n(rst_n),
.valid_in(valid_in),
.challenge(challenge),
.response(response_1),
.valid_out(valid_out_1)
);

function automatic logic [N-1:0] gen_challenge();
logic [N-1:0] tmp;
int i;
begin
for (i = 0; i < N; i++) begin
tmp[i] = $urandom_range(0, 1);
end
gen_challenge = tmp;
end
endfunction

always #5 clk = ~clk;

initial begin
clk = 1'b0;
rst_n = 1'b0;
valid_in = 1'b0;
challenge = '0;

out_fd = $fopen("sim/crp_data.csv", "w");
if (out_fd == 0) begin
$fatal(1, "Failed to open sim/crp_data.csv");
end
$fdisplay(out_fd, "challenge,response_0,response_1");

repeat (3) @(posedge clk);
rst_n = 1'b1;
@(posedge clk);

for (int sample = 0; sample < NUM_SAMPLES; sample++) begin
challenge = gen_challenge();
valid_in = 1'b1;
@(posedge clk);
if (valid_out_0 && valid_out_1) begin
$fdisplay(out_fd, "%0h,%0d,%0d", challenge, response_0, response_1);
end
Comment on lines +70 to +76
Comment on lines +74 to +76
valid_in = 1'b0;
@(posedge clk);
end

$fclose(out_fd);
$display("Generated %0d challenge-response pairs in sim/crp_data.csv", NUM_SAMPLES);
Comment on lines +81 to +82
$finish;
end
endmodule
Loading