Week‐1 Challenge‐4 - AbhishekMusku/hw4aiml-am GitHub Wiki

Challenge #4 — LLM-Guided Design of a Spiking Neuron in SystemVerilog

Overview

Inspired by the paper “Designing Silicon Brains Using LLMs”, this challenge explored the design of a synthesizable Leaky Integrate-and-Fire (LIF) neuron using only natural language interactions with an LLM (ChatGPT). The objective was to co-design a neuron module through a sequence of prompts and response-refinement loops, while learning how much the LLM can contribute to RTL development.


LIF Neuron Design via LLM Prompts

The following section documents the design and verification of a Leaky Integrate-and-Fire (LIF) neuron using LLM-assisted prompts. Additional components such as ReLU and synapse modules will be added in subsequent sections of this page.

Prompt 1: Kickstarting the Design

Prompt:
“Can you write a Verilog module for a spiking leaky integrate and fire neuron? Please include a multibit current input and a refractory period.”

LLM Response:

// Initial version with basic LIF neuron behavior
// (multi-bit input, leak, refractory, threshold)
...

Analysis:
The LLM created a complete, synthesizable neuron with parameterization and correct behavior:

  • Integration and leakage
  • Refractory period with a countdown counter
  • Spike generation at threshold

At this stage, neither overflow nor underflow were explicitly handled.


Prompt 2: Verilog Compatibility

Prompt:
“Does Verilog support logic type?”

Response Summary:

  • logic is SystemVerilog, not Verilog-2001.
  • Use reg/wire for older tools.
  • Fine to use logic in SystemVerilog-based flow.

Analysis:
Since our flow supports SystemVerilog, the original usage is acceptable.


Prompt 3: Overflow Handling

Prompt:
“Does the code take care of overflow of membrane_potential?”

Response Summary:

  • Overflow could occur with values > 255 (8-bit).
  • Suggested clamping or widening intermediates.
  • Offered a clamped update logic to prevent wraparound.

Analysis:
Although not strictly necessary under current configuration (THRESHOLD = 100, WIDTH = 8), it was good insight for future-proofing.


Prompt 4: Underflow Check

Prompt:
“Can you just check membrane potential + current - leak before updating, and clamp it to threshold if needed?”

Observation:
This led to a conversation about both overflow and underflow. The LLM pointed out that subtractive leakage could cause underflow when membrane_potential < LEAK, which results in unexpected values due to unsigned wraparound.

To fix this, the LLM updated the logic to:

if (membrane_potential > LEAK)
    membrane_potential <= membrane_potential - LEAK + input_current;
else
    membrane_potential <= input_current;

Analysis:
This was not included in the original response — only after prompting. It ensures that when the leak would exceed the current membrane value, we avoid a negative wraparound.
While not essential with THRESHOLD = 100 and small leak values, it reflects better defensive design.


Prompt 5: Spike Duration

Prompt:
“Does the code make sure spike is always 0 except when membrane_potential >= THRESHOLD?”

Response Summary:

  • Spike is reset at the start of every clock cycle.
  • Only raised to 1 for the spike event.
  • Guaranteed to be 1-cycle wide.

Analysis:
This avoids stale values on spike, which could otherwise persist and confuse downstream logic.


Prompt 6: Final Clean Version (No Clamping)

Prompt:
“Give me the final code without clamping logic.”

Final Response:

module lif_neuron #(
    parameter int WIDTH       = 8,
    parameter int THRESHOLD   = 100,
    parameter int LEAK        = 2,
    parameter int REF_PERIOD  = 10
)(
    input  logic                 clk,
    input  logic                 rst_n,
    input  logic [WIDTH-1:0]     input_current,
    output logic                 spike
);

    logic [WIDTH-1:0] membrane_potential;
    logic [$clog2(REF_PERIOD+1)-1:0] ref_counter;
    logic in_refractory;

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            membrane_potential <= 0;
            ref_counter        <= 0;
            in_refractory      <= 0;
            spike              <= 0;
        end else begin
            spike <= 0; // Default to no spike
            if (in_refractory) begin
                if (ref_counter == REF_PERIOD - 1) begin
                    in_refractory      <= 0;
                    ref_counter        <= 0;
                    membrane_potential <= 0;
                end else begin
                    ref_counter <= ref_counter + 1;
                end
            end else begin
                if (membrane_potential > LEAK)
                    membrane_potential <= membrane_potential - LEAK + input_current;
                else
                    membrane_potential <= input_current;

                if (membrane_potential >= THRESHOLD) begin
                    spike              <= 1;
                    in_refractory      <= 1;
                    ref_counter        <= 0;
                    membrane_potential <= 0;
                end
            end
        end
    end
