Delete dataset/examples
Browse files
dataset/examples/fpga_deployment_guide.ipynb
DELETED
|
@@ -1,1010 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"cells": [
|
| 3 |
-
{
|
| 4 |
-
"cell_type": "markdown",
|
| 5 |
-
"metadata": {},
|
| 6 |
-
"source": [
|
| 7 |
-
"# 🔧 Spikenaut SNN v2 - FPGA Deployment Guide\n",
|
| 8 |
-
"\n",
|
| 9 |
-
"Complete guide for deploying Spikenaut SNN v2 to Xilinx Artix-7 Basys3 FPGA.\n",
|
| 10 |
-
"\n",
|
| 11 |
-
"## What you'll learn:\n",
|
| 12 |
-
"- Understanding Q8.8 fixed-point format\n",
|
| 13 |
-
"- Loading parameters into FPGA memory\n",
|
| 14 |
-
"- Verilog implementation basics\n",
|
| 15 |
-
"- Hardware verification\n",
|
| 16 |
-
"- Performance optimization"
|
| 17 |
-
]
|
| 18 |
-
},
|
| 19 |
-
{
|
| 20 |
-
"cell_type": "markdown",
|
| 21 |
-
"metadata": {},
|
| 22 |
-
"source": [
|
| 23 |
-
"## 1. Hardware Requirements"
|
| 24 |
-
]
|
| 25 |
-
},
|
| 26 |
-
{
|
| 27 |
-
"cell_type": "code",
|
| 28 |
-
"execution_count": null,
|
| 29 |
-
"metadata": {},
|
| 30 |
-
"outputs": [],
|
| 31 |
-
"source": [
|
| 32 |
-
"# Hardware specifications\n",
|
| 33 |
-
"hardware_specs = {\n",
|
| 34 |
-
" 'fpga_board': 'Xilinx Artix-7 Basys3',\n",
|
| 35 |
-
" 'target_device': 'XC7A35T-1CPG236C',\n",
|
| 36 |
-
" 'logic_cells': 5200,\n",
|
| 37 |
-
" 'bram': 1800, # 18Kb blocks\n",
|
| 38 |
-
" 'dsp_slices': 90,\n",
|
| 39 |
-
" 'clock_speed': '1kHz (1ms resolution)',\n",
|
| 40 |
-
" 'power_consumption': '~97mW dynamic',\n",
|
| 41 |
-
" 'interface': 'UART, GPIO, PMOD'\n",
|
| 42 |
-
"}\n",
|
| 43 |
-
"\n",
|
| 44 |
-
"print(\"🔧 Hardware Requirements:\")\n",
|
| 45 |
-
"for key, value in hardware_specs.items():\n",
|
| 46 |
-
" print(f\" {key}: {value}\")\n",
|
| 47 |
-
"\n",
|
| 48 |
-
"# Memory requirements\n",
|
| 49 |
-
"memory_requirements = {\n",
|
| 50 |
-
" 'neuron_thresholds': 16 * 2, # 16 neurons, 2 bytes each\n",
|
| 51 |
-
" 'synaptic_weights': 16 * 8 * 2, # 16x8 matrix, 2 bytes each\n",
|
| 52 |
-
" 'decay_constants': 16 * 2, # 16 decay values\n",
|
| 53 |
-
" 'input_buffer': 8 * 2, # 8 input features\n",
|
| 54 |
-
" 'output_buffer': 3 * 2, # 3 output classes\n",
|
| 55 |
-
" 'total_memory_kb': (16 * 2 + 16 * 8 * 2 + 16 * 2 + 8 * 2 + 3 * 2) / 1024\n",
|
| 56 |
-
"}\n",
|
| 57 |
-
"\n",
|
| 58 |
-
"print(f\"\\n💾 Memory Requirements:\")\n",
|
| 59 |
-
"print(f\" Total memory needed: {memory_requirements['total_memory_kb']:.2f} KB\")\n",
|
| 60 |
-
"print(f\" Available BRAM: {hardware_specs['bram']} * 18Kb = {hardware_specs['bram'] * 18 / 1024:.1f} MB\")\n",
|
| 61 |
-
"print(f\" Memory utilization: {(memory_requirements['total_memory_kb'] / (hardware_specs['bram'] * 18 / 1024) * 100):.1f}%\")"
|
| 62 |
-
]
|
| 63 |
-
},
|
| 64 |
-
{
|
| 65 |
-
"cell_type": "markdown",
|
| 66 |
-
"metadata": {},
|
| 67 |
-
"source": [
|
| 68 |
-
"## 2. Q8.8 Fixed-Point Format"
|
| 69 |
-
]
|
| 70 |
-
},
|
| 71 |
-
{
|
| 72 |
-
"cell_type": "code",
|
| 73 |
-
"execution_count": null,
|
| 74 |
-
"metadata": {},
|
| 75 |
-
"outputs": [],
|
| 76 |
-
"source": [
|
| 77 |
-
"import numpy as np\n",
|
| 78 |
-
"import matplotlib.pyplot as plt\n",
|
| 79 |
-
"\n",
|
| 80 |
-
"def float_to_q8_8(value):\n",
|
| 81 |
-
" \"\"\"Convert float to Q8.8 fixed-point format\"\"\"\n",
|
| 82 |
-
" # Clamp to Q8.8 range\n",
|
| 83 |
-
" value = np.clip(value, -128, 127.996)\n",
|
| 84 |
-
" # Convert to fixed-point\n",
|
| 85 |
-
" q8_8 = int(value * 256)\n",
|
| 86 |
-
" return q8_8\n",
|
| 87 |
-
"\n",
|
| 88 |
-
"def q8_8_to_float(q8_8):\n",
|
| 89 |
-
" \"\"\"Convert Q8.8 fixed-point to float\"\"\"\n",
|
| 90 |
-
" # Convert to signed integer\n",
|
| 91 |
-
" if q8_8 >= 32768: # Negative number in two's complement\n",
|
| 92 |
-
" q8_8 = q8_8 - 65536\n",
|
| 93 |
-
" # Convert to float\n",
|
| 94 |
-
" return q8_8 / 256.0\n",
|
| 95 |
-
"\n",
|
| 96 |
-
"# Demonstrate Q8.8 conversion\n",
|
| 97 |
-
"test_values = [-1.0, -0.5, 0.0, 0.5, 1.0, 2.5, 10.0, 100.0]\n",
|
| 98 |
-
"\n",
|
| 99 |
-
"print(\"🔢 Q8.8 Fixed-Point Conversion Examples:\")\n",
|
| 100 |
-
"print(\"Float -> Q8.8 (Hex) -> Back to Float\")\n",
|
| 101 |
-
"print(\"-\" * 50)\n",
|
| 102 |
-
"\n",
|
| 103 |
-
"for val in test_values:\n",
|
| 104 |
-
" q8_8 = float_to_q8_8(val)\n",
|
| 105 |
-
" back_to_float = q8_8_to_float(q8_8)\n",
|
| 106 |
-
" error = abs(back_to_float - val)\n",
|
| 107 |
-
" \n",
|
| 108 |
-
" print(f\"{val:6.2f} -> {q8_8:04X} -> {back_to_float:6.2f} (error: {error:.6f})\")\n",
|
| 109 |
-
"\n",
|
| 110 |
-
"# Show precision characteristics\n",
|
| 111 |
-
"print(\"\\n📊 Q8.8 Precision Characteristics:\")\n",
|
| 112 |
-
"print(f\" Range: [-128.0, +127.996]\")\n",
|
| 113 |
-
"print(f\" Resolution: 1/256 ≈ 0.0039\")\n",
|
| 114 |
-
"print(f\" Dynamic range: ~128/0.0039 ≈ 32768:1\")\n",
|
| 115 |
-
"print(f\" Quantization step: 0.00390625\")\n",
|
| 116 |
-
"\n",
|
| 117 |
-
"# Visualize quantization error\n",
|
| 118 |
-
"fine_values = np.linspace(-2, 2, 1000)\n",
|
| 119 |
-
"quantized = [q8_8_to_float(float_to_q8_8(val)) for val in fine_values]\n",
|
| 120 |
-
"quantization_error = np.array(quantized) - fine_values\n",
|
| 121 |
-
"\n",
|
| 122 |
-
"plt.figure(figsize=(12, 4))\n",
|
| 123 |
-
"\n",
|
| 124 |
-
"plt.subplot(1, 2, 1)\n",
|
| 125 |
-
"plt.plot(fine_values, quantized, 'b-', alpha=0.7, label='Quantized')\n",
|
| 126 |
-
"plt.plot(fine_values, fine_values, 'r--', alpha=0.5, label='Original')\n",
|
| 127 |
-
"plt.xlabel('Input Value')\n",
|
| 128 |
-
"plt.ylabel('Output Value')\n",
|
| 129 |
-
"plt.title('Q8.8 Quantization Characteristic')\n",
|
| 130 |
-
"plt.legend()\n",
|
| 131 |
-
"plt.grid(True, alpha=0.3)\n",
|
| 132 |
-
"\n",
|
| 133 |
-
"plt.subplot(1, 2, 2)\n",
|
| 134 |
-
"plt.plot(fine_values, quantization_error, 'g-', alpha=0.7)\n",
|
| 135 |
-
"plt.xlabel('Input Value')\n",
|
| 136 |
-
"plt.ylabel('Quantization Error')\n",
|
| 137 |
-
"plt.title('Q8.8 Quantization Error')\n",
|
| 138 |
-
"plt.grid(True, alpha=0.3)\n",
|
| 139 |
-
"\n",
|
| 140 |
-
"plt.tight_layout()\n",
|
| 141 |
-
"plt.show()"
|
| 142 |
-
]
|
| 143 |
-
},
|
| 144 |
-
{
|
| 145 |
-
"cell_type": "markdown",
|
| 146 |
-
"metadata": {},
|
| 147 |
-
"source": [
|
| 148 |
-
"## 3. Loading FPGA Parameters"
|
| 149 |
-
]
|
| 150 |
-
},
|
| 151 |
-
{
|
| 152 |
-
"cell_type": "code",
|
| 153 |
-
"execution_count": null,
|
| 154 |
-
"metadata": {},
|
| 155 |
-
"outputs": [],
|
| 156 |
-
"source": [
|
| 157 |
-
"import os\n",
|
| 158 |
-
"from pathlib import Path\n",
|
| 159 |
-
"\n",
|
| 160 |
-
"# Check if parameter files exist\n",
|
| 161 |
-
"parameter_files = {\n",
|
| 162 |
-
" 'thresholds': 'parameters/parameters.mem',\n",
|
| 163 |
-
" 'weights': 'parameters/parameters_weights.mem',\n",
|
| 164 |
-
" 'decay': 'parameters/parameters_decay.mem'\n",
|
| 165 |
-
"}\n",
|
| 166 |
-
"\n",
|
| 167 |
-
"print(\"📂 Checking Parameter Files:\")\n",
|
| 168 |
-
"for name, filepath in parameter_files.items():\n",
|
| 169 |
-
" if os.path.exists(filepath):\n",
|
| 170 |
-
" print(f\" ✅ {name}: {filepath}\")\n",
|
| 171 |
-
" else:\n",
|
| 172 |
-
" print(f\" ❌ {name}: {filepath} (not found)\")\n",
|
| 173 |
-
"\n",
|
| 174 |
-
"# Load and display parameters if files exist\n",
|
| 175 |
-
"def load_mem_file(filepath, max_lines=10):\n",
|
| 176 |
-
" \"\"\"Load parameters from .mem file\"\"\"\n",
|
| 177 |
-
" if not os.path.exists(filepath):\n",
|
| 178 |
-
" return None\n",
|
| 179 |
-
" \n",
|
| 180 |
-
" parameters = []\n",
|
| 181 |
-
" with open(filepath, 'r') as f:\n",
|
| 182 |
-
" for line_num, line in enumerate(f):\n",
|
| 183 |
-
" if line_num >= max_lines:\n",
|
| 184 |
-
" break\n",
|
| 185 |
-
" line = line.strip()\n",
|
| 186 |
-
" if line:\n",
|
| 187 |
-
" # Convert hex to integer, then to float\n",
|
| 188 |
-
" hex_val = int(line, 16)\n",
|
| 189 |
-
" float_val = q8_8_to_float(hex_val)\n",
|
| 190 |
-
" parameters.append(float_val)\n",
|
| 191 |
-
" \n",
|
| 192 |
-
" return parameters\n",
|
| 193 |
-
"\n",
|
| 194 |
-
"# Load and display sample parameters\n",
|
| 195 |
-
"print(\"\\n🔍 Sample Parameters:\")\n",
|
| 196 |
-
"for name, filepath in parameter_files.items():\n",
|
| 197 |
-
" params = load_mem_file(filepath, max_lines=5)\n",
|
| 198 |
-
" if params:\n",
|
| 199 |
-
" print(f\"\\n{name.upper()} (first 5 values):\")\n",
|
| 200 |
-
" for i, val in enumerate(params):\n",
|
| 201 |
-
" print(f\" [{i}]: {val:.6f}\")\n",
|
| 202 |
-
" else:\n",
|
| 203 |
-
" print(f\"\\n{name.upper()}: File not found\")\n",
|
| 204 |
-
"\n",
|
| 205 |
-
"# Create sample parameters if files don't exist\n",
|
| 206 |
-
"if not all(os.path.exists(f) for f in parameter_files.values()):\n",
|
| 207 |
-
" print(\"\\n🔧 Creating sample parameter files...\")\n",
|
| 208 |
-
" \n",
|
| 209 |
-
" os.makedirs('parameters', exist_ok=True)\n",
|
| 210 |
-
" \n",
|
| 211 |
-
" # Sample thresholds (16 neurons)\n",
|
| 212 |
-
" with open('parameters/parameters.mem', 'w') as f:\n",
|
| 213 |
-
" for i in range(16):\n",
|
| 214 |
-
" threshold = 0.5 + i * 0.1 # 0.5 to 2.0\n",
|
| 215 |
-
" q8_8 = float_to_q8_8(threshold)\n",
|
| 216 |
-
" f.write(f\"{q8_8:04X}\\n\")\n",
|
| 217 |
-
" \n",
|
| 218 |
-
" # Sample weights (16x8 matrix)\n",
|
| 219 |
-
" with open('parameters/parameters_weights.mem', 'w') as f:\n",
|
| 220 |
-
" for i in range(16):\n",
|
| 221 |
-
" for j in range(8):\n",
|
| 222 |
-
" weight = np.random.randn() * 0.2 # Small random weights\n",
|
| 223 |
-
" q8_8 = float_to_q8_8(weight)\n",
|
| 224 |
-
" f.write(f\"{q8_8:04X}\\n\")\n",
|
| 225 |
-
" \n",
|
| 226 |
-
" # Sample decay constants (16 neurons)\n",
|
| 227 |
-
" with open('parameters/parameters_decay.mem', 'w') as f:\n",
|
| 228 |
-
" for i in range(16):\n",
|
| 229 |
-
" decay = 0.8 + i * 0.01 # 0.8 to 0.95\n",
|
| 230 |
-
" q8_8 = float_to_q8_8(decay)\n",
|
| 231 |
-
" f.write(f\"{q8_8:04X}\\n\")\n",
|
| 232 |
-
" \n",
|
| 233 |
-
" print(\"✅ Sample parameter files created in 'parameters/' directory\")"
|
| 234 |
-
]
|
| 235 |
-
},
|
| 236 |
-
{
|
| 237 |
-
"cell_type": "markdown",
|
| 238 |
-
"metadata": {},
|
| 239 |
-
"source": [
|
| 240 |
-
"## 4. Verilog Implementation"
|
| 241 |
-
]
|
| 242 |
-
},
|
| 243 |
-
{
|
| 244 |
-
"cell_type": "code",
|
| 245 |
-
"execution_count": null,
|
| 246 |
-
"metadata": {},
|
| 247 |
-
"outputs": [],
|
| 248 |
-
"source": [
|
| 249 |
-
"# Generate Verilog code for SNN implementation\n",
|
| 250 |
-
"verilog_code = '''\n",
|
| 251 |
-
"// Spikenaut SNN v2 - FPGA Implementation\n",
|
| 252 |
-
"// Xilinx Artix-7 Basys3 Target\n",
|
| 253 |
-
"// 16-neuron spiking neural network with Q8.8 fixed-point arithmetic\n",
|
| 254 |
-
"\n",
|
| 255 |
-
"module spikenaut_snn_v2 (\n",
|
| 256 |
-
" // Clock and reset\n",
|
| 257 |
-
" input wire clk,\n",
|
| 258 |
-
" input wire rst_n,\n",
|
| 259 |
-
" \n",
|
| 260 |
-
" // Input interface (8 features)\n",
|
| 261 |
-
" input wire [15:0] input_feature_0,\n",
|
| 262 |
-
" input wire [15:0] input_feature_1,\n",
|
| 263 |
-
" input wire [15:0] input_feature_2,\n",
|
| 264 |
-
" input wire [15:0] input_feature_3,\n",
|
| 265 |
-
" input wire [15:0] input_feature_4,\n",
|
| 266 |
-
" input wire [15:0] input_feature_5,\n",
|
| 267 |
-
" input wire [15:0] input_feature_6,\n",
|
| 268 |
-
" input wire [15:0] input_feature_7,\n",
|
| 269 |
-
" \n",
|
| 270 |
-
" // Control signals\n",
|
| 271 |
-
" input wire start_computation,\n",
|
| 272 |
-
" output reg computation_done,\n",
|
| 273 |
-
" \n",
|
| 274 |
-
" // Output interface (3 classes)\n",
|
| 275 |
-
" output reg [15:0] output_class_0,\n",
|
| 276 |
-
" output reg [15:0] output_class_1,\n",
|
| 277 |
-
" output reg [15:0] output_class_2,\n",
|
| 278 |
-
" \n",
|
| 279 |
-
" // Debug signals\n",
|
| 280 |
-
" output reg [3:0] active_neuron,\n",
|
| 281 |
-
" output reg [15:0] membrane_potential\n",
|
| 282 |
-
");\n",
|
| 283 |
-
"\n",
|
| 284 |
-
"// Parameters\n",
|
| 285 |
-
"parameter NEURONS = 16;\n",
|
| 286 |
-
"parameter INPUTS = 8;\n",
|
| 287 |
-
"parameter OUTPUTS = 3;\n",
|
| 288 |
-
"parameter FIXED_POINT_SHIFT = 8;\n",
|
| 289 |
-
"\n",
|
| 290 |
-
"// Memory arrays for parameters\n",
|
| 291 |
-
"reg [15:0] neuron_thresholds [0:NEURONS-1];\n",
|
| 292 |
-
"reg [15:0] synaptic_weights [0:NEURONS-1] [0:INPUTS-1];\n",
|
| 293 |
-
"reg [15:0] decay_constants [0:NEURONS-1];\n",
|
| 294 |
-
"\n",
|
| 295 |
-
"// Internal state\n",
|
| 296 |
-
"reg [15:0] membrane_potentials [0:NEURONS-1];\n",
|
| 297 |
-
"reg spike_outputs [0:NEURONS-1];\n",
|
| 298 |
-
"reg [31:0] weighted_sum;\n",
|
| 299 |
-
"reg [3:0] neuron_index;\n",
|
| 300 |
-
"reg [2:0] input_index;\n",
|
| 301 |
-
"reg [1:0] state;\n",
|
| 302 |
-
"\n",
|
| 303 |
-
"// States\n",
|
| 304 |
-
"localparam IDLE = 2'b00;\n",
|
| 305 |
-
"localparam COMPUTE = 2'b01;\n",
|
| 306 |
-
"localparam OUTPUT = 2'b10;\n",
|
| 307 |
-
"\n",
|
| 308 |
-
"// Input feature array\n",
|
| 309 |
-
"wire [15:0] input_features [0:INPUTS-1];\n",
|
| 310 |
-
"assign input_features[0] = input_feature_0;\n",
|
| 311 |
-
"assign input_features[1] = input_feature_1;\n",
|
| 312 |
-
"assign input_features[2] = input_feature_2;\n",
|
| 313 |
-
"assign input_features[3] = input_feature_3;\n",
|
| 314 |
-
"assign input_features[4] = input_feature_4;\n",
|
| 315 |
-
"assign input_features[5] = input_feature_5;\n",
|
| 316 |
-
"assign input_features[6] = input_feature_6;\n",
|
| 317 |
-
"assign input_features[7] = input_feature_7;\n",
|
| 318 |
-
"\n",
|
| 319 |
-
"// Main state machine\n",
|
| 320 |
-
"always @(posedge clk or negedge rst_n) begin\n",
|
| 321 |
-
" if (!rst_n) begin\n",
|
| 322 |
-
" // Reset state\n",
|
| 323 |
-
" state <= IDLE;\n",
|
| 324 |
-
" computation_done <= 0;\n",
|
| 325 |
-
" neuron_index <= 0;\n",
|
| 326 |
-
" input_index <= 0;\n",
|
| 327 |
-
" \n",
|
| 328 |
-
" // Clear membrane potentials\n",
|
| 329 |
-
" for (integer i = 0; i < NEURONS; i = i + 1) begin\n",
|
| 330 |
-
" membrane_potentials[i] <= 16'h0000;\n",
|
| 331 |
-
" spike_outputs[i] <= 0;\n",
|
| 332 |
-
" end\n",
|
| 333 |
-
" \n",
|
| 334 |
-
" // Clear outputs\n",
|
| 335 |
-
" output_class_0 <= 16'h0000;\n",
|
| 336 |
-
" output_class_1 <= 16'h0000;\n",
|
| 337 |
-
" output_class_2 <= 16'h0000;\n",
|
| 338 |
-
" active_neuron <= 4'h0;\n",
|
| 339 |
-
" membrane_potential <= 16'h0000;\n",
|
| 340 |
-
" \n",
|
| 341 |
-
" end else begin\n",
|
| 342 |
-
" case (state)\n",
|
| 343 |
-
" IDLE: begin\n",
|
| 344 |
-
" computation_done <= 0;\n",
|
| 345 |
-
" if (start_computation) begin\n",
|
| 346 |
-
" state <= COMPUTE;\n",
|
| 347 |
-
" neuron_index <= 0;\n",
|
| 348 |
-
" input_index <= 0;\n",
|
| 349 |
-
" end\n",
|
| 350 |
-
" end\n",
|
| 351 |
-
" \n",
|
| 352 |
-
" COMPUTE: begin\n",
|
| 353 |
-
" // Compute weighted sum for current neuron\n",
|
| 354 |
-
" if (input_index < INPUTS) begin\n",
|
| 355 |
-
" // Multiply-accumulate (Q8.8 fixed-point)\n",
|
| 356 |
-
" weighted_sum <= weighted_sum + \n",
|
| 357 |
-
" ($signed(input_features[input_index]) * $signed(synaptic_weights[neuron_index][input_index]));\n",
|
| 358 |
-
" input_index <= input_index + 1;\n",
|
| 359 |
-
" end else begin\n",
|
| 360 |
-
" // Update membrane potential with decay\n",
|
| 361 |
-
" membrane_potentials[neuron_index] <= \n",
|
| 362 |
-
" ($signed(membrane_potentials[neuron_index] * decay_constants[neuron_index]) >>> FIXED_POINT_SHIFT) + \n",
|
| 363 |
-
" ($signed(weighted_sum) >>> FIXED_POINT_SHIFT);\n",
|
| 364 |
-
" \n",
|
| 365 |
-
" // Generate spike\n",
|
| 366 |
-
" if ($signed(membrane_potentials[neuron_index]) >= $signed(neuron_thresholds[neuron_index])) begin\n",
|
| 367 |
-
" spike_outputs[neuron_index] <= 1;\n",
|
| 368 |
-
" membrane_potentials[neuron_index] <= 16'h0000; // Reset\n",
|
| 369 |
-
" end else begin\n",
|
| 370 |
-
" spike_outputs[neuron_index] <= 0;\n",
|
| 371 |
-
" end\n",
|
| 372 |
-
" \n",
|
| 373 |
-
" // Move to next neuron\n",
|
| 374 |
-
" if (neuron_index < NEURONS - 1) begin\n",
|
| 375 |
-
" neuron_index <= neuron_index + 1;\n",
|
| 376 |
-
" input_index <= 0;\n",
|
| 377 |
-
" weighted_sum <= 32'h00000000;\n",
|
| 378 |
-
" end else begin\n",
|
| 379 |
-
" state <= OUTPUT;\n",
|
| 380 |
-
" end\n",
|
| 381 |
-
" end\n",
|
| 382 |
-
" end\n",
|
| 383 |
-
" \n",
|
| 384 |
-
" OUTPUT: begin\n",
|
| 385 |
-
" // Compute output classes (simple weighted sum of spikes)\n",
|
| 386 |
-
" // Class 0: Neurons 0-5 (Kaspa)\n",
|
| 387 |
-
" // Class 1: Neurons 6-10 (Monero)\n",
|
| 388 |
-
" // Class 2: Neurons 11-15 (Other)\n",
|
| 389 |
-
" \n",
|
| 390 |
-
" output_class_0 <= spike_outputs[0] + spike_outputs[1] + spike_outputs[2] + \n",
|
| 391 |
-
" spike_outputs[3] + spike_outputs[4] + spike_outputs[5];\n",
|
| 392 |
-
" output_class_1 <= spike_outputs[6] + spike_outputs[7] + spike_outputs[8] + \n",
|
| 393 |
-
" spike_outputs[9] + spike_outputs[10];\n",
|
| 394 |
-
" output_class_2 <= spike_outputs[11] + spike_outputs[12] + spike_outputs[13] + \n",
|
| 395 |
-
" spike_outputs[14] + spike_outputs[15];\n",
|
| 396 |
-
" \n",
|
| 397 |
-
" // Update debug signals\n",
|
| 398 |
-
" active_neuron <= neuron_index;\n",
|
| 399 |
-
" membrane_potential <= membrane_potentials[neuron_index];\n",
|
| 400 |
-
" \n",
|
| 401 |
-
" state <= IDLE;\n",
|
| 402 |
-
" computation_done <= 1;\n",
|
| 403 |
-
" end\n",
|
| 404 |
-
" endcase\n",
|
| 405 |
-
" end\n",
|
| 406 |
-
"end\n",
|
| 407 |
-
"\n",
|
| 408 |
-
"// Initialize parameters from memory files (in simulation)\n",
|
| 409 |
-
"initial begin\n",
|
| 410 |
-
" // Load thresholds\n",
|
| 411 |
-
" $readmemh(\"parameters/parameters.mem\", neuron_thresholds);\n",
|
| 412 |
-
" // Load weights\n",
|
| 413 |
-
" $readmemh(\"parameters/parameters_weights.mem\", synaptic_weights);\n",
|
| 414 |
-
" // Load decay constants\n",
|
| 415 |
-
" $readmemh(\"parameters/parameters_decay.mem\", decay_constants);\n",
|
| 416 |
-
"end\n",
|
| 417 |
-
"\n",
|
| 418 |
-
"endmodule\n",
|
| 419 |
-
"'''\n",
|
| 420 |
-
"\n",
|
| 421 |
-
"# Save Verilog code\n",
|
| 422 |
-
"with open('spikenaut_snn_v2.v', 'w') as f:\n",
|
| 423 |
-
" f.write(verilog_code)\n",
|
| 424 |
-
"\n",
|
| 425 |
-
"print(\"✅ Verilog module generated: spikenaut_snn_v2.v\")\n",
|
| 426 |
-
"print(\"\\n📝 Key Features:\")\n",
|
| 427 |
-
"print(\" • 16 neurons, 8 inputs, 3 outputs\")\n",
|
| 428 |
-
"print(\" • Q8.8 fixed-point arithmetic\")\n",
|
| 429 |
-
"print(\" • Parallel weighted sum computation\")\n",
|
| 430 |
-
"print(\" • Configurable thresholds and decay\")\n",
|
| 431 |
-
"print(\" • Debug signals for monitoring\")\n",
|
| 432 |
-
"print(\" • Memory initialization from .mem files\")"
|
| 433 |
-
]
|
| 434 |
-
},
|
| 435 |
-
{
|
| 436 |
-
"cell_type": "markdown",
|
| 437 |
-
"metadata": {},
|
| 438 |
-
"source": [
|
| 439 |
-
"## 5. Testbench for Verification"
|
| 440 |
-
]
|
| 441 |
-
},
|
| 442 |
-
{
|
| 443 |
-
"cell_type": "code",
|
| 444 |
-
"execution_count": null,
|
| 445 |
-
"metadata": {},
|
| 446 |
-
"outputs": [],
|
| 447 |
-
"source": [
|
| 448 |
-
"# Generate testbench for FPGA verification\n",
|
| 449 |
-
"testbench_code = '''\n",
|
| 450 |
-
"// Testbench for Spikenaut SNN v2\n",
|
| 451 |
-
"// Verifies correct operation of the FPGA implementation\n",
|
| 452 |
-
"\n",
|
| 453 |
-
"`timescale 1ns / 1ps\n",
|
| 454 |
-
"\n",
|
| 455 |
-
"module spikenaut_snn_v2_tb;\n",
|
| 456 |
-
"\n",
|
| 457 |
-
"// Test signals\n",
|
| 458 |
-
"reg clk;\n",
|
| 459 |
-
"reg rst_n;\n",
|
| 460 |
-
"reg [15:0] input_features [0:7];\n",
|
| 461 |
-
"reg start_computation;\n",
|
| 462 |
-
"wire computation_done;\n",
|
| 463 |
-
"wire [15:0] output_classes [0:2];\n",
|
| 464 |
-
"wire [3:0] active_neuron;\n",
|
| 465 |
-
"wire [15:0] membrane_potential;\n",
|
| 466 |
-
"\n",
|
| 467 |
-
"// Device Under Test\n",
|
| 468 |
-
"spikenaut_snn_v2 dut (\n",
|
| 469 |
-
" .clk(clk),\n",
|
| 470 |
-
" .rst_n(rst_n),\n",
|
| 471 |
-
" .input_feature_0(input_features[0]),\n",
|
| 472 |
-
" .input_feature_1(input_features[1]),\n",
|
| 473 |
-
" .input_feature_2(input_features[2]),\n",
|
| 474 |
-
" .input_feature_3(input_features[3]),\n",
|
| 475 |
-
" .input_feature_4(input_features[4]),\n",
|
| 476 |
-
" .input_feature_5(input_features[5]),\n",
|
| 477 |
-
" .input_feature_6(input_features[6]),\n",
|
| 478 |
-
" .input_feature_7(input_features[7]),\n",
|
| 479 |
-
" .start_computation(start_computation),\n",
|
| 480 |
-
" .computation_done(computation_done),\n",
|
| 481 |
-
" .output_class_0(output_classes[0]),\n",
|
| 482 |
-
" .output_class_1(output_classes[1]),\n",
|
| 483 |
-
" .output_class_2(output_classes[2]),\n",
|
| 484 |
-
" .active_neuron(active_neuron),\n",
|
| 485 |
-
" .membrane_potential(membrane_potential)\n",
|
| 486 |
-
");\n",
|
| 487 |
-
"\n",
|
| 488 |
-
"// Clock generation (1kHz)\n",
|
| 489 |
-
"initial begin\n",
|
| 490 |
-
" clk = 0;\n",
|
| 491 |
-
" forever #500000 clk = ~clk; // 1ms period\n",
|
| 492 |
-
"end\n",
|
| 493 |
-
"\n",
|
| 494 |
-
"// Test stimulus\n",
|
| 495 |
-
"initial begin\n",
|
| 496 |
-
" // Initialize inputs\n",
|
| 497 |
-
" rst_n = 0;\n",
|
| 498 |
-
" start_computation = 0;\n",
|
| 499 |
-
" for (integer i = 0; i < 8; i = i + 1) begin\n",
|
| 500 |
-
" input_features[i] = 16'h0000;\n",
|
| 501 |
-
" end\n",
|
| 502 |
-
" \n",
|
| 503 |
-
" // Release reset\n",
|
| 504 |
-
" #1000000; // 1ms\n",
|
| 505 |
-
" rst_n = 1;\n",
|
| 506 |
-
" #1000000; // 1ms\n",
|
| 507 |
-
" \n",
|
| 508 |
-
" // Test Case 1: Kaspa telemetry\n",
|
| 509 |
-
" $display(\"Test Case 1: Kaspa telemetry\");\n",
|
| 510 |
-
" input_features[0] = 16'h0066; // hashrate_spike = 1 (0.4 in Q8.8)\n",
|
| 511 |
-
" input_features[1] = 16'h0000; // power_spike = 0\n",
|
| 512 |
-
" input_features[2] = 16'h0000; // temp_spike = 0\n",
|
| 513 |
-
" input_features[3] = 16'h00CC; // qubic_spike = 1 (0.8 in Q8.8)\n",
|
| 514 |
-
" input_features[4] = 16'h0066; // hashrate_normalized = 0.4\n",
|
| 515 |
-
" input_features[5] = 16'h0000; // power_efficiency = 0\n",
|
| 516 |
-
" input_features[6] = 16'h0000; // thermal_efficiency = 0\n",
|
| 517 |
-
" input_features[7] = 16'h00CC; // composite_reward = 0.8\n",
|
| 518 |
-
" \n",
|
| 519 |
-
" start_computation = 1;\n",
|
| 520 |
-
" #2000000; // 2ms\n",
|
| 521 |
-
" start_computation = 0;\n",
|
| 522 |
-
" \n",
|
| 523 |
-
" // Wait for completion\n",
|
| 524 |
-
" wait(computation_done);\n",
|
| 525 |
-
" #1000000; // 1ms\n",
|
| 526 |
-
" \n",
|
| 527 |
-
" $display(\"Results:\");\n",
|
| 528 |
-
" $display(\" Class 0 (Kaspa): %d\", output_classes[0]);\n",
|
| 529 |
-
" $display(\" Class 1 (Monero): %d\", output_classes[1]);\n",
|
| 530 |
-
" $display(\" Class 2 (Other): %d\", output_classes[2]);\n",
|
| 531 |
-
" \n",
|
| 532 |
-
" // Test Case 2: Monero telemetry\n",
|
| 533 |
-
" $display(\"Test Case 2: Monero telemetry\");\n",
|
| 534 |
-
" input_features[0] = 16'h0000; // hashrate_spike = 0\n",
|
| 535 |
-
" input_features[1] = 16'h00CC; // power_spike = 1 (0.8 in Q8.8)\n",
|
| 536 |
-
" input_features[2] = 16'h0066; // temp_spike = 1 (0.4 in Q8.8)\n",
|
| 537 |
-
" input_features[3] = 16'h0000; // qubic_spike = 0\n",
|
| 538 |
-
" input_features[4] = 16'h0033; // hashrate_normalized = 0.2\n",
|
| 539 |
-
" input_features[5] = 16'h0066; // power_efficiency = 0.4\n",
|
| 540 |
-
" input_features[6] = 16'h0033; // thermal_efficiency = 0.2\n",
|
| 541 |
-
" input_features[7] = 16'h0066; // composite_reward = 0.4\n",
|
| 542 |
-
" \n",
|
| 543 |
-
" start_computation = 1;\n",
|
| 544 |
-
" #2000000; // 2ms\n",
|
| 545 |
-
" start_computation = 0;\n",
|
| 546 |
-
" \n",
|
| 547 |
-
" // Wait for completion\n",
|
| 548 |
-
" wait(computation_done);\n",
|
| 549 |
-
" #1000000; // 1ms\n",
|
| 550 |
-
" \n",
|
| 551 |
-
" $display(\"Results:\");\n",
|
| 552 |
-
" $display(\" Class 0 (Kaspa): %d\", output_classes[0]);\n",
|
| 553 |
-
" $display(\" Class 1 (Monero): %d\", output_classes[1]);\n",
|
| 554 |
-
" $display(\" Class 2 (Other): %d\", output_classes[2]);\n",
|
| 555 |
-
" \n",
|
| 556 |
-
" // Test Case 3: No activity\n",
|
| 557 |
-
" $display(\"Test Case 3: No activity\");\n",
|
| 558 |
-
" for (integer i = 0; i < 8; i = i + 1) begin\n",
|
| 559 |
-
" input_features[i] = 16'h0000;\n",
|
| 560 |
-
" end\n",
|
| 561 |
-
" \n",
|
| 562 |
-
" start_computation = 1;\n",
|
| 563 |
-
" #2000000; // 2ms\n",
|
| 564 |
-
" start_computation = 0;\n",
|
| 565 |
-
" \n",
|
| 566 |
-
" // Wait for completion\n",
|
| 567 |
-
" wait(computation_done);\n",
|
| 568 |
-
" #1000000; // 1ms\n",
|
| 569 |
-
" \n",
|
| 570 |
-
" $display(\"Results:\");\n",
|
| 571 |
-
" $display(\" Class 0 (Kaspa): %d\", output_classes[0]);\n",
|
| 572 |
-
" $display(\" Class 1 (Monero): %d\", output_classes[1]);\n",
|
| 573 |
-
" $display(\" Class 2 (Other): %d\", output_classes[2]);\n",
|
| 574 |
-
" \n",
|
| 575 |
-
" // Finish simulation\n",
|
| 576 |
-
" $display(\"All tests completed\");\n",
|
| 577 |
-
" $finish;\n",
|
| 578 |
-
"end\n",
|
| 579 |
-
"\n",
|
| 580 |
-
"// Monitor changes\n",
|
| 581 |
-
"initial begin\n",
|
| 582 |
-
" $monitor(\"Time: %0t | State: %s | Active Neuron: %d | Membrane: %d\",\n",
|
| 583 |
-
" $time, dut.state, active_neuron, membrane_potential);\n",
|
| 584 |
-
"end\n",
|
| 585 |
-
"\n",
|
| 586 |
-
"endmodule\n",
|
| 587 |
-
"'''\n",
|
| 588 |
-
"\n",
|
| 589 |
-
"# Save testbench\n",
|
| 590 |
-
"with open('spikenaut_snn_v2_tb.v', 'w') as f:\n",
|
| 591 |
-
" f.write(testbench_code)\n",
|
| 592 |
-
"\n",
|
| 593 |
-
"print(\"✅ Testbench generated: spikenaut_snn_v2_tb.v\")\n",
|
| 594 |
-
"print(\"\\n🧪 Test Cases:\")\n",
|
| 595 |
-
"print(\" 1. Kaspa telemetry (should activate Class 0)\")\n",
|
| 596 |
-
"print(\" 2. Monero telemetry (should activate Class 1)\")\n",
|
| 597 |
-
"print(\" 3. No activity (baseline test)\")\n",
|
| 598 |
-
"print(\"\\n⚡ Simulation Commands:\")\n",
|
| 599 |
-
"print(\" vlog spikenaut_snn_v2.v spikenaut_snn_v2_tb.v\")\n",
|
| 600 |
-
"print(\" vsim -t ps spikenaut_snn_v2_tb\")\n",
|
| 601 |
-
"print(\" run -all\")"
|
| 602 |
-
]
|
| 603 |
-
},
|
| 604 |
-
{
|
| 605 |
-
"cell_type": "markdown",
|
| 606 |
-
"metadata": {},
|
| 607 |
-
"source": [
|
| 608 |
-
"## 6. Performance Analysis"
|
| 609 |
-
]
|
| 610 |
-
},
|
| 611 |
-
{
|
| 612 |
-
"cell_type": "code",
|
| 613 |
-
"execution_count": null,
|
| 614 |
-
"metadata": {},
|
| 615 |
-
"outputs": [],
|
| 616 |
-
"source": [
|
| 617 |
-
"# Performance estimation\n",
|
| 618 |
-
"performance_metrics = {\n",
|
| 619 |
-
" 'clock_frequency': '1 kHz',\n",
|
| 620 |
-
" 'computation_cycles': 16 * 8 + 16, # 16 neurons * 8 inputs + overhead\n",
|
| 621 |
-
" 'latency_ms': (16 * 8 + 16) / 1000, # At 1kHz clock\n",
|
| 622 |
-
" 'throughput_samples_per_second': 1000 / ((16 * 8 + 16) / 1000),\n",
|
| 623 |
-
" 'power_consumption_mw': 97,\n",
|
| 624 |
-
" 'energy_per_inference_uj': 97 / 1000, # μJ per inference\n",
|
| 625 |
-
" 'logic_utilization_percent': 15, # Estimated\n",
|
| 626 |
-
" 'bram_utilization_percent': 5, # Estimated\n",
|
| 627 |
-
" 'dsp_utilization_percent': 10 # Estimated\n",
|
| 628 |
-
"}\n",
|
| 629 |
-
"\n",
|
| 630 |
-
"print(\"⚡ Performance Analysis:\")\n",
|
| 631 |
-
"for metric, value in performance_metrics.items():\n",
|
| 632 |
-
" print(f\" {metric}: {value}\")\n",
|
| 633 |
-
"\n",
|
| 634 |
-
"# Compare with software implementation\n",
|
| 635 |
-
"software_comparison = {\n",
|
| 636 |
-
" 'CPU (Python)': {'latency_ms': 50, 'power_mw': 15000},\n",
|
| 637 |
-
" 'GPU (CUDA)': {'latency_ms': 5, 'power_mw': 250000},\n",
|
| 638 |
-
" 'FPGA (Spikenaut)': {'latency_ms': performance_metrics['latency_ms'], 'power_mw': performance_metrics['power_consumption_mw']}\n",
|
| 639 |
-
"}\n",
|
| 640 |
-
"\n",
|
| 641 |
-
"print(\"\\n🔄 Performance Comparison:\")\n",
|
| 642 |
-
"for platform, metrics in software_comparison.items():\n",
|
| 643 |
-
" print(f\" {platform}:\")\n",
|
| 644 |
-
" print(f\" Latency: {metrics['latency_ms']} ms\")\n",
|
| 645 |
-
" print(f\" Power: {metrics['power_mw']} mW\")\n",
|
| 646 |
-
" print(f\" Energy: {metrics['latency_ms'] * metrics['power_mw'] / 1000:.2f} μJ\")\n",
|
| 647 |
-
"\n",
|
| 648 |
-
"# Calculate speedup and efficiency\n",
|
| 649 |
-
"fpga_energy = performance_metrics['latency_ms'] * performance_metrics['power_consumption_mw'] / 1000\n",
|
| 650 |
-
"cpu_energy = software_comparison['CPU (Python)']['latency_ms'] * software_comparison['CPU (Python)']['power_mw'] / 1000\n",
|
| 651 |
-
"gpu_energy = software_comparison['GPU (CUDA)']['latency_ms'] * software_comparison['GPU (CUDA)']['power_mw'] / 1000\n",
|
| 652 |
-
"\n",
|
| 653 |
-
"print(f\"\\n🚀 Efficiency Improvements:\")\n",
|
| 654 |
-
"print(f\" FPGA vs CPU: {cpu_energy / fpga_energy:.1f}x more energy efficient\")\n",
|
| 655 |
-
"print(f\" FPGA vs GPU: {gpu_energy / fpga_energy:.1f}x more energy efficient\")\n",
|
| 656 |
-
"print(f\" Latency improvement vs CPU: {software_comparison['CPU (Python)']['latency_ms'] / performance_metrics['latency_ms']:.1f}x\")\n",
|
| 657 |
-
"print(f\" Latency improvement vs GPU: {software_comparison['GPU (CUDA)']['latency_ms'] / performance_metrics['latency_ms']:.1f}x\")\n",
|
| 658 |
-
"\n",
|
| 659 |
-
"# Visualize performance comparison\n",
|
| 660 |
-
"import matplotlib.pyplot as plt\n",
|
| 661 |
-
"\n",
|
| 662 |
-
"platforms = list(software_comparison.keys())\n",
|
| 663 |
-
"latencies = [software_comparison[p]['latency_ms'] for p in platforms]\n",
|
| 664 |
-
"powers = [software_comparison[p]['power_mw'] for p in platforms]\n",
|
| 665 |
-
"energies = [l * p / 1000 for l, p in zip(latencies, powers)]\n",
|
| 666 |
-
"\n",
|
| 667 |
-
"fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(15, 4))\n",
|
| 668 |
-
"\n",
|
| 669 |
-
"# Latency comparison\n",
|
| 670 |
-
"ax1.bar(platforms, latencies, color=['blue', 'red', 'green'])\n",
|
| 671 |
-
"ax1.set_ylabel('Latency (ms)')\n",
|
| 672 |
-
"ax1.set_title('Latency Comparison')\n",
|
| 673 |
-
"ax1.set_yscale('log')\n",
|
| 674 |
-
"\n",
|
| 675 |
-
"# Power comparison\n",
|
| 676 |
-
"ax2.bar(platforms, powers, color=['blue', 'red', 'green'])\n",
|
| 677 |
-
"ax2.set_ylabel('Power (mW)')\n",
|
| 678 |
-
"ax2.set_title('Power Comparison')\n",
|
| 679 |
-
"ax2.set_yscale('log')\n",
|
| 680 |
-
"\n",
|
| 681 |
-
"# Energy comparison\n",
|
| 682 |
-
"ax3.bar(platforms, energies, color=['blue', 'red', 'green'])\n",
|
| 683 |
-
"ax3.set_ylabel('Energy per Inference (μJ)')\n",
|
| 684 |
-
"ax3.set_title('Energy Comparison')\n",
|
| 685 |
-
"ax3.set_yscale('log')\n",
|
| 686 |
-
"\n",
|
| 687 |
-
"plt.tight_layout()\n",
|
| 688 |
-
"plt.show()"
|
| 689 |
-
]
|
| 690 |
-
},
|
| 691 |
-
{
|
| 692 |
-
"cell_type": "markdown",
|
| 693 |
-
"metadata": {},
|
| 694 |
-
"source": [
|
| 695 |
-
"## 7. Deployment Checklist"
|
| 696 |
-
]
|
| 697 |
-
},
|
| 698 |
-
{
|
| 699 |
-
"cell_type": "code",
|
| 700 |
-
"execution_count": null,
|
| 701 |
-
"metadata": {},
|
| 702 |
-
"outputs": [],
|
| 703 |
-
"source": [
|
| 704 |
-
"# Deployment checklist\n",
|
| 705 |
-
"deployment_checklist = {\n",
|
| 706 |
-
" 'Hardware': [\n",
|
| 707 |
-
" '✅ Basys3 FPGA board connected',\n",
|
| 708 |
-
" '✅ USB-JTAG programmer configured',\n",
|
| 709 |
-
" '✅ Power supply stable',\n",
|
| 710 |
-
" '✅ Clock source verified'\n",
|
| 711 |
-
" ],\n",
|
| 712 |
-
" 'Software': [\n",
|
| 713 |
-
" '✅ Vivado installed and licensed',\n",
|
| 714 |
-
" '✅ Verilog testbench passing',\n",
|
| 715 |
-
" '✅ Synthesis completed without errors',\n",
|
| 716 |
-
" '✅ Implementation successful'\n",
|
| 717 |
-
" ],\n",
|
| 718 |
-
" 'Parameters': [\n",
|
| 719 |
-
" '✅ Q8.8 conversion verified',\n",
|
| 720 |
-
" '✅ Parameter files generated',\n",
|
| 721 |
-
" '✅ Memory initialization tested',\n",
|
| 722 |
-
" '✅ Weight loading confirmed'\n",
|
| 723 |
-
" ],\n",
|
| 724 |
-
" 'Verification': [\n",
|
| 725 |
-
" '✅ Simulation results match expectations',\n",
|
| 726 |
-
" '✅ Timing constraints met',\n",
|
| 727 |
-
" '✅ Power analysis within budget',\n",
|
| 728 |
-
" '✅ Resource utilization acceptable'\n",
|
| 729 |
-
" ],\n",
|
| 730 |
-
" 'Integration': [\n",
|
| 731 |
-
" '✅ UART interface configured',\n",
|
| 732 |
-
" '✅ GPIO connections verified',\n",
|
| 733 |
-
" '✅ Real-time telemetry input tested',\n",
|
| 734 |
-
" '✅ Output format validated'\n",
|
| 735 |
-
" ]\n",
|
| 736 |
-
"}\n",
|
| 737 |
-
"\n",
|
| 738 |
-
"print(\"🚀 FPGA Deployment Checklist:\")\n",
|
| 739 |
-
"for category, items in deployment_checklist.items():\n",
|
| 740 |
-
" print(f\"\\n{category}:\")\n",
|
| 741 |
-
" for item in items:\n",
|
| 742 |
-
" print(f\" {item}\")\n",
|
| 743 |
-
"\n",
|
| 744 |
-
"# Generate deployment script\n",
|
| 745 |
-
"deployment_script = '''#!/bin/bash\n",
|
| 746 |
-
"# Spikenaut SNN v2 FPGA Deployment Script\n",
|
| 747 |
-
"\n",
|
| 748 |
-
"echo \"🦁 Spikenaut SNN v2 - FPGA Deployment\"\n",
|
| 749 |
-
"echo \"=========================================\"\n",
|
| 750 |
-
"\n",
|
| 751 |
-
"# Check prerequisites\n",
|
| 752 |
-
"echo \"📋 Checking prerequisites...\"\n",
|
| 753 |
-
"if ! command -v vivado &> /dev/null; then\n",
|
| 754 |
-
" echo \"❌ Vivado not found. Please install Xilinx Vivado.\"\n",
|
| 755 |
-
" exit 1\n",
|
| 756 |
-
"fi\n",
|
| 757 |
-
"echo \"✅ Vivado found\"\n",
|
| 758 |
-
"\n",
|
| 759 |
-
"# Check parameter files\n",
|
| 760 |
-
"echo \"📂 Checking parameter files...\"\n",
|
| 761 |
-
"for file in parameters/parameters.mem parameters/parameters_weights.mem parameters/parameters_decay.mem; do\n",
|
| 762 |
-
" if [ ! -f \"$file\" ]; then\n",
|
| 763 |
-
" echo \"❌ Missing file: $file\"\n",
|
| 764 |
-
" exit 1\n",
|
| 765 |
-
" fi\n",
|
| 766 |
-
"done\n",
|
| 767 |
-
"echo \"✅ All parameter files found\"\n",
|
| 768 |
-
"\n",
|
| 769 |
-
"# Run synthesis\n",
|
| 770 |
-
"echo \"🔨 Running synthesis...\"\n",
|
| 771 |
-
"vivado -mode batch -source synthesis_script.tcl\n",
|
| 772 |
-
"if [ $? -ne 0 ]; then\n",
|
| 773 |
-
" echo \"❌ Synthesis failed\"\n",
|
| 774 |
-
" exit 1\n",
|
| 775 |
-
"fi\n",
|
| 776 |
-
"echo \"✅ Synthesis completed\"\n",
|
| 777 |
-
"\n",
|
| 778 |
-
"# Run implementation\n",
|
| 779 |
-
"echo \"🏗️ Running implementation...\"\n",
|
| 780 |
-
"vivado -mode batch -source implementation_script.tcl\n",
|
| 781 |
-
"if [ $? -ne 0 ]; then\n",
|
| 782 |
-
" echo \"❌ Implementation failed\"\n",
|
| 783 |
-
" exit 1\n",
|
| 784 |
-
"fi\n",
|
| 785 |
-
"echo \"✅ Implementation completed\"\n",
|
| 786 |
-
"\n",
|
| 787 |
-
"# Generate bitstream\n",
|
| 788 |
-
"echo \"💾 Generating bitstream...\"\n",
|
| 789 |
-
"vivado -mode batch -source bitstream_script.tcl\n",
|
| 790 |
-
"if [ $? -ne 0 ]; then\n",
|
| 791 |
-
" echo \"❌ Bitstream generation failed\"\n",
|
| 792 |
-
" exit 1\n",
|
| 793 |
-
"fi\n",
|
| 794 |
-
"echo \"✅ Bitstream generated\"\n",
|
| 795 |
-
"\n",
|
| 796 |
-
"# Program FPGA\n",
|
| 797 |
-
"echo \"🔌 Programming FPGA...\"\n",
|
| 798 |
-
"vivado -mode batch -source program_script.tcl\n",
|
| 799 |
-
"if [ $? -ne 0 ]; then\n",
|
| 800 |
-
" echo \"❌ FPGA programming failed\"\n",
|
| 801 |
-
" exit 1\n",
|
| 802 |
-
"fi\n",
|
| 803 |
-
"echo \"✅ FPGA programmed successfully\"\n",
|
| 804 |
-
"\n",
|
| 805 |
-
"echo \"🎉 Deployment completed successfully!\"\n",
|
| 806 |
-
"echo \"🦁 Spikenaut SNN v2 is running on FPGA!\"\n",
|
| 807 |
-
"'''\n",
|
| 808 |
-
"\n",
|
| 809 |
-
"# Save deployment script\n",
|
| 810 |
-
"with open('deploy_fpga.sh', 'w') as f:\n",
|
| 811 |
-
" f.write(deployment_script)\n",
|
| 812 |
-
"\n",
|
| 813 |
-
"print(f\"\\n📜 Deployment script generated: deploy_fpga.sh\")\n",
|
| 814 |
-
"print(f\"\\n🔧 Usage:\")\n",
|
| 815 |
-
"print(f\" chmod +x deploy_fpga.sh\")\n",
|
| 816 |
-
"print(f\" ./deploy_fpga.sh\")"
|
| 817 |
-
]
|
| 818 |
-
},
|
| 819 |
-
{
|
| 820 |
-
"cell_type": "markdown",
|
| 821 |
-
"metadata": {},
|
| 822 |
-
"source": [
|
| 823 |
-
"## 8. Troubleshooting Guide"
|
| 824 |
-
]
|
| 825 |
-
},
|
| 826 |
-
{
|
| 827 |
-
"cell_type": "code",
|
| 828 |
-
"execution_count": null,
|
| 829 |
-
"metadata": {},
|
| 830 |
-
"outputs": [],
|
| 831 |
-
"source": [
|
| 832 |
-
"# Common issues and solutions\n",
|
| 833 |
-
"troubleshooting_guide = {\n",
|
| 834 |
-
" 'Synthesis Errors': {\n",
|
| 835 |
-
" 'Problem': 'Verilog synthesis fails',\n",
|
| 836 |
-
" 'Solutions': [\n",
|
| 837 |
-
" 'Check for syntax errors in Verilog code',\n",
|
| 838 |
-
" 'Verify all signals are properly declared',\n",
|
| 839 |
-
" 'Ensure memory initialization syntax is correct',\n",
|
| 840 |
-
" 'Check clock domain crossing issues'\n",
|
| 841 |
-
" ]\n",
|
| 842 |
-
" },\n",
|
| 843 |
-
" 'Timing Violations': {\n",
|
| 844 |
-
" 'Problem': 'Timing constraints not met',\n",
|
| 845 |
-
" 'Solutions': [\n",
|
| 846 |
-
" 'Reduce clock frequency',\n",
|
| 847 |
-
" 'Add pipeline stages',\n",
|
| 848 |
-
" 'Optimize critical paths',\n",
|
| 849 |
-
" 'Use DSP slices for multiplication'\n",
|
| 850 |
-
" ]\n",
|
| 851 |
-
" },\n",
|
| 852 |
-
" 'Memory Issues': {\n",
|
| 853 |
-
" 'Problem': 'Parameter loading fails',\n",
|
| 854 |
-
" 'Solutions': [\n",
|
| 855 |
-
" 'Verify .mem file format (hex values)',\n",
|
| 856 |
-
" 'Check file paths in $readmemh',\n",
|
| 857 |
-
" 'Ensure memory dimensions match',\n",
|
| 858 |
-
" 'Test with known good values'\n",
|
| 859 |
-
" ]\n",
|
| 860 |
-
" },\n",
|
| 861 |
-
" 'Incorrect Results': {\n",
|
| 862 |
-
" 'Problem': 'FPGA output differs from simulation',\n",
|
| 863 |
-
" 'Solutions': [\n",
|
| 864 |
-
" 'Check Q8.8 precision handling',\n",
|
| 865 |
-
" 'Verify signed arithmetic',\n",
|
| 866 |
-
" 'Test with known input patterns',\n",
|
| 867 |
-
" 'Compare intermediate values'\n",
|
| 868 |
-
" ]\n",
|
| 869 |
-
" },\n",
|
| 870 |
-
" 'Power Issues': {\n",
|
| 871 |
-
" 'Problem': 'Power consumption too high',\n",
|
| 872 |
-
" 'Solutions': [\n",
|
| 873 |
-
" 'Reduce clock frequency',\n",
|
| 874 |
-
" 'Optimize logic utilization',\n",
|
| 875 |
-
" 'Use clock gating',\n",
|
| 876 |
-
" 'Enable power saving modes'\n",
|
| 877 |
-
" ]\n",
|
| 878 |
-
" }\n",
|
| 879 |
-
"}\n",
|
| 880 |
-
"\n",
|
| 881 |
-
"print(\"🔧 Troubleshooting Guide:\")\n",
|
| 882 |
-
"for issue, details in troubleshooting_guide.items():\n",
|
| 883 |
-
" print(f\"\\n{issue}:\")\n",
|
| 884 |
-
" print(f\" Problem: {details['Problem']}\")\n",
|
| 885 |
-
" print(f\" Solutions:\")\n",
|
| 886 |
-
" for solution in details['Solutions']:\n",
|
| 887 |
-
" print(f\" • {solution}\")\n",
|
| 888 |
-
"\n",
|
| 889 |
-
"# Debug commands\n",
|
| 890 |
-
"debug_commands = '''\n",
|
| 891 |
-
"# Vivado debug commands\n",
|
| 892 |
-
"# Open implemented design\n",
|
| 893 |
-
"open_project spikenaut_snn_v2.xpr\n",
|
| 894 |
-
"open_run impl_1\n",
|
| 895 |
-
"\n",
|
| 896 |
-
"# Check timing\n",
|
| 897 |
-
"report_timing_summary\n",
|
| 898 |
-
"report_timing -delay_type max -max_paths 10\n",
|
| 899 |
-
"\n",
|
| 900 |
-
"# Check utilization\n",
|
| 901 |
-
"report_utilization\n",
|
| 902 |
-
"report_utilization -hierarchical\n",
|
| 903 |
-
"\n",
|
| 904 |
-
"# Check power\n",
|
| 905 |
-
"report_power\n",
|
| 906 |
-
"\n",
|
| 907 |
-
"# Debug signals (add to constraints)\n",
|
| 908 |
-
"# In XDC file:\n",
|
| 909 |
-
"# set_property DEBUG_TRUE [get_nets neuron_*]\n",
|
| 910 |
-
"# set_property DEBUG_TRUE [get_nets membrane_*]\n",
|
| 911 |
-
"\n",
|
| 912 |
-
"# Simulation debug\n",
|
| 913 |
-
"# Add to testbench:\n",
|
| 914 |
-
"# $display(\"Neuron %d: membrane=%d, spike=%d\", i, membrane[i], spike[i]);\n",
|
| 915 |
-
"# $strobe(\"Time=%0t, State=%s\", $time, state);\n",
|
| 916 |
-
"'''\n",
|
| 917 |
-
"\n",
|
| 918 |
-
"print(f\"\\n💻 Debug Commands:\")\n",
|
| 919 |
-
"print(debug_commands)"
|
| 920 |
-
]
|
| 921 |
-
},
|
| 922 |
-
{
|
| 923 |
-
"cell_type": "markdown",
|
| 924 |
-
"metadata": {},
|
| 925 |
-
"source": [
|
| 926 |
-
"## 9. Summary and Next Steps"
|
| 927 |
-
]
|
| 928 |
-
},
|
| 929 |
-
{
|
| 930 |
-
"cell_type": "code",
|
| 931 |
-
"execution_count": null,
|
| 932 |
-
"metadata": {},
|
| 933 |
-
"outputs": [],
|
| 934 |
-
"source": [
|
| 935 |
-
"print(\"🔧 Spikenaut SNN v2 FPGA Deployment Guide Complete!\")\n",
|
| 936 |
-
"print(\"=\" * 60)\n",
|
| 937 |
-
"print()\n",
|
| 938 |
-
"print(\"🎯 What You've Accomplished:\")\n",
|
| 939 |
-
"print(\" ✅ Understood Q8.8 fixed-point format\")\n",
|
| 940 |
-
"print(\" ✅ Generated Verilog implementation\")\n",
|
| 941 |
-
"print(\" ✅ Created comprehensive testbench\")\n",
|
| 942 |
-
"print(\" ✅ Analyzed performance characteristics\")\n",
|
| 943 |
-
"print(\" ✅ Prepared deployment checklist\")\n",
|
| 944 |
-
"print(\" ✅ Generated troubleshooting guide\")\n",
|
| 945 |
-
"print()\n",
|
| 946 |
-
"print(\"📁 Generated Files:\")\n",
|
| 947 |
-
"files_generated = [\n",
|
| 948 |
-
" 'spikenaut_snn_v2.v - Main Verilog module',\n",
|
| 949 |
-
" 'spikenaut_snn_v2_tb.v - Testbench',\n",
|
| 950 |
-
" 'deploy_fpga.sh - Deployment script',\n",
|
| 951 |
-
" 'parameters/ - FPGA parameter files'\n",
|
| 952 |
-
"]\n",
|
| 953 |
-
"for file in files_generated:\n",
|
| 954 |
-
" print(f\" 📄 {file}\")\n",
|
| 955 |
-
"print()\n",
|
| 956 |
-
"print(\"⚡ Key Performance Metrics:\")\n",
|
| 957 |
-
"print(f\" • Latency: {performance_metrics['latency_ms']:.1f} ms\")\n",
|
| 958 |
-
"print(f\" • Power: {performance_metrics['power_consumption_mw']} mW\")\n",
|
| 959 |
-
"print(f\" • Energy: {fpga_energy:.2f} μJ per inference\")\n",
|
| 960 |
-
"print(f\" • Efficiency: {cpu_energy / fpga_energy:.1f}x vs CPU\")\n",
|
| 961 |
-
"print()\n",
|
| 962 |
-
"print(\"🚀 Next Steps:\")\n",
|
| 963 |
-
"next_steps = [\n",
|
| 964 |
-
" \"1. Run synthesis and implementation in Vivado\",\n",
|
| 965 |
-
" \"2. Verify timing constraints are met\",\n",
|
| 966 |
-
" \"3. Program Basys3 FPGA with generated bitstream\",\n",
|
| 967 |
-
" \"4. Test with real telemetry data\",\n",
|
| 968 |
-
" \"5. Integrate with Rust telemetry system\",\n",
|
| 969 |
-
" \"6. Optimize for lower power consumption\",\n",
|
| 970 |
-
" \"7. Scale to larger neural networks\"\n",
|
| 971 |
-
"]\n",
|
| 972 |
-
"for step in next_steps:\n",
|
| 973 |
-
" print(f\" {step}\")\n",
|
| 974 |
-
"print()\n",
|
| 975 |
-
"print(\"🔗 Related Resources:\")\n",
|
| 976 |
-
"resources = [\n",
|
| 977 |
-
" \"• Dataset: https://huggingface.co/datasets/rmems/Spikenaut-SNN-v2-Telemetry-Data-Weights-Parameters\",\n",
|
| 978 |
-
" \"• Main repo: https://github.com/rmems/Eagle-Lander\",\n",
|
| 979 |
-
" \"• Basys3 documentation: https://reference.digilentinc.com/learn/programmable-logic/tutorials/basys-3-getting-started-with-xilinx-fpga-design-tools\",\n",
|
| 980 |
-
" \"• Vivado documentation: https://docs.xilinx.com/v/u/en-US/ug953-vivado-tutorial\"\n",
|
| 981 |
-
"]\n",
|
| 982 |
-
"for resource in resources:\n",
|
| 983 |
-
" print(f\" {resource}\")\n",
|
| 984 |
-
"print()\n",
|
| 985 |
-
"print(\"🦁 Happy FPGA deployment!\")\n",
|
| 986 |
-
"print(\"Your Spikenaut SNN v2 is ready for neuromorphic computing on hardware!\")"
|
| 987 |
-
]
|
| 988 |
-
}
|
| 989 |
-
],
|
| 990 |
-
"metadata": {
|
| 991 |
-
"kernelspec": {
|
| 992 |
-
"display_name": "Python 3",
|
| 993 |
-
"language": "python",
|
| 994 |
-
"name": "python3"
|
| 995 |
-
},
|
| 996 |
-
"language_info": {
|
| 997 |
-
"codemirror_mode": {
|
| 998 |
-
"name": "ipython",
|
| 999 |
-
"version": 3
|
| 1000 |
-
},
|
| 1001 |
-
"file_extension": ".py",
|
| 1002 |
-
"name": "python",
|
| 1003 |
-
"nbconvert_exporter": "python",
|
| 1004 |
-
"pygments_lexer": "ipython3",
|
| 1005 |
-
"version": "3.8.5"
|
| 1006 |
-
}
|
| 1007 |
-
},
|
| 1008 |
-
"nbformat": 4,
|
| 1009 |
-
"nbformat_minor": 4
|
| 1010 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
dataset/examples/snn_training_demo.ipynb
DELETED
|
@@ -1,871 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"cells": [
|
| 3 |
-
{
|
| 4 |
-
"cell_type": "markdown",
|
| 5 |
-
"metadata": {},
|
| 6 |
-
"source": [
|
| 7 |
-
"# 🧠 Spikenaut SNN v2 - Training Demo\n",
|
| 8 |
-
"\n",
|
| 9 |
-
"Complete training pipeline for Spiking Neural Networks using the Spikenaut dataset.\n",
|
| 10 |
-
"\n",
|
| 11 |
-
"## What you'll learn:\n",
|
| 12 |
-
"- Setting up SNN architecture\n",
|
| 13 |
-
"- Training with spike-encoded data\n",
|
| 14 |
-
"- E-prop learning implementation\n",
|
| 15 |
-
"- Performance evaluation\n",
|
| 16 |
-
"- Model export for FPGA"
|
| 17 |
-
]
|
| 18 |
-
},
|
| 19 |
-
{
|
| 20 |
-
"cell_type": "markdown",
|
| 21 |
-
"metadata": {},
|
| 22 |
-
"source": [
|
| 23 |
-
"## 1. Setup and Dependencies"
|
| 24 |
-
]
|
| 25 |
-
},
|
| 26 |
-
{
|
| 27 |
-
"cell_type": "code",
|
| 28 |
-
"execution_count": null,
|
| 29 |
-
"metadata": {},
|
| 30 |
-
"outputs": [],
|
| 31 |
-
"source": [
|
| 32 |
-
"# Install required packages\n",
|
| 33 |
-
"!pip install torch torchvision datasets numpy matplotlib seaborn tqdm -q\n",
|
| 34 |
-
"\n",
|
| 35 |
-
"import torch\n",
|
| 36 |
-
"import torch.nn as nn\n",
|
| 37 |
-
"import torch.nn.functional as F\n",
|
| 38 |
-
"from torch.utils.data import DataLoader, TensorDataset\n",
|
| 39 |
-
"import numpy as np\n",
|
| 40 |
-
"import matplotlib.pyplot as plt\n",
|
| 41 |
-
"import seaborn as sns\n",
|
| 42 |
-
"from datasets import load_dataset\n",
|
| 43 |
-
"from tqdm import tqdm\n",
|
| 44 |
-
"import json\n",
|
| 45 |
-
"import time\n",
|
| 46 |
-
"from datetime import datetime\n",
|
| 47 |
-
"\n",
|
| 48 |
-
"print(f\"PyTorch version: {torch.__version__}\")\n",
|
| 49 |
-
"print(f\"CUDA available: {torch.cuda.is_available()}\")\n",
|
| 50 |
-
"if torch.cuda.is_available():\n",
|
| 51 |
-
" print(f\"CUDA device: {torch.cuda.get_device_name()}\")\n",
|
| 52 |
-
"\n",
|
| 53 |
-
"# Set device\n",
|
| 54 |
-
"device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n",
|
| 55 |
-
"print(f\"Using device: {device}\")"
|
| 56 |
-
]
|
| 57 |
-
},
|
| 58 |
-
{
|
| 59 |
-
"cell_type": "markdown",
|
| 60 |
-
"metadata": {},
|
| 61 |
-
"source": [
|
| 62 |
-
"## 2. Load and Prepare Data"
|
| 63 |
-
]
|
| 64 |
-
},
|
| 65 |
-
{
|
| 66 |
-
"cell_type": "code",
|
| 67 |
-
"execution_count": null,
|
| 68 |
-
"metadata": {},
|
| 69 |
-
"outputs": [],
|
| 70 |
-
"source": [
|
| 71 |
-
"# Load the Spikenaut dataset\n",
|
| 72 |
-
"print(\"🦁 Loading Spikenaut SNN v2 dataset...\")\n",
|
| 73 |
-
"ds = load_dataset(\"rmems/Spikenaut-SNN-v2-Telemetry-Data-Weights-Parameters\")\n",
|
| 74 |
-
"\n",
|
| 75 |
-
"# Extract spike-encoded features\n",
|
| 76 |
-
"def extract_spikes(dataset_split):\n",
|
| 77 |
-
" \"\"\"Extract spike features from dataset\"\"\"\n",
|
| 78 |
-
" spike_cols = [\n",
|
| 79 |
-
" 'spike_hashrate', 'spike_power', 'spike_temp', 'spike_qubic',\n",
|
| 80 |
-
" 'hashrate_normalized', 'power_efficiency', 'thermal_efficiency',\n",
|
| 81 |
-
" 'composite_reward'\n",
|
| 82 |
-
" ]\n",
|
| 83 |
-
" \n",
|
| 84 |
-
" # Filter available columns\n",
|
| 85 |
-
" available_cols = [col for col in spike_cols if col in dataset_split.column_names]\n",
|
| 86 |
-
" print(f\"Available spike columns: {available_cols}\")\n",
|
| 87 |
-
" \n",
|
| 88 |
-
" # Convert to tensors\n",
|
| 89 |
-
" data = []\n",
|
| 90 |
-
" labels = []\n",
|
| 91 |
-
" \n",
|
| 92 |
-
" for i in range(len(dataset_split)):\n",
|
| 93 |
-
" sample = dataset_split[i]\n",
|
| 94 |
-
" \n",
|
| 95 |
-
" # Create feature vector\n",
|
| 96 |
-
" features = []\n",
|
| 97 |
-
" for col in available_cols:\n",
|
| 98 |
-
" if 'spike_' in col:\n",
|
| 99 |
-
" features.append(float(sample[col])) # Binary spikes\n",
|
| 100 |
-
" else:\n",
|
| 101 |
-
" features.append(float(sample[col])) # Continuous features\n",
|
| 102 |
-
" \n",
|
| 103 |
-
" # Create label (blockchain type)\n",
|
| 104 |
-
" blockchain = sample['blockchain']\n",
|
| 105 |
-
" if blockchain == 'kaspa':\n",
|
| 106 |
-
" label = 0\n",
|
| 107 |
-
" elif blockchain == 'monero':\n",
|
| 108 |
-
" label = 1\n",
|
| 109 |
-
" else:\n",
|
| 110 |
-
" label = 2\n",
|
| 111 |
-
" \n",
|
| 112 |
-
" data.append(features)\n",
|
| 113 |
-
" labels.append(label)\n",
|
| 114 |
-
" \n",
|
| 115 |
-
" return torch.tensor(data, dtype=torch.float32), torch.tensor(labels, dtype=torch.long)\n",
|
| 116 |
-
"\n",
|
| 117 |
-
"# Prepare training data\n",
|
| 118 |
-
"X_train, y_train = extract_spikes(ds['train'])\n",
|
| 119 |
-
"X_val, y_val = extract_spikes(ds['validation'])\n",
|
| 120 |
-
"X_test, y_test = extract_spikes(ds['test'])\n",
|
| 121 |
-
"\n",
|
| 122 |
-
"print(f\"📊 Data shapes:\")\n",
|
| 123 |
-
"print(f\" Train: {X_train.shape}, Labels: {y_train.shape}\")\n",
|
| 124 |
-
"print(f\" Val: {X_val.shape}, Labels: {y_val.shape}\")\n",
|
| 125 |
-
"print(f\" Test: {X_test.shape}, Labels: {y_test.shape}\")\n",
|
| 126 |
-
"\n",
|
| 127 |
-
"# Create DataLoaders\n",
|
| 128 |
-
"batch_size = 2 # Small batch due to small dataset\n",
|
| 129 |
-
"\n",
|
| 130 |
-
"train_dataset = TensorDataset(X_train, y_train)\n",
|
| 131 |
-
"val_dataset = TensorDataset(X_val, y_val)\n",
|
| 132 |
-
"test_dataset = TensorDataset(X_test, y_test)\n",
|
| 133 |
-
"\n",
|
| 134 |
-
"train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\n",
|
| 135 |
-
"val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False)\n",
|
| 136 |
-
"test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)\n",
|
| 137 |
-
"\n",
|
| 138 |
-
"print(f\"🔄 DataLoaders created with batch size {batch_size}\")"
|
| 139 |
-
]
|
| 140 |
-
},
|
| 141 |
-
{
|
| 142 |
-
"cell_type": "markdown",
|
| 143 |
-
"metadata": {},
|
| 144 |
-
"source": [
|
| 145 |
-
"## 3. SNN Architecture"
|
| 146 |
-
]
|
| 147 |
-
},
|
| 148 |
-
{
|
| 149 |
-
"cell_type": "code",
|
| 150 |
-
"execution_count": null,
|
| 151 |
-
"metadata": {},
|
| 152 |
-
"outputs": [],
|
| 153 |
-
"source": [
|
| 154 |
-
"class LIFNeuron(nn.Module):\n",
|
| 155 |
-
" \"\"\"Leaky Integrate-and-Fire Neuron\"\"\"\n",
|
| 156 |
-
" \n",
|
| 157 |
-
" def __init__(self, input_size, hidden_size, threshold=1.0, decay=0.9):\n",
|
| 158 |
-
" super(LIFNeuron, self).__init__()\n",
|
| 159 |
-
" self.input_size = input_size\n",
|
| 160 |
-
" self.hidden_size = hidden_size\n",
|
| 161 |
-
" self.threshold = threshold\n",
|
| 162 |
-
" self.decay = decay\n",
|
| 163 |
-
" \n",
|
| 164 |
-
" # Weight matrix\n",
|
| 165 |
-
" self.weight = nn.Parameter(torch.randn(input_size, hidden_size) * 0.1)\n",
|
| 166 |
-
" \n",
|
| 167 |
-
" # Membrane potential\n",
|
| 168 |
-
" self.register_buffer('membrane', torch.zeros(1, hidden_size))\n",
|
| 169 |
-
" \n",
|
| 170 |
-
" def forward(self, x):\n",
|
| 171 |
-
" batch_size = x.size(0)\n",
|
| 172 |
-
" \n",
|
| 173 |
-
" # Initialize membrane potential for new batch\n",
|
| 174 |
-
" if self.membrane.size(0) != batch_size:\n",
|
| 175 |
-
" self.membrane = torch.zeros(batch_size, self.hidden_size, device=x.device)\n",
|
| 176 |
-
" \n",
|
| 177 |
-
" # Input current\n",
|
| 178 |
-
" current = torch.matmul(x, self.weight)\n",
|
| 179 |
-
" \n",
|
| 180 |
-
" # Update membrane potential\n",
|
| 181 |
-
" self.membrane = self.membrane * self.decay + current\n",
|
| 182 |
-
" \n",
|
| 183 |
-
" # Generate spikes\n",
|
| 184 |
-
" spikes = (self.membrane > self.threshold).float()\n",
|
| 185 |
-
" \n",
|
| 186 |
-
" # Reset membrane potential after spike\n",
|
| 187 |
-
" self.membrane = self.membrane * (1 - spikes)\n",
|
| 188 |
-
" \n",
|
| 189 |
-
" return spikes, self.membrane\n",
|
| 190 |
-
"\n",
|
| 191 |
-
"class SpikenautSNN(nn.Module):\n",
|
| 192 |
-
" \"\"\"Spikenaut SNN v2 Architecture\"\"\"\n",
|
| 193 |
-
" \n",
|
| 194 |
-
" def __init__(self, input_size, hidden_size, num_classes, time_steps=10):\n",
|
| 195 |
-
" super(SpikenautSNN, self).__init__()\n",
|
| 196 |
-
" self.input_size = input_size\n",
|
| 197 |
-
" self.hidden_size = hidden_size\n",
|
| 198 |
-
" self.num_classes = num_classes\n",
|
| 199 |
-
" self.time_steps = time_steps\n",
|
| 200 |
-
" \n",
|
| 201 |
-
" # Layers\n",
|
| 202 |
-
" self.hidden_layer = LIFNeuron(input_size, hidden_size, threshold=0.5, decay=0.9)\n",
|
| 203 |
-
" self.output_layer = nn.Linear(hidden_size, num_classes)\n",
|
| 204 |
-
" \n",
|
| 205 |
-
" # For E-prop learning\n",
|
| 206 |
-
" self.register_buffer('eligibility_trace', torch.zeros(hidden_size, input_size))\n",
|
| 207 |
-
" \n",
|
| 208 |
-
" def forward(self, x):\n",
|
| 209 |
-
" batch_size = x.size(0)\n",
|
| 210 |
-
" \n",
|
| 211 |
-
" # Store outputs for each time step\n",
|
| 212 |
-
" spike_outputs = []\n",
|
| 213 |
-
" membrane_outputs = []\n",
|
| 214 |
-
" \n",
|
| 215 |
-
" # Repeat input for time steps (simulation of temporal processing)\n",
|
| 216 |
-
" for t in range(self.time_steps):\n",
|
| 217 |
-
" # Add small noise to simulate temporal variation\n",
|
| 218 |
-
" x_t = x + torch.randn_like(x) * 0.01\n",
|
| 219 |
-
" \n",
|
| 220 |
-
" # Forward through hidden layer\n",
|
| 221 |
-
" hidden_spikes, hidden_membrane = self.hidden_layer(x_t)\n",
|
| 222 |
-
" \n",
|
| 223 |
-
" # Output layer (readout)\n",
|
| 224 |
-
" output = self.output_layer(hidden_spikes)\n",
|
| 225 |
-
" \n",
|
| 226 |
-
" spike_outputs.append(output)\n",
|
| 227 |
-
" membrane_outputs.append(hidden_membrane)\n",
|
| 228 |
-
" \n",
|
| 229 |
-
" # Average over time steps\n",
|
| 230 |
-
" final_output = torch.mean(torch.stack(spike_outputs), dim=0)\n",
|
| 231 |
-
" \n",
|
| 232 |
-
" return final_output, torch.stack(membrane_outputs)\n",
|
| 233 |
-
" \n",
|
| 234 |
-
" def reset_state(self):\n",
|
| 235 |
-
" \"\"\"Reset membrane potentials and traces\"\"\"\n",
|
| 236 |
-
" self.hidden_layer.membrane.zero_()\n",
|
| 237 |
-
" self.eligibility_trace.zero_()\n",
|
| 238 |
-
"\n",
|
| 239 |
-
"# Initialize SNN\n",
|
| 240 |
-
"input_size = X_train.shape[1]\n",
|
| 241 |
-
"hidden_size = 16 # Matching Spikenaut architecture\n",
|
| 242 |
-
"num_classes = 3 # kaspa, monero, other\n",
|
| 243 |
-
"time_steps = 10\n",
|
| 244 |
-
"\n",
|
| 245 |
-
"snn = SpikenautSNN(input_size, hidden_size, num_classes, time_steps).to(device)\n",
|
| 246 |
-
"\n",
|
| 247 |
-
"print(f\"🧠 SNN Architecture:\")\n",
|
| 248 |
-
"print(f\" Input size: {input_size}\")\n",
|
| 249 |
-
"print(f\" Hidden neurons: {hidden_size}\")\n",
|
| 250 |
-
"print(f\" Output classes: {num_classes}\")\n",
|
| 251 |
-
"print(f\" Time steps: {time_steps}\")\n",
|
| 252 |
-
"print(f\" Total parameters: {sum(p.numel() for p in snn.parameters())}\")"
|
| 253 |
-
]
|
| 254 |
-
},
|
| 255 |
-
{
|
| 256 |
-
"cell_type": "markdown",
|
| 257 |
-
"metadata": {},
|
| 258 |
-
"source": [
|
| 259 |
-
"## 4. E-prop Learning Implementation"
|
| 260 |
-
]
|
| 261 |
-
},
|
| 262 |
-
{
|
| 263 |
-
"cell_type": "code",
|
| 264 |
-
"execution_count": null,
|
| 265 |
-
"metadata": {},
|
| 266 |
-
"outputs": [],
|
| 267 |
-
"source": [
|
| 268 |
-
"class EPropLoss(nn.Module):\n",
|
| 269 |
-
" \"\"\"E-prop loss function with surrogate gradients\"\"\"\n",
|
| 270 |
-
" \n",
|
| 271 |
-
" def __init__(self, surrogate='fast_sigmoid'):\n",
|
| 272 |
-
" super(EPropLoss, self).__init__()\n",
|
| 273 |
-
" self.surrogate = surrogate\n",
|
| 274 |
-
" \n",
|
| 275 |
-
" def fast_sigmoid(self, x):\n",
|
| 276 |
-
" \"\"\"Fast sigmoid surrogate gradient\"\"\"\n",
|
| 277 |
-
" return 1.0 / (1.0 + torch.abs(x))\n",
|
| 278 |
-
" \n",
|
| 279 |
-
" def forward(self, output, target, membrane_potentials):\n",
|
| 280 |
-
" \"\"\"Compute E-prop loss\"\"\"\n",
|
| 281 |
-
" # Standard cross-entropy loss\n",
|
| 282 |
-
" ce_loss = F.cross_entropy(output, target)\n",
|
| 283 |
-
" \n",
|
| 284 |
-
" # Add regularization term for spike activity\n",
|
| 285 |
-
" spike_activity = torch.mean(membrane_potentials ** 2)\n",
|
| 286 |
-
" regularization = 0.01 * spike_activity\n",
|
| 287 |
-
" \n",
|
| 288 |
-
" total_loss = ce_loss + regularization\n",
|
| 289 |
-
" \n",
|
| 290 |
-
" return total_loss, ce_loss, regularization\n",
|
| 291 |
-
"\n",
|
| 292 |
-
"class EPropOptimizer:\n",
|
| 293 |
-
" \"\"\"Custom optimizer for E-prop learning\"\"\"\n",
|
| 294 |
-
" \n",
|
| 295 |
-
" def __init__(self, model, lr=0.001, beta=0.9):\n",
|
| 296 |
-
" self.model = model\n",
|
| 297 |
-
" self.lr = lr\n",
|
| 298 |
-
" self.beta = beta\n",
|
| 299 |
-
" \n",
|
| 300 |
-
" # Initialize momentum\n",
|
| 301 |
-
" self.momentum = {}\n",
|
| 302 |
-
" for name, param in model.named_parameters():\n",
|
| 303 |
-
" self.momentum[name] = torch.zeros_like(param)\n",
|
| 304 |
-
" \n",
|
| 305 |
-
" def step(self, loss):\n",
|
| 306 |
-
" \"\"\"Perform E-prop optimization step\"\"\"\n",
|
| 307 |
-
" # Backward pass\n",
|
| 308 |
-
" loss.backward()\n",
|
| 309 |
-
" \n",
|
| 310 |
-
" # Update parameters with momentum\n",
|
| 311 |
-
" for name, param in self.model.named_parameters():\n",
|
| 312 |
-
" if param.grad is not None:\n",
|
| 313 |
-
" # Update momentum\n",
|
| 314 |
-
" self.momentum[name] = self.beta * self.momentum[name] + (1 - self.beta) * param.grad\n",
|
| 315 |
-
" \n",
|
| 316 |
-
" # Update parameters\n",
|
| 317 |
-
" param.data = param.data - self.lr * self.momentum[name]\n",
|
| 318 |
-
" \n",
|
| 319 |
-
" # Clip gradients\n",
|
| 320 |
-
" param.grad.data.clamp_(-1.0, 1.0)\n",
|
| 321 |
-
" \n",
|
| 322 |
-
" # Clear gradients\n",
|
| 323 |
-
" self.model.zero_grad()\n",
|
| 324 |
-
" \n",
|
| 325 |
-
" def zero_grad(self):\n",
|
| 326 |
-
" \"\"\"Zero gradients\"\"\"\n",
|
| 327 |
-
" self.model.zero_grad()\n",
|
| 328 |
-
"\n",
|
| 329 |
-
"# Initialize loss and optimizer\n",
|
| 330 |
-
"criterion = EPropLoss()\n",
|
| 331 |
-
"optimizer = EPropOptimizer(snn, lr=0.01, beta=0.9)\n",
|
| 332 |
-
"\n",
|
| 333 |
-
"print(\"🔬 E-prop learning components initialized\")\n",
|
| 334 |
-
"print(f\" Loss function: E-prop with fast sigmoid surrogate\")\n",
|
| 335 |
-
"print(f\" Optimizer: Custom E-prop with momentum (lr=0.01, beta=0.9)\")"
|
| 336 |
-
]
|
| 337 |
-
},
|
| 338 |
-
{
|
| 339 |
-
"cell_type": "markdown",
|
| 340 |
-
"metadata": {},
|
| 341 |
-
"source": [
|
| 342 |
-
"## 5. Training Loop"
|
| 343 |
-
]
|
| 344 |
-
},
|
| 345 |
-
{
|
| 346 |
-
"cell_type": "code",
|
| 347 |
-
"execution_count": null,
|
| 348 |
-
"metadata": {},
|
| 349 |
-
"outputs": [],
|
| 350 |
-
"source": [
|
| 351 |
-
"def train_epoch(model, train_loader, criterion, optimizer, device):\n",
|
| 352 |
-
" \"\"\"Train for one epoch\"\"\"\n",
|
| 353 |
-
" model.train()\n",
|
| 354 |
-
" total_loss = 0\n",
|
| 355 |
-
" total_ce_loss = 0\n",
|
| 356 |
-
" total_reg_loss = 0\n",
|
| 357 |
-
" correct = 0\n",
|
| 358 |
-
" total = 0\n",
|
| 359 |
-
" \n",
|
| 360 |
-
" for batch_idx, (data, target) in enumerate(train_loader):\n",
|
| 361 |
-
" data, target = data.to(device), target.to(device)\n",
|
| 362 |
-
" \n",
|
| 363 |
-
" # Reset SNN state\n",
|
| 364 |
-
" model.reset_state()\n",
|
| 365 |
-
" \n",
|
| 366 |
-
" # Forward pass\n",
|
| 367 |
-
" output, membrane_potentials = model(data)\n",
|
| 368 |
-
" \n",
|
| 369 |
-
" # Compute loss\n",
|
| 370 |
-
" loss, ce_loss, reg_loss = criterion(output, target, membrane_potentials)\n",
|
| 371 |
-
" \n",
|
| 372 |
-
" # Backward pass\n",
|
| 373 |
-
" optimizer.step(loss)\n",
|
| 374 |
-
" \n",
|
| 375 |
-
" # Statistics\n",
|
| 376 |
-
" total_loss += loss.item()\n",
|
| 377 |
-
" total_ce_loss += ce_loss.item()\n",
|
| 378 |
-
" total_reg_loss += reg_loss.item()\n",
|
| 379 |
-
" \n",
|
| 380 |
-
" # Accuracy\n",
|
| 381 |
-
" pred = output.argmax(dim=1)\n",
|
| 382 |
-
" correct += pred.eq(target).sum().item()\n",
|
| 383 |
-
" total += target.size(0)\n",
|
| 384 |
-
" \n",
|
| 385 |
-
" avg_loss = total_loss / len(train_loader)\n",
|
| 386 |
-
" avg_ce_loss = total_ce_loss / len(train_loader)\n",
|
| 387 |
-
" avg_reg_loss = total_reg_loss / len(train_loader)\n",
|
| 388 |
-
" accuracy = 100. * correct / total\n",
|
| 389 |
-
" \n",
|
| 390 |
-
" return avg_loss, avg_ce_loss, avg_reg_loss, accuracy\n",
|
| 391 |
-
"\n",
|
| 392 |
-
"def validate(model, val_loader, criterion, device):\n",
|
| 393 |
-
" \"\"\"Validate the model\"\"\"\n",
|
| 394 |
-
" model.eval()\n",
|
| 395 |
-
" total_loss = 0\n",
|
| 396 |
-
" correct = 0\n",
|
| 397 |
-
" total = 0\n",
|
| 398 |
-
" \n",
|
| 399 |
-
" with torch.no_grad():\n",
|
| 400 |
-
" for data, target in val_loader:\n",
|
| 401 |
-
" data, target = data.to(device), target.to(device)\n",
|
| 402 |
-
" \n",
|
| 403 |
-
" # Reset SNN state\n",
|
| 404 |
-
" model.reset_state()\n",
|
| 405 |
-
" \n",
|
| 406 |
-
" # Forward pass\n",
|
| 407 |
-
" output, membrane_potentials = model(data)\n",
|
| 408 |
-
" \n",
|
| 409 |
-
" # Compute loss\n",
|
| 410 |
-
" loss, ce_loss, reg_loss = criterion(output, target, membrane_potentials)\n",
|
| 411 |
-
" \n",
|
| 412 |
-
" total_loss += loss.item()\n",
|
| 413 |
-
" \n",
|
| 414 |
-
" # Accuracy\n",
|
| 415 |
-
" pred = output.argmax(dim=1)\n",
|
| 416 |
-
" correct += pred.eq(target).sum().item()\n",
|
| 417 |
-
" total += target.size(0)\n",
|
| 418 |
-
" \n",
|
| 419 |
-
" avg_loss = total_loss / len(val_loader)\n",
|
| 420 |
-
" accuracy = 100. * correct / total\n",
|
| 421 |
-
" \n",
|
| 422 |
-
" return avg_loss, accuracy\n",
|
| 423 |
-
"\n",
|
| 424 |
-
"print(\"🏃 Training functions defined\")"
|
| 425 |
-
]
|
| 426 |
-
},
|
| 427 |
-
{
|
| 428 |
-
"cell_type": "markdown",
|
| 429 |
-
"metadata": {},
|
| 430 |
-
"source": [
|
| 431 |
-
"## 6. Run Training"
|
| 432 |
-
]
|
| 433 |
-
},
|
| 434 |
-
{
|
| 435 |
-
"cell_type": "code",
|
| 436 |
-
"execution_count": null,
|
| 437 |
-
"metadata": {},
|
| 438 |
-
"outputs": [],
|
| 439 |
-
"source": [
|
| 440 |
-
"# Training configuration\n",
|
| 441 |
-
"num_epochs = 50\n",
|
| 442 |
-
"print(f\"🚀 Starting training for {num_epochs} epochs...\")\n",
|
| 443 |
-
"print(f\"📊 Training samples: {len(train_loader.dataset)}\")\n",
|
| 444 |
-
"print(f\"📊 Validation samples: {len(val_loader.dataset)}\")\n",
|
| 445 |
-
"print()\n",
|
| 446 |
-
"\n",
|
| 447 |
-
"# Training history\n",
|
| 448 |
-
"train_losses = []\n",
|
| 449 |
-
"train_accuracies = []\n",
|
| 450 |
-
"val_losses = []\n",
|
| 451 |
-
"val_accuracies = []\n",
|
| 452 |
-
"\n",
|
| 453 |
-
"best_val_acc = 0\n",
|
| 454 |
-
"best_model_state = None\n",
|
| 455 |
-
"\n",
|
| 456 |
-
"start_time = time.time()\n",
|
| 457 |
-
"\n",
|
| 458 |
-
"for epoch in range(num_epochs):\n",
|
| 459 |
-
" # Train\n",
|
| 460 |
-
" train_loss, train_ce_loss, train_reg_loss, train_acc = train_epoch(\n",
|
| 461 |
-
" snn, train_loader, criterion, optimizer, device\n",
|
| 462 |
-
" )\n",
|
| 463 |
-
" \n",
|
| 464 |
-
" # Validate\n",
|
| 465 |
-
" val_loss, val_acc = validate(snn, val_loader, criterion, device)\n",
|
| 466 |
-
" \n",
|
| 467 |
-
" # Record history\n",
|
| 468 |
-
" train_losses.append(train_loss)\n",
|
| 469 |
-
" train_accuracies.append(train_acc)\n",
|
| 470 |
-
" val_losses.append(val_loss)\n",
|
| 471 |
-
" val_accuracies.append(val_acc)\n",
|
| 472 |
-
" \n",
|
| 473 |
-
" # Save best model\n",
|
| 474 |
-
" if val_acc > best_val_acc:\n",
|
| 475 |
-
" best_val_acc = val_acc\n",
|
| 476 |
-
" best_model_state = snn.state_dict().copy()\n",
|
| 477 |
-
" \n",
|
| 478 |
-
" # Print progress\n",
|
| 479 |
-
" if epoch % 10 == 0 or epoch == num_epochs - 1:\n",
|
| 480 |
-
" print(f\"Epoch {epoch:3d}/{num_epochs:3d} | \"\n",
|
| 481 |
-
" f\"Train Loss: {train_loss:.4f} (CE: {train_ce_loss:.4f}, Reg: {train_reg_loss:.4f}) | \"\n",
|
| 482 |
-
" f\"Train Acc: {train_acc:5.2f}% | \"\n",
|
| 483 |
-
" f\"Val Loss: {val_loss:.4f} | \"\n",
|
| 484 |
-
" f\"Val Acc: {val_acc:5.2f}% | \"\n",
|
| 485 |
-
" f\"Best Val Acc: {best_val_acc:5.2f}%\")\n",
|
| 486 |
-
"\n",
|
| 487 |
-
"training_time = time.time() - start_time\n",
|
| 488 |
-
"print(f\"\\n✅ Training completed in {training_time:.2f} seconds\")\n",
|
| 489 |
-
"print(f\"🏆 Best validation accuracy: {best_val_acc:.2f}%\")\n",
|
| 490 |
-
"\n",
|
| 491 |
-
"# Load best model\n",
|
| 492 |
-
"snn.load_state_dict(best_model_state)\n",
|
| 493 |
-
"print(\"📦 Best model loaded\")"
|
| 494 |
-
]
|
| 495 |
-
},
|
| 496 |
-
{
|
| 497 |
-
"cell_type": "markdown",
|
| 498 |
-
"metadata": {},
|
| 499 |
-
"source": [
|
| 500 |
-
"## 7. Training Visualization"
|
| 501 |
-
]
|
| 502 |
-
},
|
| 503 |
-
{
|
| 504 |
-
"cell_type": "code",
|
| 505 |
-
"execution_count": null,
|
| 506 |
-
"metadata": {},
|
| 507 |
-
"outputs": [],
|
| 508 |
-
"source": [
|
| 509 |
-
"# Create training visualization\n",
|
| 510 |
-
"fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))\n",
|
| 511 |
-
"\n",
|
| 512 |
-
"# Loss curves\n",
|
| 513 |
-
"ax1.plot(train_losses, label='Train Loss', color='blue', alpha=0.8)\n",
|
| 514 |
-
"ax1.plot(val_losses, label='Validation Loss', color='red', alpha=0.8)\n",
|
| 515 |
-
"ax1.set_xlabel('Epoch')\n",
|
| 516 |
-
"ax1.set_ylabel('Loss')\n",
|
| 517 |
-
"ax1.set_title('🦁 Spikenaut SNN v2 - Training Loss')\n",
|
| 518 |
-
"ax1.legend()\n",
|
| 519 |
-
"ax1.grid(True, alpha=0.3)\n",
|
| 520 |
-
"\n",
|
| 521 |
-
"# Accuracy curves\n",
|
| 522 |
-
"ax2.plot(train_accuracies, label='Train Accuracy', color='blue', alpha=0.8)\n",
|
| 523 |
-
"ax2.plot(val_accuracies, label='Validation Accuracy', color='red', alpha=0.8)\n",
|
| 524 |
-
"ax2.set_xlabel('Epoch')\n",
|
| 525 |
-
"ax2.set_ylabel('Accuracy (%)')\n",
|
| 526 |
-
"ax2.set_title('🦁 Spikenaut SNN v2 - Training Accuracy')\n",
|
| 527 |
-
"ax2.legend()\n",
|
| 528 |
-
"ax2.grid(True, alpha=0.3)\n",
|
| 529 |
-
"\n",
|
| 530 |
-
"plt.tight_layout()\n",
|
| 531 |
-
"plt.show()\n",
|
| 532 |
-
"\n",
|
| 533 |
-
"# Print final statistics\n",
|
| 534 |
-
"print(f\"📈 Final Training Statistics:\")\n",
|
| 535 |
-
"print(f\" Final train loss: {train_losses[-1]:.4f}\")\n",
|
| 536 |
-
"print(f\" Final train accuracy: {train_accuracies[-1]:.2f}%\")\n",
|
| 537 |
-
"print(f\" Final validation loss: {val_losses[-1]:.4f}\")\n",
|
| 538 |
-
"print(f\" Final validation accuracy: {val_accuracies[-1]:.2f}%\")\n",
|
| 539 |
-
"print(f\" Best validation accuracy: {best_val_acc:.2f}%\")\n",
|
| 540 |
-
"print(f\" Training time: {training_time:.2f} seconds\")\n",
|
| 541 |
-
"print(f\" Samples per second: {len(train_loader.dataset) * num_epochs / training_time:.1f}\")"
|
| 542 |
-
]
|
| 543 |
-
},
|
| 544 |
-
{
|
| 545 |
-
"cell_type": "markdown",
|
| 546 |
-
"metadata": {},
|
| 547 |
-
"source": [
|
| 548 |
-
"## 8. Model Evaluation"
|
| 549 |
-
]
|
| 550 |
-
},
|
| 551 |
-
{
|
| 552 |
-
"cell_type": "code",
|
| 553 |
-
"execution_count": null,
|
| 554 |
-
"metadata": {},
|
| 555 |
-
"outputs": [],
|
| 556 |
-
"source": [
|
| 557 |
-
"# Test the model\n",
|
| 558 |
-
"print(\"🧪 Testing the trained SNN...\")\n",
|
| 559 |
-
"\n",
|
| 560 |
-
"test_loss, test_acc = validate(snn, test_loader, criterion, device)\n",
|
| 561 |
-
"print(f\"Test Loss: {test_loss:.4f}\")\n",
|
| 562 |
-
"print(f\"Test Accuracy: {test_acc:.2f}%\")\n",
|
| 563 |
-
"\n",
|
| 564 |
-
"# Detailed evaluation\n",
|
| 565 |
-
"snn.eval()\n",
|
| 566 |
-
"all_predictions = []\n",
|
| 567 |
-
"all_targets = []\n",
|
| 568 |
-
"all_outputs = []\n",
|
| 569 |
-
"\n",
|
| 570 |
-
"with torch.no_grad():\n",
|
| 571 |
-
" for data, target in test_loader:\n",
|
| 572 |
-
" data, target = data.to(device), target.to(device)\n",
|
| 573 |
-
" \n",
|
| 574 |
-
" # Reset SNN state\n",
|
| 575 |
-
" snn.reset_state()\n",
|
| 576 |
-
" \n",
|
| 577 |
-
" # Forward pass\n",
|
| 578 |
-
" output, membrane_potentials = snn(data)\n",
|
| 579 |
-
" \n",
|
| 580 |
-
" # Store results\n",
|
| 581 |
-
" pred = output.argmax(dim=1)\n",
|
| 582 |
-
" all_predictions.extend(pred.cpu().numpy())\n",
|
| 583 |
-
" all_targets.extend(target.cpu().numpy())\n",
|
| 584 |
-
" all_outputs.extend(output.cpu().numpy())\n",
|
| 585 |
-
"\n",
|
| 586 |
-
"# Convert to numpy arrays\n",
|
| 587 |
-
"all_predictions = np.array(all_predictions)\n",
|
| 588 |
-
"all_targets = np.array(all_targets)\n",
|
| 589 |
-
"all_outputs = np.array(all_outputs)\n",
|
| 590 |
-
"\n",
|
| 591 |
-
"# Class names\n",
|
| 592 |
-
"class_names = ['kaspa', 'monero', 'other']\n",
|
| 593 |
-
"\n",
|
| 594 |
-
"# Print classification report\n",
|
| 595 |
-
"from sklearn.metrics import classification_report, confusion_matrix\n",
|
| 596 |
-
"print(\"\\n📊 Classification Report:\")\n",
|
| 597 |
-
"print(classification_report(all_targets, all_predictions, target_names=class_names))\n",
|
| 598 |
-
"\n",
|
| 599 |
-
"# Confusion matrix\n",
|
| 600 |
-
"cm = confusion_matrix(all_targets, all_predictions)\n",
|
| 601 |
-
"plt.figure(figsize=(8, 6))\n",
|
| 602 |
-
"sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', \n",
|
| 603 |
-
" xticklabels=class_names, yticklabels=class_names)\n",
|
| 604 |
-
"plt.title('🦁 Spikenaut SNN v2 - Confusion Matrix')\n",
|
| 605 |
-
"plt.xlabel('Predicted')\n",
|
| 606 |
-
"plt.ylabel('Actual')\n",
|
| 607 |
-
"plt.tight_layout()\n",
|
| 608 |
-
"plt.show()"
|
| 609 |
-
]
|
| 610 |
-
},
|
| 611 |
-
{
|
| 612 |
-
"cell_type": "markdown",
|
| 613 |
-
"metadata": {},
|
| 614 |
-
"source": [
|
| 615 |
-
"## 9. Model Export for FPGA"
|
| 616 |
-
]
|
| 617 |
-
},
|
| 618 |
-
{
|
| 619 |
-
"cell_type": "code",
|
| 620 |
-
"execution_count": null,
|
| 621 |
-
"metadata": {},
|
| 622 |
-
"outputs": [],
|
| 623 |
-
"source": [
|
| 624 |
-
"def export_to_safetensors(model, filepath):\n",
|
| 625 |
-
" \"\"\"Export model to safetensors format\"\"\"\n",
|
| 626 |
-
" try:\n",
|
| 627 |
-
" from safetensors.torch import save_file\n",
|
| 628 |
-
" \n",
|
| 629 |
-
" # Extract parameters\n",
|
| 630 |
-
" state_dict = model.state_dict()\n",
|
| 631 |
-
" \n",
|
| 632 |
-
" # Save to safetensors\n",
|
| 633 |
-
" save_file(state_dict, filepath)\n",
|
| 634 |
-
" print(f\"✅ Model exported to {filepath}\")\n",
|
| 635 |
-
" \n",
|
| 636 |
-
" except ImportError:\n",
|
| 637 |
-
" print(\"⚠️ safetensors not installed. Install with: pip install safetensors\")\n",
|
| 638 |
-
" # Fallback to PyTorch format\n",
|
| 639 |
-
" torch.save(model.state_dict(), filepath.replace('.safetensors', '.pth'))\n",
|
| 640 |
-
" print(f\"✅ Model exported to {filepath.replace('.safetensors', '.pth')} (PyTorch format)\")\n",
|
| 641 |
-
"\n",
|
| 642 |
-
"def export_to_q8_8_format(model, filepath_prefix):\n",
|
| 643 |
-
" \"\"\"Export model weights to Q8.8 format for FPGA\"\"\"\n",
|
| 644 |
-
" \n",
|
| 645 |
-
" def float_to_q8_8(value):\n",
|
| 646 |
-
" \"\"\"Convert float to Q8.8 fixed-point\"\"\"\n",
|
| 647 |
-
" # Clamp to Q8.8 range\n",
|
| 648 |
-
" value = np.clip(value, -128, 127.996)\n",
|
| 649 |
-
" # Convert to fixed-point\n",
|
| 650 |
-
" q8_8 = int(value * 256)\n",
|
| 651 |
-
" return q8_8\n",
|
| 652 |
-
" \n",
|
| 653 |
-
" # Extract weights\n",
|
| 654 |
-
" hidden_weights = model.hidden_layer.weight.data.cpu().numpy()\n",
|
| 655 |
-
" output_weights = model.output_layer.weight.data.cpu().numpy()\n",
|
| 656 |
-
" \n",
|
| 657 |
-
" # Convert to Q8.8\n",
|
| 658 |
-
" hidden_weights_q8_8 = [[float_to_q8_8(w) for w in row] for row in hidden_weights]\n",
|
| 659 |
-
" output_weights_q8_8 = [[float_to_q8_8(w) for w in row] for row in output_weights]\n",
|
| 660 |
-
" \n",
|
| 661 |
-
" # Write to .mem files\n",
|
| 662 |
-
" with open(f\"{filepath_prefix}_hidden_weights.mem\", 'w') as f:\n",
|
| 663 |
-
" for row in hidden_weights_q8_8:\n",
|
| 664 |
-
" for weight in row:\n",
|
| 665 |
-
" f.write(f\"{weight:04X}\\n\")\n",
|
| 666 |
-
" \n",
|
| 667 |
-
" with open(f\"{filepath_prefix}_output_weights.mem\", 'w') as f:\n",
|
| 668 |
-
" for row in output_weights_q8_8:\n",
|
| 669 |
-
" for weight in row:\n",
|
| 670 |
-
" f.write(f\"{weight:04X}\\n\")\n",
|
| 671 |
-
" \n",
|
| 672 |
-
" # Thresholds and decay parameters\n",
|
| 673 |
-
" with open(f\"{filepath_prefix}_parameters.mem\", 'w') as f:\n",
|
| 674 |
-
" # Hidden layer threshold\n",
|
| 675 |
-
" threshold_q8_8 = float_to_q8_8(model.hidden_layer.threshold)\n",
|
| 676 |
-
" f.write(f\"{threshold_q8_8:04X}\\n\")\n",
|
| 677 |
-
" \n",
|
| 678 |
-
" # Hidden layer decay\n",
|
| 679 |
-
" decay_q8_8 = float_to_q8_8(model.hidden_layer.decay)\n",
|
| 680 |
-
" f.write(f\"{decay_q8_8:04X}\\n\")\n",
|
| 681 |
-
" \n",
|
| 682 |
-
" # Output layer parameters (if needed)\n",
|
| 683 |
-
" for i in range(16): # Pad to 16 parameters\n",
|
| 684 |
-
" f.write(f\"0000\\n\")\n",
|
| 685 |
-
" \n",
|
| 686 |
-
" print(f\"✅ Weights exported to Q8.8 format:\")\n",
|
| 687 |
-
" print(f\" - {filepath_prefix}_hidden_weights.mem\")\n",
|
| 688 |
-
" print(f\" - {filepath_prefix}_output_weights.mem\")\n",
|
| 689 |
-
" print(f\" - {filepath_prefix}_parameters.mem\")\n",
|
| 690 |
-
"\n",
|
| 691 |
-
"# Export model\n",
|
| 692 |
-
"print(\"📤 Exporting trained model...\")\n",
|
| 693 |
-
"\n",
|
| 694 |
-
"# Export to safetensors\n",
|
| 695 |
-
"export_to_safetensors(snn, 'spikenaut_snn_v2.safetensors')\n",
|
| 696 |
-
"\n",
|
| 697 |
-
"# Export to Q8.8 for FPGA\n",
|
| 698 |
-
"export_to_q8_8_format(snn, 'spikenaut_snn_v2')\n",
|
| 699 |
-
"\n",
|
| 700 |
-
"# Save training metadata\n",
|
| 701 |
-
"metadata = {\n",
|
| 702 |
-
" 'model_architecture': 'SpikenautSNN',\n",
|
| 703 |
-
" 'input_size': input_size,\n",
|
| 704 |
-
" 'hidden_size': hidden_size,\n",
|
| 705 |
-
" 'num_classes': num_classes,\n",
|
| 706 |
-
" 'time_steps': time_steps,\n",
|
| 707 |
-
" 'training_accuracy': float(train_accuracies[-1]),\n",
|
| 708 |
-
" 'validation_accuracy': float(best_val_acc),\n",
|
| 709 |
-
" 'test_accuracy': float(test_acc),\n",
|
| 710 |
-
" 'training_time_seconds': training_time,\n",
|
| 711 |
-
" 'num_epochs': num_epochs,\n",
|
| 712 |
-
" 'dataset': 'Spikenaut-SNN-v2-Telemetry-Data-Weights-Parameters',\n",
|
| 713 |
-
" 'export_timestamp': datetime.now().isoformat()\n",
|
| 714 |
-
"}\n",
|
| 715 |
-
"\n",
|
| 716 |
-
"with open('spikenaut_snn_v2_metadata.json', 'w') as f:\n",
|
| 717 |
-
" json.dump(metadata, f, indent=2)\n",
|
| 718 |
-
"\n",
|
| 719 |
-
"print(f\"✅ Training metadata saved to spikenaut_snn_v2_metadata.json\")"
|
| 720 |
-
]
|
| 721 |
-
},
|
| 722 |
-
{
|
| 723 |
-
"cell_type": "markdown",
|
| 724 |
-
"metadata": {},
|
| 725 |
-
"source": [
|
| 726 |
-
"## 10. Inference Demo"
|
| 727 |
-
]
|
| 728 |
-
},
|
| 729 |
-
{
|
| 730 |
-
"cell_type": "code",
|
| 731 |
-
"execution_count": null,
|
| 732 |
-
"metadata": {},
|
| 733 |
-
"outputs": [],
|
| 734 |
-
"source": [
|
| 735 |
-
"def predict_blockchain(sample_features, model, device):\n",
|
| 736 |
-
" \"\"\"Predict blockchain type from telemetry features\"\"\"\n",
|
| 737 |
-
" model.eval()\n",
|
| 738 |
-
" \n",
|
| 739 |
-
" with torch.no_grad():\n",
|
| 740 |
-
" # Convert to tensor\n",
|
| 741 |
-
" if isinstance(sample_features, (list, np.ndarray)):\n",
|
| 742 |
-
" sample_tensor = torch.tensor(sample_features, dtype=torch.float32).unsqueeze(0)\n",
|
| 743 |
-
" else:\n",
|
| 744 |
-
" sample_tensor = sample_features.unsqueeze(0)\n",
|
| 745 |
-
" \n",
|
| 746 |
-
" sample_tensor = sample_tensor.to(device)\n",
|
| 747 |
-
" \n",
|
| 748 |
-
" # Reset SNN state\n",
|
| 749 |
-
" model.reset_state()\n",
|
| 750 |
-
" \n",
|
| 751 |
-
" # Forward pass\n",
|
| 752 |
-
" output, membrane_potentials = model(sample_tensor)\n",
|
| 753 |
-
" \n",
|
| 754 |
-
" # Get prediction\n",
|
| 755 |
-
" probabilities = F.softmax(output, dim=1)\n",
|
| 756 |
-
" predicted_class = torch.argmax(probabilities, dim=1).item()\n",
|
| 757 |
-
" confidence = probabilities[0][predicted_class].item()\n",
|
| 758 |
-
" \n",
|
| 759 |
-
" return {\n",
|
| 760 |
-
" 'predicted_class': predicted_class,\n",
|
| 761 |
-
" 'predicted_blockchain': class_names[predicted_class],\n",
|
| 762 |
-
" 'confidence': confidence,\n",
|
| 763 |
-
" 'probabilities': {\n",
|
| 764 |
-
" class_names[i]: prob.item() \n",
|
| 765 |
-
" for i, prob in enumerate(probabilities[0])\n",
|
| 766 |
-
" },\n",
|
| 767 |
-
" 'membrane_potentials': membrane_potentials[0].cpu().numpy()\n",
|
| 768 |
-
" }\n",
|
| 769 |
-
"\n",
|
| 770 |
-
"# Test with sample data\n",
|
| 771 |
-
"print(\"🔮 Running inference demo...\")\n",
|
| 772 |
-
"\n",
|
| 773 |
-
"# Test with a few samples\n",
|
| 774 |
-
"for i in range(min(3, len(X_test))):\n",
|
| 775 |
-
" sample_features = X_test[i]\n",
|
| 776 |
-
" true_label = y_test[i].item()\n",
|
| 777 |
-
" true_blockchain = class_names[true_label]\n",
|
| 778 |
-
" \n",
|
| 779 |
-
" result = predict_blockchain(sample_features, snn, device)\n",
|
| 780 |
-
" \n",
|
| 781 |
-
" print(f\"\\nSample {i+1}:\")\n",
|
| 782 |
-
" print(f\" True blockchain: {true_blockchain}\")\n",
|
| 783 |
-
" print(f\" Predicted: {result['predicted_blockchain']}\")\n",
|
| 784 |
-
" print(f\" Confidence: {result['confidence']:.3f}\")\n",
|
| 785 |
-
" print(f\" Probabilities: {result['probabilities']}\")\n",
|
| 786 |
-
" print(f\" Correct: {'✅' if result['predicted_class'] == true_label else '❌'}\")\n",
|
| 787 |
-
"\n",
|
| 788 |
-
"# Visualize membrane potentials\n",
|
| 789 |
-
"if len(result['membrane_potentials']) > 0:\n",
|
| 790 |
-
" plt.figure(figsize=(10, 4))\n",
|
| 791 |
-
" plt.plot(result['membrane_potentials'], marker='o', linestyle='-')\n",
|
| 792 |
-
" plt.title('🧠 Membrane Potentials During Inference')\n",
|
| 793 |
-
" plt.xlabel('Hidden Neuron Index')\n",
|
| 794 |
-
" plt.ylabel('Membrane Potential')\n",
|
| 795 |
-
" plt.grid(True, alpha=0.3)\n",
|
| 796 |
-
" plt.show()"
|
| 797 |
-
]
|
| 798 |
-
},
|
| 799 |
-
{
|
| 800 |
-
"cell_type": "markdown",
|
| 801 |
-
"metadata": {},
|
| 802 |
-
"source": [
|
| 803 |
-
"## 11. Summary and Next Steps"
|
| 804 |
-
]
|
| 805 |
-
},
|
| 806 |
-
{
|
| 807 |
-
"cell_type": "code",
|
| 808 |
-
"execution_count": null,
|
| 809 |
-
"metadata": {},
|
| 810 |
-
"outputs": [],
|
| 811 |
-
"source": [
|
| 812 |
-
"print(\"🦁 Spikenaut SNN v2 Training Demo Complete!\")\n",
|
| 813 |
-
"print(\"=\" * 50)\n",
|
| 814 |
-
"print()\n",
|
| 815 |
-
"print(\"🏆 Results Summary:\")\n",
|
| 816 |
-
"print(f\" ✅ Trained {hidden_size}-neuron SNN for {num_epochs} epochs\")\n",
|
| 817 |
-
"print(f\" ✅ Final test accuracy: {test_acc:.2f}%\")\n",
|
| 818 |
-
"print(f\" ✅ Training time: {training_time:.2f} seconds\")\n",
|
| 819 |
-
"print(f\" ✅ Model exported to multiple formats\")\n",
|
| 820 |
-
"print()\n",
|
| 821 |
-
"print(\"📁 Generated Files:\")\n",
|
| 822 |
-
"print(\" 📄 spikenaut_snn_v2.safetensors - PyTorch model\")\n",
|
| 823 |
-
"print(\" 📄 spikenaut_snn_v2_hidden_weights.mem - FPGA weights\")\n",
|
| 824 |
-
"print(\" 📄 spikenaut_snn_v2_output_weights.mem - FPGA weights\")\n",
|
| 825 |
-
"print(\" 📄 spikenaut_snn_v2_parameters.mem - FPGA parameters\")\n",
|
| 826 |
-
"print(\" 📄 spikenaut_snn_v2_metadata.json - Training metadata\")\n",
|
| 827 |
-
"print()\n",
|
| 828 |
-
"print(\"🔬 Key Insights:\")\n",
|
| 829 |
-
"print(f\" • E-prop learning achieved {best_val_acc:.1f}% validation accuracy\")\n",
|
| 830 |
-
"print(f\" • SNN processes {input_size} features through {hidden_size} hidden neurons\")\n",
|
| 831 |
-
"print(f\" • Temporal processing over {time_steps} time steps\")\n",
|
| 832 |
-
"print(f\" • Q8.8 format ready for FPGA deployment\")\n",
|
| 833 |
-
"print()\n",
|
| 834 |
-
"print(\"🚀 Next Steps:\")\n",
|
| 835 |
-
"print(\" 1. Deploy Q8.8 weights to Basys3 FPGA\")\n",
|
| 836 |
-
"print(\" 2. Test with real-time telemetry data\")\n",
|
| 837 |
-
"print(\" 3. Implement online learning/adaptation\")\n",
|
| 838 |
-
"print(\" 4. Scale to larger datasets\")\n",
|
| 839 |
-
"print(\" 5. Integrate with Julia-Rust hybrid pipeline\")\n",
|
| 840 |
-
"print()\n",
|
| 841 |
-
"print(\"📚 Related Resources:\")\n",
|
| 842 |
-
"print(\" • Dataset: https://huggingface.co/datasets/rmems/Spikenaut-SNN-v2-Telemetry-Data-Weights-Parameters\")\n",
|
| 843 |
-
"print(\" • FPGA deployment: See parameters/ folder\")\n",
|
| 844 |
-
"print(\" • Main repository: https://github.com/rmems/Eagle-Lander\")\n",
|
| 845 |
-
"print()\n",
|
| 846 |
-
"print(\"🦁 Happy neuromorphic computing!\")"
|
| 847 |
-
]
|
| 848 |
-
}
|
| 849 |
-
],
|
| 850 |
-
"metadata": {
|
| 851 |
-
"kernelspec": {
|
| 852 |
-
"display_name": "Python 3",
|
| 853 |
-
"language": "python",
|
| 854 |
-
"name": "python3"
|
| 855 |
-
},
|
| 856 |
-
"language_info": {
|
| 857 |
-
"codemirror_mode": {
|
| 858 |
-
"name": "ipython",
|
| 859 |
-
"version": 3
|
| 860 |
-
},
|
| 861 |
-
"file_extension": ".py",
|
| 862 |
-
"mimetype": "text/x-python",
|
| 863 |
-
"name": "python",
|
| 864 |
-
"nbconvert_exporter": "python",
|
| 865 |
-
"pygments_lexer": "ipython3",
|
| 866 |
-
"version": "3.8.5"
|
| 867 |
-
}
|
| 868 |
-
},
|
| 869 |
-
"nbformat": 4,
|
| 870 |
-
"nbformat_minor": 4
|
| 871 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
dataset/examples/spike_encoding_demo.ipynb
DELETED
|
@@ -1,679 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"cells": [
|
| 3 |
-
{
|
| 4 |
-
"cell_type": "markdown",
|
| 5 |
-
"metadata": {},
|
| 6 |
-
"source": [
|
| 7 |
-
"# 🦁 Spikenaut SNN v2 - Spike Encoding Demo\n",
|
| 8 |
-
"\n",
|
| 9 |
-
"This notebook demonstrates how to load the Spikenaut SNN v2 dataset and create spike encodings for neuromorphic computing.\n",
|
| 10 |
-
"\n",
|
| 11 |
-
"## What you'll learn:\n",
|
| 12 |
-
"- Loading the Hugging Face dataset\n",
|
| 13 |
-
"- Understanding the data structure\n",
|
| 14 |
-
"- Creating custom spike encodings\n",
|
| 15 |
-
"- Visualizing spike trains\n",
|
| 16 |
-
"- Preparing data for SNN training"
|
| 17 |
-
]
|
| 18 |
-
},
|
| 19 |
-
{
|
| 20 |
-
"cell_type": "markdown",
|
| 21 |
-
"metadata": {},
|
| 22 |
-
"source": [
|
| 23 |
-
"## 1. Setup and Imports"
|
| 24 |
-
]
|
| 25 |
-
},
|
| 26 |
-
{
|
| 27 |
-
"cell_type": "code",
|
| 28 |
-
"execution_count": null,
|
| 29 |
-
"metadata": {},
|
| 30 |
-
"outputs": [],
|
| 31 |
-
"source": [
|
| 32 |
-
"# Install required packages\n",
|
| 33 |
-
"!pip install datasets numpy matplotlib seaborn scipy -q\n",
|
| 34 |
-
"\n",
|
| 35 |
-
"import json\n",
|
| 36 |
-
"import numpy as np\n",
|
| 37 |
-
"import pandas as pd\n",
|
| 38 |
-
"import matplotlib.pyplot as plt\n",
|
| 39 |
-
"import seaborn as sns\n",
|
| 40 |
-
"from datasets import load_dataset\n",
|
| 41 |
-
"from datetime import datetime\n",
|
| 42 |
-
"import warnings\n",
|
| 43 |
-
"warnings.filterwarnings('ignore')\n",
|
| 44 |
-
"\n",
|
| 45 |
-
"# Set style for better plots\n",
|
| 46 |
-
"plt.style.use('seaborn-v0_8')\n",
|
| 47 |
-
"sns.set_palette(\"husl\")"
|
| 48 |
-
]
|
| 49 |
-
},
|
| 50 |
-
{
|
| 51 |
-
"cell_type": "markdown",
|
| 52 |
-
"metadata": {},
|
| 53 |
-
"source": [
|
| 54 |
-
"## 2. Load the Dataset"
|
| 55 |
-
]
|
| 56 |
-
},
|
| 57 |
-
{
|
| 58 |
-
"cell_type": "code",
|
| 59 |
-
"execution_count": null,
|
| 60 |
-
"metadata": {},
|
| 61 |
-
"outputs": [],
|
| 62 |
-
"source": [
|
| 63 |
-
"# Load the Spikenaut SNN v2 dataset\n",
|
| 64 |
-
"print(\"🦁 Loading Spikenaut SNN v2 dataset...\")\n",
|
| 65 |
-
"ds = load_dataset(\"rmems/Spikenaut-SNN-v2-Telemetry-Data-Weights-Parameters\")\n",
|
| 66 |
-
"\n",
|
| 67 |
-
"# Examine the dataset structure\n",
|
| 68 |
-
"print(f\"Dataset splits: {list(ds.keys())}\")\n",
|
| 69 |
-
"print(f\"Training samples: {len(ds['train'])}\")\n",
|
| 70 |
-
"print(f\"Validation samples: {len(ds['validation'])}\")\n",
|
| 71 |
-
"print(f\"Test samples: {len(ds['test'])}\")\n",
|
| 72 |
-
"\n",
|
| 73 |
-
"# Show available features\n",
|
| 74 |
-
"print(f\"\\nFeatures: {list(ds['train'].features.keys())}\")"
|
| 75 |
-
]
|
| 76 |
-
},
|
| 77 |
-
{
|
| 78 |
-
"cell_type": "markdown",
|
| 79 |
-
"metadata": {},
|
| 80 |
-
"source": [
|
| 81 |
-
"## 3. Explore the Data Structure"
|
| 82 |
-
]
|
| 83 |
-
},
|
| 84 |
-
{
|
| 85 |
-
"cell_type": "code",
|
| 86 |
-
"execution_count": null,
|
| 87 |
-
"metadata": {},
|
| 88 |
-
"outputs": [],
|
| 89 |
-
"source": [
|
| 90 |
-
"# Get a sample from the training set\n",
|
| 91 |
-
"sample = ds['train'][0]\n",
|
| 92 |
-
"print(\"Sample data structure:\")\n",
|
| 93 |
-
"print(json.dumps(sample, indent=2, default=str))\n",
|
| 94 |
-
"\n",
|
| 95 |
-
"# Extract telemetry data\n",
|
| 96 |
-
"telemetry = sample['telemetry']\n",
|
| 97 |
-
"print(f\"\\n📊 Telemetry Summary:\")\n",
|
| 98 |
-
"print(f\" Hashrate: {telemetry['hashrate_mh']} MH/s\")\n",
|
| 99 |
-
"print(f\" Power: {telemetry['power_w']} W\")\n",
|
| 100 |
-
"print(f\" Temperature: {telemetry['gpu_temp_c']} °C\")\n",
|
| 101 |
-
"print(f\" Qubic Trace: {telemetry['qubic_tick_trace']}\")"
|
| 102 |
-
]
|
| 103 |
-
},
|
| 104 |
-
{
|
| 105 |
-
"cell_type": "markdown",
|
| 106 |
-
"metadata": {},
|
| 107 |
-
"source": [
|
| 108 |
-
"## 4. Basic Data Analysis"
|
| 109 |
-
]
|
| 110 |
-
},
|
| 111 |
-
{
|
| 112 |
-
"cell_type": "code",
|
| 113 |
-
"execution_count": null,
|
| 114 |
-
"metadata": {},
|
| 115 |
-
"outputs": [],
|
| 116 |
-
"source": [
|
| 117 |
-
"# Convert to pandas for easier analysis\n",
|
| 118 |
-
"train_df = ds['train'].to_pandas()\n",
|
| 119 |
-
"\n",
|
| 120 |
-
"# Extract telemetry into separate columns\n",
|
| 121 |
-
"telemetry_df = pd.json_normalize(train_df['telemetry'])\n",
|
| 122 |
-
"full_df = pd.concat([train_df.drop('telemetry', axis=1), telemetry_df], axis=1)\n",
|
| 123 |
-
"\n",
|
| 124 |
-
"print(\"📈 Dataset Statistics:\")\n",
|
| 125 |
-
"print(full_df.describe())\n",
|
| 126 |
-
"\n",
|
| 127 |
-
"# Show blockchain distribution\n",
|
| 128 |
-
"print(f\"\\n🔗 Blockchain distribution:\")\n",
|
| 129 |
-
"print(full_df['blockchain'].value_counts())"
|
| 130 |
-
]
|
| 131 |
-
},
|
| 132 |
-
{
|
| 133 |
-
"cell_type": "markdown",
|
| 134 |
-
"metadata": {},
|
| 135 |
-
"source": [
|
| 136 |
-
"## 5. Visualize Telemetry Data"
|
| 137 |
-
]
|
| 138 |
-
},
|
| 139 |
-
{
|
| 140 |
-
"cell_type": "code",
|
| 141 |
-
"execution_count": null,
|
| 142 |
-
"metadata": {},
|
| 143 |
-
"outputs": [],
|
| 144 |
-
"source": [
|
| 145 |
-
"# Create subplots for telemetry visualization\n",
|
| 146 |
-
"fig, axes = plt.subplots(2, 3, figsize=(15, 10))\n",
|
| 147 |
-
"fig.suptitle('🦁 Spikenaut SNN v2 - Telemetry Data Overview', fontsize=16)\n",
|
| 148 |
-
"\n",
|
| 149 |
-
"# Hashrate distribution\n",
|
| 150 |
-
"axes[0, 0].hist(full_df['hashrate_mh'], bins=20, alpha=0.7, color='blue')\n",
|
| 151 |
-
"axes[0, 0].set_title('Hashrate Distribution (MH/s)')\n",
|
| 152 |
-
"axes[0, 0].set_xlabel('Hashrate (MH/s)')\n",
|
| 153 |
-
"axes[0, 0].set_ylabel('Frequency')\n",
|
| 154 |
-
"\n",
|
| 155 |
-
"# Power consumption\n",
|
| 156 |
-
"axes[0, 1].hist(full_df['power_w'], bins=20, alpha=0.7, color='red')\n",
|
| 157 |
-
"axes[0, 1].set_title('Power Consumption (W)')\n",
|
| 158 |
-
"axes[0, 1].set_xlabel('Power (W)')\n",
|
| 159 |
-
"axes[0, 1].set_ylabel('Frequency')\n",
|
| 160 |
-
"\n",
|
| 161 |
-
"# GPU temperature\n",
|
| 162 |
-
"axes[0, 2].hist(full_df['gpu_temp_c'], bins=20, alpha=0.7, color='orange')\n",
|
| 163 |
-
"axes[0, 2].set_title('GPU Temperature (°C)')\n",
|
| 164 |
-
"axes[0, 2].set_xlabel('Temperature (°C)')\n",
|
| 165 |
-
"axes[0, 2].set_ylabel('Frequency')\n",
|
| 166 |
-
"\n",
|
| 167 |
-
"# Qubic trace\n",
|
| 168 |
-
"axes[1, 0].hist(full_df['qubic_tick_trace'], bins=20, alpha=0.7, color='green')\n",
|
| 169 |
-
"axes[1, 0].set_title('Qubic Tick Trace')\n",
|
| 170 |
-
"axes[1, 0].set_xlabel('Qubic Trace')\n",
|
| 171 |
-
"axes[1, 0].set_ylabel('Frequency')\n",
|
| 172 |
-
"\n",
|
| 173 |
-
"# Blockchain types\n",
|
| 174 |
-
"blockchain_counts = full_df['blockchain'].value_counts()\n",
|
| 175 |
-
"axes[1, 1].pie(blockchain_counts.values, labels=blockchain_counts.index, autopct='%1.1f%%')\n",
|
| 176 |
-
"axes[1, 1].set_title('Blockchain Distribution')\n",
|
| 177 |
-
"\n",
|
| 178 |
-
"# Time series (if timestamps available)\n",
|
| 179 |
-
"if 'timestamp' in full_df.columns:\n",
|
| 180 |
-
" timestamps = pd.to_datetime(full_df['timestamp'])\n",
|
| 181 |
-
" axes[1, 2].plot(timestamps, full_df['hashrate_mh'], marker='o', linestyle='-', alpha=0.7)\n",
|
| 182 |
-
" axes[1, 2].set_title('Hashrate Over Time')\n",
|
| 183 |
-
" axes[1, 2].set_xlabel('Time')\n",
|
| 184 |
-
" axes[1, 2].set_ylabel('Hashrate (MH/s)')\n",
|
| 185 |
-
" axes[1, 2].tick_params(axis='x', rotation=45)\n",
|
| 186 |
-
"else:\n",
|
| 187 |
-
" axes[1, 2].text(0.5, 0.5, 'Time series data\\nnot available', ha='center', va='center', transform=axes[1, 2].transAxes)\n",
|
| 188 |
-
" axes[1, 2].set_title('Hashrate Over Time')\n",
|
| 189 |
-
"\n",
|
| 190 |
-
"plt.tight_layout()\n",
|
| 191 |
-
"plt.show()"
|
| 192 |
-
]
|
| 193 |
-
},
|
| 194 |
-
{
|
| 195 |
-
"cell_type": "markdown",
|
| 196 |
-
"metadata": {},
|
| 197 |
-
"source": [
|
| 198 |
-
"## 6. Custom Spike Encoding"
|
| 199 |
-
]
|
| 200 |
-
},
|
| 201 |
-
{
|
| 202 |
-
"cell_type": "code",
|
| 203 |
-
"execution_count": null,
|
| 204 |
-
"metadata": {},
|
| 205 |
-
"outputs": [],
|
| 206 |
-
"source": [
|
| 207 |
-
"class SpikenautSpikeEncoder:\n",
|
| 208 |
-
" \"\"\"Custom spike encoder for Spikenaut SNN v2 telemetry data\"\"\"\n",
|
| 209 |
-
" \n",
|
| 210 |
-
" def __init__(self):\n",
|
| 211 |
-
" # Adaptive thresholds based on data statistics\n",
|
| 212 |
-
" self.thresholds = {\n",
|
| 213 |
-
" 'hashrate': 0.9, # MH/s\n",
|
| 214 |
-
" 'power': 390, # Watts\n",
|
| 215 |
-
" 'temp': 43, # Celsius\n",
|
| 216 |
-
" 'qubic': 0.95 # Normalized\n",
|
| 217 |
-
" }\n",
|
| 218 |
-
" \n",
|
| 219 |
-
" # Channel mapping for 16-neuron architecture\n",
|
| 220 |
-
" self.channels = [\n",
|
| 221 |
-
" 'kaspa_hashrate', 'kaspa_power', 'kaspa_temp', 'kaspa_qubic',\n",
|
| 222 |
-
" 'monero_hashrate', 'monero_power', 'monero_temp', 'monero_qubic',\n",
|
| 223 |
-
" 'qubic_hashrate', 'qubic_power', 'qubic_temp', 'qubic_qubic',\n",
|
| 224 |
-
" 'thermal_stress', 'power_efficiency', 'network_health', 'composite_reward'\n",
|
| 225 |
-
" ]\n",
|
| 226 |
-
" \n",
|
| 227 |
-
" def encode_telemetry(self, telemetry, blockchain):\n",
|
| 228 |
-
" \"\"\"Encode telemetry data into 16-channel spike vector\"\"\"\n",
|
| 229 |
-
" spikes = np.zeros(16)\n",
|
| 230 |
-
" \n",
|
| 231 |
-
" # Basic telemetry spikes\n",
|
| 232 |
-
" spikes[0] = 1 if telemetry['hashrate_mh'] > self.thresholds['hashrate'] else 0\n",
|
| 233 |
-
" spikes[1] = 1 if telemetry['power_w'] > self.thresholds['power'] else 0\n",
|
| 234 |
-
" spikes[2] = 1 if telemetry['gpu_temp_c'] > self.thresholds['temp'] else 0\n",
|
| 235 |
-
" spikes[3] = 1 if telemetry['qubic_tick_trace'] > self.thresholds['qubic'] else 0\n",
|
| 236 |
-
" \n",
|
| 237 |
-
" # Blockchain-specific mapping\n",
|
| 238 |
-
" if blockchain == 'kaspa':\n",
|
| 239 |
-
" spikes[0:4] = [spikes[0], spikes[1], spikes[2], spikes[3]]\n",
|
| 240 |
-
" elif blockchain == 'monero':\n",
|
| 241 |
-
" spikes[4:8] = [spikes[0], spikes[1], spikes[2], spikes[3]]\n",
|
| 242 |
-
" elif blockchain == 'qubic':\n",
|
| 243 |
-
" spikes[8:12] = [spikes[0], spikes[1], spikes[2], spikes[3]]\n",
|
| 244 |
-
" \n",
|
| 245 |
-
" # Derived spikes\n",
|
| 246 |
-
" thermal_stress = max(0, (telemetry['gpu_temp_c'] - 40) / 6)\n",
|
| 247 |
-
" spikes[12] = 1 if thermal_stress > 0.5 else 0\n",
|
| 248 |
-
" \n",
|
| 249 |
-
" power_efficiency = telemetry['hashrate_mh'] / (telemetry['power_w'] / 1000)\n",
|
| 250 |
-
" spikes[13] = 1 if power_efficiency > 2.5 else 0\n",
|
| 251 |
-
" \n",
|
| 252 |
-
" network_health = (telemetry['qubic_tick_trace'] + telemetry['qubic_epoch_progress']) / 2\n",
|
| 253 |
-
" spikes[14] = 1 if network_health > 0.95 else 0\n",
|
| 254 |
-
" \n",
|
| 255 |
-
" composite_reward = telemetry['reward_hint']\n",
|
| 256 |
-
" spikes[15] = 1 if composite_reward > 0.95 else 0\n",
|
| 257 |
-
" \n",
|
| 258 |
-
" return spikes\n",
|
| 259 |
-
" \n",
|
| 260 |
-
" def encode_dataset(self, dataset):\n",
|
| 261 |
-
" \"\"\"Encode entire dataset\"\"\"\n",
|
| 262 |
-
" spike_trains = []\n",
|
| 263 |
-
" \n",
|
| 264 |
-
" for i in range(len(dataset)):\n",
|
| 265 |
-
" sample = dataset[i]\n",
|
| 266 |
-
" spikes = self.encode_telemetry(sample['telemetry'], sample['blockchain'])\n",
|
| 267 |
-
" \n",
|
| 268 |
-
" spike_trains.append({\n",
|
| 269 |
-
" 'timestamp': sample.get('timestamp', f'sample_{i}'),\n",
|
| 270 |
-
" 'blockchain': sample['blockchain'],\n",
|
| 271 |
-
" 'spike_vector': spikes,\n",
|
| 272 |
-
" 'spike_count': int(np.sum(spikes))\n",
|
| 273 |
-
" })\n",
|
| 274 |
-
" \n",
|
| 275 |
-
" return spike_trains\n",
|
| 276 |
-
"\n",
|
| 277 |
-
"# Initialize encoder\n",
|
| 278 |
-
"encoder = SpikenautSpikeEncoder()\n",
|
| 279 |
-
"print(\"🔸 Spike encoder initialized\")\n",
|
| 280 |
-
"print(f\"Channels: {encoder.channels}\")"
|
| 281 |
-
]
|
| 282 |
-
},
|
| 283 |
-
{
|
| 284 |
-
"cell_type": "markdown",
|
| 285 |
-
"metadata": {},
|
| 286 |
-
"source": [
|
| 287 |
-
"## 7. Generate Spike Trains"
|
| 288 |
-
]
|
| 289 |
-
},
|
| 290 |
-
{
|
| 291 |
-
"cell_type": "code",
|
| 292 |
-
"execution_count": null,
|
| 293 |
-
"metadata": {},
|
| 294 |
-
"outputs": [],
|
| 295 |
-
"source": [
|
| 296 |
-
"# Generate spike trains for training data\n",
|
| 297 |
-
"print(\"🦁 Generating spike trains...\")\n",
|
| 298 |
-
"spike_trains = encoder.encode_dataset(ds['train'])\n",
|
| 299 |
-
"\n",
|
| 300 |
-
"# Convert to numpy for analysis\n",
|
| 301 |
-
"spike_matrix = np.array([train['spike_vector'] for train in spike_trains])\n",
|
| 302 |
-
"\n",
|
| 303 |
-
"print(f\"Generated {len(spike_trains)} spike trains\")\n",
|
| 304 |
-
"print(f\"Spike matrix shape: {spike_matrix.shape}\")\n",
|
| 305 |
-
"print(f\"Average spikes per sample: {spike_matrix.mean():.3f}\")\n",
|
| 306 |
-
"print(f\"Spike rate: {spike_matrix.mean() * 1000:.1f} Hz\")\n",
|
| 307 |
-
"\n",
|
| 308 |
-
"# Show first few spike trains\n",
|
| 309 |
-
"print(\"\\nFirst 5 spike trains:\")\n",
|
| 310 |
-
"for i, train in enumerate(spike_trains[:5]):\n",
|
| 311 |
-
" active_channels = np.where(train['spike_vector'] == 1)[0]\n",
|
| 312 |
-
" print(f\" Sample {i}: {train['spike_count']} spikes -> channels {active_channels}\")"
|
| 313 |
-
]
|
| 314 |
-
},
|
| 315 |
-
{
|
| 316 |
-
"cell_type": "markdown",
|
| 317 |
-
"metadata": {},
|
| 318 |
-
"source": [
|
| 319 |
-
"## 8. Visualize Spike Trains"
|
| 320 |
-
]
|
| 321 |
-
},
|
| 322 |
-
{
|
| 323 |
-
"cell_type": "code",
|
| 324 |
-
"execution_count": null,
|
| 325 |
-
"metadata": {},
|
| 326 |
-
"outputs": [],
|
| 327 |
-
"source": [
|
| 328 |
-
"# Create spike raster plot\n",
|
| 329 |
-
"fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8))\n",
|
| 330 |
-
"\n",
|
| 331 |
-
"# Raster plot\n",
|
| 332 |
-
"for i in range(spike_matrix.shape[1]): # For each channel\n",
|
| 333 |
-
" spike_times = np.where(spike_matrix[:, i] == 1)[0]\n",
|
| 334 |
-
" ax1.scatter(spike_times, np.ones_like(spike_times) * i, \n",
|
| 335 |
-
" s=20, alpha=0.8, label=encoder.channels[i] if i < 4 else \"\")\n",
|
| 336 |
-
"\n",
|
| 337 |
-
"ax1.set_xlabel('Time (samples)')\n",
|
| 338 |
-
"ax1.set_ylabel('Channel')\n",
|
| 339 |
-
"ax1.set_title('🦁 Spikenaut SNN v2 - Spike Raster Plot')\n",
|
| 340 |
-
"ax1.grid(True, alpha=0.3)\n",
|
| 341 |
-
"ax1.set_ylim(-0.5, 15.5)\n",
|
| 342 |
-
"\n",
|
| 343 |
-
"# Spike rate per channel\n",
|
| 344 |
-
"spike_rates = spike_matrix.mean(axis=0)\n",
|
| 345 |
-
"channel_labels = [f\"{i}: {name}\" for i, name in enumerate(encoder.channels)]\n",
|
| 346 |
-
"\n",
|
| 347 |
-
"bars = ax2.bar(range(16), spike_rates, alpha=0.7)\n",
|
| 348 |
-
"ax2.set_xlabel('Channel')\n",
|
| 349 |
-
"ax2.set_ylabel('Spike Rate')\n",
|
| 350 |
-
"ax2.set_title('Spike Rate per Channel')\n",
|
| 351 |
-
"ax2.set_xticks(range(16))\n",
|
| 352 |
-
"ax2.set_xticklabels([f\"{i}\" for i in range(16)], rotation=45)\n",
|
| 353 |
-
"ax2.grid(True, alpha=0.3)\n",
|
| 354 |
-
"\n",
|
| 355 |
-
"# Add channel labels on top of bars\n",
|
| 356 |
-
"for i, (bar, rate) in enumerate(zip(bars, spike_rates)):\n",
|
| 357 |
-
" if rate > 0:\n",
|
| 358 |
-
" ax2.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.01, \n",
|
| 359 |
-
" f'{rate:.2f}', ha='center', va='bottom', fontsize=8)\n",
|
| 360 |
-
"\n",
|
| 361 |
-
"plt.tight_layout()\n",
|
| 362 |
-
"plt.show()"
|
| 363 |
-
]
|
| 364 |
-
},
|
| 365 |
-
{
|
| 366 |
-
"cell_type": "markdown",
|
| 367 |
-
"metadata": {},
|
| 368 |
-
"source": [
|
| 369 |
-
"## 9. Correlation Analysis"
|
| 370 |
-
]
|
| 371 |
-
},
|
| 372 |
-
{
|
| 373 |
-
"cell_type": "code",
|
| 374 |
-
"execution_count": null,
|
| 375 |
-
"metadata": {},
|
| 376 |
-
"outputs": [],
|
| 377 |
-
"source": [
|
| 378 |
-
"# Compute spike correlation matrix\n",
|
| 379 |
-
"correlation_matrix = np.corrcoef(spike_matrix.T)\n",
|
| 380 |
-
"\n",
|
| 381 |
-
"# Create heatmap\n",
|
| 382 |
-
"plt.figure(figsize=(10, 8))\n",
|
| 383 |
-
"sns.heatmap(correlation_matrix, \n",
|
| 384 |
-
" xticklabels=encoder.channels,\n",
|
| 385 |
-
" yticklabels=encoder.channels,\n",
|
| 386 |
-
" annot=True, \n",
|
| 387 |
-
" cmap='coolwarm', \n",
|
| 388 |
-
" center=0,\n",
|
| 389 |
-
" fmt='.2f')\n",
|
| 390 |
-
"plt.title('🦁 Spikenaut SNN v2 - Spike Correlation Matrix')\n",
|
| 391 |
-
"plt.xticks(rotation=45, ha='right')\n",
|
| 392 |
-
"plt.yticks(rotation=0)\n",
|
| 393 |
-
"plt.tight_layout()\n",
|
| 394 |
-
"plt.show()\n",
|
| 395 |
-
"\n",
|
| 396 |
-
"# Find most correlated channel pairs\n",
|
| 397 |
-
"correlation_pairs = []\n",
|
| 398 |
-
"for i in range(16):\n",
|
| 399 |
-
" for j in range(i+1, 16):\n",
|
| 400 |
-
" corr = correlation_matrix[i, j]\n",
|
| 401 |
-
" if abs(corr) > 0.3: # Only show significant correlations\n",
|
| 402 |
-
" correlation_pairs.append({\n",
|
| 403 |
-
" 'channel1': encoder.channels[i],\n",
|
| 404 |
-
" 'channel2': encoder.channels[j],\n",
|
| 405 |
-
" 'correlation': corr\n",
|
| 406 |
-
" })\n",
|
| 407 |
-
"\n",
|
| 408 |
-
"print(\"🔗 Significant channel correlations (|r| > 0.3):\")\n",
|
| 409 |
-
"for pair in sorted(correlation_pairs, key=lambda x: abs(x['correlation']), reverse=True):\n",
|
| 410 |
-
" print(f\" {pair['channel1']} ↔ {pair['channel2']}: r = {pair['correlation']:.3f}\")"
|
| 411 |
-
]
|
| 412 |
-
},
|
| 413 |
-
{
|
| 414 |
-
"cell_type": "markdown",
|
| 415 |
-
"metadata": {},
|
| 416 |
-
"source": [
|
| 417 |
-
"## 10. Prepare Data for SNN Training"
|
| 418 |
-
]
|
| 419 |
-
},
|
| 420 |
-
{
|
| 421 |
-
"cell_type": "code",
|
| 422 |
-
"execution_count": null,
|
| 423 |
-
"metadata": {},
|
| 424 |
-
"outputs": [],
|
| 425 |
-
"source": [
|
| 426 |
-
"class SNNTrainingData:\n",
|
| 427 |
-
" \"\"\"Prepare data for Spiking Neural Network training\"\"\"\n",
|
| 428 |
-
" \n",
|
| 429 |
-
" def __init__(self, spike_trains, window_size=5):\n",
|
| 430 |
-
" self.spike_trains = spike_trains\n",
|
| 431 |
-
" self.window_size = window_size\n",
|
| 432 |
-
" \n",
|
| 433 |
-
" def create_sequences(self):\n",
|
| 434 |
-
" \"\"\"Create sequences for time-series SNN training\"\"\"\n",
|
| 435 |
-
" sequences = []\n",
|
| 436 |
-
" targets = []\n",
|
| 437 |
-
" \n",
|
| 438 |
-
" spike_matrix = np.array([train['spike_vector'] for train in self.spike_trains])\n",
|
| 439 |
-
" \n",
|
| 440 |
-
" for i in range(len(spike_matrix) - self.window_size):\n",
|
| 441 |
-
" # Input sequence\n",
|
| 442 |
-
" sequence = spike_matrix[i:i + self.window_size]\n",
|
| 443 |
-
" \n",
|
| 444 |
-
" # Target (next timestep)\n",
|
| 445 |
-
" target = spike_matrix[i + self.window_size]\n",
|
| 446 |
-
" \n",
|
| 447 |
-
" sequences.append(sequence)\n",
|
| 448 |
-
" targets.append(target)\n",
|
| 449 |
-
" \n",
|
| 450 |
-
" return np.array(sequences), np.array(targets)\n",
|
| 451 |
-
" \n",
|
| 452 |
-
" def create_classification_dataset(self):\n",
|
| 453 |
-
" \"\"\"Create dataset for classification tasks\"\"\"\n",
|
| 454 |
-
" X = np.array([train['spike_vector'] for train in self.spike_trains])\n",
|
| 455 |
-
" \n",
|
| 456 |
-
" # Create labels based on blockchain type\n",
|
| 457 |
-
" labels = []\n",
|
| 458 |
-
" for train in self.spike_trains:\n",
|
| 459 |
-
" if train['blockchain'] == 'kaspa':\n",
|
| 460 |
-
" labels.append(0)\n",
|
| 461 |
-
" elif train['blockchain'] == 'monero':\n",
|
| 462 |
-
" labels.append(1)\n",
|
| 463 |
-
" else:\n",
|
| 464 |
-
" labels.append(2)\n",
|
| 465 |
-
" \n",
|
| 466 |
-
" return X, np.array(labels)\n",
|
| 467 |
-
"\n",
|
| 468 |
-
"# Prepare training data\n",
|
| 469 |
-
"snn_data = SNNTrainingData(spike_trains, window_size=3)\n",
|
| 470 |
-
"\n",
|
| 471 |
-
"# Create sequences for time-series prediction\n",
|
| 472 |
-
"X_seq, y_seq = snn_data.create_sequences()\n",
|
| 473 |
-
"print(f\"🔄 Sequential data:\")\n",
|
| 474 |
-
"print(f\" Sequences shape: {X_seq.shape}\")\n",
|
| 475 |
-
"print(f\" Targets shape: {y_seq.shape}\")\n",
|
| 476 |
-
"\n",
|
| 477 |
-
"# Create classification dataset\n",
|
| 478 |
-
"X_cls, y_cls = snn_data.create_classification_dataset()\n",
|
| 479 |
-
"print(f\"\\n🎯 Classification data:\")\n",
|
| 480 |
-
"print(f\" Features shape: {X_cls.shape}\")\n",
|
| 481 |
-
"print(f\" Labels shape: {y_cls.shape}\")\n",
|
| 482 |
-
"print(f\" Class distribution: {np.bincount(y_cls)}\")"
|
| 483 |
-
]
|
| 484 |
-
},
|
| 485 |
-
{
|
| 486 |
-
"cell_type": "markdown",
|
| 487 |
-
"metadata": {},
|
| 488 |
-
"source": [
|
| 489 |
-
"## 11. Simple SNN Example"
|
| 490 |
-
]
|
| 491 |
-
},
|
| 492 |
-
{
|
| 493 |
-
"cell_type": "code",
|
| 494 |
-
"execution_count": null,
|
| 495 |
-
"metadata": {},
|
| 496 |
-
"outputs": [],
|
| 497 |
-
"source": [
|
| 498 |
-
"class SimpleSNN:\n",
|
| 499 |
-
" \"\"\"Simple Spiking Neural Network for demonstration\"\"\"\n",
|
| 500 |
-
" \n",
|
| 501 |
-
" def __init__(self, n_inputs=16, n_hidden=32, n_outputs=3):\n",
|
| 502 |
-
" self.n_inputs = n_inputs\n",
|
| 503 |
-
" self.n_hidden = n_hidden\n",
|
| 504 |
-
" self.n_outputs = n_outputs\n",
|
| 505 |
-
" \n",
|
| 506 |
-
" # Initialize weights (small random values)\n",
|
| 507 |
-
" self.W_in = np.random.randn(n_inputs, n_hidden) * 0.1\n",
|
| 508 |
-
" self.W_out = np.random.randn(n_hidden, n_outputs) * 0.1\n",
|
| 509 |
-
" \n",
|
| 510 |
-
" # Neuron parameters\n",
|
| 511 |
-
" self.threshold = 0.5\n",
|
| 512 |
-
" self.decay = 0.9\n",
|
| 513 |
-
" \n",
|
| 514 |
-
" def forward(self, X):\n",
|
| 515 |
-
" \"\"\"Forward pass through the SNN\"\"\"\n",
|
| 516 |
-
" batch_size = X.shape[0]\n",
|
| 517 |
-
" seq_len = X.shape[1] if len(X.shape) > 2 else 1\n",
|
| 518 |
-
" \n",
|
| 519 |
-
" # Reshape if needed\n",
|
| 520 |
-
" if len(X.shape) == 2:\n",
|
| 521 |
-
" X = X.reshape(batch_size, 1, -1)\n",
|
| 522 |
-
" seq_len = 1\n",
|
| 523 |
-
" \n",
|
| 524 |
-
" # Initialize membrane potentials\n",
|
| 525 |
-
" membrane_hidden = np.zeros((batch_size, self.n_hidden))\n",
|
| 526 |
-
" membrane_out = np.zeros((batch_size, self.n_outputs))\n",
|
| 527 |
-
" \n",
|
| 528 |
-
" # Process sequence\n",
|
| 529 |
-
" for t in range(seq_len):\n",
|
| 530 |
-
" # Input to hidden\n",
|
| 531 |
-
" hidden_input = np.dot(X[:, t, :], self.W_in)\n",
|
| 532 |
-
" membrane_hidden = membrane_hidden * self.decay + hidden_input\n",
|
| 533 |
-
" hidden_spikes = (membrane_hidden > self.threshold).astype(float)\n",
|
| 534 |
-
" \n",
|
| 535 |
-
" # Hidden to output\n",
|
| 536 |
-
" out_input = np.dot(hidden_spikes, self.W_out)\n",
|
| 537 |
-
" membrane_out = membrane_out * self.decay + out_input\n",
|
| 538 |
-
" \n",
|
| 539 |
-
" return membrane_out, hidden_spikes\n",
|
| 540 |
-
"\n",
|
| 541 |
-
"# Initialize and test SNN\n",
|
| 542 |
-
"snn = SimpleSNN()\n",
|
| 543 |
-
"print(\"🧠 Simple SNN initialized\")\n",
|
| 544 |
-
"print(f\" Input neurons: {snn.n_inputs}\")\n",
|
| 545 |
-
"print(f\" Hidden neurons: {snn.n_hidden}\")\n",
|
| 546 |
-
"print(f\" Output neurons: {snn.n_outputs}\")\n",
|
| 547 |
-
"\n",
|
| 548 |
-
"# Test with sample data\n",
|
| 549 |
-
"if len(X_seq) > 0:\n",
|
| 550 |
-
" sample_input = X_seq[:1] # Take first sample\n",
|
| 551 |
-
" output, hidden_spikes = snn.forward(sample_input)\n",
|
| 552 |
-
" \n",
|
| 553 |
-
" print(f\"\\n🔬 Test forward pass:\")\n",
|
| 554 |
-
" print(f\" Input shape: {sample_input.shape}\")\n",
|
| 555 |
-
" print(f\" Hidden spikes: {hidden_spikes.sum()} active\")\n",
|
| 556 |
-
" print(f\" Output shape: {output.shape}\")\n",
|
| 557 |
-
" print(f\" Output values: {output[0]}")"
|
| 558 |
-
]
|
| 559 |
-
},
|
| 560 |
-
{
|
| 561 |
-
"cell_type": "markdown",
|
| 562 |
-
"metadata": {},
|
| 563 |
-
"source": [
|
| 564 |
-
"## 12. Save Processed Data"
|
| 565 |
-
]
|
| 566 |
-
},
|
| 567 |
-
{
|
| 568 |
-
"cell_type": "code",
|
| 569 |
-
"execution_count": null,
|
| 570 |
-
"metadata": {},
|
| 571 |
-
"outputs": [],
|
| 572 |
-
"source": [
|
| 573 |
-
"# Save processed spike data for future use\n",
|
| 574 |
-
"import pickle\n",
|
| 575 |
-
"\n",
|
| 576 |
-
"processed_data = {\n",
|
| 577 |
-
" 'spike_trains': spike_trains,\n",
|
| 578 |
-
" 'spike_matrix': spike_matrix,\n",
|
| 579 |
-
" 'sequences': (X_seq, y_seq),\n",
|
| 580 |
-
" 'classification': (X_cls, y_cls),\n",
|
| 581 |
-
" 'encoder_channels': encoder.channels,\n",
|
| 582 |
-
" 'thresholds': encoder.thresholds\n",
|
| 583 |
-
"}\n",
|
| 584 |
-
"\n",
|
| 585 |
-
"# Save to pickle file\n",
|
| 586 |
-
"with open('spikenaut_processed_data.pkl', 'wb') as f:\n",
|
| 587 |
-
" pickle.dump(processed_data, f)\n",
|
| 588 |
-
"\n",
|
| 589 |
-
"print(\"💾 Processed data saved to 'spikenaut_processed_data.pkl'\")\n",
|
| 590 |
-
"print(\"\\n📁 Files created:\")\n",
|
| 591 |
-
"print(\" - spikenaut_processed_data.pkl (processed spike data)\")\n",
|
| 592 |
-
"\n",
|
| 593 |
-
"# Also save as JSON for compatibility\n",
|
| 594 |
-
"json_data = {\n",
|
| 595 |
-
" 'spike_trains': spike_trains,\n",
|
| 596 |
-
" 'channels': encoder.channels,\n",
|
| 597 |
-
" 'thresholds': encoder.thresholds,\n",
|
| 598 |
-
" 'statistics': {\n",
|
| 599 |
-
" 'total_samples': len(spike_trains),\n",
|
| 600 |
-
" 'avg_spikes_per_sample': float(spike_matrix.mean()),\n",
|
| 601 |
-
" 'spike_rate_hz': float(spike_matrix.mean() * 1000),\n",
|
| 602 |
-
" 'most_active_channel': int(np.argmax(spike_matrix.mean(axis=0))),\n",
|
| 603 |
-
" 'channel_correlation_avg': float(np.mean(np.abs(correlation_matrix)))\n",
|
| 604 |
-
" }\n",
|
| 605 |
-
"}\n",
|
| 606 |
-
"\n",
|
| 607 |
-
"with open('spike_analysis_results.json', 'w') as f:\n",
|
| 608 |
-
" json.dump(json_data, f, indent=2)\n",
|
| 609 |
-
"\n",
|
| 610 |
-
"print(\" - spike_analysis_results.json (summary statistics)\")"
|
| 611 |
-
]
|
| 612 |
-
},
|
| 613 |
-
{
|
| 614 |
-
"cell_type": "markdown",
|
| 615 |
-
"metadata": {},
|
| 616 |
-
"source": [
|
| 617 |
-
"## 13. Summary and Next Steps"
|
| 618 |
-
]
|
| 619 |
-
},
|
| 620 |
-
{
|
| 621 |
-
"cell_type": "code",
|
| 622 |
-
"execution_count": null,
|
| 623 |
-
"metadata": {},
|
| 624 |
-
"outputs": [],
|
| 625 |
-
"source": [
|
| 626 |
-
"print(\"🦁 Spikenaut SNN v2 - Spike Encoding Demo Complete!\")\n",
|
| 627 |
-
"print(\"=\" * 50)\n",
|
| 628 |
-
"print()\n",
|
| 629 |
-
"print(\"📊 What we accomplished:\")\n",
|
| 630 |
-
"print(f\" ✅ Loaded {len(ds['train'])} training samples\")\n",
|
| 631 |
-
"print(f\" ✅ Generated {len(spike_trains)} spike trains\")\n",
|
| 632 |
-
"print(f\" ✅ Created {len(X_seq)} sequential samples\")\n",
|
| 633 |
-
"print(f\" ✅ Built classification dataset with {len(X_cls)} samples\")\n",
|
| 634 |
-
"print(f\" ✅ Analyzed spike correlations across 16 channels\")\n",
|
| 635 |
-
"print(f\" ✅ Demonstrated simple SNN forward pass\")\n",
|
| 636 |
-
"print()\n",
|
| 637 |
-
"print(\"🔬 Key insights:\")\n",
|
| 638 |
-
"print(f\" • Average spike rate: {spike_matrix.mean() * 1000:.1f} Hz\")\n",
|
| 639 |
-
"print(f\" • Most active channel: {encoder.channels[np.argmax(spike_matrix.mean(axis=0))]}\")\n",
|
| 640 |
-
"print(f\" • Spike correlation avg: {np.mean(np.abs(correlation_matrix)):.3f}\")\n",
|
| 641 |
-
"print()\n",
|
| 642 |
-
"print(\"🚀 Next steps for your research:\")\n",
|
| 643 |
-
"print(\" 1. Train a full SNN using the sequential data\")\n",
|
| 644 |
-
"print(\" 2. Experiment with different spike encoding thresholds\")\n",
|
| 645 |
-
"print(\" 3. Try STDP learning rules on the spike trains\")\n",
|
| 646 |
-
"print(\" 4. Deploy to FPGA using the provided parameters\")\n",
|
| 647 |
-
"print(\" 5. Extend with real-time telemetry collection\")\n",
|
| 648 |
-
"print()\n",
|
| 649 |
-
"print(\"📚 Related resources:\")\n",
|
| 650 |
-
"print(\" • Dataset: https://huggingface.co/datasets/rmems/Spikenaut-SNN-v2-Telemetry-Data-Weights-Parameters\")\n",
|
| 651 |
-
"print(\" • Main repo: https://github.com/rmems/Eagle-Lander\")\n",
|
| 652 |
-
"print(\" • FPGA deployment: See parameters/ folder\")\n",
|
| 653 |
-
"print()\n",
|
| 654 |
-
"print(\"🦁 Happy spiking!\")"
|
| 655 |
-
]
|
| 656 |
-
}
|
| 657 |
-
],
|
| 658 |
-
"metadata": {
|
| 659 |
-
"kernelspec": {
|
| 660 |
-
"display_name": "Python 3",
|
| 661 |
-
"language": "python",
|
| 662 |
-
"name": "python3"
|
| 663 |
-
},
|
| 664 |
-
"language_info": {
|
| 665 |
-
"codemirror_mode": {
|
| 666 |
-
"name": "ipython",
|
| 667 |
-
"version": 3
|
| 668 |
-
},
|
| 669 |
-
"file_extension": ".py",
|
| 670 |
-
"mimetype": "text/x-python",
|
| 671 |
-
"name": "python",
|
| 672 |
-
"nbconvert_exporter": "python",
|
| 673 |
-
"pygments_lexer": "ipython3",
|
| 674 |
-
"version": "3.8.5"
|
| 675 |
-
}
|
| 676 |
-
},
|
| 677 |
-
"nbformat": 4,
|
| 678 |
-
"nbformat_minor": 4
|
| 679 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|