Repository: ChipFlow/chipflow-lib
Pull request #47: Fix: actually wire up ports. NB this also changes the port_map layout
Author: login: robtaylor
Created: 2025-03-11T18:24:21Z | Merged: 2025-03-12T12:18:42Z



Diff:
diff --git a/chipflow_lib/pin_lock.py b/chipflow_lib/pin_lock.py
index a88b961a..9dff907b 100644
--- a/chipflow_lib/pin_lock.py
+++ b/chipflow_lib/pin_lock.py
@@ -32,40 +32,42 @@ def count_member_pins(name: str, member: Dict[str, Any]) -> int:
         return member['width']
 
 
-def allocate_pins(name: str, member: Dict[str, Any], pins: List[str]) -> Tuple[Dict[str, Port], List[str]]:
+def allocate_pins(name: str, member: Dict[str, Any], pins: List[str], port_name: str = None) -> Tuple[Dict[str, Port], List[str]]:
     "Allocate pins based of Amaranth member metadata"
 
     pin_map = {}
+
     logger.debug(f"allocate_pins: name={name}, pins={pins}")
     logger.debug(f"member={pformat(member)}")
 
     if member['type'] == 'interface' and 'annotations' in member \
        and PIN_ANNOTATION_SCHEMA in member['annotations']:
         logger.debug("matched PinSignature {sig}")
-        name = name
         sig = member['annotations'][PIN_ANNOTATION_SCHEMA]
         width = sig['width']
         options = sig['options']
         pin_map[name] = {'pins': pins[0:width],
-                        'direction': sig['direction'],
-                        'type': 'io',
-                        'options': options}
+                         'direction': sig['direction'],
+                         'type': 'io',
+                         'port_name': port_name,
+                         'options': options}
         logger.debug(f"added '{name}':{pin_map[name]} to pin_map")
         return pin_map, pins[width:]
     elif member['type'] == 'interface':
         for k, v in member['members'].items():
-            n = '_'.join([name, k])
-            _map, pins = allocate_pins(n, v, pins)
+            port_name = '_'.join([name, k])
+            _map, pins = allocate_pins(k, v, pins, port_name=port_name)
             pin_map |= _map
             logger.debug(f"{pin_map},{_map}")
         return pin_map, pins
     elif member['type'] == 'port':
         logger.warning(f"Port '{name}' has no PinSignature, pin allocation likely to be wrong")
-        name = name
         width = member['width']
         pin_map[name] = {'pins': pins[0:width],
-                        'direction': member['dir'],
-                        'type': 'io'}
+                              'direction': member['dir'],
+                              'type': 'io',
+                              'port_name': port_name
+                              }
         logger.debug(f"added '{name}':{pin_map[name]} to pin_map")
         return pin_map, pins[width:]
     else:
diff --git a/chipflow_lib/platforms/silicon.py b/chipflow_lib/platforms/silicon.py
index bec848ba..20e7e7f1 100644
--- a/chipflow_lib/platforms/silicon.py
+++ b/chipflow_lib/platforms/silicon.py
@@ -9,7 +9,7 @@
 
 from amaranth.lib import wiring, io
 from amaranth.lib.cdc import FFSynchronizer
-from amaranth.lib.wiring import Component, In
+from amaranth.lib.wiring import Component, In, PureInterface, flipped, connect
 
 from amaranth.back import rtlil
 from amaranth.hdl import Fragment
