Week 6 Challenges - zanzibarcircuit/ECE510 GitHub Wiki
Challenge 19 Binary LIF Neuron
This week I chose Challenge 19, which was to implement the LIF in SystemVerilog. Because this was relatively easy, I chose this opportunity to try and do less vibe coding and learn how to do the SystemVerilog a little more on my own.
Code
The algorithm works as follows. If a spike is received, it updates the current potential p to p(t) = lambdap(t-1) + 1; else p(t) = lambdap(t-1). In other words, the potential is incremented by 1 if a spike is received; otherwise it continues to reduce the potential by the leakage factor lambda. If the threshold exceeds some value, then it sends an output spike and updates the current potential back to its reset value. The following is my code:
`timescale 1ns/1ps
module lif_neuron #(
parameter signed [7:0] LAMBDA = 8'sb0001_0100, // 1.25 in Q4.4
parameter signed [7:0] THRESHOLD = 8'sb0100_0000, // 4.0 in Q4.4
parameter signed [7:0] RESET_LEVEL = 8'sb0000_0000 // 0.0
) (
input logic clk,
input logic rst_n, // active-low reset
input logic spike_in,
output logic spike_out
);
// State registers
logic signed [7:0] P_reg, P_next;
logic signed [7:0] integrated;
// Fixed-point multiply: 8Γ8 β 16 bits (Q8.8)
wire signed [15:0] mul_full = P_reg * LAMBDA;
// Downshift to Q4.4 by taking bits [11:4]
wire signed [7:0] leak_mult = mul_full[11:4];
// Combinational next-state and integration
always_comb begin
integrated = leak_mult + spike_in; // Q4.4 + integer β Q4.4
if (integrated >= THRESHOLD)
P_next = RESET_LEVEL;
else
P_next = integrated;
end
// Sequential update and spike generation
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
P_reg <= RESET_LEVEL;
spike_out<= 1'b0;
end else begin
P_reg <= P_next;
// Fire when threshold crossed
spike_out<= (P_next == RESET_LEVEL && integrated >= THRESHOLD);
end
end
endmodule
The only particularly interesting thing about this is the multiplication. We do a normal multiplication to create a Q8.8 number and then just take the middle 8 bits to make it Q4.4.
Test Bench
I parameterized a task for my test bench that allowed me to inject a spike every n cycles over a total of m cycles. My code for that is below:
// send a 1 every `period` cycles, for `n_cycles` total
task automatic periodic_spikes(input int period, input int n_cycles);
bit saw_spike;
int i;
begin
saw_spike = 0;
// assume DUT's been reset already
for (i = 1; i <= n_cycles; i++) begin
// drive spike_in = 1 on exactly the 10th, 20th, 30th⦠cycle
spike_in = (i % period == 0) ? 1'b1 : 1'b0;
@(posedge clk);
// optional: print what you saw
if (spike_out) begin
$display("[INFO] Periodic_spikes every %0d cycles saw spike at cycle %0d", period, i+1);
saw_spike = 1;
end
end
if (!saw_spike) begin
$display("[INFO] Didn't see spike for spike at every %0d cycles after %0d total cycles", period, n_cycles);
end
end
endtask
For my first case, I sent in a spike every cycle and saw spiking every 16 cycles. For the second test case, I sent in no spikes and saw no spikes on the output, as expected. The third test case I sent in a spike every 10 cycles and saw the first spike at my 55th cycle. For the fourth test case, I sent in a spike at just the 500th cycle over 1000 cycles and never saw an output spike, also expected. My simulation output is below:
I could change the frequency if I were to play with the reset level, threshold, and lambda values, and I played with that a little bit, but my baseline parameters were as follows:
parameter signed [7:0] LAMBDA = 8'sb0001_0100, // 1.25 in Q4.4
parameter signed [7:0] THRESHOLD = 8'sb0100_0000, // 4.0 in Q4.4
parameter signed [7:0] RESET_LEVEL = 8'sb0000_0000 // 0.0
I'd be more interested to learn how you can implement typical neural networks in this environment. I figure you'd have to encode your spatial domain stuff into the time domain. Like for pixel intensity, maybe you do fewer spikes over a time period for low intensity and dense spikes for a high intensity value.