PySHDL 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- PySHDL/__init__.py +21 -0
- PySHDL/circuit.py +180 -0
- PySHDL/cli.py +191 -0
- PySHDL/py.typed +0 -0
- PySHDL/shdlc.py +1011 -0
- pyshdl-0.1.0.dist-info/METADATA +142 -0
- pyshdl-0.1.0.dist-info/RECORD +9 -0
- pyshdl-0.1.0.dist-info/WHEEL +4 -0
- pyshdl-0.1.0.dist-info/entry_points.txt +2 -0
PySHDL/__init__.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""shdl library."""
|
|
2
|
+
|
|
3
|
+
from importlib import import_module
|
|
4
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
5
|
+
|
|
6
|
+
from .circuit import Circuit
|
|
7
|
+
from .shdlc import Component, Instance, Port, SHDLParser, generate_c_code
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"Circuit",
|
|
11
|
+
"Component",
|
|
12
|
+
"Instance",
|
|
13
|
+
"Port",
|
|
14
|
+
"SHDLParser",
|
|
15
|
+
"generate_c_code",
|
|
16
|
+
"__version__",
|
|
17
|
+
]
|
|
18
|
+
try:
|
|
19
|
+
__version__ = version("shdl")
|
|
20
|
+
except PackageNotFoundError:
|
|
21
|
+
__version__ = "0.0.0"
|
PySHDL/circuit.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SHDL Driver - Simple Python interface for SHDL circuits
|
|
3
|
+
========================================================
|
|
4
|
+
|
|
5
|
+
A minimal module for compiling and driving SHDL circuits from Python.
|
|
6
|
+
Provides a clean, Pythonic API for circuit simulation.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
from shdl_driver import SHDLCircuit
|
|
10
|
+
|
|
11
|
+
# Load and compile a circuit
|
|
12
|
+
circuit = SHDLCircuit("adder16.shdl")
|
|
13
|
+
|
|
14
|
+
# Set inputs
|
|
15
|
+
circuit.poke("A", 42)
|
|
16
|
+
circuit.poke("B", 17)
|
|
17
|
+
circuit.poke("Cin", 1)
|
|
18
|
+
|
|
19
|
+
# Read outputs
|
|
20
|
+
result = circuit.peek("Sum")
|
|
21
|
+
print(f"42 + 17 + 1 = {result}")
|
|
22
|
+
|
|
23
|
+
# Advance simulation time
|
|
24
|
+
circuit.step(1)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import ctypes
|
|
28
|
+
import os
|
|
29
|
+
import subprocess
|
|
30
|
+
import tempfile
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from typing import Dict, Optional, Union
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Circuit:
|
|
36
|
+
"""
|
|
37
|
+
Interface for driving SHDL circuits from Python.
|
|
38
|
+
|
|
39
|
+
Compiles SHDL to C, builds a shared library, and provides
|
|
40
|
+
Python wrappers for the circuit API.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, shdl_file: Union[str, Path], search_paths: Optional[list] = None):
|
|
44
|
+
"""
|
|
45
|
+
Load and compile a SHDL circuit.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
shdl_file: Path to the .shdl file
|
|
49
|
+
search_paths: Optional list of directories to search for imports
|
|
50
|
+
"""
|
|
51
|
+
self.shdl_file = Path(shdl_file)
|
|
52
|
+
if not self.shdl_file.exists():
|
|
53
|
+
raise FileNotFoundError(f"SHDL file not found: {shdl_file}")
|
|
54
|
+
|
|
55
|
+
# Default search path includes SHDL_components if it exists
|
|
56
|
+
if search_paths is None:
|
|
57
|
+
search_paths = []
|
|
58
|
+
component_dir = self.shdl_file.parent / "SHDL_components"
|
|
59
|
+
if component_dir.exists():
|
|
60
|
+
search_paths.append(str(component_dir))
|
|
61
|
+
|
|
62
|
+
self.search_paths = search_paths
|
|
63
|
+
self._lib = None
|
|
64
|
+
self._compile_and_load()
|
|
65
|
+
|
|
66
|
+
def _compile_and_load(self):
|
|
67
|
+
"""Compile SHDL to C and load the shared library."""
|
|
68
|
+
# Generate C code
|
|
69
|
+
c_file = self.shdl_file.with_suffix('.c')
|
|
70
|
+
self._compile_shdl_to_c(c_file)
|
|
71
|
+
|
|
72
|
+
# Build shared library
|
|
73
|
+
so_file = self._build_shared_library(c_file)
|
|
74
|
+
|
|
75
|
+
# Load library
|
|
76
|
+
self._lib = ctypes.CDLL(str(so_file))
|
|
77
|
+
|
|
78
|
+
# Set up function signatures
|
|
79
|
+
self._lib.reset.argtypes = []
|
|
80
|
+
self._lib.reset.restype = None
|
|
81
|
+
|
|
82
|
+
self._lib.poke.argtypes = [ctypes.c_char_p, ctypes.c_uint64]
|
|
83
|
+
self._lib.poke.restype = None
|
|
84
|
+
|
|
85
|
+
self._lib.peek.argtypes = [ctypes.c_char_p]
|
|
86
|
+
self._lib.peek.restype = ctypes.c_uint64
|
|
87
|
+
|
|
88
|
+
self._lib.step.argtypes = [ctypes.c_int]
|
|
89
|
+
self._lib.step.restype = None
|
|
90
|
+
|
|
91
|
+
# Initialize circuit
|
|
92
|
+
self.reset()
|
|
93
|
+
|
|
94
|
+
def _compile_shdl_to_c(self, output_file: Path):
|
|
95
|
+
"""Compile SHDL file to C using shdlc compiler."""
|
|
96
|
+
# Look for shdlc in the same directory as the SHDL file
|
|
97
|
+
from .shdlc import generate_c_code, SHDLParser
|
|
98
|
+
shdl_parser = SHDLParser(self.search_paths)
|
|
99
|
+
component = shdl_parser.parse_file(self.shdl_file)
|
|
100
|
+
component = shdl_parser.flatten_all_levels(component)
|
|
101
|
+
c_code = generate_c_code(component)
|
|
102
|
+
with open(output_file, 'w') as f:
|
|
103
|
+
f.write(c_code)
|
|
104
|
+
|
|
105
|
+
def _build_shared_library(self, c_file: Path) -> Path:
|
|
106
|
+
"""Build a shared library from C code."""
|
|
107
|
+
so_file = c_file.with_suffix('.so')
|
|
108
|
+
|
|
109
|
+
cmd = [
|
|
110
|
+
"gcc",
|
|
111
|
+
"-shared",
|
|
112
|
+
"-fPIC",
|
|
113
|
+
"-O2",
|
|
114
|
+
"-o", str(so_file),
|
|
115
|
+
"-xc", str(c_file)
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
subprocess.run(cmd, check=True, capture_output=True, text=True)
|
|
120
|
+
except subprocess.CalledProcessError as e:
|
|
121
|
+
raise RuntimeError(
|
|
122
|
+
f"Failed to build shared library:\n{e.stderr}"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
return so_file
|
|
126
|
+
|
|
127
|
+
def reset(self):
|
|
128
|
+
"""Reset all circuit state to zero."""
|
|
129
|
+
self._lib.reset()
|
|
130
|
+
|
|
131
|
+
def poke(self, signal: str, value: int):
|
|
132
|
+
"""
|
|
133
|
+
Set an input signal to a specific value.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
signal: Name of the input signal
|
|
137
|
+
value: Integer value to set
|
|
138
|
+
"""
|
|
139
|
+
self._lib.poke(signal.encode('utf-8'), value)
|
|
140
|
+
|
|
141
|
+
def peek(self, signal: str) -> int:
|
|
142
|
+
"""
|
|
143
|
+
Read the current value of a signal.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
signal: Name of the signal (input or output)
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Current value as an integer
|
|
150
|
+
"""
|
|
151
|
+
return self._lib.peek(signal.encode('utf-8'))
|
|
152
|
+
|
|
153
|
+
def step(self, cycles: int = 1):
|
|
154
|
+
"""
|
|
155
|
+
Advance simulation by the specified number of cycles.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
cycles: Number of clock cycles to advance (default: 1)
|
|
159
|
+
"""
|
|
160
|
+
self._lib.step(cycles)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def test(self, inputs: Dict[str, int], steps: int) -> Dict[str, int]:
|
|
164
|
+
"""
|
|
165
|
+
Convenience method: set inputs and return all outputs.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
inputs: Dictionary mapping signal names to values
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
Dictionary with the same keys containing output values
|
|
172
|
+
"""
|
|
173
|
+
# Set all inputs
|
|
174
|
+
for signal, value in inputs.items():
|
|
175
|
+
self.poke(signal, value)
|
|
176
|
+
|
|
177
|
+
self.step(steps)
|
|
178
|
+
|
|
179
|
+
# Read back the same signals
|
|
180
|
+
return {signal: self.peek(signal) for signal in inputs.keys()}
|
PySHDL/cli.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
SHDL Compiler - Command-line interface
|
|
4
|
+
Usage: shdlc [options] <input.shdl>
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from .shdlc import SHDLParser, generate_c_code
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main() -> int:
|
|
16
|
+
parser = argparse.ArgumentParser(
|
|
17
|
+
description='SHDL Compiler - Compile SHDL hardware description files to C',
|
|
18
|
+
usage='%(prog)s [options] <input.shdl>'
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
parser.add_argument(
|
|
22
|
+
'input',
|
|
23
|
+
help='Input SHDL file'
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
'-o', '--output',
|
|
28
|
+
help='Output C file (default: <input>.c)',
|
|
29
|
+
default=None
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
parser.add_argument(
|
|
33
|
+
'-I', '--include',
|
|
34
|
+
action='append',
|
|
35
|
+
dest='include_paths',
|
|
36
|
+
help='Add directory to component search path',
|
|
37
|
+
default=[]
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
parser.add_argument(
|
|
41
|
+
'-c', '--compile-only',
|
|
42
|
+
action='store_true',
|
|
43
|
+
help='Generate C code only, do not compile to binary'
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
parser.add_argument(
|
|
47
|
+
'-O', '--optimize',
|
|
48
|
+
choices=['0', '1', '2', '3'],
|
|
49
|
+
default='3',
|
|
50
|
+
help='GCC optimization level (default: 3)'
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
parser.add_argument(
|
|
54
|
+
'--no-flatten',
|
|
55
|
+
action='store_true',
|
|
56
|
+
help='Do not flatten component hierarchy'
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
parser.add_argument(
|
|
60
|
+
'-v', '--verbose',
|
|
61
|
+
action='store_true',
|
|
62
|
+
help='Verbose output'
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
args = parser.parse_args()
|
|
66
|
+
|
|
67
|
+
# Validate input file
|
|
68
|
+
input_path = Path(args.input)
|
|
69
|
+
if not input_path.exists():
|
|
70
|
+
print(f"shdlc: error: {args.input}: No such file", file=sys.stderr)
|
|
71
|
+
return 1
|
|
72
|
+
|
|
73
|
+
if not input_path.suffix == '.shdl':
|
|
74
|
+
print(f"shdlc: warning: {args.input}: File does not have .shdl extension", file=sys.stderr)
|
|
75
|
+
|
|
76
|
+
# Determine output file
|
|
77
|
+
if args.output:
|
|
78
|
+
output_c = Path(args.output)
|
|
79
|
+
if not output_c.suffix == '.c' and not args.compile_only:
|
|
80
|
+
# Binary output
|
|
81
|
+
output_bin = output_c
|
|
82
|
+
output_c = input_path.with_suffix('.c')
|
|
83
|
+
else:
|
|
84
|
+
output_bin = output_c.with_suffix('')
|
|
85
|
+
else:
|
|
86
|
+
output_c = input_path.with_suffix('.c')
|
|
87
|
+
output_bin = input_path.with_suffix('')
|
|
88
|
+
|
|
89
|
+
# Setup search paths
|
|
90
|
+
search_paths = [input_path.parent]
|
|
91
|
+
|
|
92
|
+
# Add SHDL_components if it exists
|
|
93
|
+
default_lib = input_path.parent / "SHDL_components"
|
|
94
|
+
if default_lib.exists():
|
|
95
|
+
search_paths.append(default_lib)
|
|
96
|
+
|
|
97
|
+
# Add user-specified include paths
|
|
98
|
+
for inc_path in args.include_paths:
|
|
99
|
+
search_paths.append(Path(inc_path))
|
|
100
|
+
|
|
101
|
+
if args.verbose:
|
|
102
|
+
print(f"Input: {input_path}")
|
|
103
|
+
print(f"Output C: {output_c}")
|
|
104
|
+
if not args.compile_only:
|
|
105
|
+
print(f"Output binary: {output_bin}")
|
|
106
|
+
print(f"Search paths: {[str(p) for p in search_paths]}")
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
# Parse SHDL file
|
|
110
|
+
if args.verbose:
|
|
111
|
+
print("Parsing SHDL file...")
|
|
112
|
+
|
|
113
|
+
shdl_parser = SHDLParser(search_paths)
|
|
114
|
+
component = shdl_parser.parse_file(input_path)
|
|
115
|
+
|
|
116
|
+
if args.verbose:
|
|
117
|
+
print(f" Component: {component.name}")
|
|
118
|
+
print(f" Instances: {len(component.instances)}")
|
|
119
|
+
print(f" Connections: {len(component.connections)}")
|
|
120
|
+
|
|
121
|
+
# Flatten component hierarchy
|
|
122
|
+
if not args.no_flatten:
|
|
123
|
+
if args.verbose:
|
|
124
|
+
print("Flattening component hierarchy...")
|
|
125
|
+
|
|
126
|
+
component = shdl_parser.flatten_all_levels(component)
|
|
127
|
+
|
|
128
|
+
if args.verbose:
|
|
129
|
+
print(f" Instances after flattening: {len(component.instances)}")
|
|
130
|
+
print(f" Connections after flattening: {len(component.connections)}")
|
|
131
|
+
print(f" Gate types: {set(i.component_type for i in component.instances)}")
|
|
132
|
+
|
|
133
|
+
# Generate C code
|
|
134
|
+
if args.verbose:
|
|
135
|
+
print("Generating C code...")
|
|
136
|
+
|
|
137
|
+
c_code = generate_c_code(component)
|
|
138
|
+
|
|
139
|
+
with open(output_c, 'w', encoding='utf-8') as file_handle:
|
|
140
|
+
file_handle.write(c_code)
|
|
141
|
+
|
|
142
|
+
if args.verbose:
|
|
143
|
+
print(f" Generated {output_c} ({len(c_code)} bytes)")
|
|
144
|
+
|
|
145
|
+
# Compile to binary
|
|
146
|
+
if not args.compile_only:
|
|
147
|
+
if args.verbose:
|
|
148
|
+
print("Compiling C code to binary...")
|
|
149
|
+
|
|
150
|
+
# gcc -shared -fPIC -O3 your_design.c -o your_design.so
|
|
151
|
+
gcc_cmd = [
|
|
152
|
+
'gcc',
|
|
153
|
+
'-shared',
|
|
154
|
+
'-fPIC',
|
|
155
|
+
f'-O{args.optimize}',
|
|
156
|
+
str(output_c),
|
|
157
|
+
'-o', str(output_bin)+".so"
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
if args.verbose:
|
|
161
|
+
print(f" Command: {' '.join(gcc_cmd)}")
|
|
162
|
+
|
|
163
|
+
result = subprocess.run(gcc_cmd, capture_output=True, text=True)
|
|
164
|
+
|
|
165
|
+
if result.returncode != 0:
|
|
166
|
+
print(f"shdlc: error: gcc compilation failed", file=sys.stderr)
|
|
167
|
+
if result.stderr:
|
|
168
|
+
print(result.stderr, file=sys.stderr)
|
|
169
|
+
return 1
|
|
170
|
+
|
|
171
|
+
if args.verbose:
|
|
172
|
+
print(f" Generated binary: {output_bin}")
|
|
173
|
+
|
|
174
|
+
if not args.verbose:
|
|
175
|
+
# Mimic gcc's quiet success
|
|
176
|
+
pass
|
|
177
|
+
else:
|
|
178
|
+
print("Done!")
|
|
179
|
+
|
|
180
|
+
return 0
|
|
181
|
+
|
|
182
|
+
except Exception as e:
|
|
183
|
+
print(f"shdlc: error: {e}", file=sys.stderr)
|
|
184
|
+
if args.verbose:
|
|
185
|
+
import traceback
|
|
186
|
+
traceback.print_exc()
|
|
187
|
+
return 1
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
if __name__ == '__main__':
|
|
191
|
+
sys.exit(main())
|
PySHDL/py.typed
ADDED
|
File without changes
|