endmodule

Analysis:
This is the final version used — it integrates the underflow fix, drops optional overflow clamp, and has lean logic suitable for synthesis and extension.


Visualization: LIF Neuron Behavior Over Time

The diagram below illustrates how the membrane potential of a Leaky Integrate-and-Fire (LIF) neuron evolves with time. It highlights:

  • Integration of input current
  • Spiking event when the threshold is crossed
  • Reset and entry into the refractory period

LIF Neuron Behavior

Simulation and Testing

To verify the functionality of the LIF neuron module, I prompted Claude to generate a simple testbench. The testbench fed the neuron a repeated current pulse of magnitude 25 and monitored the spike output across clock cycles. The clock and reset behavior were modeled using standard always blocks, and the simulation was run long enough to observe spiking behavior and refractory enforcement.

And a few relevant lines from the simulation output (Transcript_LIF_Neuron):

# Test Case 2: Threshold crossing - Testing spike generation
# Time=              185000, Current= 30, Membrane=  0, Spike=0, Refractory=1, RefCounter= 8
# Time=              195000, Current= 30, Membrane=  0, Spike=0, Refractory=1, RefCounter= 9
# Time=              205000, Current= 30, Membrane=  0, Spike=0, Refractory=0, RefCounter= 0
# Time=              215000, Current= 30, Membrane= 30, Spike=0, Refractory=0, RefCounter= 0
# Time=              225000, Current= 30, Membrane= 58, Spike=0, Refractory=0, RefCounter= 0
# Time=              235000, Current= 30, Membrane= 86, Spike=0, Refractory=0, RefCounter= 0
# Time=              245000, Current= 30, Membrane=114, Spike=0, Refractory=0, RefCounter= 0
# Time=              255000, Current= 30, Membrane=  0, Spike=1, Refractory=1, RefCounter= 0
...

This confirms the following:

  • The neuron correctly accumulates membrane potential over time due to the continuous input_current.
  • A spike is triggered once the threshold is reached (THRESHOLD = 100), visible at Time = 255000.
  • The spike returns to 0 immediately after firing and remains suppressed during the refractory period, verifying correct timing logic.

While the testbench confirmed the core behavior (integration, spiking, refractory), it didn’t dump internal signals like membrane_potential, and it focused only on one type of stimulus. This was sufficient for basic validation but could be extended in future tests to cover edge cases and visualize membrane dynamics in more detail.


Simple 2-Layer Network

To extend the spiking neuron model into a small network, we used ChatGPT iteratively to build a simple 2-layer SNN architecture where input spikes are weighted and routed into an output layer of LIF neurons. Below, we document the key prompts, the generated responses, and our analysis.

Simple 2-Layer Network


Prompt 1

"Can you create a 2-layer spiking neural network in SystemVerilog using the LIF neuron we built earlier?"

Response Summary

The model generated a spiking_nn_2layer module with:

  • Parameterizable N_INPUT, N_OUTPUT, and WIDTH
  • A 2D array of synapse_weights
  • Accumulator output_currents for the second layer
  • Output layer composed of LIF neurons

Analysis

  • Correctly captured the architecture as fully connected
  • Reused the lif_neuron module from earlier
  • Treated input layer as external spike sources, not instantiated neurons

Prompt 2

"Explain how this spiking_nn_2layer module works."

Response Summary

The LLM broke it down step-by-step:

  • Input spikes are received as a logic [N_INPUT-1:0] vector
  • For each spiking input neuron, its weight to each output is added to output_currents
  • These currents are fed to instantiated lif_neuron modules
  • Each neuron spikes based on LIF rules

