catalyst-n1 / tb /tb_async.v
mrwabbit's picture
Initial upload: Catalyst N1 open source neuromorphic processor RTL
e4cdd5f verified
Raw
History Blame Contribute Delete
17.1 kB
// ============================================================================
// Testbench: Async Event-Driven Mode (Phase 12)
// ============================================================================
//
// Copyright 2026 Henry Arthur Shulayev Barnes / Catalyst Neuromorphic Ltd
// Company No. 17054540 — UK Patent Application No. 2602902.6
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ============================================================================
`timescale 1ns / 1ps
module tb_async;
parameter NUM_CORES = 4;
parameter CORE_ID_BITS = 2;
parameter NUM_NEURONS = 256;
parameter NEURON_BITS = 8;
parameter DATA_WIDTH = 16;
parameter MAX_FANOUT = 32;
parameter FANOUT_BITS = 5;
parameter CONN_ADDR_BITS = 13;
parameter CLK_PERIOD = 10;
reg clk, rst_n;
reg start;
reg async_enable;
reg prog_conn_we;
reg [CORE_ID_BITS-1:0] prog_conn_core;
reg [NEURON_BITS-1:0] prog_conn_src;
reg [FANOUT_BITS-1:0] prog_conn_slot;
reg [NEURON_BITS-1:0] prog_conn_target;
reg signed [DATA_WIDTH-1:0] prog_conn_weight;
reg prog_route_we;
reg [CORE_ID_BITS-1:0] prog_route_src_core;
reg [NEURON_BITS-1:0] prog_route_src_neuron;
reg [CORE_ID_BITS-1:0] prog_route_dest_core;
reg [NEURON_BITS-1:0] prog_route_dest_neuron;
reg signed [DATA_WIDTH-1:0] prog_route_weight;
reg ext_valid;
reg [CORE_ID_BITS-1:0] ext_core;
reg [NEURON_BITS-1:0] ext_neuron_id;
reg signed [DATA_WIDTH-1:0] ext_current;
wire timestep_done;
wire [NUM_CORES-1:0] spike_valid_bus;
wire [NUM_CORES*NEURON_BITS-1:0] spike_id_bus;
wire [4:0] mesh_state_out;
wire [31:0] total_spikes;
wire [31:0] timestep_count;
integer spike_count [0:NUM_CORES-1][0:NUM_NEURONS-1];
integer core_spike_total [0:NUM_CORES-1];
integer i, j;
integer pass_count;
integer fail_count;
neuromorphic_mesh #(
.NUM_CORES (NUM_CORES),
.CORE_ID_BITS (CORE_ID_BITS),
.NUM_NEURONS (NUM_NEURONS),
.NEURON_BITS (NEURON_BITS),
.DATA_WIDTH (DATA_WIDTH),
.MAX_FANOUT (MAX_FANOUT),
.FANOUT_BITS (FANOUT_BITS),
.CONN_ADDR_BITS (CONN_ADDR_BITS),
.THRESHOLD (16'sd1000),
.LEAK_RATE (16'sd3),
.REFRAC_CYCLES (3)
) dut (
.clk (clk),
.rst_n (rst_n),
.start (start),
.prog_conn_we (prog_conn_we),
.prog_conn_core (prog_conn_core),
.prog_conn_src (prog_conn_src),
.prog_conn_slot (prog_conn_slot),
.prog_conn_target (prog_conn_target),
.prog_conn_weight (prog_conn_weight),
.prog_route_we (prog_route_we),
.prog_route_src_core (prog_route_src_core),
.prog_route_src_neuron (prog_route_src_neuron),
.prog_route_dest_core (prog_route_dest_core),
.prog_route_dest_neuron(prog_route_dest_neuron),
.prog_route_weight (prog_route_weight),
.learn_enable (1'b0),
.graded_enable (1'b0),
.dendritic_enable (1'b0),
.async_enable (async_enable),
.prog_conn_comp (2'd0),
.prog_param_we (1'b0),
.prog_param_core (2'd0),
.prog_param_neuron (8'd0),
.prog_param_id (3'd0),
.prog_param_value (16'sd0),
.ext_valid (ext_valid),
.ext_core (ext_core),
.ext_neuron_id (ext_neuron_id),
.ext_current (ext_current),
.timestep_done (timestep_done),
.spike_valid_bus (spike_valid_bus),
.spike_id_bus (spike_id_bus),
.mesh_state_out (mesh_state_out),
.total_spikes (total_spikes),
.timestep_count (timestep_count)
);
initial clk = 0;
always #(CLK_PERIOD/2) clk = ~clk;
always @(posedge clk) begin
for (i = 0; i < NUM_CORES; i = i + 1) begin
if (spike_valid_bus[i]) begin
spike_count[i][spike_id_bus[i*NEURON_BITS +: NEURON_BITS]] =
spike_count[i][spike_id_bus[i*NEURON_BITS +: NEURON_BITS]] + 1;
core_spike_total[i] = core_spike_total[i] + 1;
end
end
end
initial begin
$dumpfile("async_mode.vcd");
$dumpvars(0, tb_async);
end
task add_conn;
input [CORE_ID_BITS-1:0] core;
input [NEURON_BITS-1:0] src;
input [FANOUT_BITS-1:0] slot;
input [NEURON_BITS-1:0] target;
input signed [DATA_WIDTH-1:0] weight;
begin
@(posedge clk);
prog_conn_we <= 1;
prog_conn_core <= core;
prog_conn_src <= src;
prog_conn_slot <= slot;
prog_conn_target <= target;
prog_conn_weight <= weight;
@(posedge clk);
prog_conn_we <= 0;
end
endtask
task add_route;
input [CORE_ID_BITS-1:0] src_core;
input [NEURON_BITS-1:0] src_neuron;
input [CORE_ID_BITS-1:0] dest_core;
input [NEURON_BITS-1:0] dest_neuron;
input signed [DATA_WIDTH-1:0] weight;
begin
@(posedge clk);
prog_route_we <= 1;
prog_route_src_core <= src_core;
prog_route_src_neuron <= src_neuron;
prog_route_dest_core <= dest_core;
prog_route_dest_neuron<= dest_neuron;
prog_route_weight <= weight;
@(posedge clk);
prog_route_we <= 0;
end
endtask
task apply_stimulus;
input [CORE_ID_BITS-1:0] stim_core;
input [NEURON_BITS-1:0] stim_neuron;
input signed [DATA_WIDTH-1:0] stim_current;
begin
@(posedge clk);
ext_valid <= 1;
ext_core <= stim_core;
ext_neuron_id <= stim_neuron;
ext_current <= stim_current;
@(posedge clk);
ext_valid <= 0;
end
endtask
task run_and_wait;
begin
@(posedge clk);
start <= 1;
@(posedge clk);
start <= 0;
wait(timestep_done);
@(posedge clk);
end
endtask
task run_sync_timestep;
input [CORE_ID_BITS-1:0] stim_core;
input [NEURON_BITS-1:0] stim_neuron;
input signed [DATA_WIDTH-1:0] stim_current;
begin
ext_valid <= 1;
ext_core <= stim_core;
ext_neuron_id <= stim_neuron;
ext_current <= stim_current;
@(posedge clk);
ext_valid <= 0;
@(posedge clk);
start <= 1;
@(posedge clk);
start <= 0;
wait(timestep_done);
@(posedge clk);
end
endtask
task reset_counts;
begin
for (i = 0; i < NUM_CORES; i = i + 1) begin
core_spike_total[i] = 0;
for (j = 0; j < NUM_NEURONS; j = j + 1)
spike_count[i][j] = 0;
end
end
endtask
integer t;
integer sync_spikes_total;
integer async_spikes_total;
integer cycle_start, cycle_end;
initial begin
pass_count = 0;
fail_count = 0;
for (i = 0; i < NUM_CORES; i = i + 1) begin
core_spike_total[i] = 0;
for (j = 0; j < NUM_NEURONS; j = j + 1)
spike_count[i][j] = 0;
end
rst_n = 0; start = 0; async_enable = 0;
ext_valid = 0; ext_core = 0; ext_neuron_id = 0; ext_current = 0;
prog_conn_we = 0; prog_conn_core = 0; prog_conn_src = 0;
prog_conn_slot = 0; prog_conn_target = 0; prog_conn_weight = 0;
prog_route_we = 0; prog_route_src_core = 0; prog_route_src_neuron = 0;
prog_route_dest_core = 0; prog_route_dest_neuron = 0; prog_route_weight = 0;
$display("");
$display("================================================================");
$display(" Phase 12: Async Event-Driven Mode Test");
$display(" %0d cores x %0d neurons, GALS architecture", NUM_CORES, NUM_NEURONS);
$display("================================================================");
#(CLK_PERIOD * 5);
rst_n = 1;
#(CLK_PERIOD * 5);
$display("");
$display("--- TEST 1: Basic Event Propagation (Async) ---");
// Core 0: N0→N1 intra-core chain
add_conn(0, 0, 0, 1, 16'sd1200);
// Inter-core route: Core 0 N1 → Core 1 N0
add_route(0, 1, 1, 0, 16'sd1200);
// Core 1: N0→N1 intra-core chain
add_conn(1, 0, 0, 1, 16'sd1200);
// Enable async mode
async_enable <= 1;
@(posedge clk);
// Apply stimulus to Core 0 N0 (goes to pcif[0])
apply_stimulus(0, 0, 16'sd1200);
// Run async and wait for quiescence
run_and_wait;
$display(" Core 0: N0=%0d spikes, N1=%0d spikes", spike_count[0][0], spike_count[0][1]);
$display(" Core 1: N0=%0d spikes, N1=%0d spikes", spike_count[1][0], spike_count[1][1]);
if (spike_count[0][0] >= 1 && spike_count[0][1] >= 1 &&
spike_count[1][0] >= 1 && spike_count[1][1] >= 1) begin
$display(" PASS: Spike propagated Core 0 -> Core 1 in async mode!");
pass_count = pass_count + 1;
end else begin
$display(" FAIL: Expected spikes on both cores");
fail_count = fail_count + 1;
end
$display("");
$display("--- TEST 2: Multi-Hop Async (0->1->2->3) ---");
async_enable <= 0;
@(posedge clk);
rst_n <= 0;
#(CLK_PERIOD * 3);
rst_n <= 1;
#(CLK_PERIOD * 5);
reset_counts;
// Build 4-core chain using N10-N12 (fresh neurons, no stale SRAM from Test 1)
// Core 0: N10→N11→N12
add_conn(0, 10, 0, 11, 16'sd1200);
add_conn(0, 11, 0, 12, 16'sd1200);
// Route: C0:N12 → C1:N10
add_route(0, 12, 1, 10, 16'sd1200);
// Core 1: N10→N11→N12
add_conn(1, 10, 0, 11, 16'sd1200);
add_conn(1, 11, 0, 12, 16'sd1200);
// Route: C1:N12 → C2:N10
add_route(1, 12, 2, 10, 16'sd1200);
// Core 2: N10→N11→N12
add_conn(2, 10, 0, 11, 16'sd1200);
add_conn(2, 11, 0, 12, 16'sd1200);
// Route: C2:N12 → C3:N10
add_route(2, 12, 3, 10, 16'sd1200);
// Core 3: N10→N11
add_conn(3, 10, 0, 11, 16'sd1200);
async_enable <= 1;
@(posedge clk);
// Stimulus to fresh neuron N10
apply_stimulus(0, 10, 16'sd1200);
run_and_wait;
$display(" Core 0: total=%0d spikes", core_spike_total[0]);
$display(" Core 1: total=%0d spikes", core_spike_total[1]);
$display(" Core 2: total=%0d spikes", core_spike_total[2]);
$display(" Core 3: total=%0d spikes", core_spike_total[3]);
if (core_spike_total[0] >= 1 && core_spike_total[1] >= 1 &&
core_spike_total[2] >= 1 && core_spike_total[3] >= 1) begin
$display(" PASS: Multi-hop spike traversed all 4 cores!");
pass_count = pass_count + 1;
end else begin
$display(" FAIL: Expected spikes on all 4 cores");
fail_count = fail_count + 1;
end
$display("");
$display("--- TEST 3: Quiescence Detection ---");
async_enable <= 0;
@(posedge clk);
rst_n <= 0;
#(CLK_PERIOD * 3);
rst_n <= 1;
#(CLK_PERIOD * 5);
reset_counts;
// Simple: Core 0 N20 only (fresh neuron, no stale connections/routes)
// No intra-core connections - just one neuron fires from stimulus
async_enable <= 1;
@(posedge clk);
// Apply stimulus to fresh neuron N20
apply_stimulus(0, 20, 16'sd1200);
// Capture cycle count
cycle_start = $time;
run_and_wait;
cycle_end = $time;
$display(" Quiescence reached in %0d ns", cycle_end - cycle_start);
$display(" Core 0 N20 spikes: %0d", spike_count[0][20]);
$display(" Core 1 total: %0d (should be 0)", core_spike_total[1]);
if (spike_count[0][20] >= 1 && core_spike_total[1] == 0 &&
core_spike_total[2] == 0 && core_spike_total[3] == 0) begin
$display(" PASS: Quiescence detected correctly (isolated stimulus)!");
pass_count = pass_count + 1;
end else begin
$display(" FAIL: Unexpected spike pattern");
fail_count = fail_count + 1;
end
$display("");
$display("--- TEST 4: Async vs Sync Equivalence ---");
async_enable <= 0;
@(posedge clk);
rst_n <= 0;
#(CLK_PERIOD * 3);
rst_n <= 1;
#(CLK_PERIOD * 5);
reset_counts;
// Build network: Core 0 N30→N31, route C0:N31→C1:N30, Core 1 N30→N31
add_conn(0, 30, 0, 31, 16'sd1200);
add_route(0, 31, 1, 30, 16'sd1200);
add_conn(1, 30, 0, 31, 16'sd1200);
$display(" Part A: Running in SYNC mode (10 timesteps, N30/N31)...");
async_enable <= 0;
@(posedge clk);
for (t = 0; t < 10; t = t + 1) begin
run_sync_timestep(0, 30, 16'sd200);
end
sync_spikes_total = 0;
for (i = 0; i < NUM_CORES; i = i + 1)
sync_spikes_total = sync_spikes_total + core_spike_total[i];
$display(" Sync total spikes: %0d", sync_spikes_total);
$display(" Core 0: N30=%0d, N31=%0d", spike_count[0][30], spike_count[0][31]);
$display(" Core 1: N30=%0d, N31=%0d", spike_count[1][30], spike_count[1][31]);
// Reset to clear FSMs/FIFOs (SRAMs retain, but N40/N41 are pristine)
rst_n <= 0;
#(CLK_PERIOD * 3);
rst_n <= 1;
#(CLK_PERIOD * 5);
reset_counts;
// Same topology but using N40/N41 (fresh neurons, identical initial state)
add_conn(0, 40, 0, 41, 16'sd1200);
add_route(0, 41, 1, 40, 16'sd1200);
add_conn(1, 40, 0, 41, 16'sd1200);
$display(" Part B: Running in ASYNC mode (10 async runs, N40/N41)...");
async_enable <= 1;
@(posedge clk);
for (t = 0; t < 10; t = t + 1) begin
apply_stimulus(0, 40, 16'sd200);
run_and_wait;
end
async_spikes_total = 0;
for (i = 0; i < NUM_CORES; i = i + 1)
async_spikes_total = async_spikes_total + core_spike_total[i];
$display(" Async total spikes: %0d", async_spikes_total);
$display(" Core 0: N40=%0d, N41=%0d", spike_count[0][40], spike_count[0][41]);
$display(" Core 1: N40=%0d, N41=%0d", spike_count[1][40], spike_count[1][41]);
if (sync_spikes_total == async_spikes_total) begin
$display(" PASS: Sync and async produced identical spike counts (%0d)!", sync_spikes_total);
pass_count = pass_count + 1;
end else begin
$display(" FAIL: Spike count mismatch (sync=%0d, async=%0d)", sync_spikes_total, async_spikes_total);
fail_count = fail_count + 1;
end
$display("");
$display("================================================================");
$display(" RESULTS: %0d/%0d PASSED", pass_count, pass_count + fail_count);
if (fail_count == 0)
$display(" ALL TESTS PASSED!");
else
$display(" %0d TESTS FAILED!", fail_count);
$display("================================================================");
#(CLK_PERIOD * 10);
$finish;
end
initial begin
#(CLK_PERIOD * 5000000);
$display("TIMEOUT at mesh_state=%0d, ts=%0d", mesh_state_out, timestep_count);
$finish;
end
endmodule