strix/fpga/rtl/strix_core.v

527 lines
26 KiB
Verilog

// Board-independent STRIX logic: entropy generators with health monitors, entropy byte
// 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,
// 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.
//
// Capture sources, for generator g = 0..3:
// 0x00+g processed output (spatial XOR, then temporal XOR; capture with decimation = TEMPORAL)
// 0x04+g spatial XOR of all rings, every clock
// 0x08+g enhanced output of ring RING_SELECT
// 0x0C+g single tap of ring RING_SELECT
//
// Health: a generator is healthy when it is enabled, its monitor has completed a dead-ring
// sweep, neither NIST test has failed since the last clear, and at most MAX_DEAD_RINGS rings
// are dead. Per-generator registers at 0x30 + 4g: FLAGS, RCT_EVENTS, APT_EVENTS, DEAD_RINGS.
//
// 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), 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 #(
parameter CAPTURE_ADDR_WIDTH = 15,
parameter FREQ_GATE_BITS = 24,
parameter RINGS_PER_GEN = 90,
parameter DEAD_WINDOW_BITS = 16,
parameter TICK_CYCLES = 120000, // clk cycles per millisecond
parameter GCP_TRIAL_MS = 1000
) (
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,
output reg nina_enable, // releases the WiFi module from reset (see REG_NINA_CTRL)
output reg nina_gpio0_in // GPIO0 is driven by the module (to the SAMD21), not to it
);
localparam [7:0] VERSION = 8'h0A;
localparam GENERATORS = 4;
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_FREQ_GATE = 7'h05, // read-only: gate is 2^value clk cycles
REG_GENERATORS = 7'h06, // read-only
REG_RINGS = 7'h07, // read-only: rings per generator
REG_SOURCE_A = 7'h08,
REG_DECIM_HIGH = 7'h09,
REG_DECIM_LOW = 7'h0A,
REG_CONTROL = 7'h0B, // write: bit 0 start capture, bit 1 rewind DATA,
// bit 2 start frequency measurement
REG_STATUS = 7'h0C, // bit 0 capture busy, bit 1 capture done,
// bit 2 frequency busy, bit 3 frequency valid
REG_SOURCE_B = 7'h0D,
REG_MODE = 7'h0E, // bit 0 pair capture (A and B per sample)
REG_DATA = 7'h10,
REG_RING_SELECT = 7'h11, // ring for the characterization sources and frequency
REG_FREQ_COUNT3 = 7'h12, // prescaled edge count, most significant byte first
REG_FREQ_COUNT2 = 7'h13,
REG_FREQ_COUNT1 = 7'h14,
REG_FREQ_COUNT0 = 7'h15,
REG_GEN_ENABLE = 7'h16, // bit g enables generator g
REG_TEMPORAL = 7'h17, // consecutive bits XORed per output bit, 1-8
REG_FREQ_GEN = 7'h18, // generator for the frequency measurement
REG_HEALTHY = 7'h20, // read-only: bit g = generator g healthy
REG_HEALTH_CTRL = 7'h21, // write: bit 0 clears failures and event counters
REG_RCT_CUTOFF = 7'h22, // repetition count test cutoff (run length)
REG_APT_HIGH = 7'h23, // adaptive proportion test cutoff (per 1024 bits)
REG_APT_LOW = 7'h24,
REG_MAX_DEAD = 7'h25, // dead rings tolerated per generator
REG_TRIAL_HIGH = 7'h26, // trial length in ms
REG_TRIAL_LOW = 7'h27,
REG_STATS_CTRL = 7'h28, // bit 0 von Neumann on the statistics path;
// write bit 1: restart trials, sequence 0
REG_STREAM_CTRL = 7'h29, // bits 2:0 source (0-3 generator, 4 combined),
// bit 3 von Neumann; write bit 7: flush
REG_LEVEL_HIGH = 7'h2A, // bytes waiting in the stream FIFO
REG_LEVEL_LOW = 7'h2B,
REG_DROP_HIGH = 7'h2C, // bytes dropped because the FIFO was full
REG_DROP_LOW = 7'h2D,
REG_NINA_CTRL = 7'h2E, // bit 0: WiFi module may run (otherwise held in reset);
// bit 1: GPIO0 direction, 0 SAMD21 -> module (boot
// mode selection), 1 module -> SAMD21 (its data-ready
// signal once running)
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)
REG_AMP_SUB_MS = 7'h52, // sub-trial length in ms
REG_AMP_SUBS = 7'h53, // sub-trials per trial, 1-15
REG_AMP_STAGE = 7'h54; // stage s at 0x54 + 3s: mode (0 random walk,
// 1 majority vote), parameter high, parameter low
// Per-generator health registers at 0x30 + 4g + index
localparam [1:0] GEN_FLAGS = 2'd0, // bit 0 RCT failed, 1 APT failed, 2 too many dead rings,
// 3 active, 4 sweep done, 5 healthy
GEN_RCT_EVENTS = 2'd1,
GEN_APT_EVENTS = 2'd2,
GEN_DEAD_RINGS = 2'd3;
// Generators
reg [GENERATORS-1:0] gen_enable;
reg [3:0] temporal;
reg [7:0] ring_select;
wire [GENERATORS-1:0] out_bit, out_valid, spatial_bit, ring_enhanced, ring_tap, ring_raw_tap;
reg health_clear;
reg [7:0] rct_cutoff, max_dead;
reg [10:0] apt_cutoff;
wire [GENERATORS-1:0] rct_failed, apt_failed, sweep_done, too_many_dead, healthy;
wire [7:0] rct_events [0:GENERATORS-1];
wire [7:0] apt_events [0:GENERATORS-1];
wire [7:0] dead_rings [0:GENERATORS-1];
genvar g;
generate
for (g = 0; g < GENERATORS; g = g + 1) begin : gen
wire [RINGS_PER_GEN-1:0] ring_samples;
wire active = gen_enable[g] && !reset;
entropy_generator #(.RINGS(RINGS_PER_GEN)) generator (
.clk(clk), .enable(gen_enable[g]), .temporal(temporal), .ring_select(ring_select),
.out_bit(out_bit[g]), .out_valid(out_valid[g]), .spatial_bit(spatial_bit[g]),
.ring_enhanced(ring_enhanced[g]), .ring_tap(ring_tap[g]),
.ring_raw_tap(ring_raw_tap[g]), .ring_samples(ring_samples)
);
health_monitor #(.RINGS(RINGS_PER_GEN), .DEAD_WINDOW_BITS(DEAD_WINDOW_BITS)) monitor (
.clk(clk), .active(active), .clear(health_clear),
.sample_bit(spatial_bit[g]), .ring_samples(ring_samples),
.rct_cutoff(rct_cutoff), .apt_cutoff(apt_cutoff),
.rct_failed(rct_failed[g]), .apt_failed(apt_failed[g]),
.rct_events(rct_events[g]), .apt_events(apt_events[g]),
.dead_rings(dead_rings[g]), .sweep_done(sweep_done[g])
);
assign too_many_dead[g] = sweep_done[g] && dead_rings[g] > max_dead;
assign healthy[g] = active && sweep_done[g] && !rct_failed[g] && !apt_failed[g]
&& !too_many_dead[g];
end
endgenerate
// Statistics. All generators share the temporal setting and run in lockstep, so
// generator 0's valid strobe times every channel.
reg [15:0] trial_ms;
reg stats_von_neumann, stats_restart;
reg [7:0] stats_index;
wire [7:0] stats_byte;
wire trial_end;
// Registered inputs keep the health logic and the combining XOR off the counter paths;
// every channel is delayed by the same cycle, so they stay aligned.
reg [GENERATORS:0] stats_bits;
reg stats_valid;
reg [GENERATORS-1:0] stats_healthy;
always @(posedge clk) begin
stats_healthy <= healthy;
stats_bits <= {^(out_bit & healthy), out_bit};
stats_valid <= out_valid[0];
end
// Entropy byte stream, fed from the same registered outputs. A generator that is not
// healthy (or no healthy generator, for the combined channel) feeds nothing.
reg [2:0] stream_source;
reg stream_von_neumann, stream_flush, stream_pop;
wire [7:0] stream_head;
wire stream_available;
wire [12:0] stream_level;
wire [15:0] stream_dropped;
wire stream_source_healthy = stream_source == 3'd4 ? |stats_healthy
: stats_healthy[stream_source[1:0]];
entropy_stream #(.ADDR_WIDTH(12)) stream (
.clk(clk), .reset(reset), .flush(stream_flush),
.bit_in(stats_bits[stream_source]), .valid(stats_valid && stream_source_healthy),
.von_neumann(stream_von_neumann), .pop(stream_pop),
.head(stream_head), .available(stream_available), .level(stream_level),
.dropped(stream_dropped)
);
// Bias amplifier, fed from the same registered generator outputs
reg [GENERATORS-1:0] amp_sources;
reg amp_von_neumann, amp_restart;
reg [7:0] amp_sub_ms;
reg [3:0] amp_subtrials;
reg [2:0] amp_stage_mode;
reg [47:0] amp_stage_param;
reg [7:0] amp_index;
wire [7:0] amp_byte;
wire amp_trial_end;
bias_amplifier #(.GENERATORS(GENERATORS), .TICK_CYCLES(TICK_CYCLES)) amplifier (
.clk(clk), .reset(reset), .gen_bit(stats_bits[GENERATORS-1:0]), .gen_valid(stats_valid),
.sources(amp_sources & stats_healthy), .von_neumann(amp_von_neumann),
.stage_mode(amp_stage_mode), .stage_param(amp_stage_param),
.sub_ms(amp_sub_ms), .subtrials(amp_subtrials), .restart(amp_restart),
.read_index(amp_index), .read_byte(amp_byte), .trial_end(amp_trial_end)
);
stats_block #(.CHANNELS(GENERATORS + 1), .TICK_CYCLES(TICK_CYCLES)) stats (
// Only healthy generators are counted; the combined channel while any is healthy.
.clk(clk), .reset(reset), .bits(stats_bits),
.valid({GENERATORS + 1{stats_valid}} & {|stats_healthy, stats_healthy}),
.von_neumann(stats_von_neumann), .trial_ms(trial_ms), .restart(stats_restart),
.healthy(stats_healthy), .read_index(stats_index), .read_byte(stats_byte),
.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;
reg bit_a, bit_b;
wire [15:0] all_sources = {ring_tap, ring_enhanced, spatial_bit, out_bit};
always @(posedge clk) begin
bit_a <= all_sources[source_a];
bit_b <= all_sources[source_b];
end
// Capture
reg pair_mode;
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), .bit_a(bit_a), .bit_b(bit_b), .pair_mode(pair_mode),
.decimation(decimation), .start(capture_start),
.busy(capture_busy), .done(capture_done),
.read_addr(prefetch_addr), .read_data(capture_byte)
);
// Frequency counter
reg [1:0] freq_gen;
reg freq_start;
wire freq_busy, freq_valid;
wire [31:0] freq_count;
freq_counter #(.GATE_BITS(FREQ_GATE_BITS)) counter (
.clk(clk), .reset(reset), .signal(ring_raw_tap[freq_gen]), .start(freq_start),
.busy(freq_busy), .valid(freq_valid), .count(freq_count)
);
// SPI
wire selected, rx_valid, rx_is_first, tx_loaded;
wire [7:0] rx_byte;
reg [7:0] tx_byte, tx_byte_next;
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;
function [7:0] gen_register(input [6:0] addr);
reg [1:0] n;
begin
n = addr[3:2];
if (addr[6:4] != 3'b011)
gen_register = 8'h00;
else case (addr[1:0])
GEN_FLAGS: gen_register = {2'd0, healthy[n], sweep_done[n], gen_enable[n],
too_many_dead[n], apt_failed[n], rct_failed[n]};
GEN_RCT_EVENTS: gen_register = rct_events[n];
GEN_APT_EVENTS: gen_register = apt_events[n];
default: gen_register = sweep_done[n] ? dead_rings[n] : 8'h00;
endcase
end
endfunction
always @(*) begin
case (reg_addr)
REG_ID0: tx_byte_next = "S";
REG_ID1: tx_byte_next = "T";
REG_ID2: tx_byte_next = "R";
REG_ID3: tx_byte_next = "X";
REG_VERSION: tx_byte_next = VERSION;
REG_FREQ_GATE: tx_byte_next = FREQ_GATE_BITS;
REG_GENERATORS: tx_byte_next = GENERATORS;
REG_RINGS: tx_byte_next = RINGS_PER_GEN;
REG_SOURCE_A: tx_byte_next = {4'd0, source_a};
REG_DECIM_HIGH: tx_byte_next = decimation[15:8];
REG_DECIM_LOW: tx_byte_next = decimation[7:0];
REG_STATUS: tx_byte_next = {4'd0, freq_valid, freq_busy, capture_done, capture_busy};
REG_SOURCE_B: tx_byte_next = {4'd0, source_b};
REG_MODE: tx_byte_next = {7'd0, pair_mode};
REG_RING_SELECT: tx_byte_next = ring_select;
REG_FREQ_COUNT3: tx_byte_next = freq_count[31:24];
REG_FREQ_COUNT2: tx_byte_next = freq_count[23:16];
REG_FREQ_COUNT1: tx_byte_next = freq_count[15:8];
REG_FREQ_COUNT0: tx_byte_next = freq_count[7:0];
REG_GEN_ENABLE: tx_byte_next = {4'd0, gen_enable};
REG_TEMPORAL: tx_byte_next = {4'd0, temporal};
REG_FREQ_GEN: tx_byte_next = {6'd0, freq_gen};
REG_HEALTHY: tx_byte_next = {4'd0, healthy};
REG_RCT_CUTOFF: tx_byte_next = rct_cutoff;
REG_APT_HIGH: tx_byte_next = {5'd0, apt_cutoff[10:8]};
REG_APT_LOW: tx_byte_next = apt_cutoff[7:0];
REG_MAX_DEAD: tx_byte_next = max_dead;
REG_TRIAL_HIGH: tx_byte_next = trial_ms[15:8];
REG_TRIAL_LOW: tx_byte_next = trial_ms[7:0];
REG_STATS_CTRL: tx_byte_next = {7'd0, stats_von_neumann};
REG_STREAM_CTRL: tx_byte_next = {4'd0, stream_von_neumann, stream_source};
REG_LEVEL_HIGH: tx_byte_next = {3'd0, stream_level[12:8]};
REG_LEVEL_LOW: tx_byte_next = stream_level[7:0];
REG_DROP_HIGH: tx_byte_next = stream_dropped[15:8];
REG_DROP_LOW: tx_byte_next = stream_dropped[7:0];
REG_NINA_CTRL: tx_byte_next = {6'd0, nina_gpio0_in, nina_enable};
REG_AMP_CTRL: tx_byte_next = {7'd0, amp_von_neumann};
REG_AMP_SOURCES: tx_byte_next = {4'd0, amp_sources};
REG_AMP_SUB_MS: tx_byte_next = amp_sub_ms;
REG_AMP_SUBS: tx_byte_next = {4'd0, amp_subtrials};
7'h54: tx_byte_next = {7'd0, amp_stage_mode[0]};
7'h55: tx_byte_next = amp_stage_param[15:8];
7'h56: tx_byte_next = amp_stage_param[7:0];
7'h57: tx_byte_next = {7'd0, amp_stage_mode[1]};
7'h58: tx_byte_next = amp_stage_param[31:24];
7'h59: tx_byte_next = amp_stage_param[23:16];
7'h5A: tx_byte_next = {7'd0, amp_stage_mode[2]};
7'h5B: tx_byte_next = amp_stage_param[47:40];
7'h5C: tx_byte_next = amp_stage_param[39:32];
default: tx_byte_next = gen_register(reg_addr);
endcase
end
// Registered read-back. The next byte is needed only half an SCK period after the previous
// one completes. Plain registers are static, so their wide multiplexer gets an extra
// pipeline stage; burst registers, whose bytes change while they are read, take a short
// path.
reg [7:0] register_byte;
reg [7:0] burst_byte;
reg is_burst;
always @(*) begin
is_burst = 1'b1;
case (reg_addr)
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;
is_burst = 1'b0;
end
endcase
end
always @(posedge clk) begin
register_byte <= tx_byte_next;
tx_byte <= is_burst ? burst_byte : register_byte;
end
always @(posedge clk) begin
capture_start <= 1'b0;
freq_start <= 1'b0;
health_clear <= 1'b0;
stats_restart <= 1'b0;
amp_restart <= 1'b0;
stream_flush <= 1'b0;
stream_pop <= 1'b0;
if (reset) begin
gen_enable <= {GENERATORS{1'b1}};
temporal <= 4'd5;
ring_select <= 8'd0;
source_a <= 4'h0;
source_b <= 4'h1;
pair_mode <= 1'b0;
decimation <= 16'd5;
freq_gen <= 2'd0;
rct_cutoff <= 8'd81; // 0.5 bits/bit claimed, alpha 2^-40
apt_cutoff <= 11'd824; // same, 1024-bit window
max_dead <= 8'd8; // measured sizing tolerates about 17
health_clear <= 1'b1;
trial_ms <= 16'd200;
stats_von_neumann <= 1'b0;
stats_index <= 8'd0;
amp_sources <= {GENERATORS{1'b1}};
amp_von_neumann <= 1'b0;
amp_sub_ms <= 8'd40;
amp_subtrials <= 4'd5;
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;
stream_von_neumann <= 1'b0;
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)
amp_index <= 8'd0;
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)
stats_index <= stats_index + 8'd1;
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 && reg_addr == REG_STREAM) begin
stream_pop <= 1'b1; // the byte has been clocked out
end else if (!is_read) begin
case (reg_addr)
REG_SOURCE_A: source_a <= rx_byte[3:0];
REG_SOURCE_B: source_b <= rx_byte[3:0];
REG_MODE: pair_mode <= rx_byte[0];
REG_DECIM_HIGH: decimation[15:8] <= rx_byte;
REG_DECIM_LOW: decimation[7:0] <= rx_byte;
REG_RING_SELECT: if (rx_byte < RINGS_PER_GEN) ring_select <= rx_byte;
REG_GEN_ENABLE: gen_enable <= rx_byte[GENERATORS-1:0];
REG_TEMPORAL: if (rx_byte >= 8'd1 && rx_byte <= 8'd8) temporal <= rx_byte[3:0];
REG_FREQ_GEN: freq_gen <= rx_byte[1:0];
REG_HEALTH_CTRL: health_clear <= rx_byte[0];
REG_RCT_CUTOFF: if (rx_byte >= 8'd2) rct_cutoff <= rx_byte;
REG_APT_HIGH: apt_cutoff[10:8] <= rx_byte[2:0];
REG_APT_LOW: apt_cutoff[7:0] <= rx_byte;
REG_MAX_DEAD: max_dead <= rx_byte;
REG_TRIAL_HIGH: trial_ms[15:8] <= rx_byte;
REG_TRIAL_LOW: trial_ms[7:0] <= rx_byte;
REG_NINA_CTRL: begin
nina_enable <= rx_byte[0];
nina_gpio0_in <= rx_byte[1];
end
REG_STREAM_CTRL: begin
if (rx_byte[2:0] <= 3'd4) stream_source <= rx_byte[2:0];
stream_von_neumann <= rx_byte[3];
stream_flush <= rx_byte[7];
end
REG_AMP_CTRL: begin
amp_von_neumann <= rx_byte[0];
amp_restart <= rx_byte[1];
end
REG_AMP_SOURCES: amp_sources <= rx_byte[GENERATORS-1:0];
REG_AMP_SUB_MS: if (rx_byte != 8'd0) amp_sub_ms <= rx_byte;
REG_AMP_SUBS: if (rx_byte >= 8'd1 && rx_byte <= 8'd15) amp_subtrials <= rx_byte[3:0];
7'h54: amp_stage_mode[0] <= rx_byte[0];
7'h55: amp_stage_param[15:8] <= rx_byte;
7'h56: amp_stage_param[7:0] <= rx_byte;
7'h57: amp_stage_mode[1] <= rx_byte[0];
7'h58: amp_stage_param[31:24] <= rx_byte;
7'h59: amp_stage_param[23:16] <= rx_byte;
7'h5A: amp_stage_mode[2] <= rx_byte[0];
7'h5B: amp_stage_param[47:40] <= rx_byte;
7'h5C: amp_stage_param[39:32] <= rx_byte;
REG_STATS_CTRL: begin
stats_von_neumann <= rx_byte[0];
stats_restart <= rx_byte[1];
end
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}};
if (rx_byte[2]) freq_start <= 1'b1;
end
default: ;
endcase
end
end
end
end
endmodule