@@ -72,21 +72,29 @@ def __init__(self,
         self._invert = invert
         self._options = port.options
 
-        self._i = self._o = self._oe = Signal(1)
         if self._direction in (io.Direction.Input, io.Direction.Bidir):
             self._i = Signal(port.width, name=f"{component}_{name}__i")
         if self._direction in (io.Direction.Output, io.Direction.Bidir):
             self._o = Signal(port.width, name=f"{component}_{name}__o")
         if self._direction is io.Direction.Bidir:
             if "all_have_oe" in self._options and self._options["all_have_oe"]:
-                self._oe = Signal(port.width, name=f"{component}_{name}__oe")
+                self._oe = Signal(port.width, name=f"{component}_{name}__oe", init=-1)
             else:
-                self._oe = Signal(1, name=f"{component}_{name}__oe")
+                self._oe = Signal(1, name=f"{component}_{name}__oe", init=-1)
 
         self._pins = port.pins
         logger.debug(f"Created SiliconPlatformPort {name}, width={len(port.pins)},dir{self._direction}")
 
+    def wire(self, m: Module, interface: PureInterface):
+        assert self._direction == interface.signature.direction
+        if hasattr(interface, 'i'):
+            m.d.comb += interface.i.eq(self.i)
+        for d in ['o', 'oe']:
+            if hasattr(interface, d):
+                m.d.comb += getattr(self, d).eq(getattr(interface, d))
+
     @property
+
     def i(self):
         if self._i is None:
             raise AttributeError("SiliconPlatformPort with output direction does not have an "
@@ -229,6 +237,10 @@ def __init__(self, config):
         self._ports = {}
         self._files = {}
 
+    @property
+    def ports(self):
+        return self._ports
+
     def instantiate_ports(self, m: Module):
         if hasattr(self, "pinlock"):
             return
@@ -237,7 +249,7 @@ def instantiate_ports(self, m: Module):
         for component, iface in pinlock.port_map.items():
             for k, v in iface.items():
                 for name, port in v.items():
-                    self._ports[name] = SiliconPlatformPort(component, name, port)
+                    self._ports[port.port_name] = SiliconPlatformPort(component, name, port)
 
         for clock, name in self._config["chipflow"]["clocks"].items():
             if name not in pinlock.package.clocks:
@@ -246,6 +258,7 @@ def instantiate_ports(self, m: Module):
             port_data = pinlock.package.clocks[name]
             port = SiliconPlatformPort(component, name, port_data, invert=True)
             self._ports[name] = port
+
             if clock == 'default':
                 clock = 'sync'
             setattr(m.domains, clock, ClockDomain(name=clock))
@@ -311,13 +324,14 @@ def _prepare(self, elaboratable, name="top"):
         # Check that only a single clock domain is used.
         self._check_clock_domains(fragment)
 
-        # Prepare toplevel ports according to chipflow.toml.
+        # Prepare toplevel ports according to pinlock
         ports = []
         for port_name, port in self._ports.items():
             if port.direction in (io.Direction.Input, io.Direction.Bidir):
                 ports.append((f"io${port_name}$i", port.i, PortDirection.Input))
             if port.direction in (io.Direction.Output, io.Direction.Bidir):
                 ports.append((f"io${port_name}$o", port.o, PortDirection.Output))
+            if port.direction is io.Direction.Bidir:
                 ports.append((f"io${port_name}$oe", port.oe, PortDirection.Output))
 
         # Prepare design for RTLIL conversion.
diff --git a/chipflow_lib/platforms/utils.py b/chipflow_lib/platforms/utils.py
index 02502af5..69cc4abd 100644
--- a/chipflow_lib/platforms/utils.py
+++ b/chipflow_lib/platforms/utils.py
@@ -98,6 +98,16 @@ def __init__(self, direction: io.Direction, width: int = 1, all_have_oe: bool =
 
         super().__init__(sig)
 
+    @property
+    def direction(self):
+        return self._direction
+
+    def width(self):
+        return self._width
+
+    def options(self):
+        return self._options
+
     def annotations(self, *args):
         annotations = wiring.Signature.annotations(self, *args)
         pin_annotation = _PinAnnotation(direction=self._direction, width=self._width, options=self._options)
@@ -307,6 +317,7 @@ def sortpins(self, pins: Union[List[str], Set[str]]) -> List[str]:
 class Port(pydantic.BaseModel):
     type: str
     pins: List[str]
+    port_name: str
     direction: Optional[str] = None
     options: Optional[dict] = None
 
@@ -337,13 +348,13 @@ def check_pad(self, name: str, defn: dict):
     def add_pad(self, name: str, defn: dict):
         match defn:
             case {"type": "clock", "loc": loc}:
-                self.clocks[name] = Port(type="clock", pins=[loc], direction=io.Direction.Input)
+                self.clocks[name] = Port(type="clock", pins=[loc], direction=io.Direction.Input, port_name=name)
             case {"type": "reset", "loc": loc}:
-                self.resets[name] = Port(type="reset", pins=[loc], direction=io.Direction.Input)
+                self.resets[name] = Port(type="reset", pins=[loc], direction=io.Direction.Input, port_name=name)
             case {"type": "power", "loc": loc}:
-                self.power[name] = Port(type="power", pins=[loc])
+                self.power[name] = Port(type="power", pins=[loc], port_name=name)
             case {"type": "ground", "loc": loc}:
-                self.power[name] = Port(type="ground", pins=[loc])
+                self.power[name] = Port(type="ground", pins=[loc], port_name=name)
             case _:
                 pass
 
diff --git a/chipflow_lib/steps/silicon.py b/chipflow_lib/steps/silicon.py
index 2f2b56e7..740d5a55 100644
--- a/chipflow_lib/steps/silicon.py
+++ b/chipflow_lib/steps/silicon.py
@@ -12,7 +12,9 @@
 import time
 
 import dotenv
+from pprint import pprint
 from amaranth import *
+from amaranth.lib.wiring import connect, flipped
 
 from .. import ChipFlowError
 from ..platforms import SiliconPlatform, top_interfaces
@@ -43,6 +45,13 @@ def elaborate(self, platform: SiliconPlatform):
         for n, t in top.items():
             setattr(m.submodules, n, t)
 
+        for component, iface in platform.pinlock.port_map.items():
+            for iface_name, member, in iface.items():
+                for name, port in member.items():
+                    platform.ports[port.port_name].wire(
+                            m,
+                            getattr(getattr(top[component], iface_name), name)
+                            )
         return m
 
 


Reviews:
user: login: lanserge
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T19:14:46Z
user: login: lanserge
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T19:38:33Z
user: login: lanserge
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T19:48:33Z
user: login: robtaylor
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T12:02:04Z
user: login: lanserge
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T12:03:51Z
user: login: robtaylor
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T12:11:00Z

Review comments:
path: chipflow_lib/platforms/silicon.py
user: login: lanserge
body: At the `line 335`, both `$o` and` $oe` pins will be created for output only direction pad, I think this should be for `bidir` only as all output only interfaces are not using `$oe`. Maybe we could keep it this way if `$oe` will have default `init=1`, but then backend will need to ignore `$oe` for technologies with output only pads that don't have `oe`.
created_at: 2025-03-11T19:14:46Z
author_association: CONTRIBUTOR
path: chipflow_lib/platforms/silicon.py
user: login: lanserge
body: Would be great at the `lines 75, 82, 84` of the `platform/silicon.py` to have `self.oe` signal be created with `init=True`, so the output will be enabled by default if `oe` not used in the design. For `line 82` it needs `init` with `1` for each bit.
created_at: 2025-03-11T19:38:33Z
author_association: CONTRIBUTOR
path: chipflow_lib/platforms/silicon.py
user: login: lanserge
body: Originally it were two types of output ports `o` a and `oe`, I think the idea was that `o` has only `$o` signal while `oe` has both `$o` and `$oe` signals. And it was great idea, as it covers two different output pad cases for different technologies (`helvellyn2` output pad don't have `oe`, `ihp_sg13g2` output pad has both `o` and `oe`). But unfortunately the pad type (`o` and `oe` types) not propagated into `SiliconPlatformPort` constructor and replaced with port direction which has only three types: `input`,  `output` and `bidir`, so backend will need to ignore one signal, either `i` in the `bidir` used for output with `o` and `oe`, or if we have `o` and `oe` for output  then ignore `oe` for technology pad without it.
created_at: 2025-03-11T19:48:33Z
author_association: CONTRIBUTOR
path: chipflow_lib/platforms/silicon.py
user: login: robtaylor
body: as discussed, o+oe isn't supported yet, see #31 
created_at: 2025-03-12T12:02:04Z
author_association: CONTRIBUTOR
path: chipflow_lib/platforms/silicon.py
user: login: robtaylor
body: Note OE/O default init should done by the IP, but we set OE high by default just in case they are undriven
created_at: 2025-03-12T12:11:00Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-11T18:26:36Z
body: <!-- Pytest Coverage Comment: build -->
| Tests | Skipped | Failures | Errors | Time |
| ----- | ------- | -------- | -------- | ------------------ |
| 10 | 0 :zzz: | 0 :x: | 0 :fire: | 1.053s :stopwatch: |

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-12T12:02:29Z
body: [PR Preview Action](https://github.com/rossjrw/pr-preview-action) v1.6.0
:---:
Preview removed because the pull request was closed.
2025-03-12 12:19 UTC
<!-- Sticky Pull Request Commentpr-preview -->
author_association: NONE