feat: add FPGA raw jitter capture design with SPI register interface
This commit is contained in:
parent
3ee8a5c3db
commit
0332d8eb33
13 changed files with 936 additions and 2 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -17,3 +17,6 @@ fpga/**/simulation/
|
||||||
|
|
||||||
# Devbox
|
# Devbox
|
||||||
.devbox/
|
.devbox/
|
||||||
|
|
||||||
|
# Python virtualenv created by devbox
|
||||||
|
.venv/
|
||||||
|
|
|
||||||
16
devbox.json
16
devbox.json
|
|
@ -1,6 +1,13 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.17.2/.schema/devbox.schema.json",
|
"$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.17.2/.schema/devbox.schema.json",
|
||||||
"packages": ["arduino-cli@latest", "path:./nix#quartus"],
|
"packages": [
|
||||||
|
"arduino-cli@latest",
|
||||||
|
"path:./nix#quartus",
|
||||||
|
"python312@latest",
|
||||||
|
"python312Packages.pyserial@latest",
|
||||||
|
"python312Packages.numpy@latest",
|
||||||
|
"iverilog@latest"
|
||||||
|
],
|
||||||
"shell": {
|
"shell": {
|
||||||
"init_hook": [
|
"init_hook": [
|
||||||
"echo 'Welcome to devbox!' > /dev/null"
|
"echo 'Welcome to devbox!' > /dev/null"
|
||||||
|
|
@ -14,6 +21,11 @@
|
||||||
"build": [
|
"build": [
|
||||||
"arduino-cli compile --fqbn arduino:samd:mkrvidor4000 arduino/strix"
|
"arduino-cli compile --fqbn arduino:samd:mkrvidor4000 arduino/strix"
|
||||||
],
|
],
|
||||||
|
"sim": [
|
||||||
|
"mkdir -p /tmp/strix-sim",
|
||||||
|
"iverilog -g2005 -DSIM -o /tmp/strix-sim/tb_strix_core fpga/sim/tb_strix_core.v fpga/rtl/strix_core.v fpga/rtl/entropy_gen.v fpga/rtl/ring_osc.v fpga/rtl/raw_capture.v fpga/rtl/spi_slave.v",
|
||||||
|
"vvp -n /tmp/strix-sim/tb_strix_core"
|
||||||
|
],
|
||||||
"fpga": [
|
"fpga": [
|
||||||
"cd fpga/src && quartus_sh --flow compile strix"
|
"cd fpga/src && quartus_sh --flow compile strix"
|
||||||
]
|
]
|
||||||
|
|
|
||||||
213
devbox.lock
213
devbox.lock
|
|
@ -52,6 +52,219 @@
|
||||||
"github:NixOS/nixpkgs/nixpkgs-unstable": {
|
"github:NixOS/nixpkgs/nixpkgs-unstable": {
|
||||||
"last_modified": "2026-09-29T03:29:29Z",
|
"last_modified": "2026-09-29T03:29:29Z",
|
||||||
"resolved": "github:NixOS/nixpkgs/b6c8664de9b6cc07fe5666a29f91884ba81197c4?lastModified=1790652569&narHash=sha256-641r7xrlSOHYoktL9%2FaArPIYNgZJ9eM5URzSEOtPJDY%3D"
|
"resolved": "github:NixOS/nixpkgs/b6c8664de9b6cc07fe5666a29f91884ba81197c4?lastModified=1790652569&narHash=sha256-641r7xrlSOHYoktL9%2FaArPIYNgZJ9eM5URzSEOtPJDY%3D"
|
||||||
|
},
|
||||||
|
"iverilog@latest": {
|
||||||
|
"last_modified": "2026-10-02T22:55:44Z",
|
||||||
|
"resolved": "github:NixOS/nixpkgs/55ba7f49ef2962b42cbd126522b7df5f95037679#iverilog",
|
||||||
|
"source": "devbox-search",
|
||||||
|
"version": "13.0",
|
||||||
|
"systems": {
|
||||||
|
"aarch64-darwin": {
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "out",
|
||||||
|
"path": "/nix/store/p2jawqjs75q5y5mzmz97mzsy41fnpk3z-iverilog-13.0",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"store_path": "/nix/store/p2jawqjs75q5y5mzmz97mzsy41fnpk3z-iverilog-13.0"
|
||||||
|
},
|
||||||
|
"aarch64-linux": {
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "out",
|
||||||
|
"path": "/nix/store/z850am8r4302c3l5vscpylb7gnrd01am-iverilog-13.0",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"store_path": "/nix/store/z850am8r4302c3l5vscpylb7gnrd01am-iverilog-13.0"
|
||||||
|
},
|
||||||
|
"x86_64-linux": {
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "out",
|
||||||
|
"path": "/nix/store/d66hg1vhaqd0ij8iznymb79s4h4ach7c-iverilog-13.0",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"store_path": "/nix/store/d66hg1vhaqd0ij8iznymb79s4h4ach7c-iverilog-13.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"python312@latest": {
|
||||||
|
"last_modified": "2026-10-02T22:55:44Z",
|
||||||
|
"plugin_version": "0.0.4",
|
||||||
|
"resolved": "github:NixOS/nixpkgs/55ba7f49ef2962b42cbd126522b7df5f95037679#python312",
|
||||||
|
"source": "devbox-search",
|
||||||
|
"version": "3.12.15",
|
||||||
|
"systems": {
|
||||||
|
"aarch64-darwin": {
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "out",
|
||||||
|
"path": "/nix/store/wki0ba66wcjwx0yxn36bgr08jgcn9bdj-python3-3.12.15",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"store_path": "/nix/store/wki0ba66wcjwx0yxn36bgr08jgcn9bdj-python3-3.12.15"
|
||||||
|
},
|
||||||
|
"aarch64-linux": {
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "out",
|
||||||
|
"path": "/nix/store/pan3ykbb4wj84c6lhy2zb27pifyd48af-python3-3.12.15",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "debug",
|
||||||
|
"path": "/nix/store/q5h1455f2swvmwpqkiswrl946fgs2h24-python3-3.12.15-debug"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"store_path": "/nix/store/pan3ykbb4wj84c6lhy2zb27pifyd48af-python3-3.12.15"
|
||||||
|
},
|
||||||
|
"x86_64-linux": {
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "out",
|
||||||
|
"path": "/nix/store/v7wvwm1bpm6kzlqgw33m98arm7sw40ln-python3-3.12.15",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "debug",
|
||||||
|
"path": "/nix/store/8965kbzqdvqmjy5zs4lxvy09g1qy9szm-python3-3.12.15-debug"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"store_path": "/nix/store/v7wvwm1bpm6kzlqgw33m98arm7sw40ln-python3-3.12.15"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"python312Packages.numpy@latest": {
|
||||||
|
"last_modified": "2025-11-23T21:50:36Z",
|
||||||
|
"resolved": "github:NixOS/nixpkgs/ee09932cedcef15aaf476f9343d1dea2cb77e261#python312Packages.numpy",
|
||||||
|
"source": "devbox-search",
|
||||||
|
"version": "2.3.4",
|
||||||
|
"systems": {
|
||||||
|
"aarch64-darwin": {
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "out",
|
||||||
|
"path": "/nix/store/x2ij7c58vq7cw1j5fvxfcm6r7ixanrwl-python3.12-numpy-2.3.4",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dist",
|
||||||
|
"path": "/nix/store/n62ay9ig7glbx774kz1kx958pmlh60l2-python3.12-numpy-2.3.4-dist"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"store_path": "/nix/store/x2ij7c58vq7cw1j5fvxfcm6r7ixanrwl-python3.12-numpy-2.3.4"
|
||||||
|
},
|
||||||
|
"aarch64-linux": {
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "out",
|
||||||
|
"path": "/nix/store/awrd1xis641s1mqbc2qqah9aadf3dr5a-python3.12-numpy-2.3.4",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dist",
|
||||||
|
"path": "/nix/store/i0xbq8jvfbdw1i6spmr99ar07xq02yzl-python3.12-numpy-2.3.4-dist"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"store_path": "/nix/store/awrd1xis641s1mqbc2qqah9aadf3dr5a-python3.12-numpy-2.3.4"
|
||||||
|
},
|
||||||
|
"x86_64-darwin": {
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "out",
|
||||||
|
"path": "/nix/store/9q4xvx19yv07xm65ifzkwpn4cd6g33i8-python3.12-numpy-2.3.4",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dist",
|
||||||
|
"path": "/nix/store/0l5qc7i7pdrpycijb1ys607vx8h7661b-python3.12-numpy-2.3.4-dist"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"store_path": "/nix/store/9q4xvx19yv07xm65ifzkwpn4cd6g33i8-python3.12-numpy-2.3.4"
|
||||||
|
},
|
||||||
|
"x86_64-linux": {
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "out",
|
||||||
|
"path": "/nix/store/0cgbqmgzzzhxmqsdaxa2hqz5d50a28cp-python3.12-numpy-2.3.4",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dist",
|
||||||
|
"path": "/nix/store/0kqkffxakw86i90a7zpfjicxmhzzds83-python3.12-numpy-2.3.4-dist"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"store_path": "/nix/store/0cgbqmgzzzhxmqsdaxa2hqz5d50a28cp-python3.12-numpy-2.3.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"python312Packages.pyserial@latest": {
|
||||||
|
"last_modified": "2025-11-23T21:50:36Z",
|
||||||
|
"resolved": "github:NixOS/nixpkgs/ee09932cedcef15aaf476f9343d1dea2cb77e261#python312Packages.pyserial",
|
||||||
|
"source": "devbox-search",
|
||||||
|
"version": "3.5",
|
||||||
|
"systems": {
|
||||||
|
"aarch64-darwin": {
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "out",
|
||||||
|
"path": "/nix/store/69ldajx5m2cas9k3p7nr5d8923sgdi6y-python3.12-pyserial-3.5",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dist",
|
||||||
|
"path": "/nix/store/zzrgf4i785a0zl6p33syzmk9h1h1ngs3-python3.12-pyserial-3.5-dist"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"store_path": "/nix/store/69ldajx5m2cas9k3p7nr5d8923sgdi6y-python3.12-pyserial-3.5"
|
||||||
|
},
|
||||||
|
"aarch64-linux": {
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "out",
|
||||||
|
"path": "/nix/store/v2yf4ssrygrh14s2n3hfq8w10h64jr6b-python3.12-pyserial-3.5",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dist",
|
||||||
|
"path": "/nix/store/i0siiab84gysw5bpjyy7qr052w2psss0-python3.12-pyserial-3.5-dist"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"store_path": "/nix/store/v2yf4ssrygrh14s2n3hfq8w10h64jr6b-python3.12-pyserial-3.5"
|
||||||
|
},
|
||||||
|
"x86_64-darwin": {
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "out",
|
||||||
|
"path": "/nix/store/h12lybacn8cfcxdwwlb5n5z1c1lcqpc4-python3.12-pyserial-3.5",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dist",
|
||||||
|
"path": "/nix/store/ajdpw1zb0lwjd1dmyjdf2nxxg0vpbrhd-python3.12-pyserial-3.5-dist"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"store_path": "/nix/store/h12lybacn8cfcxdwwlb5n5z1c1lcqpc4-python3.12-pyserial-3.5"
|
||||||
|
},
|
||||||
|
"x86_64-linux": {
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "out",
|
||||||
|
"path": "/nix/store/j90zi1mxv9dvqmsjqicnaafm22bp3qca-python3.12-pyserial-3.5",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dist",
|
||||||
|
"path": "/nix/store/2l5xicsrirfp0v8bawhsva1856c4zakd-python3.12-pyserial-3.5-dist"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"store_path": "/nix/store/j90zi1mxv9dvqmsjqicnaafm22bp3qca-python3.12-pyserial-3.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
34
fpga/rtl/entropy_gen.v
Normal file
34
fpga/rtl/entropy_gen.v
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
// One generator: two rings of different lengths whose enhanced outputs are XORed, plus
|
||||||
|
// the intermediate signals exposed for jitter characterization. All four are sampled
|
||||||
|
// every clock.
|
||||||
|
module entropy_gen #(
|
||||||
|
parameter LENGTH_A = 12,
|
||||||
|
parameter LENGTH_B = 10
|
||||||
|
) (
|
||||||
|
input wire clk,
|
||||||
|
input wire enable,
|
||||||
|
output wire [3:0] samples // {pair XOR, enhanced B, enhanced A, single tap of A}
|
||||||
|
);
|
||||||
|
|
||||||
|
wire ring_a_tap, ring_a_enhanced, ring_b_tap, ring_b_enhanced;
|
||||||
|
|
||||||
|
ring_osc #(.LENGTH(LENGTH_A)) ring_a (
|
||||||
|
.enable(enable), .first_tap(ring_a_tap), .enhanced(ring_a_enhanced)
|
||||||
|
);
|
||||||
|
ring_osc #(.LENGTH(LENGTH_B)) ring_b (
|
||||||
|
.enable(enable), .first_tap(ring_b_tap), .enhanced(ring_b_enhanced)
|
||||||
|
);
|
||||||
|
|
||||||
|
(* keep *) wire pair_xor = ring_a_enhanced ^ ring_b_enhanced;
|
||||||
|
|
||||||
|
// sample_raw catches the asynchronous ring signals and may go metastable;
|
||||||
|
// sample_sync resolves it. The SDC file cuts timing into sample_raw.
|
||||||
|
reg [3:0] sample_raw, sample_sync;
|
||||||
|
always @(posedge clk) begin
|
||||||
|
sample_raw <= {pair_xor, ring_b_enhanced, ring_a_enhanced, ring_a_tap};
|
||||||
|
sample_sync <= sample_raw;
|
||||||
|
end
|
||||||
|
|
||||||
|
assign samples = sample_sync;
|
||||||
|
|
||||||
|
endmodule
|
||||||
67
fpga/rtl/raw_capture.v
Normal file
67
fpga/rtl/raw_capture.v
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
// Captures one burst of sampled bits into block RAM for offline analysis. Keeps every
|
||||||
|
// decimation-th sample of the selected source and packs them MSB first into bytes.
|
||||||
|
module raw_capture #(
|
||||||
|
parameter ADDR_WIDTH = 15 // 2^15 bytes = 256 Kbit per burst
|
||||||
|
) (
|
||||||
|
input wire clk,
|
||||||
|
input wire reset,
|
||||||
|
input wire [3:0] samples,
|
||||||
|
input wire [1:0] source,
|
||||||
|
input wire [15:0] decimation, // 0 and 1 both mean every sample
|
||||||
|
input wire start,
|
||||||
|
output reg busy,
|
||||||
|
output reg done,
|
||||||
|
input wire [ADDR_WIDTH-1:0] read_addr,
|
||||||
|
output reg [7:0] read_data
|
||||||
|
);
|
||||||
|
|
||||||
|
reg [7:0] buffer [0:(1 << ADDR_WIDTH) - 1];
|
||||||
|
|
||||||
|
reg [15:0] sample_count;
|
||||||
|
reg [2:0] bit_count;
|
||||||
|
reg [6:0] partial_byte;
|
||||||
|
reg [ADDR_WIDTH-1:0] next_addr;
|
||||||
|
reg write_enable;
|
||||||
|
reg [ADDR_WIDTH-1:0] write_addr;
|
||||||
|
reg [7:0] write_data;
|
||||||
|
|
||||||
|
wire keep_sample = (sample_count == 16'd0);
|
||||||
|
wire count_wraps = (decimation <= 16'd1) || (sample_count == decimation - 16'd1);
|
||||||
|
wire new_bit = samples[source];
|
||||||
|
|
||||||
|
always @(posedge clk) begin
|
||||||
|
write_enable <= 1'b0;
|
||||||
|
if (reset) begin
|
||||||
|
busy <= 1'b0;
|
||||||
|
done <= 1'b0;
|
||||||
|
end else if (start) begin
|
||||||
|
busy <= 1'b1;
|
||||||
|
done <= 1'b0;
|
||||||
|
sample_count <= 16'd0;
|
||||||
|
bit_count <= 3'd0;
|
||||||
|
next_addr <= {ADDR_WIDTH{1'b0}};
|
||||||
|
end else if (busy) begin
|
||||||
|
sample_count <= count_wraps ? 16'd0 : sample_count + 16'd1;
|
||||||
|
if (keep_sample) begin
|
||||||
|
partial_byte <= {partial_byte[5:0], new_bit};
|
||||||
|
bit_count <= bit_count + 3'd1;
|
||||||
|
if (bit_count == 3'd7) begin
|
||||||
|
write_enable <= 1'b1;
|
||||||
|
write_addr <= next_addr;
|
||||||
|
write_data <= {partial_byte, new_bit};
|
||||||
|
next_addr <= next_addr + 1'b1;
|
||||||
|
if (&next_addr) begin
|
||||||
|
busy <= 1'b0;
|
||||||
|
done <= 1'b1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
always @(posedge clk) begin
|
||||||
|
if (write_enable) buffer[write_addr] <= write_data;
|
||||||
|
read_data <= buffer[read_addr];
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
||||||
43
fpga/rtl/ring_osc.v
Normal file
43
fpga/rtl/ring_osc.v
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
// Free-running ring oscillator built from LCELL buffers so synthesis keeps every stage.
|
||||||
|
// Three evenly spaced taps are XORed into an "enhanced" output at three times the ring
|
||||||
|
// frequency, which triples the chance that a sample lands on a jittery edge.
|
||||||
|
module ring_osc #(
|
||||||
|
parameter LENGTH = 12 // stages in the ring; exactly one of them inverts
|
||||||
|
) (
|
||||||
|
input wire enable, // low stops the ring (all stages settle high)
|
||||||
|
output wire first_tap,
|
||||||
|
output wire enhanced
|
||||||
|
);
|
||||||
|
|
||||||
|
`ifdef SIM
|
||||||
|
// Behavioural stand-in: independent taps toggling at random intervals, enough to
|
||||||
|
// exercise the synchronous logic downstream. It has no physical meaning.
|
||||||
|
reg [2:0] taps = 3'b000;
|
||||||
|
always begin
|
||||||
|
#(1 + {$random} % 7);
|
||||||
|
if (enable) taps[{$random} % 3] = ~taps[{$random} % 3];
|
||||||
|
else taps = 3'b111;
|
||||||
|
end
|
||||||
|
assign first_tap = taps[0];
|
||||||
|
assign enhanced = ^taps;
|
||||||
|
`else
|
||||||
|
// No keep attribute here: each lcell is already one logic cell, and keep on these
|
||||||
|
// nets makes Quartus insert a second cell per stage.
|
||||||
|
wire [LENGTH-1:0] stage;
|
||||||
|
|
||||||
|
lcell inverting_stage (.in(~(stage[LENGTH-1] & enable)), .out(stage[0]));
|
||||||
|
|
||||||
|
genvar i;
|
||||||
|
generate
|
||||||
|
for (i = 1; i < LENGTH; i = i + 1) begin : delay_stage
|
||||||
|
lcell buffer (.in(stage[i-1]), .out(stage[i]));
|
||||||
|
end
|
||||||
|
endgenerate
|
||||||
|
|
||||||
|
(* keep *) wire tap_xor = stage[0] ^ stage[LENGTH/3] ^ stage[(2*LENGTH)/3];
|
||||||
|
|
||||||
|
assign first_tap = stage[0];
|
||||||
|
assign enhanced = tap_xor;
|
||||||
|
`endif
|
||||||
|
|
||||||
|
endmodule
|
||||||
69
fpga/rtl/spi_slave.v
Normal file
69
fpga/rtl/spi_slave.v
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
// SPI mode 0 slave, oversampled in the system clock domain (SCK must stay below about a
|
||||||
|
// tenth of clk). MISO changes a few clk cycles after each SCK falling edge, which leaves
|
||||||
|
// enough margin for the master's next rising edge at 8 MHz SCK with a 120 MHz clk.
|
||||||
|
module spi_slave (
|
||||||
|
input wire clk,
|
||||||
|
input wire reset,
|
||||||
|
input wire sck,
|
||||||
|
input wire mosi,
|
||||||
|
input wire ss_n,
|
||||||
|
output wire miso,
|
||||||
|
output wire selected, // ss_n asserted (synchronized)
|
||||||
|
output reg rx_valid, // pulse: rx_byte holds a complete byte
|
||||||
|
output reg [7:0] rx_byte,
|
||||||
|
output reg rx_is_first, // rx_byte is the first byte of the transaction
|
||||||
|
input wire [7:0] tx_byte, // sampled when the next byte starts shifting out
|
||||||
|
output reg tx_loaded // pulse: tx_byte was taken
|
||||||
|
);
|
||||||
|
|
||||||
|
reg [2:0] sck_history, ss_n_history;
|
||||||
|
reg [1:0] mosi_history;
|
||||||
|
|
||||||
|
always @(posedge clk) begin
|
||||||
|
sck_history <= {sck_history[1:0], sck};
|
||||||
|
ss_n_history <= {ss_n_history[1:0], ss_n};
|
||||||
|
mosi_history <= {mosi_history[0], mosi};
|
||||||
|
end
|
||||||
|
|
||||||
|
wire sck_rising = (sck_history[2:1] == 2'b01);
|
||||||
|
wire sck_falling = (sck_history[2:1] == 2'b10);
|
||||||
|
wire mosi_bit = mosi_history[1];
|
||||||
|
assign selected = ~ss_n_history[1];
|
||||||
|
|
||||||
|
reg [2:0] bit_count;
|
||||||
|
reg [6:0] rx_shift;
|
||||||
|
reg [7:0] tx_shift;
|
||||||
|
reg expecting_first;
|
||||||
|
|
||||||
|
assign miso = tx_shift[7];
|
||||||
|
|
||||||
|
always @(posedge clk) begin
|
||||||
|
rx_valid <= 1'b0;
|
||||||
|
tx_loaded <= 1'b0;
|
||||||
|
if (reset || !selected) begin
|
||||||
|
bit_count <= 3'd0;
|
||||||
|
expecting_first <= 1'b1;
|
||||||
|
tx_shift <= 8'h00; // MISO is don't-care during the command byte
|
||||||
|
end else begin
|
||||||
|
if (sck_rising) begin
|
||||||
|
rx_shift <= {rx_shift[5:0], mosi_bit};
|
||||||
|
bit_count <= bit_count + 3'd1;
|
||||||
|
if (bit_count == 3'd7) begin
|
||||||
|
rx_valid <= 1'b1;
|
||||||
|
rx_byte <= {rx_shift, mosi_bit};
|
||||||
|
rx_is_first <= expecting_first;
|
||||||
|
expecting_first <= 1'b0;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if (sck_falling) begin
|
||||||
|
if (bit_count == 3'd0) begin
|
||||||
|
tx_shift <= tx_byte;
|
||||||
|
tx_loaded <= 1'b1;
|
||||||
|
end else begin
|
||||||
|
tx_shift <= {tx_shift[6:0], 1'b0};
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
||||||
85
fpga/rtl/strix.v
Normal file
85
fpga/rtl/strix.v
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
// STRIX top level for the Arduino MKR Vidor 4000. Port names match the Vidor constraints
|
||||||
|
// in strix.qsf; unused board pins are left out and stay tri-stated.
|
||||||
|
module strix (
|
||||||
|
input wire iCLK, // 48 MHz from the SAMD21 (GCLK0)
|
||||||
|
|
||||||
|
inout wire [14:0] bMKR_D, // header D0-D14, shared with the SAMD21
|
||||||
|
inout wire [6:0] bMKR_A, // header A0-A6, shared with the SAMD21
|
||||||
|
|
||||||
|
// NINA-W102 WiFi module
|
||||||
|
inout wire bWM_PIO1, // NINA MISO
|
||||||
|
inout wire bWM_PIO7, // NINA ACK (GPIO33)
|
||||||
|
inout wire bWM_PIO27, // NINA GPIO0
|
||||||
|
inout wire bWM_PIO28, // NINA SS (GPIO5)
|
||||||
|
inout wire bWM_PIO29, // NINA SCK
|
||||||
|
inout wire bWM_PIO36, // NINA MOSI
|
||||||
|
input wire iWM_TX,
|
||||||
|
inout wire oWM_RX,
|
||||||
|
inout wire oWM_RESET,
|
||||||
|
|
||||||
|
output wire oSAM_INT // interrupt line to the SAMD21
|
||||||
|
);
|
||||||
|
|
||||||
|
// Clock and reset
|
||||||
|
|
||||||
|
wire clk, pll_locked;
|
||||||
|
|
||||||
|
sys_pll pll (.inclk0(iCLK), .c0(clk), .locked(pll_locked));
|
||||||
|
|
||||||
|
reg [2:0] reset_pipe = 3'b111;
|
||||||
|
always @(posedge clk or negedge pll_locked)
|
||||||
|
if (!pll_locked) reset_pipe <= 3'b111;
|
||||||
|
else reset_pipe <= {reset_pipe[1:0], 1'b0};
|
||||||
|
wire reset = reset_pipe[2];
|
||||||
|
|
||||||
|
// NINA pass-through: the SAMD21 talks to the WiFi module as if wired directly.
|
||||||
|
|
||||||
|
wire sam_nina_mosi = bMKR_D[8]; // SERCOM1
|
||||||
|
wire sam_nina_sck = bMKR_D[9];
|
||||||
|
wire sam_nina_ss = bMKR_D[5];
|
||||||
|
wire sam_nina_gpio0 = bMKR_D[6];
|
||||||
|
wire sam_nina_resetn = bMKR_D[7];
|
||||||
|
wire sam_uart_tx = bMKR_D[14];
|
||||||
|
|
||||||
|
assign bWM_PIO36 = sam_nina_mosi;
|
||||||
|
assign bWM_PIO29 = sam_nina_sck;
|
||||||
|
assign bWM_PIO28 = sam_nina_ss;
|
||||||
|
assign bWM_PIO27 = sam_nina_gpio0;
|
||||||
|
assign oWM_RESET = sam_nina_resetn;
|
||||||
|
assign oWM_RX = sam_uart_tx;
|
||||||
|
assign bWM_PIO1 = 1'bz; // inputs from NINA
|
||||||
|
assign bWM_PIO7 = 1'bz;
|
||||||
|
|
||||||
|
wire nina_miso = bWM_PIO1; // to D10
|
||||||
|
wire nina_ack = bWM_PIO7; // to D4
|
||||||
|
wire nina_uart_tx = iWM_TX; // to D13
|
||||||
|
|
||||||
|
// STRIX SPI link on SAMD21 SERCOM0 alternate pins: A3 MOSI, A4 SCK, A5 SS, A6 MISO.
|
||||||
|
|
||||||
|
wire strix_mosi = bMKR_A[3];
|
||||||
|
wire strix_sck = bMKR_A[4];
|
||||||
|
wire strix_ss_n = bMKR_A[5];
|
||||||
|
wire strix_miso, capture_done;
|
||||||
|
|
||||||
|
strix_core core (
|
||||||
|
.clk(clk), .reset(reset),
|
||||||
|
.spi_sck(strix_sck), .spi_mosi(strix_mosi), .spi_ss_n(strix_ss_n),
|
||||||
|
.spi_miso(strix_miso), .capture_done(capture_done)
|
||||||
|
);
|
||||||
|
|
||||||
|
assign oSAM_INT = capture_done;
|
||||||
|
|
||||||
|
// Header pins driven by the FPGA; all others are inputs.
|
||||||
|
|
||||||
|
genvar d;
|
||||||
|
generate
|
||||||
|
for (d = 0; d < 15; d = d + 1) begin : header_d
|
||||||
|
assign bMKR_D[d] = (d == 4) ? nina_ack :
|
||||||
|
(d == 10) ? nina_miso :
|
||||||
|
(d == 13) ? nina_uart_tx : 1'bz;
|
||||||
|
end
|
||||||
|
endgenerate
|
||||||
|
|
||||||
|
assign bMKR_A = {strix_miso, 6'bzzzzzz};
|
||||||
|
|
||||||
|
endmodule
|
||||||
130
fpga/rtl/strix_core.v
Normal file
130
fpga/rtl/strix_core.v
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
// Board-independent STRIX logic: entropy generator, raw capture 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,
|
||||||
|
// bit 7 = read (1) or write (0), bits 6:0 = register address. A write stores the following
|
||||||
|
// byte(s) in that register. A read returns the register in every following byte; DATA
|
||||||
|
// advances through the capture buffer with each byte, continuing across transactions.
|
||||||
|
module strix_core #(
|
||||||
|
parameter CAPTURE_ADDR_WIDTH = 15
|
||||||
|
) (
|
||||||
|
input wire clk,
|
||||||
|
input wire reset,
|
||||||
|
input wire spi_sck,
|
||||||
|
input wire spi_mosi,
|
||||||
|
input wire spi_ss_n,
|
||||||
|
output wire spi_miso,
|
||||||
|
output wire capture_done
|
||||||
|
);
|
||||||
|
|
||||||
|
localparam [7:0] VERSION = 8'h01;
|
||||||
|
|
||||||
|
localparam [6:0] REG_ID0 = 7'h00, // "STRX"
|
||||||
|
REG_ID1 = 7'h01,
|
||||||
|
REG_ID2 = 7'h02,
|
||||||
|
REG_ID3 = 7'h03,
|
||||||
|
REG_VERSION = 7'h04,
|
||||||
|
REG_SOURCE = 7'h08, // 0 tap A, 1 enhanced A, 2 enhanced B, 3 pair
|
||||||
|
REG_DECIM_HIGH = 7'h09,
|
||||||
|
REG_DECIM_LOW = 7'h0A,
|
||||||
|
REG_CONTROL = 7'h0B, // write: bit 0 start capture, bit 1 rewind DATA
|
||||||
|
REG_STATUS = 7'h0C, // bit 0 busy, bit 1 done
|
||||||
|
REG_DATA = 7'h10;
|
||||||
|
|
||||||
|
// Entropy and capture
|
||||||
|
|
||||||
|
wire [3:0] samples;
|
||||||
|
|
||||||
|
entropy_gen generator (.clk(clk), .enable(1'b1), .samples(samples));
|
||||||
|
|
||||||
|
reg [1:0] source;
|
||||||
|
reg [15:0] decimation;
|
||||||
|
reg capture_start;
|
||||||
|
wire capture_busy;
|
||||||
|
reg [CAPTURE_ADDR_WIDTH-1:0] prefetch_addr;
|
||||||
|
wire [7:0] capture_byte;
|
||||||
|
|
||||||
|
raw_capture #(.ADDR_WIDTH(CAPTURE_ADDR_WIDTH)) capture (
|
||||||
|
.clk(clk), .reset(reset), .samples(samples), .source(source),
|
||||||
|
.decimation(decimation), .start(capture_start),
|
||||||
|
.busy(capture_busy), .done(capture_done),
|
||||||
|
.read_addr(prefetch_addr), .read_data(capture_byte)
|
||||||
|
);
|
||||||
|
|
||||||
|
// SPI
|
||||||
|
|
||||||
|
wire selected, rx_valid, rx_is_first, tx_loaded;
|
||||||
|
wire [7:0] rx_byte;
|
||||||
|
reg [7:0] tx_byte;
|
||||||
|
|
||||||
|
spi_slave spi (
|
||||||
|
.clk(clk), .reset(reset),
|
||||||
|
.sck(spi_sck), .mosi(spi_mosi), .ss_n(spi_ss_n), .miso(spi_miso),
|
||||||
|
.selected(selected), .rx_valid(rx_valid), .rx_byte(rx_byte),
|
||||||
|
.rx_is_first(rx_is_first), .tx_byte(tx_byte), .tx_loaded(tx_loaded)
|
||||||
|
);
|
||||||
|
|
||||||
|
reg is_read;
|
||||||
|
reg [6:0] reg_addr;
|
||||||
|
|
||||||
|
// DATA bytes are prefetched one ahead: prefetch_addr moves when a byte is loaded for
|
||||||
|
// shifting, bytes_read only when it has been fully clocked out. Between transactions
|
||||||
|
// prefetch_addr rewinds to bytes_read so the trailing prefetch is not lost.
|
||||||
|
reg [CAPTURE_ADDR_WIDTH-1:0] bytes_read;
|
||||||
|
|
||||||
|
wire reading_data = is_read && reg_addr == REG_DATA;
|
||||||
|
|
||||||
|
always @(*) begin
|
||||||
|
case (reg_addr)
|
||||||
|
REG_ID0: tx_byte = "S";
|
||||||
|
REG_ID1: tx_byte = "T";
|
||||||
|
REG_ID2: tx_byte = "R";
|
||||||
|
REG_ID3: tx_byte = "X";
|
||||||
|
REG_VERSION: tx_byte = VERSION;
|
||||||
|
REG_SOURCE: tx_byte = {6'd0, source};
|
||||||
|
REG_DECIM_HIGH: tx_byte = decimation[15:8];
|
||||||
|
REG_DECIM_LOW: tx_byte = decimation[7:0];
|
||||||
|
REG_STATUS: tx_byte = {6'd0, capture_done, capture_busy};
|
||||||
|
REG_DATA: tx_byte = capture_byte;
|
||||||
|
default: tx_byte = 8'h00;
|
||||||
|
endcase
|
||||||
|
end
|
||||||
|
|
||||||
|
always @(posedge clk) begin
|
||||||
|
capture_start <= 1'b0;
|
||||||
|
if (reset) begin
|
||||||
|
source <= 2'd3;
|
||||||
|
decimation <= 16'd1;
|
||||||
|
is_read <= 1'b0;
|
||||||
|
reg_addr <= 7'd0;
|
||||||
|
bytes_read <= {CAPTURE_ADDR_WIDTH{1'b0}};
|
||||||
|
prefetch_addr <= {CAPTURE_ADDR_WIDTH{1'b0}};
|
||||||
|
end else begin
|
||||||
|
if (!selected)
|
||||||
|
prefetch_addr <= bytes_read;
|
||||||
|
else if (tx_loaded && reading_data)
|
||||||
|
prefetch_addr <= prefetch_addr + 1'b1;
|
||||||
|
|
||||||
|
if (rx_valid) begin
|
||||||
|
if (rx_is_first) begin
|
||||||
|
is_read <= rx_byte[7];
|
||||||
|
reg_addr <= rx_byte[6:0];
|
||||||
|
end else if (reading_data) begin
|
||||||
|
bytes_read <= bytes_read + 1'b1;
|
||||||
|
end else if (!is_read) begin
|
||||||
|
case (reg_addr)
|
||||||
|
REG_SOURCE: source <= rx_byte[1:0];
|
||||||
|
REG_DECIM_HIGH: decimation[15:8] <= rx_byte;
|
||||||
|
REG_DECIM_LOW: decimation[7:0] <= rx_byte;
|
||||||
|
REG_CONTROL: begin
|
||||||
|
if (rx_byte[0]) capture_start <= 1'b1;
|
||||||
|
if (rx_byte[0] || rx_byte[1]) bytes_read <= {CAPTURE_ADDR_WIDTH{1'b0}};
|
||||||
|
end
|
||||||
|
default: ;
|
||||||
|
endcase
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
||||||
100
fpga/rtl/sys_pll.v
Normal file
100
fpga/rtl/sys_pll.v
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
// 48 MHz in, 120 MHz out (x5 / 2). Same altpll settings style as Arduino's Vidor
|
||||||
|
// SYSTEM_PLL, reduced to one output.
|
||||||
|
module sys_pll (
|
||||||
|
input wire inclk0,
|
||||||
|
output wire c0,
|
||||||
|
output wire locked
|
||||||
|
);
|
||||||
|
|
||||||
|
wire [4:0] clk;
|
||||||
|
|
||||||
|
altpll altpll_component (
|
||||||
|
.inclk ({1'b0, inclk0}),
|
||||||
|
.clk (clk),
|
||||||
|
.locked (locked),
|
||||||
|
.areset (1'b0),
|
||||||
|
.activeclock (),
|
||||||
|
.clkbad (),
|
||||||
|
.clkena ({6{1'b1}}),
|
||||||
|
.clkloss (),
|
||||||
|
.clkswitch (1'b0),
|
||||||
|
.configupdate (1'b0),
|
||||||
|
.enable0 (),
|
||||||
|
.enable1 (),
|
||||||
|
.extclk (),
|
||||||
|
.extclkena ({4{1'b1}}),
|
||||||
|
.fbin (1'b1),
|
||||||
|
.fbmimicbidir (),
|
||||||
|
.fbout (),
|
||||||
|
.fref (),
|
||||||
|
.icdrclk (),
|
||||||
|
.pfdena (1'b1),
|
||||||
|
.phasecounterselect ({4{1'b1}}),
|
||||||
|
.phasedone (),
|
||||||
|
.phasestep (1'b1),
|
||||||
|
.phaseupdown (1'b1),
|
||||||
|
.pllena (1'b1),
|
||||||
|
.scanaclr (1'b0),
|
||||||
|
.scanclk (1'b0),
|
||||||
|
.scanclkena (1'b1),
|
||||||
|
.scandata (1'b0),
|
||||||
|
.scandataout (),
|
||||||
|
.scandone (),
|
||||||
|
.scanread (1'b0),
|
||||||
|
.scanwrite (1'b0),
|
||||||
|
.sclkout0 (),
|
||||||
|
.sclkout1 (),
|
||||||
|
.vcooverrange (),
|
||||||
|
.vcounderrange ()
|
||||||
|
);
|
||||||
|
|
||||||
|
defparam
|
||||||
|
altpll_component.bandwidth_type = "AUTO",
|
||||||
|
altpll_component.clk0_divide_by = 2,
|
||||||
|
altpll_component.clk0_duty_cycle = 50,
|
||||||
|
altpll_component.clk0_multiply_by = 5,
|
||||||
|
altpll_component.clk0_phase_shift = "0",
|
||||||
|
altpll_component.compensate_clock = "CLK0",
|
||||||
|
altpll_component.inclk0_input_frequency = 20833,
|
||||||
|
altpll_component.intended_device_family = "Cyclone 10 LP",
|
||||||
|
altpll_component.lpm_hint = "CBX_MODULE_PREFIX=sys_pll",
|
||||||
|
altpll_component.lpm_type = "altpll",
|
||||||
|
altpll_component.operation_mode = "NORMAL",
|
||||||
|
altpll_component.pll_type = "AUTO",
|
||||||
|
altpll_component.port_activeclock = "PORT_UNUSED",
|
||||||
|
altpll_component.port_areset = "PORT_UNUSED",
|
||||||
|
altpll_component.port_clkbad0 = "PORT_UNUSED",
|
||||||
|
altpll_component.port_clkbad1 = "PORT_UNUSED",
|
||||||
|
altpll_component.port_clkloss = "PORT_UNUSED",
|
||||||
|
altpll_component.port_clkswitch = "PORT_UNUSED",
|
||||||
|
altpll_component.port_configupdate = "PORT_UNUSED",
|
||||||
|
altpll_component.port_fbin = "PORT_UNUSED",
|
||||||
|
altpll_component.port_inclk0 = "PORT_USED",
|
||||||
|
altpll_component.port_inclk1 = "PORT_UNUSED",
|
||||||
|
altpll_component.port_locked = "PORT_USED",
|
||||||
|
altpll_component.port_pfdena = "PORT_UNUSED",
|
||||||
|
altpll_component.port_phasecounterselect = "PORT_UNUSED",
|
||||||
|
altpll_component.port_phasedone = "PORT_UNUSED",
|
||||||
|
altpll_component.port_phasestep = "PORT_UNUSED",
|
||||||
|
altpll_component.port_phaseupdown = "PORT_UNUSED",
|
||||||
|
altpll_component.port_pllena = "PORT_UNUSED",
|
||||||
|
altpll_component.port_scanaclr = "PORT_UNUSED",
|
||||||
|
altpll_component.port_scanclk = "PORT_UNUSED",
|
||||||
|
altpll_component.port_scanclkena = "PORT_UNUSED",
|
||||||
|
altpll_component.port_scandata = "PORT_UNUSED",
|
||||||
|
altpll_component.port_scandataout = "PORT_UNUSED",
|
||||||
|
altpll_component.port_scandone = "PORT_UNUSED",
|
||||||
|
altpll_component.port_scanread = "PORT_UNUSED",
|
||||||
|
altpll_component.port_scanwrite = "PORT_UNUSED",
|
||||||
|
altpll_component.port_clk0 = "PORT_USED",
|
||||||
|
altpll_component.port_clk1 = "PORT_UNUSED",
|
||||||
|
altpll_component.port_clk2 = "PORT_UNUSED",
|
||||||
|
altpll_component.port_clk3 = "PORT_UNUSED",
|
||||||
|
altpll_component.port_clk4 = "PORT_UNUSED",
|
||||||
|
altpll_component.port_clk5 = "PORT_UNUSED",
|
||||||
|
altpll_component.self_reset_on_loss_lock = "OFF",
|
||||||
|
altpll_component.width_clock = 5;
|
||||||
|
|
||||||
|
assign c0 = clk[0];
|
||||||
|
|
||||||
|
endmodule
|
||||||
139
fpga/sim/tb_strix_core.v
Normal file
139
fpga/sim/tb_strix_core.v
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
// Testbench for strix_core: SPI register access and a small raw capture read back in
|
||||||
|
// two transactions. Rings are replaced by the SIM model in ring_osc.v.
|
||||||
|
`timescale 1ns / 1ps
|
||||||
|
|
||||||
|
module tb_strix_core;
|
||||||
|
|
||||||
|
localparam CAPTURE_ADDR_WIDTH = 6; // 64-byte buffer keeps the simulation short
|
||||||
|
localparam CAPTURE_BYTES = 1 << CAPTURE_ADDR_WIDTH;
|
||||||
|
localparam SCK_HALF = 62.5; // 8 MHz SCK
|
||||||
|
|
||||||
|
reg clk = 1'b0;
|
||||||
|
always #4.1667 clk = ~clk; // 120 MHz
|
||||||
|
|
||||||
|
reg reset = 1'b1;
|
||||||
|
reg sck = 1'b0, mosi = 1'b0, ss_n = 1'b1;
|
||||||
|
wire miso, done;
|
||||||
|
|
||||||
|
strix_core #(.CAPTURE_ADDR_WIDTH(CAPTURE_ADDR_WIDTH)) dut (
|
||||||
|
.clk(clk), .reset(reset),
|
||||||
|
.spi_sck(sck), .spi_mosi(mosi), .spi_ss_n(ss_n), .spi_miso(miso),
|
||||||
|
.capture_done(done)
|
||||||
|
);
|
||||||
|
|
||||||
|
integer errors = 0;
|
||||||
|
|
||||||
|
task spi_byte(input [7:0] out, output [7:0] in);
|
||||||
|
integer i;
|
||||||
|
begin
|
||||||
|
for (i = 7; i >= 0; i = i - 1) begin
|
||||||
|
mosi = out[i];
|
||||||
|
#SCK_HALF sck = 1'b1;
|
||||||
|
in[i] = miso;
|
||||||
|
#SCK_HALF sck = 1'b0;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
task write_reg(input [6:0] addr, input [7:0] value);
|
||||||
|
reg [7:0] dummy;
|
||||||
|
begin
|
||||||
|
ss_n = 1'b0; #SCK_HALF;
|
||||||
|
spi_byte({1'b0, addr}, dummy);
|
||||||
|
spi_byte(value, dummy);
|
||||||
|
#SCK_HALF ss_n = 1'b1; #(4 * SCK_HALF);
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
task read_reg(input [6:0] addr, output [7:0] value);
|
||||||
|
reg [7:0] dummy;
|
||||||
|
begin
|
||||||
|
ss_n = 1'b0; #SCK_HALF;
|
||||||
|
spi_byte({1'b1, addr}, dummy);
|
||||||
|
spi_byte(8'h00, value);
|
||||||
|
#SCK_HALF ss_n = 1'b1; #(4 * SCK_HALF);
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
task read_data(input integer first, input integer count);
|
||||||
|
reg [7:0] dummy, value;
|
||||||
|
integer i;
|
||||||
|
begin
|
||||||
|
ss_n = 1'b0; #SCK_HALF;
|
||||||
|
spi_byte({1'b1, 7'h10}, dummy);
|
||||||
|
for (i = 0; i < count; i = i + 1) begin
|
||||||
|
spi_byte(8'h00, value);
|
||||||
|
if (value !== dut.capture.buffer[first + i]) begin
|
||||||
|
$display("FAIL data[%0d]: got %02x, expected %02x", first + i, value, dut.capture.buffer[first + i]);
|
||||||
|
errors = errors + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
#SCK_HALF ss_n = 1'b1; #(4 * SCK_HALF);
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
task expect(input [7:0] got, input [7:0] want, input [8*16-1:0] what);
|
||||||
|
if (got !== want) begin
|
||||||
|
$display("FAIL %0s: got %02x, expected %02x", what, got, want);
|
||||||
|
errors = errors + 1;
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
reg [7:0] v;
|
||||||
|
integer n;
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
#100 reset = 1'b0;
|
||||||
|
#200;
|
||||||
|
|
||||||
|
read_reg(7'h00, v); expect(v, "S", "ID0");
|
||||||
|
read_reg(7'h03, v); expect(v, "X", "ID3");
|
||||||
|
read_reg(7'h04, v); expect(v, 8'h01, "VERSION");
|
||||||
|
|
||||||
|
write_reg(7'h08, 8'h01);
|
||||||
|
write_reg(7'h09, 8'h00);
|
||||||
|
write_reg(7'h0A, 8'h03);
|
||||||
|
read_reg(7'h08, v); expect(v, 8'h01, "SRC");
|
||||||
|
read_reg(7'h0A, v); expect(v, 8'h03, "DECIM_L");
|
||||||
|
|
||||||
|
write_reg(7'h0B, 8'h01);
|
||||||
|
n = 0;
|
||||||
|
read_reg(7'h0C, v);
|
||||||
|
while (v[1] !== 1'b1 && n < 1000) begin
|
||||||
|
read_reg(7'h0C, v);
|
||||||
|
n = n + 1;
|
||||||
|
end
|
||||||
|
expect(v, 8'h02, "STATUS");
|
||||||
|
if (done !== 1'b1) begin
|
||||||
|
$display("FAIL capture_done not set");
|
||||||
|
errors = errors + 1;
|
||||||
|
end
|
||||||
|
|
||||||
|
// Captured bytes must be defined and vary, otherwise the comparisons below prove nothing.
|
||||||
|
n = 0;
|
||||||
|
for (v = 0; v < CAPTURE_BYTES - 1; v = v + 1) begin
|
||||||
|
if (^dut.capture.buffer[v] === 1'bx) begin
|
||||||
|
$display("FAIL mem[%0d] undefined", v);
|
||||||
|
errors = errors + 1;
|
||||||
|
end
|
||||||
|
if (dut.capture.buffer[v] !== dut.capture.buffer[v + 1]) n = n + 1;
|
||||||
|
end
|
||||||
|
if (n == 0) begin
|
||||||
|
$display("FAIL capture buffer is constant");
|
||||||
|
errors = errors + 1;
|
||||||
|
end
|
||||||
|
|
||||||
|
// Read the buffer in two transactions; the second must continue where the first stopped.
|
||||||
|
read_data(0, CAPTURE_BYTES / 2);
|
||||||
|
read_data(CAPTURE_BYTES / 2, CAPTURE_BYTES / 2);
|
||||||
|
|
||||||
|
// Rewind and read the first bytes again.
|
||||||
|
write_reg(7'h0B, 8'h02);
|
||||||
|
read_data(0, 4);
|
||||||
|
|
||||||
|
if (errors == 0) $display("PASS");
|
||||||
|
else $display("%0d error(s)", errors);
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
||||||
|
|
@ -55,6 +55,31 @@ set_global_assignment -name EDA_GENERATE_FUNCTIONAL_NETLIST OFF -section_id eda_
|
||||||
set_global_assignment -name EDA_GENERATE_FUNCTIONAL_NETLIST OFF -section_id eda_board_design_symbol
|
set_global_assignment -name EDA_GENERATE_FUNCTIONAL_NETLIST OFF -section_id eda_board_design_symbol
|
||||||
set_global_assignment -name EDA_GENERATE_FUNCTIONAL_NETLIST OFF -section_id eda_board_design_signal_integrity
|
set_global_assignment -name EDA_GENERATE_FUNCTIONAL_NETLIST OFF -section_id eda_board_design_signal_integrity
|
||||||
set_global_assignment -name EDA_GENERATE_FUNCTIONAL_NETLIST OFF -section_id eda_board_design_boundary_scan
|
set_global_assignment -name EDA_GENERATE_FUNCTIONAL_NETLIST OFF -section_id eda_board_design_boundary_scan
|
||||||
|
|
||||||
|
# Sources and constraints
|
||||||
|
set_global_assignment -name VERILOG_FILE ../rtl/strix.v
|
||||||
|
set_global_assignment -name VERILOG_FILE ../rtl/sys_pll.v
|
||||||
|
set_global_assignment -name VERILOG_FILE ../rtl/strix_core.v
|
||||||
|
set_global_assignment -name VERILOG_FILE ../rtl/entropy_gen.v
|
||||||
|
set_global_assignment -name VERILOG_FILE ../rtl/ring_osc.v
|
||||||
|
set_global_assignment -name VERILOG_FILE ../rtl/raw_capture.v
|
||||||
|
set_global_assignment -name VERILOG_FILE ../rtl/spi_slave.v
|
||||||
|
set_global_assignment -name SDC_FILE strix.sdc
|
||||||
|
|
||||||
|
# Configuration settings matching Arduino's Vidor projects; the .ttf is the input for the
|
||||||
|
# flash image embedded in the SAMD21 sketch.
|
||||||
|
set_global_assignment -name STRATIXV_CONFIGURATION_SCHEME "PASSIVE SERIAL"
|
||||||
|
set_global_assignment -name USE_CONFIGURATION_DEVICE ON
|
||||||
|
set_global_assignment -name CYCLONEIII_CONFIGURATION_DEVICE EPCS16
|
||||||
|
set_global_assignment -name CRC_ERROR_OPEN_DRAIN OFF
|
||||||
|
set_global_assignment -name STRATIX_DEVICE_IO_STANDARD "3.3-V LVTTL"
|
||||||
|
set_global_assignment -name CYCLONEII_RESERVE_NCEO_AFTER_CONFIGURATION "USE AS REGULAR IO"
|
||||||
|
set_global_assignment -name ENABLE_CONFIGURATION_PINS OFF
|
||||||
|
set_global_assignment -name ENABLE_BOOT_SEL_PIN OFF
|
||||||
|
set_global_assignment -name CONFIGURATION_VCCIO_LEVEL AUTO
|
||||||
|
set_global_assignment -name ON_CHIP_BITSTREAM_DECOMPRESSION ON
|
||||||
|
set_global_assignment -name GENERATE_TTF_FILE ON
|
||||||
|
|
||||||
set_location_assignment PIN_E2 -to iCLK
|
set_location_assignment PIN_E2 -to iCLK
|
||||||
set_location_assignment PIN_E1 -to iRESETn
|
set_location_assignment PIN_E1 -to iRESETn
|
||||||
set_location_assignment PIN_E10 -to oSDRAM_ADDR[11]
|
set_location_assignment PIN_E10 -to oSDRAM_ADDR[11]
|
||||||
|
|
|
||||||
14
fpga/src/strix.sdc
Normal file
14
fpga/src/strix.sdc
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
create_clock -name iCLK -period 20.833 [get_ports {iCLK}]
|
||||||
|
derive_pll_clocks
|
||||||
|
derive_clock_uncertainty
|
||||||
|
|
||||||
|
# Ring oscillator outputs are asynchronous by design; the first sampling stage absorbs them.
|
||||||
|
set_false_path -to [get_registers {*entropy_gen:generator|sample_raw[*]}]
|
||||||
|
|
||||||
|
# SPI inputs are synchronized in spi_slave; MISO timing relies on the half-SCK margin.
|
||||||
|
set_false_path -from [get_ports {bMKR_A[*]}]
|
||||||
|
set_false_path -to [get_ports {bMKR_A[*]}]
|
||||||
|
set_false_path -to [get_ports {oSAM_INT}]
|
||||||
|
|
||||||
|
# NINA pass-through is purely combinational between pins.
|
||||||
|
set_false_path -from [get_ports {bMKR_D[*] bWM_PIO* iWM_TX}] -to [get_ports {bMKR_D[*] bWM_PIO* oWM_RX oWM_RESET}]
|
||||||
Loading…
Add table
Add a link
Reference in a new issue