PySHDL 0.1.3__tar.gz → 0.1.4__tar.gz
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-0.1.3 → pyshdl-0.1.4}/.gitignore +3 -1
- {pyshdl-0.1.3 → pyshdl-0.1.4}/DOCS.md +66 -0
- {pyshdl-0.1.3 → pyshdl-0.1.4}/PKG-INFO +3 -2
- {pyshdl-0.1.3 → pyshdl-0.1.4}/README.md +2 -1
- pyshdl-0.1.4/examples/SHDL_components/compare7.shdl +23 -0
- {pyshdl-0.1.3 → pyshdl-0.1.4}/examples/interacting.py +11 -1
- {pyshdl-0.1.3 → pyshdl-0.1.4}/pyproject.toml +1 -1
- {pyshdl-0.1.3 → pyshdl-0.1.4}/src/PySHDL/__init__.py +2 -2
- {pyshdl-0.1.3 → pyshdl-0.1.4}/src/PySHDL/shdlc.py +134 -1
- {pyshdl-0.1.3 → pyshdl-0.1.4}/uv.lock +2 -2
- {pyshdl-0.1.3 → pyshdl-0.1.4}/C_API.md +0 -0
- {pyshdl-0.1.3 → pyshdl-0.1.4}/examples/SHDL_components/addSub16.shdl +0 -0
- {pyshdl-0.1.3 → pyshdl-0.1.4}/examples/SHDL_components/fullAdder.shdl +0 -0
- {pyshdl-0.1.3 → pyshdl-0.1.4}/examples/SHDL_components/reg16.shdl +0 -0
- {pyshdl-0.1.3 → pyshdl-0.1.4}/src/PySHDL/circuit.py +0 -0
- {pyshdl-0.1.3 → pyshdl-0.1.4}/src/PySHDL/cli.py +0 -0
- {pyshdl-0.1.3 → pyshdl-0.1.4}/src/PySHDL/py.typed +0 -0
|
@@ -191,6 +191,72 @@ A[1] # LSB of A
|
|
|
191
191
|
A[8] # MSB of 8-bit A
|
|
192
192
|
```
|
|
193
193
|
|
|
194
|
+
### Constants
|
|
195
|
+
|
|
196
|
+
Constants allow you to define named values that can be used throughout your component definition. This makes your code more maintainable and easier to parameterize.
|
|
197
|
+
|
|
198
|
+
**Syntax:**
|
|
199
|
+
|
|
200
|
+
```shdl
|
|
201
|
+
CONSTANT_NAME = value;
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Where `value` can be:
|
|
205
|
+
- Decimal: `WIDTH = 16;`
|
|
206
|
+
- Binary: `MASK = 0b1111;`
|
|
207
|
+
- Hexadecimal: `FLAGS = 0xFF;`
|
|
208
|
+
|
|
209
|
+
**Rules:**
|
|
210
|
+
- Constant names must be all uppercase with underscores (e.g., `WIDTH`, `BUS_SIZE`, `NUM_GATES`)
|
|
211
|
+
- Any uppercase identifier followed by `= number;` is treated as a constant
|
|
212
|
+
- Constants must be declared inside the component body, before their use
|
|
213
|
+
- Constant values must be non-negative integers
|
|
214
|
+
- Constants are substituted before generator expansion
|
|
215
|
+
|
|
216
|
+
**Usage:**
|
|
217
|
+
|
|
218
|
+
Constants can be used in two ways:
|
|
219
|
+
|
|
220
|
+
1. **As scalar values** in connections (for constants 0 or 1):
|
|
221
|
+
```shdl
|
|
222
|
+
TRUE = 1;
|
|
223
|
+
FALSE = 0;
|
|
224
|
+
A -> gate1.A;
|
|
225
|
+
TRUE -> gate1.B; # Ties input B to constant 1
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
2. **As bit-indexed vectors** in connections:
|
|
229
|
+
```shdl
|
|
230
|
+
COMP = 7; # 0b111 in binary
|
|
231
|
+
COMP[1] -> gate1.A; # Bit 1 (LSB) of COMP
|
|
232
|
+
COMP[2] -> gate2.A; # Bit 2 of COMP
|
|
233
|
+
COMP[3] -> gate3.A; # Bit 3 (MSB) of COMP
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Example:**
|
|
237
|
+
|
|
238
|
+
```shdl
|
|
239
|
+
component ParamAdder(A[16], B[16], Cin) -> (Sum[16], Cout) {
|
|
240
|
+
WIDTH = 16;
|
|
241
|
+
LAST_BIT = 16;
|
|
242
|
+
|
|
243
|
+
# Create WIDTH full adders
|
|
244
|
+
>i[WIDTH]{
|
|
245
|
+
fa{i}: FullAdder;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
connect {
|
|
249
|
+
# ... connections using WIDTH and LAST_BIT ...
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**Use Cases:**
|
|
255
|
+
- Defining bus widths: `BUS_WIDTH = 32;`
|
|
256
|
+
- Setting array sizes: `>i[BUS_WIDTH]{ ... }`
|
|
257
|
+
- Parameterizing generator ranges: `>i[1, WIDTH]{ ... }`
|
|
258
|
+
- Making components more maintainable and reusable
|
|
259
|
+
|
|
194
260
|
---
|
|
195
261
|
|
|
196
262
|
## Imports and Modules
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PySHDL
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: SHDL (Simple Hardware Description Language) is a minimal HDL designed for creating digital circuits and easily simulating them. It compiles directly to C for maximum performance and portability. PySHDL is the Python interface for SHDL.
|
|
5
5
|
Project-URL: Homepage, https://github.com/rafa-rrayes/SHDL
|
|
6
6
|
Project-URL: Repository, https://github.com/rafa-rrayes/SHDL
|
|
@@ -20,6 +20,7 @@ A lightweight hardware description language and Python driver for digital circui
|
|
|
20
20
|
- ⚡ **C Backend** - Compiles to optimized C code for fast simulation and portability
|
|
21
21
|
- 🔧 **Command Line Tools** - Built-in compiler and utilities
|
|
22
22
|
- 📦 **Component Reuse** - Import and compose reusable circuit components
|
|
23
|
+
- 🔢 **Constants Support** - Use named constants for parameterizable designs
|
|
23
24
|
|
|
24
25
|
## Installation
|
|
25
26
|
|
|
@@ -128,7 +129,7 @@ See the `examples/` directory for more complete examples:
|
|
|
128
129
|
|
|
129
130
|
## Documentation
|
|
130
131
|
Github repository: [rafa-rrayes/SHDL](https://github.com/rafa-rrayes/SHDL)
|
|
131
|
-
For more detailed documentation, see [DOCS.md](DOCS.md)
|
|
132
|
+
For more detailed documentation, see [DOCS.md](https://github.com/rafa-rrayes/SHDL/blob/master/DOCS.md)
|
|
132
133
|
|
|
133
134
|
## Requirements
|
|
134
135
|
|
|
@@ -9,6 +9,7 @@ A lightweight hardware description language and Python driver for digital circui
|
|
|
9
9
|
- ⚡ **C Backend** - Compiles to optimized C code for fast simulation and portability
|
|
10
10
|
- 🔧 **Command Line Tools** - Built-in compiler and utilities
|
|
11
11
|
- 📦 **Component Reuse** - Import and compose reusable circuit components
|
|
12
|
+
- 🔢 **Constants Support** - Use named constants for parameterizable designs
|
|
12
13
|
|
|
13
14
|
## Installation
|
|
14
15
|
|
|
@@ -117,7 +118,7 @@ See the `examples/` directory for more complete examples:
|
|
|
117
118
|
|
|
118
119
|
## Documentation
|
|
119
120
|
Github repository: [rafa-rrayes/SHDL](https://github.com/rafa-rrayes/SHDL)
|
|
120
|
-
For more detailed documentation, see [DOCS.md](DOCS.md)
|
|
121
|
+
For more detailed documentation, see [DOCS.md](https://github.com/rafa-rrayes/SHDL/blob/master/DOCS.md)
|
|
121
122
|
|
|
122
123
|
## Requirements
|
|
123
124
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
use stdgates::{AND};
|
|
2
|
+
|
|
3
|
+
component Comp7(A[3]) -> (True) {
|
|
4
|
+
COMP = 7; # 0b111
|
|
5
|
+
|
|
6
|
+
>i[3]{
|
|
7
|
+
and{i}: AND;
|
|
8
|
+
}
|
|
9
|
+
and_out1: AND;
|
|
10
|
+
and_out2: AND;
|
|
11
|
+
|
|
12
|
+
connect {
|
|
13
|
+
>i[3]{
|
|
14
|
+
A[{i}] -> and{i}.A;
|
|
15
|
+
COMP[{i}] -> and{i}.B;
|
|
16
|
+
}
|
|
17
|
+
and1.O -> and_out1.A;
|
|
18
|
+
and2.O -> and_out1.B;
|
|
19
|
+
and_out1.O -> and_out2.A;
|
|
20
|
+
and3.O -> and_out2.B;
|
|
21
|
+
and_out2.O -> True;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
from PySHDL import Circuit
|
|
2
2
|
|
|
3
|
+
def test_comp7():
|
|
4
|
+
circuit = Circuit("examples/SHDL_components/compare7.shdl")
|
|
5
|
+
|
|
6
|
+
for val in range(8):
|
|
7
|
+
circuit.poke("A", val)
|
|
8
|
+
circuit.step(10)
|
|
9
|
+
result = circuit.peek("True")
|
|
10
|
+
print(f"Compare7 input: {val}, output: {result}")
|
|
11
|
+
|
|
3
12
|
def test_register16():
|
|
4
13
|
circuit = Circuit("examples/SHDL_components/reg16.shdl")
|
|
5
14
|
|
|
@@ -37,4 +46,5 @@ if __name__ == "__main__":
|
|
|
37
46
|
result = circuit.peek("Sum")
|
|
38
47
|
|
|
39
48
|
print(f"{A} + {B} + {Cin} = {result}, expected {A + B + Cin}")
|
|
40
|
-
test_register16()
|
|
49
|
+
test_register16()
|
|
50
|
+
test_comp7()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "PySHDL"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.4"
|
|
4
4
|
description = "SHDL (Simple Hardware Description Language) is a minimal HDL designed for creating digital circuits and easily simulating them. It compiles directly to C for maximum performance and portability. PySHDL is the Python interface for SHDL."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""shdl library."""
|
|
2
2
|
|
|
3
3
|
from importlib import import_module
|
|
4
4
|
from importlib.metadata import PackageNotFoundError, version
|
|
@@ -16,6 +16,6 @@ __all__ = [
|
|
|
16
16
|
"__version__",
|
|
17
17
|
]
|
|
18
18
|
try:
|
|
19
|
-
__version__ = version("
|
|
19
|
+
__version__ = version("shdl")
|
|
20
20
|
except PackageNotFoundError:
|
|
21
21
|
__version__ = "0.0.0"
|
|
@@ -59,6 +59,7 @@ class Component:
|
|
|
59
59
|
instances: List[Instance] = field(default_factory=list)
|
|
60
60
|
connections: List[Tuple[str, str]] = field(default_factory=list) # (from, to)
|
|
61
61
|
imports: Dict[str, List[str]] = field(default_factory=dict) # module -> [components]
|
|
62
|
+
constants: Dict[str, int] = field(default_factory=dict) # constant_name -> value
|
|
62
63
|
|
|
63
64
|
|
|
64
65
|
class SHDLParser:
|
|
@@ -142,6 +143,9 @@ class SHDLParser:
|
|
|
142
143
|
# Parse outputs
|
|
143
144
|
comp.outputs = self._parse_ports(outputs_str, is_input=False)
|
|
144
145
|
|
|
146
|
+
# Parse constants
|
|
147
|
+
comp.constants = self._parse_constants(body)
|
|
148
|
+
|
|
145
149
|
# Parse body (instances and connections)
|
|
146
150
|
self._parse_body(comp, body)
|
|
147
151
|
|
|
@@ -171,11 +175,81 @@ class SHDLParser:
|
|
|
171
175
|
|
|
172
176
|
return ports
|
|
173
177
|
|
|
178
|
+
def _parse_constants(self, body: str) -> Dict[str, int]:
|
|
179
|
+
"""
|
|
180
|
+
Parse constant declarations from component body.
|
|
181
|
+
Syntax: NAME = value;
|
|
182
|
+
Example: WIDTH = 16; or COMP = 0b111; or MASK = 0xFF;
|
|
183
|
+
Constants are identified by uppercase names.
|
|
184
|
+
Supports decimal, binary (0b), and hexadecimal (0x) literals.
|
|
185
|
+
"""
|
|
186
|
+
constants = {}
|
|
187
|
+
|
|
188
|
+
# Match: UPPERCASE_NAME = value;
|
|
189
|
+
# Constants must be all uppercase with optional underscores
|
|
190
|
+
# Value can be: decimal (123), binary (0b1010), or hex (0xAF)
|
|
191
|
+
pattern = r'([A-Z_][A-Z0-9_]*)\s*=\s*(0[bB][01]+|0[xX][0-9a-fA-F]+|\d+)\s*;'
|
|
192
|
+
|
|
193
|
+
for match in re.finditer(pattern, body):
|
|
194
|
+
name = match.group(1)
|
|
195
|
+
value_str = match.group(2)
|
|
196
|
+
|
|
197
|
+
# Parse the value based on prefix
|
|
198
|
+
if value_str.startswith(('0b', '0B')):
|
|
199
|
+
value = int(value_str[2:], 2) # Binary
|
|
200
|
+
elif value_str.startswith(('0x', '0X')):
|
|
201
|
+
value = int(value_str[2:], 16) # Hexadecimal
|
|
202
|
+
else:
|
|
203
|
+
value = int(value_str) # Decimal
|
|
204
|
+
|
|
205
|
+
constants[name] = value
|
|
206
|
+
|
|
207
|
+
return constants
|
|
208
|
+
|
|
209
|
+
def _add_constant_drivers(self, comp: Component, body: str):
|
|
210
|
+
"""
|
|
211
|
+
Build a mapping of constant bit signals to their values (VCC/GND).
|
|
212
|
+
This will be used to resolve references during connection parsing.
|
|
213
|
+
"""
|
|
214
|
+
# Find all synthetic constant bit signals in the body
|
|
215
|
+
const_bit_pattern = r'__const_([A-Z_][A-Z0-9_]*)_bit_(\d+)'
|
|
216
|
+
matches = re.findall(const_bit_pattern, body)
|
|
217
|
+
|
|
218
|
+
# Build mapping: signal_name -> '__VCC' or '__GND'
|
|
219
|
+
const_bit_map = {}
|
|
220
|
+
|
|
221
|
+
for const_name, bit_str in matches:
|
|
222
|
+
bit_index = int(bit_str)
|
|
223
|
+
|
|
224
|
+
# Get the constant value
|
|
225
|
+
if const_name not in comp.constants:
|
|
226
|
+
continue
|
|
227
|
+
|
|
228
|
+
const_value = comp.constants[const_name]
|
|
229
|
+
|
|
230
|
+
# Extract the bit value (1-indexed, LSB is bit 1)
|
|
231
|
+
bit_value = (const_value >> (bit_index - 1)) & 1
|
|
232
|
+
|
|
233
|
+
# Create a signal name for this constant bit
|
|
234
|
+
signal_name = f'__const_{const_name}_bit_{bit_index}'
|
|
235
|
+
|
|
236
|
+
# Map to VCC or GND
|
|
237
|
+
const_bit_map[signal_name] = '__VCC' if bit_value == 1 else '__GND'
|
|
238
|
+
|
|
239
|
+
# Store the mapping in the component for use during connection parsing
|
|
240
|
+
comp.constants_bit_map = const_bit_map
|
|
241
|
+
|
|
174
242
|
def _parse_body(self, comp: Component, body: str):
|
|
175
243
|
"""Parse component body (instances and connections)."""
|
|
176
|
-
#
|
|
244
|
+
# Substitute constants first
|
|
245
|
+
body = self._substitute_constants(body, comp.constants)
|
|
246
|
+
|
|
247
|
+
# Expand generators
|
|
177
248
|
body = self._expand_generators(body)
|
|
178
249
|
|
|
250
|
+
# Add constant bit drivers AFTER generator expansion
|
|
251
|
+
self._add_constant_drivers(comp, body)
|
|
252
|
+
|
|
179
253
|
# Split into instances and connections
|
|
180
254
|
connect_match = re.search(r'connect\s*\{(.*)\}', body, re.DOTALL)
|
|
181
255
|
|
|
@@ -192,6 +266,44 @@ class SHDLParser:
|
|
|
192
266
|
# Parse connections
|
|
193
267
|
self._parse_connections(comp, connections_part)
|
|
194
268
|
|
|
269
|
+
def _substitute_constants(self, body: str, constants: Dict[str, int]) -> str:
|
|
270
|
+
"""
|
|
271
|
+
Substitute constant names with their values in the body.
|
|
272
|
+
Constants can be used:
|
|
273
|
+
1. As scalar values in connections: TRUE -> a1.B; (becomes __VCC or __GND)
|
|
274
|
+
2. As bit-indexed vectors: COMP[{i}] -> gate.A; (becomes __const_COMP_bit_N)
|
|
275
|
+
"""
|
|
276
|
+
# Remove constant declarations from the body
|
|
277
|
+
body = re.sub(r'[A-Z_][A-Z0-9_]*\s*=\s*(?:0[bB][01]+|0[xX][0-9a-fA-F]+|\d+)\s*;', '', body)
|
|
278
|
+
|
|
279
|
+
# For each constant, we need to handle two cases:
|
|
280
|
+
# 1. Direct usage: CONSTANT_NAME (not followed by [)
|
|
281
|
+
# 2. Bit-indexed usage: CONSTANT_NAME[index]
|
|
282
|
+
|
|
283
|
+
for const_name, const_value in constants.items():
|
|
284
|
+
# Case 1: Bit-indexed usage CONST[N] - convert to synthetic signal name
|
|
285
|
+
# CONST[1] becomes __const_CONST_bit_1, CONST[2] becomes __const_CONST_bit_2, etc.
|
|
286
|
+
def replace_bit_index(match):
|
|
287
|
+
bit_index = match.group(1)
|
|
288
|
+
return f'__const_{const_name}_bit_{bit_index}'
|
|
289
|
+
|
|
290
|
+
# Replace CONST[...] with synthetic signal name
|
|
291
|
+
body = re.sub(rf'\b{const_name}\[([^\]]+)\]', replace_bit_index, body)
|
|
292
|
+
|
|
293
|
+
# Case 2: Direct scalar usage - replace with __VCC or __GND for 0/1,
|
|
294
|
+
# or leave as numeric value for other constants
|
|
295
|
+
# Use word boundaries to avoid partial matches
|
|
296
|
+
if const_value == 0:
|
|
297
|
+
body = re.sub(r'\b' + const_name + r'\b', '__GND', body)
|
|
298
|
+
elif const_value == 1:
|
|
299
|
+
body = re.sub(r'\b' + const_name + r'\b', '__VCC', body)
|
|
300
|
+
else:
|
|
301
|
+
# For other values, replace with the numeric value
|
|
302
|
+
# This is useful for parameterization in generators
|
|
303
|
+
body = re.sub(r'\b' + const_name + r'\b', str(const_value), body)
|
|
304
|
+
|
|
305
|
+
return body
|
|
306
|
+
|
|
195
307
|
def _expand_generators(self, body: str) -> str:
|
|
196
308
|
"""
|
|
197
309
|
Expand generator syntax:
|
|
@@ -269,9 +381,19 @@ class SHDLParser:
|
|
|
269
381
|
# Match: signal -> port;
|
|
270
382
|
pattern = r'([^\s;]+)\s*->\s*([^\s;]+)\s*;'
|
|
271
383
|
|
|
384
|
+
# Get the constant bit mapping if it exists
|
|
385
|
+
const_bit_map = getattr(comp, 'constants_bit_map', {})
|
|
386
|
+
|
|
272
387
|
for match in re.finditer(pattern, connections_str):
|
|
273
388
|
from_sig = match.group(1).strip()
|
|
274
389
|
to_sig = match.group(2).strip()
|
|
390
|
+
|
|
391
|
+
# Resolve constant bit signals to VCC/GND
|
|
392
|
+
if from_sig in const_bit_map:
|
|
393
|
+
from_sig = const_bit_map[from_sig]
|
|
394
|
+
if to_sig in const_bit_map:
|
|
395
|
+
to_sig = const_bit_map[to_sig]
|
|
396
|
+
|
|
275
397
|
comp.connections.append((from_sig, to_sig))
|
|
276
398
|
|
|
277
399
|
def _load_imports(self, comp: Component, base_path: Path):
|
|
@@ -666,6 +788,17 @@ def generate_c_code(component: Component) -> str:
|
|
|
666
788
|
# constant zero for unconnected pins?
|
|
667
789
|
if src == CONST_ZERO:
|
|
668
790
|
return '0u'
|
|
791
|
+
# VCC (constant 1)?
|
|
792
|
+
if src == '__VCC':
|
|
793
|
+
return '1u'
|
|
794
|
+
# GND (constant 0)?
|
|
795
|
+
if src == '__GND':
|
|
796
|
+
return '0u'
|
|
797
|
+
# Synthetic constant bit signal?
|
|
798
|
+
if src.startswith('__const_'):
|
|
799
|
+
# These should have been resolved to __VCC or __GND via connections
|
|
800
|
+
# but if we see them here directly, treat as error
|
|
801
|
+
raise ValueError(f'Unresolved constant signal: {src}')
|
|
669
802
|
# input bit?
|
|
670
803
|
if is_bit(src):
|
|
671
804
|
base, idx = parse_bit(src)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|