PySHDL 0.1.4__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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PySHDL
3
- Version: 0.1.4
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "PySHDL"
3
- version = "0.1.4"
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 = [
@@ -423,23 +423,52 @@ class SHDLParser:
423
423
 
424
424
  # --- Helpers -------------------------------------------------------------
425
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
+
426
454
  def ports_of(defn: "Component"):
427
455
  return {p.name for p in defn.inputs}, {p.name for p in defn.outputs}
428
456
 
429
457
  def output_drivers(defn: "Component"):
430
458
  """
431
- 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'
432
461
  """
433
462
  m = {}
434
463
  for src, dst in defn.connections:
435
- if '.' not in dst: # bare output port
464
+ if '.' not in dst: # bare output port (possibly with bit index)
436
465
  m[dst] = src
437
466
  return m
438
467
 
439
468
  def prefixed_internal(pin: str, inst_name: str):
440
469
  """
441
470
  Prefix internal instance pins: 'x1.O' -> 'fa1_x1.O'.
442
- Leave bare ports ('A', 'Sum') unchanged (handled elsewhere).
471
+ Leave bare ports ('A', 'Sum', 'A[1]') unchanged (handled elsewhere).
443
472
  """
444
473
  if '.' in pin:
445
474
  inst, port = pin.split('.', 1)
@@ -447,8 +476,12 @@ class SHDLParser:
447
476
  return pin
448
477
 
449
478
  def is_abstract_pin(token: str):
450
- # an abstract pin looks like 'fa1.Cin' or 'fa1.Cout'
451
- 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
452
485
 
453
486
  # Index original connections
454
487
  conns_from = defaultdict(list)
@@ -479,38 +512,50 @@ class SHDLParser:
479
512
  "out_drivers": out_drv,
480
513
  }
481
514
 
482
- # Global maps for substitution
515
+ # Global maps for substitution with bit-indexed support
483
516
  # (1) Abstract OUTPUT -> concrete internal driver pin (prefixed)
484
517
  abstract_out_to_driver = {}
485
518
  for inst in instances_to_inline:
486
519
  meta = inlined_meta[inst.name]
487
520
  for outp, driver_pin in meta["out_drivers"].items():
488
- abstract = f"{inst.name}.{outp}" # e.g., 'fa1.Cout'
489
- 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)
490
524
  abstract_out_to_driver[abstract] = concrete
491
525
 
492
526
  def resolve_output_ref(token: str) -> str:
493
- """Replace 'inst.OutPort' with its internal driver pin, repeatedly if needed."""
527
+ """Replace 'inst.OutPort' or 'inst.OutPort[i]' with its internal driver pin."""
494
528
  while token in abstract_out_to_driver:
495
529
  token = abstract_out_to_driver[token]
496
530
  return token
497
531
 
498
532
  # (2) Abstract INPUT -> resolved external net (after output resolution)
533
+ # Now handles bit-indexed inputs like 'inv.A[1]'
499
534
  abstract_in_to_net = {}
500
535
  for inst in instances_to_inline:
501
536
  meta = inlined_meta[inst.name]
537
+
538
+ # For each input port, check all possible bit-indexed references
502
539
  for inp in meta["in_ports"]:
503
- abs_input = f"{inst.name}.{inp}" # e.g., 'fa2.Cin'
540
+ # First check non-indexed reference
541
+ abs_input = f"{inst.name}.{inp}"
504
542
  if abs_input in conns_to and conns_to[abs_input]:
505
- upstream = conns_to[abs_input][-1][0] # last writer wins
506
- 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)
507
545
  abstract_in_to_net[abs_input] = upstream
508
- else:
509
- # Unconnected input; leave unmapped. (Could default/raise as needed.)
510
- 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
511
556
 
512
557
  def resolve_input_ref(token: str) -> str:
513
- """Replace 'inst.InPort' with its resolved external net, repeatedly if needed."""
558
+ """Replace 'inst.InPort' or 'inst.InPort[i]' with its resolved external net."""
514
559
  seen = set()
515
560
  while token in abstract_in_to_net and token not in seen:
516
561
  seen.add(token)
