Skip to content

HDL Generation

Verilog RTL generation from Python SNN descriptions.

  • generate_verilog() — Convert SC layer/neuron descriptions to synthesizable Verilog modules. Supports Q8.8 fixed-point, LFSR encoders, popcount trees, and event-driven AER.
  • IR compiler pipeline: Python → intermediate representation → SystemVerilog / MLIR (CIRCT backend)

19 hand-written Verilog modules + equation-to-Verilog compiler for arbitrary ODEs.

from sc_neurocore.hdl_gen import generate_verilog

sc_neurocore.hdl_gen

sc_neurocore.hdl_gen -- Tier: research (experimental / research).

VerilogGenerator

Generates Top-Level Verilog for a defined SC Network.

Source code in src/sc_neurocore/hdl_gen/verilog_generator.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
class VerilogGenerator:
    """
    Generates Top-Level Verilog for a defined SC Network.
    """

    def __init__(self, module_name="sc_network_top") -> None:
        self.module_name = module_name
        self.layers = []
        self.wires = []
        self.instances = []

    def add_layer(self, layer_type: str, name: str, params: Dict[str, Any]) -> None:
        self.layers.append({"type": layer_type, "name": name, "params": params})

    def generate(self) -> str:
        """
        Emits Verilog code.
        """
        code = f"module {self.module_name} (\n"
        code += "    input wire clk,\n"
        code += "    input wire rst_n,\n"
        # Determine I/O from first/last layer logic (simplified)
        code += "    input wire [7:0] input_bus,\n"
        code += "    output wire [7:0] output_bus\n"
        code += ");\n\n"

        code += "    // Internal Signals\n"
        # Generate wires for connections
        for i in range(len(self.layers) - 1):
            code += f"    wire [7:0] layer_{i}_to_{i + 1};\n"

        code += "\n"

        # Instantiate Layers
        for i, layer in enumerate(self.layers):
            l_type = layer["type"]
            l_name = layer["name"]

            # Simple Dense Layer instantiation logic
            if l_type == "Dense":
                code += f"    // Layer {i}: {l_name}\n"
                code += "    sc_dense_layer_core #(\n"
                code += f"        .NUM_NEURONS({layer['params'].get('n_neurons', 10)})\n"
                code += f"    ) {l_name}_inst (\n"
                code += "        .clk(clk),\n"
                code += "        .rst_n(rst_n),\n"

                # Connect Input
                if i == 0:
                    code += "        .input_bus(input_bus),\n"
                else:
                    code += f"        .input_bus(layer_{i - 1}_to_{i}),\n"

                # Connect Output
                if i == len(self.layers) - 1:
                    code += "        .output_bus(output_bus)\n"
                else:
                    code += f"        .output_bus(layer_{i}_to_{i + 1})\n"

                code += "    );\n\n"

        code += "endmodule\n"
        return code

    def save_to_file(self, path: str) -> None:
        try:
            with open(path, "w") as f:
                f.write(self.generate())
        except OSError as exc:
            logger.error("Failed to write Verilog to %s: %s", path, exc)
            raise

generate()

Emits Verilog code.

