|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| `timescale 1ns / 1ps
|
|
|
| module tb_dendritic;
|
|
|
| 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;
|
| reg rst_n;
|
| reg start;
|
| reg learn_enable;
|
| reg graded_enable;
|
| reg dendritic_enable;
|
| reg ext_valid;
|
| reg [NEURON_BITS-1:0] ext_neuron_id;
|
| reg signed [DATA_WIDTH-1:0] ext_current;
|
| reg conn_we;
|
| reg [NEURON_BITS-1:0] conn_src;
|
| reg [FANOUT_BITS-1:0] conn_slot;
|
| reg [NEURON_BITS-1:0] conn_target;
|
| reg signed [DATA_WIDTH-1:0] conn_weight;
|
| reg [1:0] conn_comp;
|
|
|
| wire timestep_done;
|
| wire spike_out_valid;
|
| wire [NEURON_BITS-1:0] spike_out_id;
|
| wire [7:0] spike_out_payload;
|
| wire [4:0] state_out;
|
| wire [31:0] total_spikes;
|
| wire [31:0] timestep_count;
|
|
|
| scalable_core_v2 #(
|
| .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),
|
| .RESTING_POT (16'sd0),
|
| .REFRAC_CYCLES (2),
|
| .DEND_THRESHOLD(16'sd0),
|
| .TRACE_MAX (8'd100),
|
| .TRACE_DECAY (8'd10),
|
| .LEARN_SHIFT (3),
|
| .GRADE_SHIFT (7),
|
| .WEIGHT_MAX (16'sd2000),
|
| .WEIGHT_MIN (16'sd0)
|
| ) dut (
|
| .clk (clk),
|
| .rst_n (rst_n),
|
| .start (start),
|
| .learn_enable (learn_enable),
|
| .graded_enable (graded_enable),
|
| .dendritic_enable(dendritic_enable),
|
| .ext_valid (ext_valid),
|
| .ext_neuron_id (ext_neuron_id),
|
| .ext_current (ext_current),
|
| .conn_we (conn_we),
|
| .conn_src (conn_src),
|
| .conn_slot (conn_slot),
|
| .conn_target (conn_target),
|
| .conn_weight (conn_weight),
|
| .conn_comp (conn_comp),
|
| .prog_param_we (1'b0),
|
| .prog_param_neuron(8'd0),
|
| .prog_param_id (3'd0),
|
| .prog_param_value(16'sd0),
|
| .timestep_done (timestep_done),
|
| .spike_out_valid(spike_out_valid),
|
| .spike_out_id (spike_out_id),
|
| .spike_out_payload(spike_out_payload),
|
| .state_out (state_out),
|
| .total_spikes (total_spikes),
|
| .timestep_count (timestep_count)
|
| );
|
|
|
| initial clk = 0;
|
| always #(CLK_PERIOD/2) clk = ~clk;
|
|
|
| task program_conn;
|
| 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;
|
| input [1:0] comp;
|
| begin
|
| @(posedge clk);
|
| conn_we <= 1;
|
| conn_src <= src;
|
| conn_slot <= slot;
|
| conn_target <= target;
|
| conn_weight <= weight;
|
| conn_comp <= comp;
|
| @(posedge clk);
|
| conn_we <= 0;
|
| conn_comp <= 0;
|
| @(posedge clk);
|
| end
|
| endtask
|
|
|
| task stimulate;
|
| input [NEURON_BITS-1:0] neuron;
|
| input signed [DATA_WIDTH-1:0] current;
|
| begin
|
| @(posedge clk);
|
| ext_valid <= 1;
|
| 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;
|
| wait(timestep_done);
|
| @(posedge clk);
|
| end
|
| endtask
|
|
|
|
|
| reg param_we_r;
|
| reg [7:0] param_neuron_r;
|
| reg [2:0] param_id_r;
|
| reg signed [DATA_WIDTH-1:0] param_value_r;
|
|
|
|
|
| task set_param;
|
| input [NEURON_BITS-1:0] neuron;
|
| input [2:0] param_id;
|
| input signed [DATA_WIDTH-1:0] value;
|
| begin
|
|
|
| case (param_id)
|
| 3'd0: dut.threshold_mem.mem[neuron] = value;
|
| 3'd1: dut.leak_mem.mem[neuron] = value;
|
| 3'd2: dut.rest_mem.mem[neuron] = value;
|
| 3'd3: dut.refrac_cfg_mem.mem[neuron] = value[7:0];
|
| 3'd4: dut.dend_thr_mem.mem[neuron] = value;
|
| endcase
|
| end
|
| endtask
|
|
|
|
|
| function signed [DATA_WIDTH-1:0] read_potential;
|
| input [NEURON_BITS-1:0] neuron;
|
| begin
|
| read_potential = dut.neuron_mem.mem[neuron];
|
| end
|
| endfunction
|
|
|
|
|
| function signed [DATA_WIDTH-1:0] read_dend_acc;
|
| input [NEURON_BITS-1:0] neuron;
|
| input [1:0] dend_id;
|
| begin
|
| case (dend_id)
|
| 2'd1: read_dend_acc = dut.dend_acc_1_mem.mem[neuron];
|
| 2'd2: read_dend_acc = dut.dend_acc_2_mem.mem[neuron];
|
| 2'd3: read_dend_acc = dut.dend_acc_3_mem.mem[neuron];
|
| default: read_dend_acc = dut.acc_mem.mem[neuron];
|
| endcase
|
| end
|
| endfunction
|
|
|
| integer spike_count;
|
| reg [7:0] last_spike_id;
|
|
|
| always @(posedge clk) begin
|
| if (spike_out_valid) begin
|
| spike_count = spike_count + 1;
|
| last_spike_id = spike_out_id;
|
| end
|
| end
|
|
|
| integer pass_count, fail_count;
|
| integer i;
|
| reg signed [DATA_WIDTH-1:0] pot_val;
|
|
|
| initial begin
|
| rst_n = 0;
|
| start = 0;
|
| learn_enable = 0;
|
| graded_enable = 0;
|
| dendritic_enable = 0;
|
| ext_valid = 0;
|
| conn_we = 0;
|
| conn_src = 0;
|
| conn_slot = 0;
|
| conn_target = 0;
|
| conn_weight = 0;
|
| conn_comp = 0;
|
| ext_neuron_id = 0;
|
| ext_current = 0;
|
| spike_count = 0;
|
| pass_count = 0;
|
| fail_count = 0;
|
|
|
| #(CLK_PERIOD * 5);
|
| rst_n = 1;
|
| #(CLK_PERIOD * 3);
|
|
|
| $display("");
|
| $display("================================================================");
|
| $display(" Dendritic Compartments Test (Phase 10)");
|
| $display("================================================================");
|
|
|
|
|
|
|
| $display("");
|
| $display("--- TEST 1: Backward Compatibility (soma-only) ---");
|
|
|
| dendritic_enable = 0;
|
| program_conn(8'd0, 5'd0, 8'd2, 16'sd1200, 2'd0);
|
|
|
| stimulate(8'd0, 16'sd1200);
|
| spike_count = 0;
|
| run_timestep;
|
| $display(" TS1: N0 spikes=%0d", spike_count);
|
|
|
| run_timestep;
|
| pot_val = read_potential(8'd2);
|
|
|
| $display(" TS2: N2 potential after delivery = %0d, spikes=%0d", pot_val, spike_count);
|
|
|
| if (spike_count >= 2) begin
|
| $display(" PASS: Both N0 and N2 spiked (backward compat)");
|
| pass_count = pass_count + 1;
|
| end else begin
|
| $display(" FAIL: Expected >=2 spikes, got %0d", spike_count);
|
| fail_count = fail_count + 1;
|
| end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| $display("");
|
| $display("--- TEST 2: Compartment Routing ---");
|
|
|
| dendritic_enable = 1;
|
| program_conn(8'd10, 5'd0, 8'd12, 16'sd600, 2'd1);
|
| program_conn(8'd10, 5'd1, 8'd14, 16'sd600, 2'd0);
|
|
|
|
|
| stimulate(8'd10, 16'sd1200);
|
| spike_count = 0;
|
| run_timestep;
|
| $display(" TS: N10 spiked, spikes=%0d", spike_count);
|
|
|
| run_timestep;
|
|
|
|
|
| begin : test2_block
|
| reg signed [DATA_WIDTH-1:0] pot_n12, pot_n14;
|
| pot_n12 = read_potential(8'd12);
|
| pot_n14 = read_potential(8'd14);
|
| $display(" N12 (dendrite path) potential = %0d", pot_n12);
|
| $display(" N14 (soma path) potential = %0d", pot_n14);
|
|
|
| if (pot_n12 > 0 && pot_n14 > 0) begin
|
| $display(" PASS: Both paths delivered current (N12=%0d, N14=%0d)", pot_n12, pot_n14);
|
| pass_count = pass_count + 1;
|
| end else begin
|
| $display(" FAIL: Expected both >0 (N12=%0d, N14=%0d)", pot_n12, pot_n14);
|
| fail_count = fail_count + 1;
|
| end
|
| end
|
|
|
|
|
|
|
|
|
|
|
|
|
| $display("");
|
| $display("--- TEST 3: Dendritic Threshold Filtering ---");
|
|
|
| dendritic_enable = 1;
|
| set_param(8'd22, 3'd4, 16'sd300);
|
|
|
|
|
| program_conn(8'd20, 5'd0, 8'd22, 16'sd200, 2'd1);
|
|
|
|
|
| stimulate(8'd20, 16'sd1200);
|
| spike_count = 0;
|
| run_timestep;
|
|
|
| run_timestep;
|
|
|
|
|
|
|
| pot_val = read_potential(8'd22);
|
| $display(" Weak input (200 < thr 300): N22 potential = %0d (expected ~0)", pot_val);
|
|
|
| if (pot_val <= 16'sd0) begin
|
| $display(" PASS: Weak dendritic input filtered out");
|
| pass_count = pass_count + 1;
|
| end else begin
|
| $display(" FAIL: Expected <=0, got %0d", pot_val);
|
| fail_count = fail_count + 1;
|
| end
|
|
|
|
|
| program_conn(8'd21, 5'd0, 8'd22, 16'sd500, 2'd2);
|
|
|
| stimulate(8'd21, 16'sd1200);
|
| run_timestep;
|
|
|
| run_timestep;
|
|
|
|
|
| pot_val = read_potential(8'd22);
|
| $display(" Strong input (500 > thr 300): N22 potential = %0d (expected ~197)", pot_val);
|
|
|
| if (pot_val > 16'sd0) begin
|
| $display(" PASS: Strong dendritic input passed through (%0d)", pot_val);
|
| pass_count = pass_count + 1;
|
| end else begin
|
| $display(" FAIL: Expected >0, got %0d", pot_val);
|
| fail_count = fail_count + 1;
|
| end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| $display("");
|
| $display("--- TEST 4: Coincidence Detection (AND gate) ---");
|
|
|
| dendritic_enable = 1;
|
|
|
|
|
| program_conn(8'd30, 5'd0, 8'd32, 16'sd400, 2'd1);
|
| program_conn(8'd31, 5'd0, 8'd32, 16'sd400, 2'd2);
|
|
|
| stimulate(8'd30, 16'sd1200);
|
| spike_count = 0;
|
| run_timestep;
|
|
|
| stimulate(8'd32, 16'sd300);
|
| run_timestep;
|
|
|
| begin : test4a_block
|
| integer spikes_single;
|
| spikes_single = spike_count;
|
| pot_val = read_potential(8'd32);
|
| $display(" Part A (single dend): N32 pot=%0d, spikes=%0d", pot_val, spikes_single);
|
|
|
| if (spikes_single == 1) begin
|
| $display(" PASS: No N32 spike with single dendrite");
|
| pass_count = pass_count + 1;
|
| end else if (last_spike_id != 8'd32) begin
|
| $display(" PASS: No N32 spike with single dendrite (spikes=%0d)", spikes_single);
|
| pass_count = pass_count + 1;
|
| end else begin
|
| $display(" FAIL: N32 spiked with single dendrite");
|
| fail_count = fail_count + 1;
|
| end
|
| end
|
|
|
|
|
| program_conn(8'd33, 5'd0, 8'd35, 16'sd400, 2'd1);
|
| program_conn(8'd34, 5'd0, 8'd35, 16'sd400, 2'd2);
|
|
|
| stimulate(8'd33, 16'sd1200);
|
| stimulate(8'd34, 16'sd1200);
|
| spike_count = 0;
|
| run_timestep;
|
| $display(" Part B: N33+N34 spiked, spikes=%0d", spike_count);
|
|
|
| stimulate(8'd35, 16'sd300);
|
| run_timestep;
|
|
|
| begin : test4b_block
|
| pot_val = read_potential(8'd35);
|
| $display(" Part B: N35 pot=%0d, total_spikes=%0d", pot_val, spike_count);
|
|
|
| if (spike_count >= 3) begin
|
| $display(" PASS: Coincidence spike! N35 fired with both dendrites (%0d spikes)", spike_count);
|
| pass_count = pass_count + 1;
|
| end else begin
|
| $display(" FAIL: Expected >=3 spikes (N33+N34+N35), got %0d", spike_count);
|
| fail_count = fail_count + 1;
|
| end
|
| end
|
|
|
|
|
|
|
|
|
|
|
| $display("");
|
| $display("--- TEST 5: Dendritic Enable Toggle ---");
|
|
|
| program_conn(8'd40, 5'd0, 8'd42, 16'sd1200, 2'd1);
|
|
|
|
|
| dendritic_enable = 0;
|
| stimulate(8'd40, 16'sd1200);
|
| spike_count = 0;
|
| run_timestep;
|
|
|
| run_timestep;
|
|
|
| pot_val = read_potential(8'd42);
|
| $display(" dendritic_enable=0: N42 potential = %0d", pot_val);
|
|
|
| begin : test5a_block
|
| integer spikes_off;
|
| spikes_off = spike_count;
|
| if (pot_val <= 16'sd0) begin
|
| $display(" PASS: Dendrite ignored when disabled (pot=%0d)", pot_val);
|
| pass_count = pass_count + 1;
|
| end else begin
|
| $display(" FAIL: Expected pot<=0 when disabled, got %0d", pot_val);
|
| fail_count = fail_count + 1;
|
| end
|
| end
|
|
|
|
|
| dendritic_enable = 1;
|
| program_conn(8'd50, 5'd0, 8'd52, 16'sd1200, 2'd1);
|
|
|
| stimulate(8'd50, 16'sd1200);
|
| spike_count = 0;
|
| run_timestep;
|
|
|
| run_timestep;
|
|
|
| pot_val = read_potential(8'd52);
|
| $display(" dendritic_enable=1: N52 potential = %0d (0 if spiked)", pot_val);
|
|
|
| if (spike_count >= 2) begin
|
| $display(" PASS: Dendrite active when enabled, N52 spiked");
|
| pass_count = pass_count + 1;
|
| end else begin
|
| $display(" FAIL: Expected N52 to spike, spikes=%0d", spike_count);
|
| fail_count = fail_count + 1;
|
| end
|
|
|
| $display("");
|
| $display("================================================================");
|
| $display(" DENDRITIC COMPARTMENT TEST RESULTS: %0d PASS, %0d FAIL", pass_count, fail_count);
|
| $display("================================================================");
|
| if (fail_count == 0)
|
| $display(" ALL TESTS PASSED");
|
| else
|
| $display(" SOME TESTS FAILED");
|
| $display("================================================================");
|
|
|
| #(CLK_PERIOD * 10);
|
| $finish;
|
| end
|
|
|
| initial begin
|
| #(CLK_PERIOD * 5_000_000);
|
| $display("TIMEOUT");
|
| $finish;
|
| end
|
|
|
| endmodule
|
|
|