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.
@@ -1,4 +1,5 @@
1
1
  .gitignore
2
+
2
3
  # Python-generated files
3
4
  __pycache__/
4
5
  *.py[oc]
@@ -13,4 +14,5 @@ wheels/
13
14
 
14
15
  *.c
15
16
  *.so
16
- .python-version
17
+ .python-version
18
+ .DS_Store
@@ -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
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"
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
- """PySHDL library."""
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("PySHDL")
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
- # Expand generators first
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 (e.g., 'Sum' -> 'x2.O').
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
- # an abstract pin looks like 'fa1.Cin' or 'fa1.Cout'
329
- return '.' in token and '[' not in token and token.count('.') == 1
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
- abstract = f"{inst.name}.{outp}" # e.g., 'fa1.Cout'
367
- concrete = prefixed_internal(driver_pin, inst.name) # 'fa1_o1.O'
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, repeatedly if needed."""
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
- abs_input = f"{inst.name}.{inp}" # e.g., 'fa2.Cin'
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] # last writer wins
384
- upstream = resolve_output_ref(upstream) # resolve if it was 'faX.Out'
543
+ upstream = conns_to[abs_input][-1][0]
544
+ upstream = resolve_output_ref(upstream)
385
545
  abstract_in_to_net[abs_input] = upstream
386
- else:
387
- # Unconnected input; leave unmapped. (Could default/raise as needed.)
388
- pass
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, repeatedly if needed."""
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, port = dst.split('.', 1)
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 port in inlined_meta[inst_name]["in_ports"]
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
- # - child input ports -> resolved external net (input map)
429
- # - internal pins -> prefixed_internal(...)
430
- # - child output ports are NOT emitted as ports (we wire their drivers elsewhere)
431
- input_external = {p: abstract_in_to_net.get(f"{inst.name}.{p}") for p in meta["in_ports"]}
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; or raise if strict is desired
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 (e.g., 'fa1.Cout' -> X)
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 not in conns_from:
458
- continue
459
- driver = resolve_output_ref(abs_out) # should give prefixed internal pin
460
- for (_src, dst) in conns_from[abs_out]:
461
- if is_inlined_abstract_input(dst):
462
- # Skip; the receiving child will handle via its internal mapping.
463
- continue
464
- # Otherwise, we can keep the destination:
465
- new_connections.append((driver, dst))
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) # just in case a chain pointed to an abstract input
487
- d2 = resolve_output_ref(d) # unlikely for destinations, but safe
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 (shouldn't happen)
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 (e.g., 'FullAdder')
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)
@@ -1,8 +1,8 @@
1
1
  version = 1
2
- revision = 2
2
+ revision = 3
3
3
  requires-python = ">=3.9"
4
4
 
5
5
  [[package]]
6
6
  name = "pyshdl"
7
- version = "0.1.2"
7
+ version = "0.1.3"
8
8
  source = { editable = "." }
File without changes
File without changes
File without changes
File without changes