Analysis

  • Excellent explanation of conditional addition
  • Clarified that this implements weighted spike routing
  • Interpreted spike-based transmission as a binary-gated weight application

Prompt 3

"Is this really a 2-layer network if there are no neurons in the input layer?"

Response Summary

The LLM clarified:

  • Only the output layer contains instantiated neurons
  • The input layer is abstract — just a set of spike sources (e.g., from a testbench or external sensors)
  • For a full 2-layer network, we’d need to instantiate lif_neuron modules in the input layer too

Analysis

  • Provided important architectural clarification
  • Distinguished between abstract spike sources vs real neurons
  • Prompt helped correct a possible misunderstanding in design hierarchy

Prompt 4

"Why use temporary variables like output_currents before feeding them to the neurons?"

Response Summary

The LLM listed several reasons:

  • Allows accumulation from multiple inputs per output
  • Cleanly separates spike routing logic from neuron dynamics
  • Avoids overwriting issues or logic entanglement

Example Snippet

if (input_spikes[i]) begin
    output_currents[j] += synapse_weights[i][j];
end

Analysis

  • Justified a good design decision
  • Encouraged modular thinking
  • Helped highlight hardware timing concerns in SNNs

Prompt 5

"Can you modify the design so that I can write to the synapse weights using an external interface? I want to be able to specify the input-output index and the value, kind of like memory-mapped SPI."

Response Summary

The LLM extended the design by:

  • Adding control inputs: write_en, write_data, write_row, write_col
  • Writing the given value to the indexed location in the synapse_weights array
  • Preserving all previous SNN behavior (spike routing, LIF dynamics)

Example Snippet

if (write_en) begin
    synapse_weights[write_row][write_col] <= write_data;
end

Analysis

  • Enabled real-time reconfiguration of weights
  • Mimics a memory-mapped peripheral interface
  • Makes the design more hardware-realistic (usable for training, learning, or dynamic modulation)
  • Major usability boost — this version can now be integrated with a microcontroller or learning algorithm

Final Design: spiking_nn_2layer

The final SystemVerilog module included:

  • Fully connected weighted spike routing
  • Output layer of LIF neurons
  • Runtime-writeable synapse weights via external control interface
  • Combinational spike accumulation + clean modular separation

Reflections

The LLM was consistently helpful in converting architectural intent into HDL. Prompt chaining was crucial — each stage unlocked new capabilities:

  • From fixed connectivity to parameterized
  • From hardcoded weights to externally writable ones
  • From abstract inputs to neuron-layered discussions However, while the LLM produced clean and modular code, it occasionally:
  • It tends to oversimplify terms like “2-layer” unless specified
  • It can produce syntactically correct code that needs validation (e.g., timing, synthesis constraints)

Careful analysis and follow-ups were essential to avoid misinterpretation, especially around hardware structure and biological naming conventions.

Simulation Analysis

To validate the spiking_nn_2layer module, a testbench was used that performs the following:

  • Programs synapse weights using the external interface.
  • Applies controlled spike inputs on different input channels.
  • Monitors output spike behavior from the LIF neurons.

The simulation demonstrates proper interaction across all major components:

  • Weight Programming via Interface
    The testbench initializes synaptic weights dynamically:
W[0][0] = 50
W[1][0] = 60
W[2][0] = 70

