11# amaranth: UnusedElaboratable=no
2- # type: ignore[reportAttributeAccessIssue]
32
43# SPDX-License-Identifier: BSD-2-Clause
54import logging
87import subprocess
98
109from dataclasses import dataclass
10+ from pprint import pformat
11+ from typing import TYPE_CHECKING , List , Dict
1112
1213from amaranth import Module , Signal , Cat , ClockDomain , ClockSignal , ResetSignal
1314
2021from amaranth .hdl ._ir import PortDirection
2122
2223from .. 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
6872class 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
198325class IOBuffer (io .Buffer ):
@@ -258,7 +385,9 @@ def elaborate(self, platform):
258385
259386
260387class 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