Two related Phase 5 additions, both threaded the same way (a new runtime field defaulting to the pre-existing behavior, settable per-layer via the descriptor table or per-run via SET_BASE): Configurable activation functions: - neuron_parallel.v gains a 2-bit `activation` port (ACT_NONE = linear + two-sided INT8 saturate, ACT_RELU = the original hardwired behavior, kept as the default so every pre-existing caller/testbench is unaffected), threaded through neuron_memory.v. - spi_engine.v: SET_BASE sel=6 (single-layer path); the descriptor table gains a 7th byte (multi-layer path). - Verified in neuron_parallel_tb.v (negative pass-through + negative saturation to -128) and end-to-end in spi_neuron_top_runnetwork_tb.v (a real negative accumulator that ACT_RELU would clamp to 0 comes through unclamped under ACT_NONE, over real SPI/RAM). Runtime network width (one bitstream, any topology up to its build-time max, entirely host-configured over SPI): - neuron_parallel.v gains n_inputs_real, bounding its MAC group loop (n_inputs_real/PARALLEL groups instead of the fixed build-time count). neuron_memory.v gains n_inputs_real/n_neurons_real, bounding its X/W RAM-read loop and its neuron loop. All default to the build-time max, so unconnected callers are unaffected. n_inputs_real must stay a multiple of PARALLEL (same constraint N_INPUTS itself is held to at elaboration time, now the caller's runtime responsibility). - spi_engine.v: SET_BASE sel=7/8 (single-layer path); the descriptor table grows to 11 bytes/layer (+n_inputs_real +n_neurons_real, multi-layer path) -- layer_sequencer.v also now copies only n_neurons_real bytes into the ping-pong buffer, not the full build width. - This is real early termination, not bookkeeping: no RAM zero-padding needed for the unused tail, and it measurably completes faster. neuron_parallel_tb.v TEST 7: 3 cycles vs 6 for a reduced-vs-full run, with garbage loaded into the skipped lanes to prove they're never read. neuron_memory_tb.v TEST 5: through the real PSRAM stack, 209 cycles vs 788. layer_sequencer_tb.v proves a reduced n_neurons_real shortens the ping-pong copy-out itself (bytes beyond the real count stay untouched, not just differing). docs/FPGA-NeuralNetwork-Engine.md: §8.1 opcode/SET_BASE table, new "Runtime network width" subsection, Phase 5 checklist, Current Status table, and the "Core architectural principle" statement updated to reflect that topology (not just trained parameters) is now host-configured at runtime up to a build-time ceiling. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WQV3vS9TXaGDJ5cRfnfidt
658 lines
24 KiB
Verilog
658 lines
24 KiB
Verilog
`timescale 1ns/1ps
|
|
|
|
// ================================================================
|
|
// SPI_ENGINE - opcode/protocol FSM + register bank
|
|
//
|
|
// Implements the v1 draft protocol in docs/FPGA-NeuralNetwork-Engine.md
|
|
// §8.1 on top of the byte-level interface exposed by spi_slave.v.
|
|
// One opcode byte per CS-low transaction (§8.1 framing).
|
|
//
|
|
// IMPORTANT (see rtl/spi_slave.v for the full contract):
|
|
// - tx_byte is driven COMBINATIONALLY from current state, so it is
|
|
// always correct whenever spi_slave.v prefetches it (at cs_fell
|
|
// and at every byte boundary) -- no explicit reaction needed.
|
|
// - Any stateful pointer (RAM address, response byte index) is
|
|
// advanced on rx_valid, which fires exactly once per REAL byte
|
|
// transferred -- never on tx_byte_req, which fires one extra
|
|
// "phantom" time after the last byte of a transaction.
|
|
//
|
|
// RAM byte-level master port uses the same convention as
|
|
// neuron_memory.v's external mem_* port (byte address, byte data,
|
|
// req/ready handshake) so it can share an arbiter + int8_memory_access
|
|
// + memory_interface chain with neuron_memory at the top level.
|
|
//
|
|
// v1 LIMITATION (documented, not yet solved): WRITE_RAM/READ_RAM
|
|
// have no backpressure to the SPI master. Each received/produced
|
|
// byte must be fully processed by this engine before the next
|
|
// SCLK-driven byte boundary arrives, i.e. the host must not clock
|
|
// RAM-touching commands faster than one RAM transaction (a handful
|
|
// of `clk` cycles) per SPI byte period. This is a reasonable
|
|
// constraint for bulk-loading weights/bias/input at initialization,
|
|
// not a real-time path.
|
|
// ================================================================
|
|
|
|
module spi_engine #(
|
|
parameter ADDR_WIDTH = 22,
|
|
parameter DATA_WIDTH = 8,
|
|
parameter N_INPUTS = 32,
|
|
parameter N_NEURONS = 1,
|
|
parameter PARALLEL = 8
|
|
)(
|
|
input wire clk,
|
|
input wire rst,
|
|
|
|
// ------------------------------------------------------------
|
|
// Byte-level interface from/to spi_slave.v
|
|
// ------------------------------------------------------------
|
|
|
|
input wire [7:0] rx_byte,
|
|
input wire rx_valid,
|
|
input wire cs_start,
|
|
input wire cs_end,
|
|
|
|
output wire [7:0] tx_byte,
|
|
input wire tx_byte_req, // unused on purpose, see header note
|
|
|
|
// ------------------------------------------------------------
|
|
// RAM byte-level master port (byte address, byte data)
|
|
// ------------------------------------------------------------
|
|
|
|
output reg ram_req,
|
|
output reg ram_wr,
|
|
output reg [ADDR_WIDTH-1:0] ram_addr,
|
|
output reg signed [7:0] ram_wdata,
|
|
|
|
input wire signed [7:0] ram_rdata,
|
|
input wire ram_ready,
|
|
|
|
// ------------------------------------------------------------
|
|
// neuron_memory control
|
|
// ------------------------------------------------------------
|
|
|
|
output reg [ADDR_WIDTH-1:0] x_base,
|
|
output reg [ADDR_WIDTH-1:0] w_base,
|
|
output reg [ADDR_WIDTH-1:0] bias_addr,
|
|
output reg [1:0] activation,
|
|
output reg [15:0] n_inputs_real,
|
|
output reg [15:0] n_neurons_real,
|
|
|
|
output reg nm_start,
|
|
input wire nm_busy,
|
|
input wire nm_done, // one-cycle pulse
|
|
|
|
input wire signed [DATA_WIDTH*N_NEURONS-1:0] y_bus,
|
|
|
|
output reg nm_soft_rst,
|
|
|
|
// ------------------------------------------------------------
|
|
// layer_sequencer control (Phase 5: RUN_NETWORK opcode)
|
|
// ------------------------------------------------------------
|
|
|
|
output reg [ADDR_WIDTH-1:0] table_base,
|
|
output reg [ADDR_WIDTH-1:0] buf_a_base,
|
|
output reg [ADDR_WIDTH-1:0] buf_b_base,
|
|
|
|
output reg run_start, // one-cycle pulse
|
|
output reg [7:0] run_num_layers,
|
|
|
|
input wire seq_busy,
|
|
input wire seq_done // one-cycle pulse
|
|
);
|
|
|
|
// ============================================================
|
|
// OPCODES (docs §8.1 -- values are draft/example, see header)
|
|
// ============================================================
|
|
|
|
localparam OP_NOP = 8'h00;
|
|
localparam OP_WRITE_RAM = 8'h01;
|
|
localparam OP_READ_RAM = 8'h02;
|
|
localparam OP_RESET = 8'h0F;
|
|
localparam OP_SET_BASE = 8'h10;
|
|
localparam OP_START = 8'h20;
|
|
localparam OP_STATUS = 8'h21;
|
|
localparam OP_READ_OUTPUT = 8'h22;
|
|
localparam OP_RUN_NETWORK = 8'h23;
|
|
localparam OP_READ_CONFIG = 8'h30;
|
|
|
|
// SET_BASE selector values
|
|
localparam SEL_X_BASE = 8'h00;
|
|
localparam SEL_W_BASE = 8'h01;
|
|
localparam SEL_BIAS_ADDR = 8'h02;
|
|
localparam SEL_TABLE_BASE = 8'h03;
|
|
localparam SEL_BUF_A_BASE = 8'h04;
|
|
localparam SEL_BUF_B_BASE = 8'h05;
|
|
localparam SEL_ACTIVATION = 8'h06;
|
|
localparam SEL_N_INPUTS = 8'h07;
|
|
localparam SEL_N_NEURONS = 8'h08;
|
|
|
|
// ============================================================
|
|
// STATES
|
|
// ============================================================
|
|
|
|
localparam ST_OPCODE = 4'd0;
|
|
localparam ST_SETBASE_SEL = 4'd1;
|
|
localparam ST_ADDR = 4'd2; // 3 bytes, MSB first
|
|
localparam ST_LEN = 4'd3; // 2 bytes, MSB first
|
|
localparam ST_WRITE_DATA = 4'd4;
|
|
localparam ST_WRITE_ISSUE = 4'd5;
|
|
localparam ST_WRITE_WAIT = 4'd6;
|
|
localparam ST_READ_ISSUE = 4'd7;
|
|
localparam ST_READ_WAIT = 4'd8;
|
|
localparam ST_READ_DATA = 4'd9;
|
|
localparam ST_RESP = 4'd10; // STATUS / READ_OUTPUT / READ_CONFIG
|
|
localparam ST_IGNORE = 4'd11;
|
|
localparam ST_RUNNET = 4'd12; // RUN_NETWORK: 1 payload byte (num_layers)
|
|
|
|
reg [3:0] state;
|
|
reg [7:0] opcode;
|
|
|
|
// Generic byte-position counter for ADDR (0..2) / LEN (0..1)
|
|
reg [1:0] byte_pos;
|
|
|
|
reg [23:0] addr_acc; // 3-byte accumulator, byte address
|
|
reg [15:0] len_acc; // 2-byte accumulator, transfer length
|
|
reg [15:0] len_remaining;
|
|
|
|
reg [ADDR_WIDTH-1:0] cur_addr;
|
|
|
|
reg pending_write;
|
|
reg [7:0] pending_wdata;
|
|
|
|
reg [7:0] cur_read_byte;
|
|
|
|
reg [3:0] resp_index; // response byte index (max needed: 8, READ_CONFIG)
|
|
reg [3:0] resp_len; // total bytes for the current response opcode
|
|
|
|
// ============================================================
|
|
// STICKY STATUS.done LATCH
|
|
//
|
|
// neuron_memory.done is a one-cycle pulse; STATUS must hold it
|
|
// until the host actually reads STATUS (or issues RESET), or a
|
|
// slow SPI poll would almost certainly miss it. See docs §8.1.
|
|
// ============================================================
|
|
|
|
reg status_done_sticky;
|
|
|
|
// net_mode: set while a RUN_NETWORK (multi-layer) job is in
|
|
// flight (from the accepted opcode until layer_sequencer's
|
|
// final seq_done), so STATUS.done latches on the sequencer's
|
|
// seq_done rather than on each intermediate per-layer nm_done
|
|
// pulse -- see done_event below.
|
|
reg net_mode;
|
|
|
|
wire busy_all = nm_busy | seq_busy;
|
|
wire done_event = net_mode ? seq_done : nm_done;
|
|
|
|
wire status_read_now = (state == ST_RESP) && (opcode == OP_STATUS) && rx_valid;
|
|
|
|
// status_snapshot: the STATUS byte is latched once, when the
|
|
// OP_STATUS opcode itself is accepted (ST_OPCODE, below), not
|
|
// read live/combinationally throughout ST_RESP. Without this,
|
|
// a done_event landing WHILE a STATUS response byte is already
|
|
// mid-transmission races the clear-on-read logic: the host can
|
|
// end up shifting out a stale pre-done byte while this engine
|
|
// simultaneously treats the sticky bit as "delivered" and
|
|
// clears it -- silently dropping the done transition forever
|
|
// (found via sim/spi_neuron_top_runnetwork_tb.v: a done_event
|
|
// landing mid-poll during continuous STATUS polling reproduces
|
|
// this every time). Freezing the byte at opcode-accept time and
|
|
// gating the clear on what was ACTUALLY snapshotted (below)
|
|
// closes the race: a done_event that lands too late to make it
|
|
// into this snapshot is simply reported on the next poll
|
|
// instead of being lost.
|
|
reg [7:0] status_snapshot;
|
|
|
|
always @(posedge clk) begin
|
|
if (rst) begin
|
|
status_done_sticky <= 1'b0;
|
|
end else if (nm_soft_rst) begin
|
|
status_done_sticky <= 1'b0;
|
|
end else if (done_event) begin
|
|
status_done_sticky <= 1'b1;
|
|
end else if (status_read_now && status_snapshot[1]) begin
|
|
status_done_sticky <= 1'b0;
|
|
end
|
|
end
|
|
|
|
// ============================================================
|
|
// tx_byte: fully combinational, always reflects "the byte to
|
|
// send right now" for the current state/response index. This
|
|
// is what spi_slave.v prefetches via tx_byte_req -- see the
|
|
// module header for why this must not depend on tx_byte_req.
|
|
// ============================================================
|
|
|
|
reg [7:0] tx_byte_comb;
|
|
|
|
always @(*) begin
|
|
tx_byte_comb = 8'h00;
|
|
|
|
case (state)
|
|
|
|
ST_READ_DATA: tx_byte_comb = cur_read_byte;
|
|
|
|
ST_RESP: begin
|
|
case (opcode)
|
|
|
|
OP_STATUS: tx_byte_comb = status_snapshot;
|
|
|
|
OP_READ_OUTPUT: begin
|
|
if (resp_index < N_NEURONS)
|
|
tx_byte_comb = y_bus[resp_index*DATA_WIDTH +: DATA_WIDTH];
|
|
else
|
|
tx_byte_comb = 8'h00;
|
|
end
|
|
|
|
OP_READ_CONFIG: begin
|
|
case (resp_index)
|
|
4'd0: tx_byte_comb = ADDR_WIDTH[7:0];
|
|
4'd1: tx_byte_comb = N_INPUTS[15:8];
|
|
4'd2: tx_byte_comb = N_INPUTS[7:0];
|
|
4'd3: tx_byte_comb = N_NEURONS[7:0];
|
|
4'd4: tx_byte_comb = PARALLEL[7:0];
|
|
4'd5: tx_byte_comb = DATA_WIDTH[7:0];
|
|
4'd6: tx_byte_comb = 8'h00; // protocol version 0x0001, high byte
|
|
4'd7: tx_byte_comb = 8'h01; // protocol version 0x0001, low byte
|
|
default: tx_byte_comb = 8'h00;
|
|
endcase
|
|
end
|
|
|
|
default: tx_byte_comb = 8'h00;
|
|
|
|
endcase
|
|
end
|
|
|
|
default: tx_byte_comb = 8'h00;
|
|
|
|
endcase
|
|
end
|
|
|
|
assign tx_byte = tx_byte_comb;
|
|
|
|
// ============================================================
|
|
// MAIN FSM
|
|
// ============================================================
|
|
|
|
always @(posedge clk) begin
|
|
|
|
if (rst) begin
|
|
|
|
state <= ST_OPCODE;
|
|
opcode <= 8'h00;
|
|
byte_pos <= 2'd0;
|
|
addr_acc <= 24'h0;
|
|
len_acc <= 16'h0;
|
|
len_remaining <= 16'h0;
|
|
cur_addr <= {ADDR_WIDTH{1'b0}};
|
|
pending_write <= 1'b0;
|
|
pending_wdata <= 8'h00;
|
|
cur_read_byte <= 8'h00;
|
|
resp_index <= 4'd0;
|
|
resp_len <= 4'd0;
|
|
status_snapshot <= 8'h00;
|
|
|
|
ram_req <= 1'b0;
|
|
ram_wr <= 1'b0;
|
|
ram_addr <= {ADDR_WIDTH{1'b0}};
|
|
ram_wdata <= 8'sd0;
|
|
|
|
x_base <= {ADDR_WIDTH{1'b0}};
|
|
w_base <= {ADDR_WIDTH{1'b0}};
|
|
bias_addr <= {ADDR_WIDTH{1'b0}};
|
|
activation <= 2'd1; // ACT_RELU, matches neuron_parallel's own default
|
|
n_inputs_real <= N_INPUTS[15:0];
|
|
n_neurons_real <= N_NEURONS[15:0];
|
|
|
|
nm_start <= 1'b0;
|
|
nm_soft_rst <= 1'b0;
|
|
|
|
table_base <= {ADDR_WIDTH{1'b0}};
|
|
buf_a_base <= {ADDR_WIDTH{1'b0}};
|
|
buf_b_base <= {ADDR_WIDTH{1'b0}};
|
|
run_start <= 1'b0;
|
|
run_num_layers <= 8'h00;
|
|
net_mode <= 1'b0;
|
|
|
|
end else begin
|
|
|
|
// --------------------------------------------------
|
|
// Default pulses
|
|
// --------------------------------------------------
|
|
ram_req <= 1'b0;
|
|
nm_start <= 1'b0;
|
|
nm_soft_rst <= 1'b0;
|
|
run_start <= 1'b0;
|
|
|
|
if (seq_done) begin
|
|
net_mode <= 1'b0;
|
|
end
|
|
|
|
if (cs_end) begin
|
|
|
|
// End of transaction: always return to opcode wait,
|
|
// regardless of where we were (defensive: a short
|
|
// or malformed transaction cannot wedge the engine).
|
|
state <= ST_OPCODE;
|
|
|
|
end else begin
|
|
|
|
case (state)
|
|
|
|
// =============================================
|
|
// OPCODE
|
|
// =============================================
|
|
|
|
ST_OPCODE: begin
|
|
|
|
if (rx_valid) begin
|
|
|
|
opcode <= rx_byte;
|
|
byte_pos <= 2'd0;
|
|
|
|
case (rx_byte)
|
|
|
|
OP_WRITE_RAM, OP_READ_RAM: begin
|
|
addr_acc <= 24'h0;
|
|
state <= ST_ADDR;
|
|
end
|
|
|
|
OP_SET_BASE: begin
|
|
state <= ST_SETBASE_SEL;
|
|
end
|
|
|
|
OP_START: begin
|
|
if (!busy_all)
|
|
nm_start <= 1'b1;
|
|
state <= ST_IGNORE;
|
|
end
|
|
|
|
OP_RUN_NETWORK: begin
|
|
state <= ST_RUNNET;
|
|
end
|
|
|
|
OP_RESET: begin
|
|
nm_soft_rst <= 1'b1;
|
|
net_mode <= 1'b0;
|
|
state <= ST_IGNORE;
|
|
end
|
|
|
|
OP_STATUS: begin
|
|
resp_index <= 4'd0;
|
|
resp_len <= 4'd1;
|
|
status_snapshot <= {6'b0, status_done_sticky, busy_all};
|
|
state <= ST_RESP;
|
|
end
|
|
|
|
OP_READ_OUTPUT: begin
|
|
resp_index <= 4'd0;
|
|
resp_len <= N_NEURONS[3:0];
|
|
state <= ST_RESP;
|
|
end
|
|
|
|
OP_READ_CONFIG: begin
|
|
resp_index <= 4'd0;
|
|
resp_len <= 4'd8;
|
|
state <= ST_RESP;
|
|
end
|
|
|
|
default: begin // OP_NOP and unknown opcodes
|
|
state <= ST_IGNORE;
|
|
end
|
|
|
|
endcase
|
|
|
|
end
|
|
|
|
end
|
|
|
|
// =============================================
|
|
// SET_BASE: 1 selector byte, then 3 addr bytes
|
|
// =============================================
|
|
|
|
ST_SETBASE_SEL: begin
|
|
|
|
if (rx_valid) begin
|
|
addr_acc <= 24'h0;
|
|
// Reuse `len_acc[7:0]` as a 1-byte stash
|
|
// for the selector between states.
|
|
len_acc[7:0] <= rx_byte;
|
|
state <= ST_ADDR;
|
|
end
|
|
|
|
end
|
|
|
|
// =============================================
|
|
// ADDR: 3 bytes, MSB first
|
|
// Shared by WRITE_RAM / READ_RAM / SET_BASE.
|
|
// =============================================
|
|
|
|
ST_ADDR: begin
|
|
|
|
if (rx_valid) begin
|
|
|
|
addr_acc <= {addr_acc[15:0], rx_byte};
|
|
|
|
if (byte_pos == 2'd2) begin
|
|
|
|
byte_pos <= 2'd0;
|
|
|
|
if (opcode == OP_SET_BASE) begin
|
|
|
|
case (len_acc[7:0])
|
|
SEL_X_BASE: x_base <= {addr_acc[15:0], rx_byte};
|
|
SEL_W_BASE: w_base <= {addr_acc[15:0], rx_byte};
|
|
SEL_BIAS_ADDR: bias_addr <= {addr_acc[15:0], rx_byte};
|
|
SEL_TABLE_BASE: table_base <= {addr_acc[15:0], rx_byte};
|
|
SEL_BUF_A_BASE: buf_a_base <= {addr_acc[15:0], rx_byte};
|
|
SEL_BUF_B_BASE: buf_b_base <= {addr_acc[15:0], rx_byte};
|
|
SEL_ACTIVATION: activation <= rx_byte[1:0]; // low 2 bits of the low addr byte
|
|
SEL_N_INPUTS: n_inputs_real <= {addr_acc[7:0], rx_byte}; // low 2 of the 3 addr bytes, BE
|
|
SEL_N_NEURONS: n_neurons_real <= {addr_acc[7:0], rx_byte}; // low 2 of the 3 addr bytes, BE
|
|
default: ; // reserved selector: ignored
|
|
endcase
|
|
|
|
state <= ST_IGNORE;
|
|
|
|
end else begin
|
|
|
|
cur_addr <= {addr_acc[15:0], rx_byte};
|
|
len_acc <= 16'h0;
|
|
state <= ST_LEN;
|
|
|
|
end
|
|
|
|
end else begin
|
|
|
|
byte_pos <= byte_pos + 2'd1;
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
// =============================================
|
|
// LEN: 2 bytes, MSB first (WRITE_RAM / READ_RAM)
|
|
// =============================================
|
|
|
|
ST_LEN: begin
|
|
|
|
if (rx_valid) begin
|
|
|
|
len_acc <= {len_acc[7:0], rx_byte};
|
|
|
|
if (byte_pos == 2'd1) begin
|
|
|
|
len_remaining <= {len_acc[7:0], rx_byte};
|
|
byte_pos <= 2'd0;
|
|
|
|
if ({len_acc[7:0], rx_byte} == 16'h0) begin
|
|
|
|
state <= ST_IGNORE;
|
|
|
|
end else if (opcode == OP_WRITE_RAM) begin
|
|
|
|
state <= ST_WRITE_DATA;
|
|
|
|
end else begin // OP_READ_RAM
|
|
|
|
state <= ST_READ_ISSUE;
|
|
|
|
end
|
|
|
|
end else begin
|
|
|
|
byte_pos <= byte_pos + 2'd1;
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
// =============================================
|
|
// WRITE_RAM: accept one data byte, write it,
|
|
// repeat for len_remaining bytes.
|
|
// =============================================
|
|
|
|
ST_WRITE_DATA: begin
|
|
|
|
if (rx_valid) begin
|
|
pending_wdata <= rx_byte;
|
|
state <= ST_WRITE_ISSUE;
|
|
end
|
|
|
|
end
|
|
|
|
ST_WRITE_ISSUE: begin
|
|
|
|
ram_req <= 1'b1;
|
|
ram_wr <= 1'b1;
|
|
ram_addr <= cur_addr;
|
|
ram_wdata <= $signed(pending_wdata);
|
|
|
|
state <= ST_WRITE_WAIT;
|
|
|
|
end
|
|
|
|
ST_WRITE_WAIT: begin
|
|
|
|
if (ram_ready) begin
|
|
|
|
cur_addr <= cur_addr + 1'b1;
|
|
len_remaining <= len_remaining - 16'd1;
|
|
|
|
if (len_remaining == 16'd1)
|
|
state <= ST_IGNORE;
|
|
else
|
|
state <= ST_WRITE_DATA;
|
|
|
|
end
|
|
|
|
end
|
|
|
|
// =============================================
|
|
// READ_RAM: prefetch one byte, serve it, repeat.
|
|
// =============================================
|
|
|
|
ST_READ_ISSUE: begin
|
|
|
|
ram_req <= 1'b1;
|
|
ram_wr <= 1'b0;
|
|
ram_addr <= cur_addr;
|
|
|
|
state <= ST_READ_WAIT;
|
|
|
|
end
|
|
|
|
ST_READ_WAIT: begin
|
|
|
|
if (ram_ready) begin
|
|
cur_read_byte <= ram_rdata[7:0];
|
|
state <= ST_READ_DATA;
|
|
end
|
|
|
|
end
|
|
|
|
ST_READ_DATA: begin
|
|
|
|
// rx_valid marks that the response byte
|
|
// currently on tx_byte has been shifted out
|
|
// and a (dummy) MOSI byte was received in
|
|
// exchange -- advance to the next one.
|
|
if (rx_valid) begin
|
|
|
|
cur_addr <= cur_addr + 1'b1;
|
|
len_remaining <= len_remaining - 16'd1;
|
|
|
|
if (len_remaining == 16'd1)
|
|
state <= ST_IGNORE;
|
|
else
|
|
state <= ST_READ_ISSUE;
|
|
|
|
end
|
|
|
|
end
|
|
|
|
// =============================================
|
|
// RUN_NETWORK: 1 payload byte (num_layers),
|
|
// then pulse run_start for layer_sequencer.
|
|
// No-op (ignored, like OP_START) if the compute
|
|
// engine is already busy in any form.
|
|
// =============================================
|
|
|
|
ST_RUNNET: begin
|
|
|
|
if (rx_valid) begin
|
|
|
|
if (!busy_all) begin
|
|
run_start <= 1'b1;
|
|
run_num_layers <= rx_byte;
|
|
net_mode <= 1'b1;
|
|
end
|
|
|
|
state <= ST_IGNORE;
|
|
|
|
end
|
|
|
|
end
|
|
|
|
// =============================================
|
|
// STATUS / READ_OUTPUT / READ_CONFIG response
|
|
// =============================================
|
|
|
|
ST_RESP: begin
|
|
|
|
if (rx_valid) begin
|
|
|
|
if (resp_index == resp_len - 4'd1)
|
|
state <= ST_IGNORE;
|
|
else
|
|
resp_index <= resp_index + 4'd1;
|
|
|
|
end
|
|
|
|
end
|
|
|
|
// =============================================
|
|
// IGNORE: transaction's meaningful bytes are
|
|
// done; ignore anything else until cs_end.
|
|
// =============================================
|
|
|
|
ST_IGNORE: begin
|
|
// intentionally empty
|
|
end
|
|
|
|
default: begin
|
|
state <= ST_OPCODE;
|
|
end
|
|
|
|
endcase
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
endmodule
|