Skip to content

Commit a4cfb9c

Browse files
committed
Wire up the extra signals for sky130 io
1 parent 21cc8c2 commit a4cfb9c

File tree

5 files changed

+244
-106
lines changed

5 files changed

+244
-106
lines changed

chipflow_lib/platforms/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@
99
from .silicon import SiliconPlatformPort, SiliconPlatform
1010
from .sim import SimPlatform
1111
from .utils import (
12-
IO_ANNOTATION_SCHEMA, IOSignature, IOModel,
12+
IO_ANNOTATION_SCHEMA, IOSignature, IOModel, IODriveMode, IOTripPoint, IOModelOptions,
1313
OutputIOSignature, InputIOSignature, BidirIOSignature,
1414
)
1515
from ._packages import PACKAGE_DEFINITIONS
1616

17-
__all__ = ['IO_ANNOTATION_SCHEMA', 'IOSignature', 'IOModel',
17+
__all__ = ['IO_ANNOTATION_SCHEMA', 'IOSignature',
18+
'IOModel', 'IOModelOptions', 'IODriveMode', 'IOTripPoint',
1819
'OutputIOSignature', 'InputIOSignature', 'BidirIOSignature',
1920
'SiliconPlatformPort', 'SiliconPlatform',
2021
'SimPlatform',

chipflow_lib/platforms/silicon.py

Lines changed: 196 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
# amaranth: UnusedElaboratable=no
2-
# type: ignore[reportAttributeAccessIssue]
32

43
# SPDX-License-Identifier: BSD-2-Clause
54
import logging
@@ -8,6 +7,8 @@
87
import subprocess
98

109
from dataclasses import dataclass
10+
from pprint import pformat
11+
from typing import TYPE_CHECKING, List, Dict
1112

1213
from amaranth import Module, Signal, Cat, ClockDomain, ClockSignal, ResetSignal
1314

@@ -20,7 +21,10 @@
2021
from amaranth.hdl._ir import PortDirection
2122

2223
from .. import ChipFlowError
23-
from .utils import load_pinlock, Port
24+
from .utils import load_pinlock, PortDesc, Pin, IOModel, IODriveMode, IOTripPoint, Process
25+
26+
if TYPE_CHECKING:
27+
from ..config_models import Config
2428

2529
__all__ = ["SiliconPlatformPort", "SiliconPlatform"]
2630

@@ -67,47 +71,67 @@ def elaborate(self, platform):
6771

6872
class SiliconPlatformPort(io.PortLike):
6973
def __init__(self,
70-
component: str,
7174
name: str,
72-
port: Port,
75+
port_desc: PortDesc,
7376
*,
7477
invert: bool = False):
75-
self._direction = io.Direction(port.iomodel['direction'])
78+
self._port_desc = port_desc
7679
self._invert = invert
77-
self._iomodel = port.iomodel
78-
self._pins = port.pins if port.pins else []
80+
self._name = name
7981

8082
# Initialize signal attributes to None
8183
self._i = None
8284
self._o = None
8385
self._oe = None
8486

8587
# Create signals based on direction
86-
if self._direction in (io.Direction.Input, io.Direction.Bidir):
87-
self._i = Signal(port.width, name=f"{component}_{name}__i")
88-
if self._direction in (io.Direction.Output, io.Direction.Bidir):
89-
self._o = Signal(port.width, name=f"{component}_{name}__o")
90-
if self._direction is io.Direction.Bidir:
91-
if "individual_oe" in self._iomodel and self._iomodel["individual_oe"]:
92-
self._oe = Signal(port.width, name=f"{component}_{name}__oe", init=-1)
88+
if self.direction in (io.Direction.Input, io.Direction.Bidir):
89+
self._i = Signal(self._port_desc.width, name=f"{self._name}__i")
90+
if self.direction in (io.Direction.Output, io.Direction.Bidir):
91+
self._o = Signal(self._port_desc.width, name=f"{self._name}__o")
92+
if self.direction is io.Direction.Bidir:
93+
if "individual_oe" in self.iomodel and self.iomodel["individual_oe"]:
94+
self._oe = Signal(self._port_desc.width, name=f"{self._name}__oe", init=-1)
9395
else:
94-
self._oe = Signal(1, name=f"{component}_{name}__oe", init=-1)
95-
elif self._direction is io.Direction.Output:
96+
self._oe = Signal(1, name=f"{self._name}__oe", init=-1)
97+
elif self.direction is io.Direction.Output:
9698
# Always create an _oe for output ports
97-
self._oe = Signal(1, name=f"{component}_{name}__oe", init=-1)
99+
self._oe = Signal(1, name=f"{self._name}__oe", init=-1)
98100

99-
logger.debug(f"Created SiliconPlatformPort {name}, width={len(self._pins)},dir{self._direction}")
101+
logger.debug(f"Created SiliconPlatformPort {self._name}, invert={invert} with port description:\n{pformat(self._port_desc)}")
100102

101103
def wire(self, m: Module, interface: PureInterface):
102-
assert self._direction == interface.signature.direction #type: ignore
104+
assert self.direction == interface.signature.direction #type: ignore
103105
if hasattr(interface, 'i'):
104106
m.d.comb += interface.i.eq(self.i) # type: ignore
105107
for d in ['o', 'oe']:
106108
if hasattr(interface, d):
107109
m.d.comb += getattr(self, d).eq(getattr(interface, d))
108110

111+
def instantiate_toplevel(self):
112+
ports = []
113+
if self.direction in (io.Direction.Input, io.Direction.Bidir):
114+
ports.append((f"io${self._name}$i", self.i, PortDirection.Input))
115+
if self.direction in (io.Direction.Output, io.Direction.Bidir):
116+
ports.append((f"io${self._name}$o", self.o, PortDirection.Output))
117+
if self.direction is io.Direction.Bidir:
118+
ports.append((f"io${self._name}$oe", self.oe, PortDirection.Output))
119+
return ports
120+
121+
@property
122+
def name(self) -> str:
123+
return self._name
124+
125+
@property
126+
def pins(self) -> List[Pin]:
127+
return self._port_desc.pins if self._port_desc.pins else []
128+
109129
@property
130+
def iomodel(self) -> IOModel:
131+
return self._port_desc.iomodel
110132

133+
134+
@property
111135
def i(self):
112136
if self._i is None:
113137
raise AttributeError("SiliconPlatformPort with output direction does not have an "
@@ -130,69 +154,172 @@ def oe(self):
130154

131155
@property
132156
def direction(self):
133-
return self._direction
134-
135-
@property
136-
def pins(self):
137-
return self._pins
157+
return self._port_desc.iomodel['direction']
138158

139159
@property
140160
def invert(self):
141161
return self._invert
142162

143163

144164
def __len__(self):
145-
if self._direction is io.Direction.Input:
165+
if self.direction is io.Direction.Input:
146166
return len(self.i)
147-
if self._direction is io.Direction.Output:
167+
if self.direction is io.Direction.Output:
148168
return len(self.o)
149-
if self._direction is io.Direction.Bidir:
169+
if self.direction is io.Direction.Bidir:
150170
assert len(self.i) == len(self.o)
151-
if 'individual_oe' in self._iomodel and self._iomodel["individual_oe"]:
171+
if 'individual_oe' in self.iomodel and self.iomodel["individual_oe"]:
152172
assert len(self.o) == len(self.oe)
153173
else:
154174
assert len(self.oe) == 1
155175
return len(self.i)
156176
assert False # :nocov:
157177

158178
def __getitem__(self, key):
159-
result = object.__new__(type(self))
160-
result._i = None if self._i is None else self._i[key]
161-
result._o = None if self._o is None else self._o[key]
162-
result._oe = None if self._oe is None else self._oe[key]
163-
result._invert = self._invert
164-
result._direction = self._direction
165-
result._iomodel = self._iomodel
166-
result._pins = self._pins
167-
return result
179+
return NotImplemented
168180

169181
def __invert__(self):
170-
result = object.__new__(type(self))
171-
result._i = self._i
172-
result._o = self._o
173-
result._oe = self._oe
174-
result._invert = not self._invert
175-
result._direction = self._direction
176-
result._iomodel = self._iomodel
177-
result._pins = self._pins
182+
result = SiliconPlatformPort(self._name, self._port_desc, invert=not self.invert)
178183
return result
179184

180185
def __add__(self, other):
181-
direction = self._direction & other._direction
182-
result = object.__new__(type(self))
183-
result._i = None if direction is io.Direction.Output else Cat(self._i, other._i)
184-
result._o = None if direction is io.Direction.Input else Cat(self._o, other._o)
185-
result._oe = None if direction is io.Direction.Input else Cat(self._oe, other._oe)
186-
result._invert = self._invert
187-
result._direction = direction
188-
result._iomodel = self._iomodel
189-
result._pins = self._pins + other._pins
186+
return NotImplemented
187+
188+
def __repr__(self):
189+
return (f"SiliconPlatformPort(name={self._name}, invert={self._invert}, iomode={self.iomodel})")
190+
191+
192+
class Sky130Port(SiliconPlatformPort):
193+
"""
194+
Specialisation of `SiliconPlatformPort` for the `Skywater sky130_fd_io__gpiov2 IO cell <https://skywater-pdk.readthedocs.io/en/main/contents/libraries/sky130_fd_io/docs/user_guide.html>`_
195+
196+
Includes wires and configuration for `Drive Modes <IODriveMode>`, `Input buffer trip point <IOTripPoint>`and buffer control~
197+
"""
198+
199+
_DriveMode_map = {
200+
# Strong pull-up, weak pull-down
201+
IODriveMode.STRONG_UP_WEAK_DOWN: 0b011,
202+
# Weak pull-up, Strong pull-down
203+
IODriveMode.WEAK_UP_STRONG_DOWN: 0b010,
204+
# Open drain with strong pull-down
205+
IODriveMode.OPEN_DRAIN_STRONG_DOWN: 0b100,
206+
# Open drain-with strong pull-up
207+
IODriveMode.OPEN_DRAIN_STRONG_UP: 0b101,
208+
# Strong pull-up, weak pull-down
209+
IODriveMode.STRONG_UP_STRONG_DOWN: 0b110,
210+
# Weak pull-up, weak pull-down
211+
IODriveMode.WEAK_UP_WEAK_DOWN: 0b111
212+
}
213+
214+
_VTrip_map = {
215+
# CMOS level switching (30%/70%) referenced to IO power domain
216+
IOTripPoint.CMOS: (0, 0),
217+
# TTL level switching (low < 0.8v, high > 2.0v) referenced to IO power domain
218+
IOTripPoint.TTL: (0, 1),
219+
# CMOS level switching referenced to core power domain (e.g. low power mode)
220+
IOTripPoint.VCORE: (1,0),
221+
# CMOS level switching referenced to external reference voltage (e.g. low power mode)
222+
# Only available on sky130_fd_io__gpio_ovtv2
223+
# VREF
224+
}
225+
226+
227+
# TODO: slew rate, hold points
228+
def __init__(self,
229+
name: str,
230+
port_desc: PortDesc,
231+
*,
232+
invert: bool = False):
233+
super().__init__(name, port_desc, invert=invert)
234+
235+
# keep a list of signals we create
236+
self._signals = []
237+
238+
# Now create the signals for ``gpio_oeb`` (``oe_n``), ``gpio_inp_dis`` (``ie``)
239+
self._oe_n = None
240+
self._ie = None
241+
242+
if self._oe is not None:
243+
self._oe_n = Signal(self._oe.width, name=f"{self._name}$oeb")
244+
self._signals.append((self._oe_n, PortDirection.Output))
245+
if self._i is not None:
246+
self._ie = Signal(self._i.width, name=f"{self._name}$inp_dis")
247+
self._signals.append((self._ie, PortDirection.Input))
248+
249+
# Port Configuration
250+
# Input voltage trip level
251+
if self.direction in (io.Direction.Input, io.Direction.Bidir):
252+
if 'trip_point' in port_desc.iomodel:
253+
trip_point = port_desc.iomodel['trip_point']
254+
if trip_point not in __class__._VTrip_map:
255+
raise ChipFlowError(f"Trip point `{trip_point}` not available for {__class__.__name__}")
256+
ib_mode_init, vtrip_init = __class__._VTrip_map[trip_point]
257+
else:
258+
ib_mode_init = vtrip_init = 0
259+
260+
self._gpio_ib_mode_sel = Signal(1, name=f"{self._name}$ib_mode_sel", init=ib_mode_init)
261+
self._signals.append((self._gpio_ib_mode_sel, PortDirection.Output))
262+
self._gpio_vtrip_sel = Signal(1, name=f"{self._name}$vtrip_sel", init=vtrip_init)
263+
self._signals.append((self._gpio_vtrip_sel, PortDirection.Output))
264+
265+
# Drive mode
266+
if self.direction in (io.Direction.Output, io.Direction.Bidir):
267+
if 'drive_mode' in port_desc.iomodel:
268+
dm = port_desc.iomodel['drive_mode']
269+
else:
270+
dm = IODriveMode.STRONG_UP_STRONG_DOWN
271+
dm_init = __class__._DriveMode_map[dm]
272+
self._gpio_dm = Signal(3, name=f"{self._name}$dm", init=dm_init)
273+
self._signals.append((self._gpio_dm, PortDirection.Output))
274+
275+
# Not enabled yet:
276+
self._gpio_slow_sel = None # Select slew rate
277+
self._gpio_holdover = None # Hold mode
278+
# Analog config, not enabled yet
279+
# see https://skywater-pdk.readthedocs.io/en/main/contents/libraries/sky130_fd_io/docs/user_guide.html#analog-functionality
280+
self._gpio_analog_en = None # analog enable
281+
self._gpio_analog_sel = None # analog mux select
282+
self._gpio_analog_pol = None # analog mux select
283+
284+
def wire(self, m: Module, interface: PureInterface):
285+
super().wire(m, interface)
286+
# don't wire up oe_n
287+
if hasattr(interface, 'ie'):
288+
m.d.comb += interface.ie.eq(self._ie) # type: ignore
289+
# wire up oe_n = ~oe
290+
if self._oe is not None:
291+
assert self._oe_n is not None
292+
m.d.comb += self._oe_n.eq(~self._oe)
293+
294+
def instantiate_toplevel(self):
295+
ports = super().instantiate_toplevel()
296+
for s, d in self._signals:
297+
print(f"appending io${s.name}")
298+
ports.append((f"io${s.name}", s, d))
299+
return ports
300+
301+
@property
302+
def ie(self):
303+
if self._ie is None:
304+
raise AttributeError("SiliconPlatformPort with input direction does not have an "
305+
"input enable signal")
306+
return self._ie
307+
308+
def __invert__(self):
309+
result = Sky130Port(self._name, self._port_desc, invert=not self.invert)
190310
return result
191311

192312
def __repr__(self):
193-
return (f"SiliconPlatformPort(direction={repr(self._direction)}, width={len(self)}, "
194-
f"i={repr(self._i)}, o={repr(self._o)}, oe={repr(self._oe)}, "
195-
f"invert={repr(self._invert)})")
313+
return (f"Sky130Port(name={self._name}, invert={self._invert}, iomode={self.iomodel})")
314+
315+
316+
317+
def port_for_process(p: Process):
318+
match p:
319+
case Process.SKY130:
320+
return Sky130Port
321+
case Process.GF180 | Process.HELVELLYN2 | Process.GF130BCD | Process.IHP_SG13G2:
322+
return SiliconPlatformPort
196323

197324

198325
class IOBuffer(io.Buffer):
@@ -258,7 +385,9 @@ def elaborate(self, platform):
258385

259386

260387
class SiliconPlatform:
261-
def __init__(self, config):
388+
def __init__(self, config: 'Config'):
389+
if not config.chipflow.silicon:
390+
raise ChipFlowError(f"I can't build for silicon without a [chipflow.silicon] section to guide me!")
262391
self._config = config
263392
self._ports = {}
264393
self._files = {}
@@ -269,24 +398,28 @@ def ports(self):
269398
return self._ports
270399

271400
def instantiate_ports(self, m: Module):
401+
assert self._config.chipflow.silicon
272402
if hasattr(self, "pinlock"):
273403
return
274404

275405
pinlock = load_pinlock()
276406
for component, iface in pinlock.port_map.ports.items():
277-
for k, v in iface.items():
407+
for interface, v in iface.items():
278408
for name, port in v.items():
279-
self._ports[port.port_name] = SiliconPlatformPort(component, name, port)
409+
self._ports[port.port_name] = port_for_process(self._config.chipflow.silicon.process)(port.port_name, port)
280410

411+
print(pformat(self._ports))
281412
for clock in pinlock.port_map.get_clocks():
413+
assert 'clock_domain' in clock.iomodel
282414
domain = name=clock.iomodel['clock_domain']
283415
setattr(m.domains, domain, ClockDomain(name=domain))
284416
clk_buffer = io.Buffer("i", self._ports[clock.port_name])
285417
setattr(m.submodules, "clk_buffer_" + domain, clk_buffer)
286418
m.d.comb += ClockSignal().eq(clk_buffer.i) #type: ignore[reportAttributeAccessIssue]
287419

288420
for reset in pinlock.port_map.get_resets():
289-
domain = name=clock.iomodel['clock_domain']
421+
assert 'clock_domain' in reset.iomodel
422+
domain = name=reset.iomodel['clock_domain']
290423
rst_buffer = io.Buffer("i", self._ports[reset.port_name])
291424
setattr(m.submodules, reset.port_name, rst_buffer)
292425
setattr(m.submodules, reset.port_name + "_sync", FFSynchronizer(rst_buffer.i, ResetSignal())) #type: ignore[reportAttributeAccessIssue]
@@ -343,13 +476,8 @@ def _prepare(self, elaboratable, name="top"):
343476

344477
# Prepare toplevel ports according to pinlock
345478
ports = []
346-
for port_name, port in self._ports.items():
347-
if port.direction in (io.Direction.Input, io.Direction.Bidir):
348-
ports.append((f"io${port_name}$i", port.i, PortDirection.Input))
349-
if port.direction in (io.Direction.Output, io.Direction.Bidir):
350-
ports.append((f"io${port_name}$o", port.o, PortDirection.Output))
351-
if port.direction is io.Direction.Bidir:
352-
ports.append((f"io${port_name}$oe", port.oe, PortDirection.Output))
479+
for port in self._ports.values():
480+
ports.extend(port.instantiate_toplevel())
353481

354482
# Prepare design for RTLIL conversion.
355483
return fragment.prepare(ports)

0 commit comments

Comments
 (0)