catalyst-n1 / tb /tb_p23c_scale.v
mrwabbit's picture
Initial upload: Catalyst N1 open source neuromorphic processor RTL
e4cdd5f verified
// ============================================================================
// tb_p23c_scale.v - P23C Scale Parity Tests
// ============================================================================
//
// 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_p23c_scale;
reg clk, rst_n;
initial clk = 0;
always #5 clk = ~clk;
integer pass_count = 0;
integer fail_count = 0;
localparam CIB = 4, NN = 16, NB = 4, DW = 16;
localparam PD = 65536, PAB = 16, NC = 4;
reg nm_start, nm_prog_pool_we, nm_prog_index_we, nm_prog_route_we;
reg nm_prog_param_we, nm_ext_valid, nm_probe_read;
reg [CIB-1:0] nm_prog_pool_core, nm_prog_index_core, nm_prog_route_src_core;
reg [CIB-1:0] nm_prog_route_dest_core, nm_prog_param_core, nm_ext_core, nm_probe_core;
reg [PAB-1:0] nm_prog_pool_addr, nm_prog_index_base, nm_probe_pool_addr;
reg [NB-1:0] nm_prog_pool_src, nm_prog_pool_target, nm_prog_index_neuron;
reg [NB-1:0] nm_prog_route_src_neuron, nm_prog_route_dest_neuron;
reg [NB-1:0] nm_prog_param_neuron, nm_ext_neuron_id, nm_probe_neuron;
reg signed [DW-1:0] nm_prog_pool_weight, nm_prog_route_weight;
reg signed [DW-1:0] nm_prog_param_value, nm_ext_current;
reg [1:0] nm_prog_pool_comp, nm_prog_index_format;
reg [9:0] nm_prog_index_count;
reg [2:0] nm_prog_route_slot;
reg [4:0] nm_prog_param_id, nm_probe_state_id;
wire signed [DW-1:0] nm_probe_data;
wire nm_probe_valid, nm_timestep_done;
async_noc_mesh #(
.NUM_CORES(NC), .CORE_ID_BITS(CIB),
.NUM_NEURONS(NN), .NEURON_BITS(NB),
.DATA_WIDTH(DW), .POOL_DEPTH(PD), .POOL_ADDR_BITS(PAB),
.COUNT_BITS(10), .THRESHOLD(16'sd500),
.LEAK_RATE(16'sd0), .REFRAC_CYCLES(0),
.DUAL_NOC(1), .MESH_X(2), .MESH_Y(2)
) noc (
.clk(clk), .rst_n(rst_n), .start(nm_start),
.prog_pool_we(nm_prog_pool_we), .prog_pool_core(nm_prog_pool_core),
.prog_pool_addr(nm_prog_pool_addr), .prog_pool_src(nm_prog_pool_src),
.prog_pool_target(nm_prog_pool_target), .prog_pool_weight(nm_prog_pool_weight),
.prog_pool_comp(nm_prog_pool_comp),
.prog_index_we(nm_prog_index_we), .prog_index_core(nm_prog_index_core),
.prog_index_neuron(nm_prog_index_neuron), .prog_index_base(nm_prog_index_base),
.prog_index_count(nm_prog_index_count), .prog_index_format(nm_prog_index_format),
.prog_route_we(nm_prog_route_we),
.prog_route_src_core(nm_prog_route_src_core),
.prog_route_src_neuron(nm_prog_route_src_neuron),
.prog_route_slot(nm_prog_route_slot),
.prog_route_dest_core(nm_prog_route_dest_core),
.prog_route_dest_neuron(nm_prog_route_dest_neuron),
.prog_route_weight(nm_prog_route_weight),
.prog_global_route_we(1'b0),
.prog_global_route_src_core(0), .prog_global_route_src_neuron(0),
.prog_global_route_slot(0), .prog_global_route_dest_core(0),
.prog_global_route_dest_neuron(0), .prog_global_route_weight(0),
.learn_enable(1'b0), .graded_enable(1'b0), .dendritic_enable(1'b0),
.async_enable(1'b0), .threefactor_enable(1'b0), .noise_enable(1'b0),
.skip_idle_enable(1'b0), .scale_u_enable(1'b0), .reward_value(16'sd0),
.prog_delay_we(1'b0), .prog_delay_core(0), .prog_delay_addr(0), .prog_delay_value(0),
.prog_ucode_we(1'b0), .prog_ucode_core(0), .prog_ucode_addr(0), .prog_ucode_data(0),
.prog_param_we(nm_prog_param_we), .prog_param_core(nm_prog_param_core),
.prog_param_neuron(nm_prog_param_neuron), .prog_param_id(nm_prog_param_id),
.prog_param_value(nm_prog_param_value),
.ext_valid(nm_ext_valid), .ext_core(nm_ext_core),
.ext_neuron_id(nm_ext_neuron_id), .ext_current(nm_ext_current),
.probe_read(nm_probe_read), .probe_core(nm_probe_core),
.probe_neuron(nm_probe_neuron), .probe_state_id(nm_probe_state_id),
.probe_pool_addr(nm_probe_pool_addr),
.probe_data(nm_probe_data), .probe_valid(nm_probe_valid),
.timestep_done(nm_timestep_done),
.spike_valid_bus(), .spike_id_bus(),
.mesh_state_out(), .total_spikes(), .timestep_count(),
.core_idle_bus(),
.link_tx_push(), .link_tx_core(), .link_tx_neuron(), .link_tx_payload(),
.link_tx_full(1'b0),
.link_rx_core(0), .link_rx_neuron(0), .link_rx_current(0),
.link_rx_pop(), .link_rx_empty(1'b1)
);
localparam MCR_CB = 14;
reg mcr_tx_push, mcr_rx_pop;
reg [MCR_CB-1:0] mcr_tx_dest;
reg [6:0] mcr_tx_core;
reg [9:0] mcr_tx_neuron;
reg [7:0] mcr_tx_payload;
wire mcr_tx_full, mcr_rx_empty;
wire [MCR_CB-1:0] mcr_rx_src;
wire [6:0] mcr_rx_core;
wire [9:0] mcr_rx_neuron;
wire signed [15:0] mcr_rx_current;
wire [7:0] mcr_link_data;
wire mcr_link_valid;
multi_chip_router #(
.NUM_LINKS(1), .CHIP_ID_BITS(MCR_CB),
.CORE_ID_BITS(7), .NEURON_BITS(10),
.DATA_WIDTH(16), .TX_DEPTH(16), .RX_DEPTH(16)
) mcr (
.clk(clk), .rst_n(rst_n),
.my_chip_id(14'd42),
.tx_push(mcr_tx_push), .tx_dest_chip(mcr_tx_dest),
.tx_core(mcr_tx_core), .tx_neuron(mcr_tx_neuron),
.tx_payload(mcr_tx_payload), .tx_full(mcr_tx_full),
.rx_src_chip(mcr_rx_src), .rx_core(mcr_rx_core),
.rx_neuron(mcr_rx_neuron), .rx_current(mcr_rx_current),
.rx_pop(mcr_rx_pop), .rx_empty(mcr_rx_empty),
.link_tx_data(mcr_link_data), .link_tx_valid(mcr_link_valid),
.link_tx_ready(1'b1),
.link_rx_data(mcr_link_data), // loopback
.link_rx_valid(mcr_link_valid), // loopback
.link_rx_ready()
);
task clear_inputs;
begin
nm_start = 0; nm_prog_pool_we = 0; nm_prog_index_we = 0;
nm_prog_route_we = 0; nm_prog_param_we = 0;
nm_ext_valid = 0; nm_probe_read = 0;
mcr_tx_push = 0; mcr_rx_pop = 0;
end
endtask
task prog_param(input [CIB-1:0] core, input [NB-1:0] neuron,
input [4:0] pid, input signed [DW-1:0] val);
begin
@(posedge clk);
nm_prog_param_we = 1; nm_prog_param_core = core;
nm_prog_param_neuron = neuron; nm_prog_param_id = pid;
nm_prog_param_value = val;
@(posedge clk); nm_prog_param_we = 0;
end
endtask
task prog_pool(input [CIB-1:0] core, input [PAB-1:0] addr,
input [NB-1:0] src, input [NB-1:0] target,
input signed [DW-1:0] weight);
begin
@(posedge clk);
nm_prog_pool_we = 1; nm_prog_pool_core = core;
nm_prog_pool_addr = addr; nm_prog_pool_src = src;
nm_prog_pool_target = target; nm_prog_pool_weight = weight;
nm_prog_pool_comp = 0;
@(posedge clk); nm_prog_pool_we = 0;
end
endtask
task prog_index(input [CIB-1:0] core, input [NB-1:0] neuron,
input [PAB-1:0] base, input [9:0] count);
begin
@(posedge clk);
nm_prog_index_we = 1; nm_prog_index_core = core;
nm_prog_index_neuron = neuron; nm_prog_index_base = base;
nm_prog_index_count = count; nm_prog_index_format = 2'd0;
@(posedge clk); nm_prog_index_we = 0;
end
endtask
task prog_route(input [CIB-1:0] src_core, input [NB-1:0] src_nrn,
input [2:0] slot,
input [CIB-1:0] dst_core, input [NB-1:0] dst_nrn,
input signed [DW-1:0] weight);
begin
@(posedge clk);
nm_prog_route_we = 1;
nm_prog_route_src_core = src_core; nm_prog_route_src_neuron = src_nrn;
nm_prog_route_slot = slot;
nm_prog_route_dest_core = dst_core; nm_prog_route_dest_neuron = dst_nrn;
nm_prog_route_weight = weight;
@(posedge clk); nm_prog_route_we = 0;
end
endtask
task inject(input [CIB-1:0] core, input [NB-1:0] neuron,
input signed [DW-1:0] current);
begin
@(posedge clk);
nm_ext_valid = 1; nm_ext_core = core;
nm_ext_neuron_id = neuron; nm_ext_current = current;
@(posedge clk); nm_ext_valid = 0;
end
endtask
task run_timestep;
begin
@(posedge clk); nm_start = 1;
@(posedge clk); nm_start = 0;
wait(nm_timestep_done);
repeat(5) @(posedge clk);
end
endtask
task probe_check(input [CIB-1:0] core, input [NB-1:0] neuron,
input [4:0] sid, input signed [DW-1:0] expected,
input [255:0] label);
begin
@(posedge clk);
nm_probe_read = 1; nm_probe_core = core;
nm_probe_neuron = neuron; nm_probe_state_id = sid;
nm_probe_pool_addr = 0;
@(posedge clk); nm_probe_read = 0;
repeat(3) @(posedge clk);
if (nm_probe_data == expected) begin
$display("PASSED: %0s (got %0d)", label, nm_probe_data);
pass_count = pass_count + 1;
end else begin
$display("FAILED: %0s - expected %0d, got %0d", label, expected, nm_probe_data);
fail_count = fail_count + 1;
end
end
endtask
initial begin
$display("=== P23C Scale Parity Tests ===");
rst_n = 0;
clear_inputs;
nm_prog_pool_core = 0; nm_prog_pool_addr = 0;
nm_prog_pool_src = 0; nm_prog_pool_target = 0;
nm_prog_pool_weight = 0; nm_prog_pool_comp = 0;
nm_prog_index_core = 0; nm_prog_index_neuron = 0;
nm_prog_index_base = 0; nm_prog_index_count = 0; nm_prog_index_format = 0;
nm_prog_route_src_core = 0; nm_prog_route_src_neuron = 0;
nm_prog_route_slot = 0; nm_prog_route_dest_core = 0;
nm_prog_route_dest_neuron = 0; nm_prog_route_weight = 0;
nm_prog_param_core = 0; nm_prog_param_neuron = 0;
nm_prog_param_id = 0; nm_prog_param_value = 0;
nm_ext_core = 0; nm_ext_neuron_id = 0; nm_ext_current = 0;
nm_probe_core = 0; nm_probe_neuron = 0;
nm_probe_state_id = 0; nm_probe_pool_addr = 0;
mcr_tx_dest = 0; mcr_tx_core = 0; mcr_tx_neuron = 0; mcr_tx_payload = 0;
repeat(10) @(posedge clk);
rst_n = 1;
repeat(5) @(posedge clk);
// Core 0 neuron 0: threshold=10
prog_param(4'd0, 4'd0, 5'd0, 16'sd10);
// Core 1 neuron 0: threshold=10
prog_param(4'd1, 4'd0, 5'd0, 16'sd10);
// Pool entry at address 50000 in core 0: target=1, weight=123
prog_pool(4'd0, 16'd50000, 4'd0, 4'd1, 16'sd123);
// Index for core 0 neuron 0: base=50000, count=1
prog_index(4'd0, 4'd0, 16'd50000, 10'd1);
// Route: core 0 neuron 0 → core 3 neuron 2, weight=100 (even→net A)
prog_route(4'd0, 4'd0, 3'd0, 4'd3, 4'd2, 16'sd100);
// Route: core 1 neuron 0 → core 2 neuron 2, weight=200 (odd→net B)
prog_route(4'd1, 4'd0, 3'd0, 4'd2, 4'd2, 16'sd200);
repeat(5) @(posedge clk);
inject(4'd0, 4'd0, 16'sd600); // Core 0 neuron 0
inject(4'd1, 4'd0, 16'sd600); // Core 1 neuron 0
repeat(3) @(posedge clk);
run_timestep; // Timestep 1: neurons spike, spikes captured + routed
run_timestep;
// TEST 1: Pool depth - synapse at addr 50000 (delivered in ts2's DELIVER phase)
probe_check(4'd0, 4'd1, 5'd0, 16'sd123, "T1: Pool depth 65K synapse@50000");
// TEST 2: Dual NoC net A - core 0 (even) → core 3
probe_check(4'd3, 4'd2, 5'd0, 16'sd100, "T2: Dual NoC netA core0->core3");
// TEST 3: Dual NoC net B - core 1 (odd) → core 2
probe_check(4'd2, 4'd2, 5'd0, 16'sd200, "T3: Dual NoC netB core1->core2");
@(posedge clk);
mcr_tx_push = 1;
mcr_tx_dest = 14'd12345;
mcr_tx_core = 7'd99;
mcr_tx_neuron = 10'd511;
mcr_tx_payload = 8'd128;
@(posedge clk); mcr_tx_push = 0;
// Wait for serialization + deserialization (loopback ~15 cycles)
repeat(50) @(posedge clk);
if (!mcr_rx_empty) begin
if (mcr_rx_src == 14'd42 && mcr_rx_core == 7'd99 &&
mcr_rx_neuron == 10'd511 && mcr_rx_current[7:0] == 8'd128) begin
$display("PASSED: T4: Wide chip 14-bit loopback (src=%0d core=%0d nrn=%0d pay=%0d)",
mcr_rx_src, mcr_rx_core, mcr_rx_neuron, mcr_rx_current[7:0]);
pass_count = pass_count + 1;
end else begin
$display("FAILED: T4: src=%0d(exp42) core=%0d(exp99) nrn=%0d(exp511) cur=%0d(exp128)",
mcr_rx_src, mcr_rx_core, mcr_rx_neuron, mcr_rx_current);
fail_count = fail_count + 1;
end
end else begin
$display("FAILED: T4: RX FIFO empty after loopback");
fail_count = fail_count + 1;
end
$display("");
$display("=== P23C RESULTS: %0d passed, %0d failed ===", pass_count, fail_count);
if (fail_count == 0)
$display("ALL P23C TESTS PASSED");
$finish;
end
initial begin
#5000000;
$display("TIMEOUT");
$finish;
end
endmodule