@@ -522,10 +567,10 @@ class SHDLParser:
522
567
  def is_inlined_abstract_input(dst: str) -> bool:
523
568
  if not is_abstract_pin(dst):
524
569
  return False
525
- inst_name, port = dst.split('.', 1)
570
+ inst_name, port_name, bit_idx = parse_pin_ref(dst)
526
571
  if inst_name not in inlined_meta:
527
572
  return False
528
- return port in inlined_meta[inst_name]["in_ports"]
573
+ return port_name in inlined_meta[inst_name]["in_ports"]
529
574
 
530
575
  # Bring in internals for each inlined child
531
576
  for inst in instances_to_inline:
@@ -546,18 +591,31 @@ class SHDLParser:
546
591
  if s not in merged_imports[lib]:
547
592
  merged_imports[lib].append(s)
548
593
 
549
- # (c) Recreate child's internal wiring with substitutions:
550
- # - child input ports -> resolved external net (input map)
551
- # - internal pins -> prefixed_internal(...)
552
- # - child output ports are NOT emitted as ports (we wire their drivers elsewhere)
553
- 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]
554
612
 
555
613
  for src, dst in child_def.connections:
556
614
  # Resolve source
557
- if '.' not in src: # child input port
615
+ if '.' not in src: # child input port (may have bit index)
558
616
  upstream = input_external.get(src)
559
617
  if upstream is None:
560
- # Skip unconnected; or raise if strict is desired
618
+ # Skip unconnected
561
619
  continue
562
620
  flat_src = resolve_output_ref(upstream)
563
621
  else:
@@ -571,20 +629,26 @@ class SHDLParser:
571
629
  flat_dst = prefixed_internal(dst, inst.name)
572
630
  new_connections.append((flat_src, flat_dst))
573
631
 
574
- # (d) Rewire parent edges that referenced child OUTPUTS (e.g., 'fa1.Cout' -> X)
575
- # But do NOT create edges into abstract inputs of other inlined children
576
- # (those will be realized by that child's own internal wiring above).
632
+ # (d) Rewire parent edges that referenced child OUTPUTS
577
633
  for outp in meta["out_ports"]:
634
+ # Check non-indexed outputs
578
635
  abs_out = f"{inst.name}.{outp}"
579
- if abs_out not in conns_from:
580
- continue
581
- driver = resolve_output_ref(abs_out) # should give prefixed internal pin
582
- for (_src, dst) in conns_from[abs_out]:
583
- if is_inlined_abstract_input(dst):
584
- # Skip; the receiving child will handle via its internal mapping.
585
- continue
586
- # Otherwise, we can keep the destination:
587
- 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))
588
652
 
589
653
  # Keep original parent connections that don't touch any abstract pins of inlined children
590
654
  abstract_pins = set()
@@ -592,6 +656,13 @@ class SHDLParser:
592
656
  meta = inlined_meta[inst.name]
593
657
  for p in meta["in_ports"] | meta["out_ports"]:
594
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)
595
666
 
596
667
  def touches_any_abstract(conn):
597
668
  s, d = conn
@@ -605,15 +676,15 @@ class SHDLParser:
605
676
  finalized = []
606
677
  for s, d in new_connections:
607
678
  s2 = resolve_output_ref(s)
608
- s2 = resolve_input_ref(s2) # just in case a chain pointed to an abstract input
609
- d2 = resolve_output_ref(d) # unlikely for destinations, but safe
679
+ s2 = resolve_input_ref(s2)
680
+ d2 = resolve_output_ref(d)
610
681
  d2 = resolve_input_ref(d2)
611
- # Drop any connection that still targets an abstract input (shouldn't happen)
682
+ # Drop any connection that still targets an abstract input
612
683
  if is_inlined_abstract_input(d2):
613
684
  continue
614
685
  finalized.append((s2, d2))
615
686
 
616
- # Optional: prune imports of fully inlined component types (e.g., 'FullAdder')
687
+ # Optional: prune imports of fully inlined component types
617
688
  if merged_imports:
618
689
  cleaned_imports = {}
619
690
  for lib, syms in merged_imports.items():
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes