PySHDL 0.1.3__tar.gz → 0.1.5__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.5}/.gitignore +3 -1
- {pyshdl-0.1.3 → pyshdl-0.1.5}/DOCS.md +66 -0
- {pyshdl-0.1.3 → pyshdl-0.1.5}/PKG-INFO +3 -2
- {pyshdl-0.1.3 → pyshdl-0.1.5}/README.md +2 -1
- pyshdl-0.1.5/examples/SHDL_components/compare7.shdl +23 -0
- {pyshdl-0.1.3 → pyshdl-0.1.5}/examples/interacting.py +11 -1
- {pyshdl-0.1.3 → pyshdl-0.1.5}/pyproject.toml +1 -1
- {pyshdl-0.1.3 → pyshdl-0.1.5}/src/PySHDL/__init__.py +2 -2
- {pyshdl-0.1.3 → pyshdl-0.1.5}/src/PySHDL/shdlc.py +246 -42
- {pyshdl-0.1.3 → pyshdl-0.1.5}/uv.lock +2 -2
- {pyshdl-0.1.3 → pyshdl-0.1.5}/C_API.md +0 -0
- {pyshdl-0.1.3 → pyshdl-0.1.5}/examples/SHDL_components/addSub16.shdl +0 -0
- {pyshdl-0.1.3 → pyshdl-0.1.5}/examples/SHDL_components/fullAdder.shdl +0 -0
- {pyshdl-0.1.3 → pyshdl-0.1.5}/examples/SHDL_components/reg16.shdl +0 -0
- {pyshdl-0.1.3 → pyshdl-0.1.5}/src/PySHDL/circuit.py +0 -0
- {pyshdl-0.1.3 → pyshdl-0.1.5}/src/PySHDL/cli.py +0 -0
- {pyshdl-0.1.3 → pyshdl-0.1.5}/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.5
|
|
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.5"
|
|
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):
|
|
@@ -301,23 +423,52 @@ class SHDLParser:
|
|
|
301
423
|
|
|
302
424
|
# --- Helpers -------------------------------------------------------------
|
|
303
425
|
|
|
426
|
+
def parse_pin_ref(pin: str):
|
|
427
|
+
"""
|
|
428
|
+
Parse a pin reference into components.
|
|
429
|
+
Returns: (instance_name, port_name, bit_index) or (None, port_name, bit_index)
|
|
430
|
+
|
|
431
|
+
Examples:
|
|
432
|
+
'inv.A[1]' -> ('inv', 'A', 1)
|
|
433
|
+
'fa1.Cout' -> ('fa1', 'Cout', None)
|
|
434
|
+
'A[5]' -> (None, 'A', 5)
|
|
435
|
+
'Sum' -> (None, 'Sum', None)
|
|
436
|
+
"""
|
|
437
|
+
# Check for instance.port pattern
|
|
438
|
+
if '.' in pin:
|
|
439
|
+
inst_part, port_part = pin.split('.', 1)
|
|
440
|
+
# Check for bit index in port part
|
|
441
|
+
match = re.match(r'(\w+)\[(\d+)\]', port_part)
|
|
442
|
+
if match:
|
|
443
|
+
return (inst_part, match.group(1), int(match.group(2)))
|
|
444
|
+
else:
|
|
445
|
+
return (inst_part, port_part, None)
|
|
446
|
+
else:
|
|
447
|
+
# No instance, just port (possibly with bit index)
|
|
448
|
+
match = re.match(r'(\w+)\[(\d+)\]', pin)
|
|
449
|
+
if match:
|
|
450
|
+
return (None, match.group(1), int(match.group(2)))
|
|
451
|
+
else:
|
|
452
|
+
return (None, pin, None)
|
|
453
|
+
|
|
304
454
|
def ports_of(defn: "Component"):
|
|
305
455
|
return {p.name for p in defn.inputs}, {p.name for p in defn.outputs}
|
|
306
456
|
|
|
307
457
|
def output_drivers(defn: "Component"):
|
|
308
458
|
"""
|
|
309
|
-
Map child output port -> internal driver pin
|
|
459
|
+
Map child output port (with optional bit index) -> internal driver pin.
|
|
460
|
+
Examples: 'Sum' -> 'x2.O', 'Y[1]' -> 'g1.O'
|
|
310
461
|
"""
|
|
311
462
|
m = {}
|
|
312
463
|
for src, dst in defn.connections:
|
|
313
|
-
if '.' not in dst: # bare output port
|
|
464
|
+
if '.' not in dst: # bare output port (possibly with bit index)
|
|
314
465
|
m[dst] = src
|
|
315
466
|
return m
|
|
316
467
|
|
|
317
468
|
def prefixed_internal(pin: str, inst_name: str):
|
|
318
469
|
"""
|
|
319
470
|
Prefix internal instance pins: 'x1.O' -> 'fa1_x1.O'.
|
|
320
|
-
Leave bare ports ('A', 'Sum') unchanged (handled elsewhere).
|
|
471
|
+
Leave bare ports ('A', 'Sum', 'A[1]') unchanged (handled elsewhere).
|
|
321
472
|
"""
|
|
322
473
|
if '.' in pin:
|
|
323
474
|
inst, port = pin.split('.', 1)
|
|
@@ -325,8 +476,12 @@ class SHDLParser:
|
|
|
325
476
|
return pin
|
|
326
477
|
|
|
327
478
|
def is_abstract_pin(token: str):
|
|
328
|
-
|
|
329
|
-
|
|
479
|
+
"""
|
|
480
|
+
Check if token is an abstract pin reference (references an instance).
|
|
481
|
+
Examples: 'fa1.Cin', 'inv.A[1]' -> True
|
|
482
|
+
'A[1]', 'Sum' -> False
|
|
483
|
+
"""
|
|
484
|
+
return '.' in token
|
|
330
485
|
|
|
331
486
|
# Index original connections
|
|
332
487
|
conns_from = defaultdict(list)
|
|
@@ -357,38 +512,50 @@ class SHDLParser:
|
|
|
357
512
|
"out_drivers": out_drv,
|
|
358
513
|
}
|
|
359
514
|
|
|
360
|
-
# Global maps for substitution
|
|
515
|
+
# Global maps for substitution with bit-indexed support
|
|
361
516
|
# (1) Abstract OUTPUT -> concrete internal driver pin (prefixed)
|
|
362
517
|
abstract_out_to_driver = {}
|
|
363
518
|
for inst in instances_to_inline:
|
|
364
519
|
meta = inlined_meta[inst.name]
|
|
365
520
|
for outp, driver_pin in meta["out_drivers"].items():
|
|
366
|
-
|
|
367
|
-
|
|
521
|
+
# outp might be 'Y[1]' or 'Cout'
|
|
522
|
+
abstract = f"{inst.name}.{outp}"
|
|
523
|
+
concrete = prefixed_internal(driver_pin, inst.name)
|
|
368
524
|
abstract_out_to_driver[abstract] = concrete
|
|
369
525
|
|
|
370
526
|
def resolve_output_ref(token: str) -> str:
|
|
371
|
-
"""Replace 'inst.OutPort' with its internal driver pin
|
|
527
|
+
"""Replace 'inst.OutPort' or 'inst.OutPort[i]' with its internal driver pin."""
|
|
372
528
|
while token in abstract_out_to_driver:
|
|
373
529
|
token = abstract_out_to_driver[token]
|
|
374
530
|
return token
|
|
375
531
|
|
|
376
532
|
# (2) Abstract INPUT -> resolved external net (after output resolution)
|
|
533
|
+
# Now handles bit-indexed inputs like 'inv.A[1]'
|
|
377
534
|
abstract_in_to_net = {}
|
|
378
535
|
for inst in instances_to_inline:
|
|
379
536
|
meta = inlined_meta[inst.name]
|
|
537
|
+
|
|
538
|
+
# For each input port, check all possible bit-indexed references
|
|
380
539
|
for inp in meta["in_ports"]:
|
|
381
|
-
|
|
540
|
+
# First check non-indexed reference
|
|
541
|
+
abs_input = f"{inst.name}.{inp}"
|
|
382
542
|
if abs_input in conns_to and conns_to[abs_input]:
|
|
383
|
-
upstream = conns_to[abs_input][-1][0]
|
|
384
|
-
upstream = resolve_output_ref(upstream)
|
|
543
|
+
upstream = conns_to[abs_input][-1][0]
|
|
544
|
+
upstream = resolve_output_ref(upstream)
|
|
385
545
|
abstract_in_to_net[abs_input] = upstream
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
546
|
+
|
|
547
|
+
# Also check for bit-indexed references in the connections
|
|
548
|
+
for conn_dst in conns_to.keys():
|
|
549
|
+
inst_name, port_name, bit_idx = parse_pin_ref(conn_dst)
|
|
550
|
+
if inst_name == inst.name and port_name == inp and bit_idx is not None:
|
|
551
|
+
# This is a bit-indexed reference to this instance's input
|
|
552
|
+
if conns_to[conn_dst]:
|
|
553
|
+
upstream = conns_to[conn_dst][-1][0]
|
|
554
|
+
upstream = resolve_output_ref(upstream)
|
|
555
|
+
abstract_in_to_net[conn_dst] = upstream
|
|
389
556
|
|
|
390
557
|
def resolve_input_ref(token: str) -> str:
|
|
391
|
-
"""Replace 'inst.InPort' with its resolved external net
|
|
558
|
+
"""Replace 'inst.InPort' or 'inst.InPort[i]' with its resolved external net."""
|
|
392
559
|
seen = set()
|
|
393
560
|
while token in abstract_in_to_net and token not in seen:
|
|
394
561
|
seen.add(token)
|
|
@@ -400,10 +567,10 @@ class SHDLParser:
|
|
|
400
567
|
def is_inlined_abstract_input(dst: str) -> bool:
|
|
401
568
|
if not is_abstract_pin(dst):
|
|
402
569
|
return False
|
|
403
|
-
inst_name,
|
|
570
|
+
inst_name, port_name, bit_idx = parse_pin_ref(dst)
|
|
404
571
|
if inst_name not in inlined_meta:
|
|
405
572
|
return False
|
|
406
|
-
return
|
|
573
|
+
return port_name in inlined_meta[inst_name]["in_ports"]
|
|
407
574
|
|
|
408
575
|
# Bring in internals for each inlined child
|
|
409
576
|
for inst in instances_to_inline:
|
|
@@ -424,18 +591,31 @@ class SHDLParser:
|
|
|
424
591
|
if s not in merged_imports[lib]:
|
|
425
592
|
merged_imports[lib].append(s)
|
|
426
593
|
|
|
427
|
-
# (c) Recreate child's internal wiring with substitutions
|
|
428
|
-
#
|
|
429
|
-
#
|
|
430
|
-
|
|
431
|
-
|
|
594
|
+
# (c) Recreate child's internal wiring with substitutions
|
|
595
|
+
# Build input mapping: for each child input port (with possible bit index),
|
|
596
|
+
# find what it should map to from the parent's connections
|
|
597
|
+
input_external = {}
|
|
598
|
+
|
|
599
|
+
# Check direct port mappings (non-indexed)
|
|
600
|
+
for p in meta["in_ports"]:
|
|
601
|
+
abs_ref = f"{inst.name}.{p}"
|
|
602
|
+
if abs_ref in abstract_in_to_net:
|
|
603
|
+
input_external[p] = abstract_in_to_net[abs_ref]
|
|
604
|
+
|
|
605
|
+
# Check bit-indexed port mappings
|
|
606
|
+
for conn_dst in abstract_in_to_net.keys():
|
|
607
|
+
inst_name, port_name, bit_idx = parse_pin_ref(conn_dst)
|
|
608
|
+
if inst_name == inst.name and port_name in meta["in_ports"] and bit_idx is not None:
|
|
609
|
+
# Store with the bit index: 'A[1]' -> upstream net
|
|
610
|
+
port_with_idx = f"{port_name}[{bit_idx}]"
|
|
611
|
+
input_external[port_with_idx] = abstract_in_to_net[conn_dst]
|
|
432
612
|
|
|
433
613
|
for src, dst in child_def.connections:
|
|
434
614
|
# Resolve source
|
|
435
|
-
if '.' not in src: # child input port
|
|
615
|
+
if '.' not in src: # child input port (may have bit index)
|
|
436
616
|
upstream = input_external.get(src)
|
|
437
617
|
if upstream is None:
|
|
438
|
-
# Skip unconnected
|
|
618
|
+
# Skip unconnected
|
|
439
619
|
continue
|
|
440
620
|
flat_src = resolve_output_ref(upstream)
|
|
441
621
|
else:
|
|
@@ -449,20 +629,26 @@ class SHDLParser:
|
|
|
449
629
|
flat_dst = prefixed_internal(dst, inst.name)
|
|
450
630
|
new_connections.append((flat_src, flat_dst))
|
|
451
631
|
|
|
452
|
-
# (d) Rewire parent edges that referenced child OUTPUTS
|
|
453
|
-
# But do NOT create edges into abstract inputs of other inlined children
|
|
454
|
-
# (those will be realized by that child's own internal wiring above).
|
|
632
|
+
# (d) Rewire parent edges that referenced child OUTPUTS
|
|
455
633
|
for outp in meta["out_ports"]:
|
|
634
|
+
# Check non-indexed outputs
|
|
456
635
|
abs_out = f"{inst.name}.{outp}"
|
|
457
|
-
if abs_out
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
636
|
+
if abs_out in conns_from:
|
|
637
|
+
driver = resolve_output_ref(abs_out)
|
|
638
|
+
for (_src, dst) in conns_from[abs_out]:
|
|
639
|
+
if is_inlined_abstract_input(dst):
|
|
640
|
+
continue
|
|
641
|
+
new_connections.append((driver, dst))
|
|
642
|
+
|
|
643
|
+
# Check bit-indexed outputs
|
|
644
|
+
for conn_src in conns_from.keys():
|
|
645
|
+
inst_name, port_name, bit_idx = parse_pin_ref(conn_src)
|
|
646
|
+
if inst_name == inst.name and port_name == outp and bit_idx is not None:
|
|
647
|
+
driver = resolve_output_ref(conn_src)
|
|
648
|
+
for (_src, dst) in conns_from[conn_src]:
|
|
649
|
+
if is_inlined_abstract_input(dst):
|
|
650
|
+
continue
|
|
651
|
+
new_connections.append((driver, dst))
|
|
466
652
|
|
|
467
653
|
# Keep original parent connections that don't touch any abstract pins of inlined children
|
|
468
654
|
abstract_pins = set()
|
|
@@ -470,6 +656,13 @@ class SHDLParser:
|
|
|
470
656
|
meta = inlined_meta[inst.name]
|
|
471
657
|
for p in meta["in_ports"] | meta["out_ports"]:
|
|
472
658
|
abstract_pins.add(f"{inst.name}.{p}")
|
|
659
|
+
|
|
660
|
+
# Also add bit-indexed versions
|
|
661
|
+
for conn in parent.connections:
|
|
662
|
+
for pin in [conn[0], conn[1]]:
|
|
663
|
+
inst_name, port_name, bit_idx = parse_pin_ref(pin)
|
|
664
|
+
if inst_name and inst_name in inlined_meta:
|
|
665
|
+
abstract_pins.add(pin)
|
|
473
666
|
|
|
474
667
|
def touches_any_abstract(conn):
|
|
475
668
|
s, d = conn
|
|
@@ -483,15 +676,15 @@ class SHDLParser:
|
|
|
483
676
|
finalized = []
|
|
484
677
|
for s, d in new_connections:
|
|
485
678
|
s2 = resolve_output_ref(s)
|
|
486
|
-
s2 = resolve_input_ref(s2)
|
|
487
|
-
d2 = resolve_output_ref(d)
|
|
679
|
+
s2 = resolve_input_ref(s2)
|
|
680
|
+
d2 = resolve_output_ref(d)
|
|
488
681
|
d2 = resolve_input_ref(d2)
|
|
489
|
-
# Drop any connection that still targets an abstract input
|
|
682
|
+
# Drop any connection that still targets an abstract input
|
|
490
683
|
if is_inlined_abstract_input(d2):
|
|
491
684
|
continue
|
|
492
685
|
finalized.append((s2, d2))
|
|
493
686
|
|
|
494
|
-
# Optional: prune imports of fully inlined component types
|
|
687
|
+
# Optional: prune imports of fully inlined component types
|
|
495
688
|
if merged_imports:
|
|
496
689
|
cleaned_imports = {}
|
|
497
690
|
for lib, syms in merged_imports.items():
|
|
@@ -666,6 +859,17 @@ def generate_c_code(component: Component) -> str:
|
|
|
666
859
|
# constant zero for unconnected pins?
|
|
667
860
|
if src == CONST_ZERO:
|
|
668
861
|
return '0u'
|
|
862
|
+
# VCC (constant 1)?
|
|
863
|
+
if src == '__VCC':
|
|
864
|
+
return '1u'
|
|
865
|
+
# GND (constant 0)?
|
|
866
|
+
if src == '__GND':
|
|
867
|
+
return '0u'
|
|
868
|
+
# Synthetic constant bit signal?
|
|
869
|
+
if src.startswith('__const_'):
|
|
870
|
+
# These should have been resolved to __VCC or __GND via connections
|
|
871
|
+
# but if we see them here directly, treat as error
|
|
872
|
+
raise ValueError(f'Unresolved constant signal: {src}')
|
|
669
873
|
# input bit?
|
|
670
874
|
if is_bit(src):
|
|
671
875
|
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
|