@@ -11,5 +11,201 @@ class GlasgowECP5Platform(GlasgowPlatform, LatticeECP5Platform):
1111 def bitstream_filename (self , design_name ):
1212 return f"{ design_name } .bit"
1313
14- def get_pll (self , pll ):
15- raise NotImplementedError ("get_pll() not implemented for ECP5" )
14+
15+ # This is copied from `amaranth.vendor._lattice.InnerBuffer` as-is.
16+ class _InnerBuffer (wiring .Component ):
17+ """A private component used to implement ``lib.io`` buffers.
18+
19+ Works like ``lib.io.Buffer``, with the following differences:
20+
21+ - ``port.invert`` is ignored (handling the inversion is the outer buffer's responsibility)
22+ - ``t`` is per-pin inverted output enable
23+ """
24+
25+ def __init__ (self , direction , port ):
26+ self .direction = direction
27+ self .port = port
28+ members = {}
29+ if direction is not io .Direction .Output :
30+ members ["i" ] = wiring .In (len (port ))
31+ if direction is not io .Direction .Input :
32+ members ["o" ] = wiring .Out (len (port ))
33+ members ["t" ] = wiring .Out (len (port ))
34+ super ().__init__ (wiring .Signature (members ).flip ())
35+
36+ def elaborate (self , platform ):
37+ m = Module ()
38+
39+ if isinstance (self .port , io .SingleEndedPort ):
40+ io_port = self .port .io
41+ elif isinstance (self .port , io .DifferentialPort ):
42+ io_port = self .port .p
43+ else :
44+ raise TypeError (f"Unknown port type { self .port !r} " )
45+
46+ for bit in range (len (self .port )):
47+ name = f"buf{ bit } "
48+ if self .direction is io .Direction .Input :
49+ m .submodules [name ] = Instance ("IB" ,
50+ i_I = io_port [bit ],
51+ o_O = self .i [bit ],
52+ )
53+ elif self .direction is io .Direction .Output :
54+ m .submodules [name ] = Instance ("OBZ" ,
55+ i_T = self .t [bit ],
56+ i_I = self .o [bit ],
57+ o_O = io_port [bit ],
58+ )
59+ elif self .direction is io .Direction .Bidir :
60+ m .submodules [name ] = Instance ("BB" ,
61+ i_T = self .t [bit ],
62+ i_I = self .o [bit ],
63+ o_O = self .i [bit ],
64+ io_B = io_port [bit ],
65+ )
66+ else :
67+ assert False # :nocov:
68+
69+ return m
70+
71+
72+ # Patterned after `io.DDRBuffer`, but with somewhat more questionable design choices. Would likely
73+ # not be accepted upstream as-is for a number of reasons; a major one is that IDDRX2F.ALIGNWD input
74+ # is not connected at all.
75+ class QDRBuffer (wiring .Component ):
76+ class Signature (wiring .Signature ):
77+ def __init__ (self , direction , width ):
78+ self ._direction = io .Direction (direction )
79+ self ._width = operator .index (width )
80+ members = {}
81+ if self ._direction is not io .Direction .Output :
82+ members ["i" ] = wiring .In (data .ArrayLayout (self ._width , 4 ))
83+ if self ._direction is not io .Direction .Input :
84+ members ["o" ] = wiring .Out (data .ArrayLayout (self ._width , 4 ))
85+ members ["oe" ] = wiring .Out (1 , init = int (self ._direction is io .Direction .Output ))
86+ super ().__init__ (members )
87+
88+ @property
89+ def direction (self ):
90+ return self ._direction
91+
92+ @property
93+ def width (self ):
94+ return self ._width
95+
96+ def __eq__ (self , other ):
97+ return (type (self ) is type (other ) and self .direction == other .direction and
98+ self .width == other .width )
99+
100+ def __repr__ (self ):
101+ return f"QDRBuffer.Signature({ self .direction } , { self .width } )"
102+
103+ def __init__ (self , direction , port , * , i_domain = None , o_domain = None ):
104+ if not isinstance (port , io .PortLike ):
105+ raise TypeError (f"'port' must be a 'PortLike', not { port !r} " )
106+ self ._port = port
107+ super ().__init__ (QDRBuffer .Signature (direction , len (port )).flip ())
108+ if self .signature .direction is not io .Direction .Output :
109+ self ._i_domain = i_domain or "sync"
110+ else :
111+ if i_domain is not None :
112+ raise ValueError ("Output buffer doesn't have an input domain" )
113+ self ._i_domain = None
114+ if self .signature .direction is not io .Direction .Input :
115+ self ._o_domain = o_domain or "sync"
116+ else :
117+ if o_domain is not None :
118+ raise ValueError ("Input buffer doesn't have an output domain" )
119+ self ._o_domain = None
120+ if port .direction is io .Direction .Input and self .direction is not io .Direction .Input :
121+ raise ValueError (f"Input port cannot be used with { self .direction .name } buffer" )
122+ if port .direction is io .Direction .Output and self .direction is not io .Direction .Output :
123+ raise ValueError (f"Output port cannot be used with { self .direction .name } buffer" )
124+
125+ @property
126+ def port (self ):
127+ return self ._port
128+
129+ @property
130+ def direction (self ):
131+ return self .signature .direction
132+
133+ @property
134+ def i_domain (self ):
135+ return self ._i_domain
136+
137+ @property
138+ def o_domain (self ):
139+ return self ._o_domain
140+
141+ def elaborate (self , platform ):
142+ assert isinstance (platform , LatticeECP5Platform ), "QDR buffers are only supported on ECP5"
143+
144+ m = Module ()
145+
146+ m .submodules .buf = buf = _InnerBuffer (self .direction , self .port )
147+ inv_mask = sum (inv << bit for bit , inv in enumerate (self .port .invert ))
148+
149+ if self .direction is not io .Direction .Output :
150+ m .submodules += RequirePosedge (self .i_domain )
151+ i0_inv = Signal (len (self .port ))
152+ i1_inv = Signal (len (self .port ))
153+ i2_inv = Signal (len (self .port ))
154+ i3_inv = Signal (len (self .port ))
155+ for bit in range (len (self .port )):
156+ m .submodules [f"i_ddr{ bit } " ] = Instance ("IDDRX2F" ,
157+ # https://github.com/YosysHQ/nextpnr/issues/1749
158+ # i_ALIGNWD=0,
159+ i_SCLK = ClockSignal (self .i_domain ),
160+ i_ECLK = ClockSignal ("edge" ),
161+ i_RST = ResetSignal ("edge" ),
162+ i_D = buf .i [bit ],
163+ o_Q0 = i0_inv [bit ],
164+ o_Q1 = i1_inv [bit ],
165+ o_Q2 = i2_inv [bit ],
166+ o_Q3 = i3_inv [bit ],
167+ )
168+ m .d .comb += self .i [0 ].eq (i0_inv ^ inv_mask )
169+ m .d .comb += self .i [1 ].eq (i1_inv ^ inv_mask )
170+ m .d .comb += self .i [2 ].eq (i2_inv ^ inv_mask )
171+ m .d .comb += self .i [3 ].eq (i3_inv ^ inv_mask )
172+
173+ if self .direction is not io .Direction .Input :
174+ m .submodules += RequirePosedge (self .o_domain )
175+ o0_inv = Signal (len (self .port ))
176+ o1_inv = Signal (len (self .port ))
177+ o2_inv = Signal (len (self .port ))
178+ o3_inv = Signal (len (self .port ))
179+ m .d .comb += [
180+ o0_inv .eq (self .o [0 ] ^ inv_mask ),
181+ o1_inv .eq (self .o [1 ] ^ inv_mask ),
182+ o2_inv .eq (self .o [2 ] ^ inv_mask ),
183+ o3_inv .eq (self .o [3 ] ^ inv_mask ),
184+ ]
185+ for bit in range (len (self .port )):
186+ m .submodules [f"o_ddr{ bit } " ] = Instance ("ODDRX2F" ,
187+ i_SCLK = ClockSignal (self .o_domain ),
188+ i_ECLK = ClockSignal ("edge" ),
189+ i_RST = ResetSignal ("edge" ),
190+ i_D0 = o0_inv [bit ],
191+ i_D1 = o1_inv [bit ],
192+ i_D2 = o2_inv [bit ],
193+ i_D3 = o3_inv [bit ],
194+ o_Q = buf .o [bit ],
195+ )
196+
197+ oe = ~ self .oe
198+ for stage in range (2 ):
199+ oe_reg = Signal (name = f"oe_delay{ stage } " )
200+ m .d [self .o_domain ] += oe_reg .eq (oe )
201+ oe = oe_reg
202+ for bit in range (len (buf .t )):
203+ m .submodules [f"oe_ff{ bit } " ] = Instance ("OFS1P3DX" ,
204+ i_SCLK = ClockSignal (self .o_domain ),
205+ i_SP = Const (1 ),
206+ i_CD = ResetSignal ("edge" ),
207+ i_D = oe ,
208+ o_Q = buf .t [bit ],
209+ )
210+
211+ return m
0 commit comments