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.
- {pyshdl-0.1.4 → pyshdl-0.1.5}/PKG-INFO +1 -1
- {pyshdl-0.1.4 → pyshdl-0.1.5}/pyproject.toml +1 -1
- {pyshdl-0.1.4 → pyshdl-0.1.5}/src/PySHDL/shdlc.py +112 -41
- {pyshdl-0.1.4 → pyshdl-0.1.5}/.gitignore +0 -0
- {pyshdl-0.1.4 → pyshdl-0.1.5}/C_API.md +0 -0
- {pyshdl-0.1.4 → pyshdl-0.1.5}/DOCS.md +0 -0
- {pyshdl-0.1.4 → pyshdl-0.1.5}/README.md +0 -0
- {pyshdl-0.1.4 → pyshdl-0.1.5}/examples/SHDL_components/addSub16.shdl +0 -0
- {pyshdl-0.1.4 → pyshdl-0.1.5}/examples/SHDL_components/compare7.shdl +0 -0
- {pyshdl-0.1.4 → pyshdl-0.1.5}/examples/SHDL_components/fullAdder.shdl +0 -0
- {pyshdl-0.1.4 → pyshdl-0.1.5}/examples/SHDL_components/reg16.shdl +0 -0
- {pyshdl-0.1.4 → pyshdl-0.1.5}/examples/interacting.py +0 -0
- {pyshdl-0.1.4 → pyshdl-0.1.5}/src/PySHDL/__init__.py +0 -0
- {pyshdl-0.1.4 → pyshdl-0.1.5}/src/PySHDL/circuit.py +0 -0
- {pyshdl-0.1.4 → pyshdl-0.1.5}/src/PySHDL/cli.py +0 -0
- {pyshdl-0.1.4 → pyshdl-0.1.5}/src/PySHDL/py.typed +0 -0
- {pyshdl-0.1.4 → pyshdl-0.1.5}/uv.lock +0 -0
|
@@ -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
|
|
@@ -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 = [
|
|
@@ -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
|
|
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
|
-
|
|
451
|
-
|
|
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
|
-
|
|
489
|
-
|
|
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
|
|
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
|
-
|
|
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]
|
|
506
|
-
upstream = resolve_output_ref(upstream)
|
|
543
|
+
upstream = conns_to[abs_input][-1][0]
|
|
544
|
+
upstream = resolve_output_ref(upstream)
|
|
507
545
|
abstract_in_to_net[abs_input] = upstream
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
#
|
|
551
|
-
#
|
|
552
|
-
|
|
553
|
-
|
|
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
|
|
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
|
|
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
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
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)
|
|
609
|
-
d2 = resolve_output_ref(d)
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|