These weights are written using write_row, write_col, write_data, and write_en.

  • Input Spike Propagation
    Inputs are applied at various times (input_spikes = 3'b001, etc.), and their effects are accurately integrated by the output neuron.

  • Output Spiking Behavior Matches LIF Model
    The neuron does not spike immediately — it requires multiple weighted spikes to accumulate current. The firing happens after expected delays, with the output resetting due to the refractory period.


Representative Transcript Snippet:

# Time: 200
input_spikes = 3'b001
output_spikes = 0
output_currents[0] = 50

# Time: 220
input_spikes = 3'b010
output_spikes = 0
output_currents[0] = 110

# Time: 240
input_spikes = 3'b000
output_spikes = 1
membrane_potential = 110 -> 0 (fired and reset)

This shows that:

  • Two inputs fire sequentially: one at time 200 and one at 220.
  • Their respective weights (50 and 60) accumulate in output_currents[0].
  • At time 240, the output neuron spikes — evidence of correct thresholding, membrane integration, and reset logic.

Summary

The simulation confirms:

  • Correct configuration of synapse weights
  • Proper summation of weighted inputs into LIF dynamics
  • Expected neuron firing behavior once the threshold is met

The evidence confirms that the interface, spike propagation, and neuron logic all function coherently. The design is reliable for simple 2-layer SNN modeling and ready for scale-up to more complex topologies or learning rules.


SPI & TOP Module (LLM-Aided Design)

This section documents the iterative design of the SPI interface and TOP Module, based entirely on actual prompts and responses exchanged with the LLM. Unlike the flawed version in the reference paper, this design emphasizes synthesizability, correct protocol behavior (SPI Mode 0), and proper integration with a 2-layer spiking neuron array.


Prompt 1

User:

Now I need you to design an SPI interface to communicate with the network module above. Please don’t copy the SPI module from paper directly; it has both design, logical and syntax errors. Use it only as a reference.

LLM Response (Summary):
The assistant generated a spi_interface module with:

  • Parameterizable ADDR_WIDTH and DATA_WIDTH
  • Clean 3-state FSM: IDLE, RECEIVE, PROCESS
  • Shift register for capturing 16-bit command frames (8-bit address + 8-bit data)
  • Read-back support via data_in → spi_miso
  • Edge detection for spi_clk and spi_cs_n
  • Single-cycle write_en pulse

Analysis:

  • Major improvement over the paper: edge detection implemented using spi_clk_prev and spi_cs_n_prev
  • Correct FSM separation (sequential + combinational)
  • Proper SPI timing for Mode 0
  • Domain synchronization to clk not formally addressed

Prompt 2

User:

Okay now explain how does your SPI work, and on what edge are you doing what. You can use a walkthrough example. Also include concepts of SPI as I forgot them long ago. Then the state machine.

LLM Response (Summary):
Detailed breakdown of SPI concepts and protocol:

  • SPI Mode 0 explanation
  • Sample MOSI on rising edge, drive MISO on falling edge
  • Example of writing 0x42 to address 0x10
  • FSM transition diagram and ASCII waveform

Analysis:

  • Accurate Mode 0 timing
  • Explained “sampling vs shifting” clearly
  • Great documentation aid
  • Minor clarification needed on write_en cycle

Prompt 3

User:

I understood the SPI module. Now explain how the top module works — how are neuron, 2-layer network, and everything connected. Where are values generated, how are they passed and why. Explain everything.

LLM Response (Summary):
Described integration in spiking_nn_top:

  • spi_interface connected to register file
  • Parameters and weights mapped via address decoding
  • Weights reshaped from flat to 2D and passed to spiking_nn_2layer

Analysis:

  • Modular register map
  • Flat → 2D weight conversion clean and scalable
  • Full SPI-driven configurability
  • No register address range checking

Technical Summary of the Final Design

spi_interface.sv

  • FSM: IDLE → RECEIVE → PROCESS
  • Shift register captures [ADDR, DATA]
  • write_en one-cycle pulse in PROCESS
  • data_in drives spi_miso during readback

spiking_nn_top.sv

  • Maps:
    • 0x00 → THRESHOLD
    • 0x01 → LEAK_RATE
    • 0x02 → REF_PERIOD
    • 0x10+ → WEIGHTS
  • Reshapes weights from 1D to 2D
  • Parameters passed to neuron network

SPI Communication Diagram

               ┌──────────────────────┐
               │     SPI Master       │
               │  (e.g., MCU / Host)  │
               └─────────┬────────────┘
                         │
         ┌───────────────▼────────────────┐
         │         spiking_nn_top         │
         │ ┌────────────────────────────┐ │
         │ │      spi_interface         │ │
         │ │                            │ │
         │ │  SCLK   ◄────────────────┐ │ │
         │ │  MOSI   ◄────────────────┘ │ │
         │ │  MISO   ─────────────────► │ │
         │ │  CS_N   ◄────────────────┐ │ │
         │ └───────┬──────────────────┘ │
         │         ▼                    ▼
         │ ┌──────────────────────────────┐
         │ │     Register File + MUX      │◄────── Internal bus
         │ │  - THRESHOLD                 │
         │ │  - LEAK_RATE                 │
         │ │  - REF_PERIOD                │
         │ │  - WEIGHTS (Flat Array)      │
         │ └────────────┬─────────────────┘
         │              ▼
         │ ┌──────────────────────────────┐
         │ │   2-Layer Spiking NN Core    │
         │ └──────────────────────────────┘
         └────────────────────────────────┘

📎 Transcript Evidence:

# Test Case 3: Single input spike
# Time=7375000: Output spikes=001
# Time=7385000: Output spikes=010
...
# Time=7445000: Output spikes=100

# Test Case 4: Multiple input spikes
# Time=8075000: Output spikes=111
# Time=8205000: Output spikes=111

Analysis

  • The SPI interface correctly programmed all neuron parameters and weights before stimulus application.
  • Neuron dynamics are clearly visible:
    • In Test Case 3, a single input spike causes one neuron to spike at a time.
    • In Test Case 4, multiple simultaneous inputs trigger all output neurons.
  • Staggered spikes in the logs suggest proper threshold accumulation and refractory behavior.
  • Consistent multi-cycle output spikes (like 111 persisting across time steps) confirm sustained input effect.

Conclusion

This simulation demonstrates that the full path — from SPI-based configuration to spiking output — was functionally correct. The network responds dynamically to varied input, validating the effectiveness of LLM-assisted design for complex HDL systems.


Reflections

What Went Well

  • SPI FSM and shift logic were cleanly scaffolded by LLM
  • Mode 0 behavior was correctly implemented after prompting
  • Flat-to-2D weights allowed scalable design

Challenges

  • Needed clarification on domain synchronization and edge timing
  • Had to manually ensure write_en was pulsed
  • Read path needed proper indexing for bit_counter

Takeaway

The LLM worked great as a co-designer when given precise, protocol-level prompts. While not always perfect, it significantly accelerated the design process and offered better structure than the original flawed SPI in the reference paper.


ReLU Neuron Hardware Design (vs. LIF)

This secton explores replacing a Leaky Integrate-and-Fire (LIF) neuron with a Rectified Linear Unit (ReLU) neuron in a synthesizable hardware implementation, along with a comparison of their properties, use cases, and trade-offs.


ReLU Neuron: Hardware Implementation

We designed a simple synthesizable ReLU neuron module in SystemVerilog. The ReLU neuron outputs the input value if it is positive; otherwise, it outputs zero. This is commonly expressed as:

output = max(0, input)

SystemVerilog Module: relu_neuron.sv

module relu_neuron #(
    parameter DATA_WIDTH = 16
)(
    input  logic                     clk,
    input  logic                     rst_n,
    input  logic signed [DATA_WIDTH-1:0] input_val,
    input  logic                     input_valid,
    output logic signed [DATA_WIDTH-1:0] output_val,
    output logic                     output_valid
);

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            output_val   <= '0;
            output_valid <= 1'b0;
        end else if (input_valid) begin
            output_val   <= (input_val > 0) ? input_val : '0;
            output_valid <= 1'b1;
        end else begin
            output_valid <= 1'b0;
        end
    end

