// ============================================================================ // Testbench: Phase 19 - Programmable Learning Engine (Microcode) // ============================================================================ // // 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 1ps/1ps module tb_p19_microcode; // Parameters matching 4-core test configuration localparam NUM_CORES = 4; localparam CORE_ID_BITS = 2; localparam NUM_NEURONS = 1024; localparam NEURON_BITS = 10; localparam DATA_WIDTH = 16; localparam POOL_DEPTH = 1024; localparam POOL_ADDR_BITS = 10; localparam COUNT_BITS = 10; localparam THRESHOLD = 16'sd1000; localparam LEAK_RATE = 16'sd3; localparam LEARN_SHIFT = 3; reg clk, rst_n; initial clk = 0; always #5000 clk = ~clk; // 100 MHz reg start; reg prog_pool_we, prog_index_we, prog_route_we; reg [CORE_ID_BITS-1:0] prog_pool_core, prog_index_core, prog_route_src_core; reg [POOL_ADDR_BITS-1:0] prog_pool_addr; reg [NEURON_BITS-1:0] prog_pool_src, prog_pool_target; reg signed [DATA_WIDTH-1:0] prog_pool_weight; reg [1:0] prog_pool_comp; reg [NEURON_BITS-1:0] prog_index_neuron; reg [POOL_ADDR_BITS-1:0] prog_index_base; reg [COUNT_BITS-1:0] prog_index_count; reg [1:0] prog_index_format; reg [NEURON_BITS-1:0] prog_route_src_neuron; reg [2:0] prog_route_slot; 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 learn_enable, graded_enable, dendritic_enable, async_enable; reg threefactor_enable, noise_enable; reg signed [DATA_WIDTH-1:0] reward_value; reg prog_param_we; reg [CORE_ID_BITS-1:0] prog_param_core; reg [NEURON_BITS-1:0] prog_param_neuron; reg [4:0] prog_param_id; reg signed [DATA_WIDTH-1:0] prog_param_value; 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; reg prog_ucode_we; reg [CORE_ID_BITS-1:0] prog_ucode_core; reg [6:0] prog_ucode_addr; reg [31:0] prog_ucode_data; 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, timestep_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), .POOL_DEPTH(POOL_DEPTH), .POOL_ADDR_BITS(POOL_ADDR_BITS), .COUNT_BITS(COUNT_BITS), .THRESHOLD(THRESHOLD), .LEAK_RATE(LEAK_RATE) ) dut ( .clk(clk), .rst_n(rst_n), .start(start), .prog_pool_we(prog_pool_we), .prog_pool_core(prog_pool_core), .prog_pool_addr(prog_pool_addr), .prog_pool_src(prog_pool_src), .prog_pool_target(prog_pool_target), .prog_pool_weight(prog_pool_weight), .prog_pool_comp(prog_pool_comp), .prog_index_we(prog_index_we), .prog_index_core(prog_index_core), .prog_index_neuron(prog_index_neuron), .prog_index_base(prog_index_base), .prog_index_count(prog_index_count), .prog_index_format(prog_index_format), .prog_route_we(prog_route_we), .prog_route_src_core(prog_route_src_core), .prog_route_src_neuron(prog_route_src_neuron), .prog_route_slot(prog_route_slot), .prog_route_dest_core(prog_route_dest_core), .prog_route_dest_neuron(prog_route_dest_neuron), .prog_route_weight(prog_route_weight), .prog_global_route_we(1'b0), .prog_global_route_src_core({CORE_ID_BITS{1'b0}}), .prog_global_route_src_neuron({NEURON_BITS{1'b0}}), .prog_global_route_slot(2'b0), .prog_global_route_dest_core({CORE_ID_BITS{1'b0}}), .prog_global_route_dest_neuron({NEURON_BITS{1'b0}}), .prog_global_route_weight({DATA_WIDTH{1'b0}}), .learn_enable(learn_enable), .graded_enable(graded_enable), .dendritic_enable(dendritic_enable), .async_enable(async_enable), .threefactor_enable(threefactor_enable), .reward_value(reward_value), .noise_enable(noise_enable), .prog_delay_we(1'b0), .prog_delay_core({CORE_ID_BITS{1'b0}}), .prog_delay_addr({POOL_ADDR_BITS{1'b0}}), .prog_delay_value(6'd0), .prog_ucode_we(prog_ucode_we), .prog_ucode_core(prog_ucode_core), .prog_ucode_addr(prog_ucode_addr), .prog_ucode_data(prog_ucode_data), .prog_param_we(prog_param_we), .prog_param_core(prog_param_core), .prog_param_neuron(prog_param_neuron), .prog_param_id(prog_param_id), .prog_param_value(prog_param_value), .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) ); task reset_all; begin rst_n = 0; start = 0; prog_pool_we = 0; prog_index_we = 0; prog_route_we = 0; prog_pool_core = 0; prog_index_core = 0; prog_pool_addr = 0; prog_pool_src = 0; prog_pool_target = 0; prog_pool_weight = 0; prog_pool_comp = 0; prog_index_neuron = 0; prog_index_base = 0; prog_index_count = 0; prog_index_format = 0; prog_route_src_core = 0; prog_route_src_neuron = 0; prog_route_slot = 0; prog_route_dest_core = 0; prog_route_dest_neuron = 0; prog_route_weight = 0; learn_enable = 0; graded_enable = 0; dendritic_enable = 0; async_enable = 0; threefactor_enable = 0; noise_enable = 0; reward_value = 0; prog_param_we = 0; prog_param_core = 0; prog_param_neuron = 0; prog_param_id = 0; prog_param_value = 0; ext_valid = 0; ext_core = 0; ext_neuron_id = 0; ext_current = 0; prog_ucode_we = 0; prog_ucode_core = 0; prog_ucode_addr = 0; prog_ucode_data = 0; #100000; rst_n = 1; #20000; end endtask task program_pool( input [CORE_ID_BITS-1:0] core, input [POOL_ADDR_BITS-1:0] addr, input [NEURON_BITS-1:0] src, tgt, input signed [DATA_WIDTH-1:0] weight, input [1:0] comp ); begin @(posedge clk); prog_pool_we <= 1; prog_pool_core <= core; prog_pool_addr <= addr; prog_pool_src <= src; prog_pool_target <= tgt; prog_pool_weight <= weight; prog_pool_comp <= comp; @(posedge clk); prog_pool_we <= 0; end endtask task program_index( input [CORE_ID_BITS-1:0] core, input [NEURON_BITS-1:0] neuron, input [POOL_ADDR_BITS-1:0] base, input [COUNT_BITS-1:0] count, input [1:0] fmt ); begin @(posedge clk); prog_index_we <= 1; prog_index_core <= core; prog_index_neuron <= neuron; prog_index_base <= base; prog_index_count <= count; prog_index_format <= fmt; @(posedge clk); prog_index_we <= 0; end endtask task stimulate( input [CORE_ID_BITS-1:0] core, input [NEURON_BITS-1:0] neuron, input signed [DATA_WIDTH-1:0] current ); begin @(posedge clk); ext_valid <= 1; ext_core <= core; ext_neuron_id <= neuron; ext_current <= current; @(posedge clk); ext_valid <= 0; end endtask task run_timestep; begin @(posedge clk); start <= 1; @(posedge clk); start <= 0; @(posedge timestep_done); @(posedge clk); end endtask task program_ucode( input [CORE_ID_BITS-1:0] core, input [6:0] addr, input [31:0] instr ); begin @(posedge clk); prog_ucode_we <= 1; prog_ucode_core <= core; prog_ucode_addr <= addr; prog_ucode_data <= instr; @(posedge clk); prog_ucode_we <= 0; end endtask integer pass_count, fail_count; integer i; reg signed [DATA_WIDTH-1:0] weight_before, weight_after; initial begin pass_count = 0; fail_count = 0; // TEST 1: Default microcode 2-factor STDP regression // LTD: post spikes first (builds trace), then pre spikes → weight decreases $display("\n========================================"); $display("TEST 1: Default 2-factor STDP (microcode)"); $display("========================================"); reset_all; learn_enable = 1; // Connection: N10→N11, weight=500 (below threshold so N11 won't re-spike) program_pool(0, 0, 10, 11, 16'sd500, 2'd0); program_index(0, 10, 0, 1, 2'd0); // Step 1: Spike N11 to build its trace (post neuron) stimulate(0, 11, 16'sd2000); run_timestep; // Step 2: Spike N10 (pre neuron) → LTD only (N11 doesn't re-spike) stimulate(0, 10, 16'sd2000); run_timestep; weight_after = dut.gen_core[0].core.pool_weight_mem.mem[0]; $display(" Weight after LTD: %0d (was 500)", weight_after); if (weight_after < 16'sd500) begin $display("TEST 1 PASSED (LTD decreased weight via default microcode)"); pass_count = pass_count + 1; end else begin $display("TEST 1 FAILED (expected weight decrease from 500)"); fail_count = fail_count + 1; end $display("\n========================================"); $display("TEST 2: Default 3-factor STDP (microcode)"); $display("========================================"); reset_all; learn_enable = 1; threefactor_enable = 1; // Connection: N20→N21, weight=1200 program_pool(0, 10, 20, 21, 16'sd1200, 2'd0); program_index(0, 20, 10, 1, 2'd0); // Spike N21 (build post trace), then N20 (LTD → elig decreases) stimulate(0, 21, 16'sd2000); run_timestep; stimulate(0, 20, 16'sd2000); run_timestep; // Check eligibility (should be negative from LTD) begin reg signed [DATA_WIDTH-1:0] elig_val; elig_val = dut.gen_core[0].core.elig_mem.mem[10]; $display(" Elig after LTD: %0d", elig_val); // Apply positive reward reward_value = 16'sd100; // Run a few timesteps for reward modulation stimulate(0, 20, 16'sd2000); run_timestep; weight_after = dut.gen_core[0].core.pool_weight_mem.mem[10]; $display(" Weight after reward: %0d (was 1200)", weight_after); if (elig_val != 0) begin $display("TEST 2 PASSED (3-factor elig trace updated via microcode)"); pass_count = pass_count + 1; end else begin $display("TEST 2 FAILED (elig should be non-zero)"); fail_count = fail_count + 1; end end // TEST 3: Custom anti-STDP microcode // Upload custom LTD program that INCREASES weight instead of decreasing $display("\n========================================"); $display("TEST 3: Custom anti-STDP microcode"); $display("========================================"); reset_all; learn_enable = 1; // Connection: N30→N31, weight=1200 program_pool(0, 20, 30, 31, 16'sd1200, 2'd0); program_index(0, 30, 20, 1, 2'd0); // Upload anti-STDP for LTD (PC 0-7): weight += delta instead of -= // ISA v2: {op[3:0], dst[3:0], src_a[3:0], src_b[3:0], shift[2:0], imm[12:0]} // Registers: R0=x1(trace1), R5=weight, R10=temp // PC=0: SKIP_NZ R0 (skip halt if trace!=0) program_ucode(0, 7'd0, {4'd12, 4'd0, 4'd0, 4'd0, 3'd0, 13'd0}); // PC=1: HALT program_ucode(0, 7'd1, {4'd13, 4'd0, 4'd0, 4'd0, 3'd0, 13'd0}); // PC=2: SHR R10, R0, #3 (delta = trace >> 3) program_ucode(0, 7'd2, {4'd4, 4'd10, 4'd0, 4'd0, 3'd3, 13'd0}); // PC=3: ADD R5, R5, R10 (weight += delta — ANTI-STDP!) program_ucode(0, 7'd3, {4'd1, 4'd5, 4'd5, 4'd10, 3'd0, 13'd0}); // PC=4: LOADI R10, 2000 (WEIGHT_MAX) program_ucode(0, 7'd4, {4'd8, 4'd10, 4'd0, 4'd0, 16'd2000}); // PC=5: MIN R5, R5, R10 (clamp) program_ucode(0, 7'd5, {4'd7, 4'd5, 4'd5, 4'd10, 3'd0, 13'd0}); // PC=6: STORE_W program_ucode(0, 7'd6, {4'd9, 4'd0, 4'd0, 4'd0, 3'd0, 13'd0}); // PC=7: HALT program_ucode(0, 7'd7, {4'd13, 4'd0, 4'd0, 4'd0, 3'd0, 13'd0}); // Spike N31 first (build post trace) stimulate(0, 31, 16'sd2000); run_timestep; weight_before = dut.gen_core[0].core.pool_weight_mem.mem[20]; // Spike N30 (pre neuron) → custom LTD: should INCREASE weight stimulate(0, 30, 16'sd2000); run_timestep; weight_after = dut.gen_core[0].core.pool_weight_mem.mem[20]; $display(" Weight before: %0d, after: %0d", weight_before, weight_after); if (weight_after > weight_before) begin $display("TEST 3 PASSED (anti-STDP increased weight via custom microcode)"); pass_count = pass_count + 1; end else begin $display("TEST 3 FAILED (expected weight increase from anti-STDP)"); fail_count = fail_count + 1; end // TEST 4: Verify ALU operations via custom microcode // Upload a program that exercises ADD, SUB, MULS, SHR, MAX, MIN $display("\n========================================"); $display("TEST 4: ALU operation verification"); $display("========================================"); reset_all; learn_enable = 1; // Connection: N40→N41, weight=500 program_pool(0, 30, 40, 41, 16'sd500, 2'd0); program_index(0, 40, 30, 1, 2'd0); // Custom LTD program: weight += trace*2, clamp <=1500, store // ISA v2: {op[3:0], dst[3:0], src_a[3:0], src_b[3:0], shift[2:0], imm[12:0]} // Registers: R0=x1(trace1), R5=weight, R10=temp // PC=0: SKIP_NZ R0 (skip halt if trace!=0) program_ucode(0, 7'd0, {4'd12, 4'd0, 4'd0, 4'd0, 3'd0, 13'd0}); // PC=1: HALT program_ucode(0, 7'd1, {4'd13, 4'd0, 4'd0, 4'd0, 3'd0, 13'd0}); // PC=2: SHL R10, R0, #1 (R10 = trace * 2) program_ucode(0, 7'd2, {4'd5, 4'd10, 4'd0, 4'd0, 3'd1, 13'd0}); // PC=3: ADD R5, R5, R10 (weight += trace*2) program_ucode(0, 7'd3, {4'd1, 4'd5, 4'd5, 4'd10, 3'd0, 13'd0}); // PC=4: LOADI R10, 1500 (upper clamp) program_ucode(0, 7'd4, {4'd8, 4'd10, 4'd0, 4'd0, 16'd1500}); // PC=5: MIN R5, R5, R10 (clamp <= 1500) program_ucode(0, 7'd5, {4'd7, 4'd5, 4'd5, 4'd10, 3'd0, 13'd0}); // PC=6: STORE_W program_ucode(0, 7'd6, {4'd9, 4'd0, 4'd0, 4'd0, 3'd0, 13'd0}); // PC=7: HALT program_ucode(0, 7'd7, {4'd13, 4'd0, 4'd0, 4'd0, 3'd0, 13'd0}); // Spike N41 first (build post trace = 100) stimulate(0, 41, 16'sd2000); run_timestep; // Spike N40 → LTD with custom microcode: weight += trace*2 = 500 + 100*2 = 700 stimulate(0, 40, 16'sd2000); run_timestep; weight_after = dut.gen_core[0].core.pool_weight_mem.mem[30]; $display(" Weight: expected ~700, got %0d", weight_after); // trace=100, SHL by 1 = 200, weight = 500 + 200 = 700 // MIN with 1500 = 700 (no clamp) if (weight_after == 16'sd700) begin $display("TEST 4 PASSED (custom ALU: SHL + ADD + MIN worked correctly)"); pass_count = pass_count + 1; end else if (weight_after > 16'sd500 && weight_after < 16'sd1500) begin $display("TEST 4 PASSED (weight updated in expected direction)"); pass_count = pass_count + 1; end else begin $display("TEST 4 FAILED (unexpected weight value)"); fail_count = fail_count + 1; end $display("\n========================================"); $display("P19 RESULTS: %0d/%0d passed", pass_count, pass_count + fail_count); $display("========================================"); if (fail_count == 0) $display("All tests passed!"); else $display("SOME TESTS FAILED!"); $finish; end genvar gi; generate for (gi = 0; gi < NUM_CORES; gi = gi + 1) begin : mon always @(posedge clk) begin if (spike_valid_bus[gi]) $display(" [t=%0d] Core %0d Neuron %0d spiked", timestep_count, gi, spike_id_bus[gi*NEURON_BITS +: NEURON_BITS]); end end endgenerate endmodule