Using PySHDL
PySHDL is the Python library for working with SHDL circuits. This guide covers the Python API for simulating circuits.
The Circuit Class
The main interface for simulation is the Circuit class:
from SHDL import Circuit
# Load a circuit from a file
circuit = Circuit("myCircuit.shdl")
# Set inputs
circuit.poke("A", 42)
circuit.poke("B", 17)
# Advance simulation
circuit.step()
# Read outputs
result = circuit.peek("Sum")
print(result)
# Clean up
circuit.close()
Context Manager (Recommended)
Use the context manager for automatic cleanup:
from SHDL import Circuit
with Circuit("adder16.shdl") as circuit:
circuit["A"] = 100
circuit["B"] = 200
circuit.step()
print(circuit["Sum"]) # 300
# Automatically cleaned up here
Dict-like Interface
For convenience, you can use dictionary syntax:
# These are equivalent:
circuit.poke("A", 42)
circuit["A"] = 42
# These are equivalent:
value = circuit.peek("Sum")
value = circuit["Sum"]
Circuit Information
Get information about the loaded circuit:
with Circuit("adder16.shdl") as circuit:
print(circuit.name) # "Adder16"
print(circuit.inputs) # ["A", "B", "Cin"]
print(circuit.outputs) # ["Sum", "Cout"]
# Detailed port info
for port in circuit.info.inputs:
print(f"{port.name}: {port.width} bits (max: {port.max_value})")
Multi-bit Signals
SHDL supports multi-bit signals (vectors). Values are passed as integers:
with Circuit("adder16.shdl") as circuit:
# Set 16-bit values
circuit["A"] = 0xFFFF # 65535
circuit["B"] = 0x0001 # 1
circuit.step()
# Read result (also 16 bits)
print(circuit["Sum"]) # 0 (overflow)
print(circuit["Cout"]) # 1 (carry out)
Testing Circuits
Example of testing an 8-bit adder:
from SHDL import Circuit
import random
def test_adder8():
with Circuit("adder8.shdl") as adder:
# Test 100 random additions
for _ in range(100):
a = random.randint(0, 255)
b = random.randint(0, 255)
adder["A"] = a
adder["B"] = b
adder["Cin"] = 0
adder.step()
expected_sum = (a + b) & 0xFF
expected_cout = 1 if (a + b) > 255 else 0
assert adder["Sum"] == expected_sum
assert adder["Cout"] == expected_cout
print("All tests passed!")
test_adder8()
Compilation Pipeline
PySHDL compiles circuits through several stages:
┌─────────────────┐
│ Expanded SHDL │ ← Your .shdl file
│ (generators, │
│ imports, etc) │
└────────┬────────┘
│ Flattener
▼
┌─────────────────┐
│ Base SHDL │ ← Primitives only
│ (AND, OR, NOT, │
│ XOR, VCC, GND)│
└────────┬────────┘
│ Compiler
▼
┌─────────────────┐
│ C Code │ ← Generated C
└────────┬────────┘
│ clang
▼
┌─────────────────┐
│ Shared Library │ ← .dylib/.so/.dll
└────────┬────────┘
│ ctypes
▼
┌─────────────────┐
│ Circuit │ ← Python interface
└─────────────────┘
Advanced Options
Custom Include Paths
If your circuit imports from other files:
circuit = Circuit(
"cpu.shdl",
include_paths=["./components", "./alu"]
)
Selecting a Component
If a file has multiple components:
circuit = Circuit(
"components.shdl",
component="FullAdder" # Compile this specific component
)
Keeping the Compiled Library
For debugging, keep the generated library:
circuit = Circuit(
"circuit.shdl",
keep_library=True,
library_dir="./build"
)
Debugging with SHDB
For interactive debugging with breakpoints, signal inspection, and waveforms, see the SHDB Debugger documentation.
Quick example:
# Compile with debug info
shdlc -g circuit.shdl -c -o libcircuit.dylib
# Start debugger
shdb libcircuit.dylib
Error Handling
from SHDL import Circuit, CompilationError, SimulationError
try:
with Circuit("broken.shdl") as circuit:
circuit.step()
except CompilationError as e:
print(f"Failed to compile: {e}")
except SimulationError as e:
print(f"Simulation error: {e}")
Low-Level API
For more control, use the individual modules:
from SHDL import parse_file, Flattener, format_base_shdl
# Parse SHDL file
module = parse_file("circuit.shdl")
# Flatten to Base SHDL
flattener = Flattener()
flattener.load_file("circuit.shdl")
base_shdl = flattener.flatten_to_base_shdl("MyComponent")
# Print the flattened result
print(format_base_shdl(base_shdl))