endmodule

ReLU as a replacement to LIF in the design

Replacing LIF with ReLU would:

  • Turn the system into a non-spiking feedforward neural network.
  • Require removing/replacing all time-domain logic (accumulators, refractory filters).
  • Eliminate event-driven sparsity, increasing power and data bandwidth.
  • Be easier to implement and highly parallelizable (ideal for FPGAs). So the idea is viable if the goal shifts from neuromorphic inference to a more conventional neural net accelerator.

LIF vs. ReLU: Summary Comparison

This table summarizes the key differences between Leaky Integrate-and-Fire (LIF) and Rectified Linear Unit (ReLU) neurons from both algorithmic and hardware design perspectives.

Aspect LIF Neuron ReLU Neuron
Domain Spiking Neural Networks (SNNs) Artificial Neural Networks (ANNs)
Output Binary spike (0 or 1) Real-valued: max(0, input)
Stateful? Yes — membrane potential with decay No — stateless
Time-dependent? Yes — integrates over time No — instantaneous
Power Efficiency High — sparse/event-driven Moderate — dense compute
Hardware Cost Higher — accumulators, decay logic Lower — single comparator/MUX
Training Unsupervised or surrogate gradients Supervised (backpropagation)
Biological Realism High Low
Synthesis Friendly Moderate Very high
Use Cases Low-power edge AI, neuromorphic research CNNs, DNNs, hardware inference accelerators