Source code in src/sc_neurocore/hdl_gen/verilog_generator.py
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
def generate(self) -> str:
    """
    Emits Verilog code.
    """
    code = f"module {self.module_name} (\n"
    code += "    input wire clk,\n"
    code += "    input wire rst_n,\n"
    # Determine I/O from first/last layer logic (simplified)
    code += "    input wire [7:0] input_bus,\n"
    code += "    output wire [7:0] output_bus\n"
    code += ");\n\n"

    code += "    // Internal Signals\n"
    # Generate wires for connections
    for i in range(len(self.layers) - 1):
        code += f"    wire [7:0] layer_{i}_to_{i + 1};\n"

    code += "\n"

    # Instantiate Layers
    for i, layer in enumerate(self.layers):
        l_type = layer["type"]
        l_name = layer["name"]

        # Simple Dense Layer instantiation logic
        if l_type == "Dense":
            code += f"    // Layer {i}: {l_name}\n"
            code += "    sc_dense_layer_core #(\n"
            code += f"        .NUM_NEURONS({layer['params'].get('n_neurons', 10)})\n"
            code += f"    ) {l_name}_inst (\n"
            code += "        .clk(clk),\n"
            code += "        .rst_n(rst_n),\n"

            # Connect Input
            if i == 0:
                code += "        .input_bus(input_bus),\n"
            else:
                code += f"        .input_bus(layer_{i - 1}_to_{i}),\n"

            # Connect Output
            if i == len(self.layers) - 1:
                code += "        .output_bus(output_bus)\n"
            else:
                code += f"        .output_bus(layer_{i}_to_{i + 1})\n"

            code += "    );\n\n"

    code += "endmodule\n"
    return code

SpiceGenerator

Generates SPICE netlists for Memristive Crossbars.

Source code in src/sc_neurocore/hdl_gen/spice_generator.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class SpiceGenerator:
    """
    Generates SPICE netlists for Memristive Crossbars.
    """

    @staticmethod
    def generate_crossbar(weights: np.ndarray[Any, Any], filename: str) -> None:
        """
        weights: (Rows, Cols) - Conductance values [0, 1] mapped to [G_off, G_on].
        """
        rows, cols = weights.shape
        g_on = 100e-6  # 100 uS (10 kOhm)
        g_off = 1e-6  # 1 uS (1 MOhm)

        netlist = f"* Memristor Crossbar {rows}x{cols}\n"
        netlist += ".PARAM VDD=1.0\n\n"

        # Inputs
        for r in range(rows):
            netlist += f"Vin_{r} in_{r} 0 DC 0.0\n"

        # Memristors
        for r in range(rows):
            for c in range(cols):
                w = weights[r, c]
                g = g_off + w * (g_on - g_off)
                r_val = 1.0 / g
                netlist += f"R_{r}_{c} in_{r} out_{c} {r_val:.2f}\n"

        # Outputs (current sensing ideally, here just nodes)
        # Add load resistors
        for c in range(cols):
            netlist += f"Rload_{c} out_{c} 0 1k\n"

        netlist += "\n.END\n"

        with open(filename, "w") as f:
            f.write(netlist)
        logger.info("SPICE Netlist saved to %s", filename)

generate_crossbar(weights, filename) staticmethod

weights: (Rows, Cols) - Conductance values [0, 1] mapped to [G_off, G_on].

Source code in src/sc_neurocore/hdl_gen/spice_generator.py
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@staticmethod
def generate_crossbar(weights: np.ndarray[Any, Any], filename: str) -> None:
    """
    weights: (Rows, Cols) - Conductance values [0, 1] mapped to [G_off, G_on].
    """
    rows, cols = weights.shape
    g_on = 100e-6  # 100 uS (10 kOhm)
    g_off = 1e-6  # 1 uS (1 MOhm)

    netlist = f"* Memristor Crossbar {rows}x{cols}\n"
    netlist += ".PARAM VDD=1.0\n\n"

    # Inputs
    for r in range(rows):
        netlist += f"Vin_{r} in_{r} 0 DC 0.0\n"

    # Memristors
    for r in range(rows):
        for c in range(cols):
            w = weights[r, c]
            g = g_off + w * (g_on - g_off)
            r_val = 1.0 / g
            netlist += f"R_{r}_{c} in_{r} out_{c} {r_val:.2f}\n"

    # Outputs (current sensing ideally, here just nodes)
    # Add load resistors
    for c in range(cols):
        netlist += f"Rload_{c} out_{c} 0 1k\n"

    netlist += "\n.END\n"

    with open(filename, "w") as f:
        f.write(netlist)
    logger.info("SPICE Netlist saved to %s", filename)