Comparison of Spiking Neuron Array Components

Between ChatGPT (Paper) Versions and User Versions

This section provides a detailed comparison of four major modules from the "Designing Silicon Brains using LLM" paper with revised versions implemented in this challenge. The analysis evaluates functionality, synthesis-friendliness, correctness, and improvements.


1. LIF Neuron

Functionality

  • ChatGPT: Basic LIF neuron with programmable threshold, leak, and refractory period. Resets spike within a single clocked block.
  • User: Uses FSM-like design, clean state separation, more accurate modeling of refractory behavior.

Synthesis-Friendliness

  • ChatGPT: Syntactically correct but lacks clarity in handling unsigned math.
  • User: Clear logic typing and modular design suited for synthesis.

Correctness

  • ChatGPT: Logic errors around pulse resetting and wraparound risks.
  • User: Correct and verifiable neuron behavior.

2. Neuron Network

Functionality

  • ChatGPT: Fully connected 2-layer network using flattened arrays.
  • User: Parametric, modular SystemVerilog implementation with scoped generates.

Synthesis-Friendliness

  • ChatGPT: Misuses 2D ports, uses initial blocks.
  • User: Clean and reusable code, no initial blocks, synthesizable logic.

Correctness

  • ChatGPT: Error-prone indexing and bad 2D-to-1D mapping.
  • User: Clear spike-weight accumulation and correct modular behavior.

Improvements to ChatGPT

  • Flatten 2D arrays properly
  • Use generate blocks
  • Avoid initial blocks
  • Refactor for clarity

3. SPI Interface

Functionality

  • ChatGPT: SPI slave with FSM and shift register, but ignores sclk.
  • User: Fully SPI-compliant slave module using sclk, cs, and correct bit sampling.

Synthesis-Friendliness

  • ChatGPT: Uses typedef enum in .v file, multiply driven nets.
  • User: Uses clean state management, edge detection, and valid/rw control.

Correctness

  • ChatGPT: Timing issues and protocol violations.
  • User: Meets timing and protocol needs.

Improvements to ChatGPT

  • Use sclk for SPI FSM
  • Remove SystemVerilog constructs from .v
  • Fix write_enable driving
  • Add handshake control

4. Top Module

Functionality

  • ChatGPT: Instantiates modules but lacks decoding and state control.
  • User: Adds address decoding, configuration gating, and FSM for inference.

Synthesis-Friendliness

  • ChatGPT: Flat structure, global signal issues.
  • User: Modular, FSM-driven, and memory-mapped.

Correctness

  • ChatGPT: No isolation between config and inference.
  • User: Safe config loading, scalable design.

Improvements to ChatGPT

  • Add address decoding
  • Gate config with FSM
  • Separate config/infer phases

Summary Table

Component User Version Strengths ChatGPT Fix Suggestions
LIF Neuron Structured FSM, proper refractory logic Fix unsigned math, logic typing
Network Parametric and modular Flatten ports, avoid initial
SPI True SPI, correct FSM and bit timing Use sclk, fix FSM style
Top Address decoding, gated config logic Add FSM, phase separation

LLM Acknowledgment and Code Access

This work was developed with the assistance of large language models (LLMs), specifically ChatGPT and Claude, which were used for iterative prompt-based hardware design. These models helped generate baseline code, guided debugging, and enabled improvements through conversational refinement.

All code related to the Spiking Neural Network, including:

  • SystemVerilog modules
  • ChatGPT and Claude prompt transcripts
  • Simulation outputs and testbenches

…can be found on the GitHub repository under the Week 1 directory.