Skip to content

Commit 028d5d8

Browse files
wanda-phiwhitequark
authored andcommitted
hdl._ir, lib, vendor: add RequirePosedge, use it whenever required.
1 parent d3c312c commit 028d5d8

File tree

9 files changed

+110
-2
lines changed

9 files changed

+110
-2
lines changed

amaranth/hdl/_ir.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010

1111
__all__ = [
1212
"AlreadyElaborated", "UnusedElaboratable", "Elaboratable", "DuplicateElaboratable",
13-
"DriverConflict", "Fragment", "Instance", "IOBufferInstance", "PortDirection", "Design",
14-
"build_netlist",
13+
"DomainRequirementFailed", "DriverConflict",
14+
"Fragment", "Instance", "IOBufferInstance", "RequirePosedge", "PortDirection",
15+
"Design", "build_netlist",
1516
]
1617

1718

@@ -40,6 +41,11 @@ class DuplicateElaboratable(Exception):
4041
pass
4142

4243

44+
class DomainRequirementFailed(Exception):
45+
"""Raised when a module has unsatisfied requirements about a clock domain, such as getting
46+
a negedge domain when only posedge domains are supported."""
47+
48+
4349
class Fragment:
4450
@staticmethod
4551
def get(obj, platform):
@@ -385,6 +391,19 @@ def __init__(self, port, *, i=None, o=None, oe=None, src_loc_at=0, src_loc=None)
385391
self.src_loc = src_loc or tracer.get_src_loc(src_loc_at)
386392

387393

394+
class RequirePosedge(Fragment):
395+
"""A special fragment that requires a given domain to have :py:`clk_edge="pos"`, failing
396+
elaboration otherwise.
397+
398+
This is a private interface, without a stability guarantee.
399+
"""
400+
401+
def __init__(self, domain, *, src_loc_at=0, src_loc=None):
402+
super().__init__()
403+
self._domain = domain
404+
self.src_loc = src_loc or tracer.get_src_loc(src_loc_at)
405+
406+
388407
def _add_name(assigned_names, name):
389408
if name in assigned_names:
390409
name = f"{name}${len(assigned_names)}"
@@ -429,6 +448,7 @@ def __init__(self, fragment: Fragment, ports, *, hierarchy):
429448
else:
430449
self._use_signal(fragment, conn)
431450
self._assign_names(fragment, hierarchy)
451+
self._check_domain_requires()
432452

433453
def _compute_fragment_depth_parent(self, fragment: Fragment, parent: "Fragment | None", depth: int):
434454
"""Recursively computes every fragment's depth and parent."""
@@ -576,6 +596,8 @@ def _collect_used_signals(self, fragment: Fragment):
576596
self._use_signal(fragment, domain.clk)
577597
if domain.rst is not None:
578598
self._use_signal(fragment, domain.rst)
599+
elif isinstance(fragment, _ir.RequirePosedge):
600+
pass
579601
else:
580602
for domain_name, statements in fragment.statements.items():
581603
if domain_name != "comb":
@@ -662,6 +684,18 @@ def _assign_names(self, fragment: Fragment, hierarchy: "tuple[str]"):
662684
subfragment_name = _add_name(frag_info.assigned_names, subfragment_name)
663685
self._assign_names(subfragment, hierarchy=(*hierarchy, subfragment_name))
664686

687+
def _check_domain_requires(self):
688+
for fragment, fragment_info in self.fragments.items():
689+
if isinstance(fragment, RequirePosedge):
690+
domain = fragment.domains[fragment._domain]
691+
if domain.clk_edge != "pos":
692+
if fragment.src_loc is None:
693+
src_loc = "<unknown>:0"
694+
else:
695+
src_loc = f"{fragment.src_loc[0]}:{fragment.src_loc[1]}"
696+
fragment_name = ".".join(fragment_info.name)
697+
raise DomainRequirementFailed(f"Domain {domain.name} has a negedge clock, but posedge clock is required by {fragment_name} at {src_loc}")
698+
665699
def lookup_domain(self, domain, context):
666700
if domain == "comb":
667701
raise KeyError("comb")
@@ -1491,6 +1525,8 @@ def emit_fragment(self, fragment: _ir.Fragment, parent_module_idx: 'int | None',
14911525
assert parent_module_idx is not None
14921526
self.emit_iobuffer(parent_module_idx, fragment)
14931527
self.fragment_module_idx[fragment] = parent_module_idx
1528+
elif isinstance(fragment, _ir.RequirePosedge):
1529+
pass
14941530
elif type(fragment) is _ir.Fragment:
14951531
module_idx = self.netlist.add_module(parent_module_idx, fragment_name, src_loc=fragment.src_loc, cell_src_loc=cell_src_loc)
14961532
self.fragment_module_idx[fragment] = module_idx

amaranth/hdl/_xfrm.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,8 @@ def on_fragment(self, fragment):
314314
oe=fragment.oe,
315315
src_loc=fragment.src_loc,
316316
)
317+
elif isinstance(fragment, RequirePosedge):
318+
new_fragment = RequirePosedge(fragment._domain, src_loc=fragment.src_loc)
317319
else:
318320
new_fragment = Fragment(src_loc=fragment.src_loc)
319321
new_fragment.attrs = OrderedDict(fragment.attrs)
@@ -445,6 +447,8 @@ def on_fragment(self, fragment):
445447
self.on_value(port._data)
446448
self.on_value(port._en)
447449
self._add_used_domain(port._domain)
450+
if isinstance(fragment, RequirePosedge):
451+
self._add_used_domain(fragment._domain)
448452

449453
if isinstance(fragment, Instance):
450454
for name, (value, dir) in fragment.ports.items():
@@ -535,6 +539,12 @@ def map_memory_ports(self, fragment, new_fragment):
535539
if port._domain in self.domain_map:
536540
port._domain = self.domain_map[port._domain]
537541

542+
def on_fragment(self, fragment):
543+
new_fragment = super().on_fragment(fragment)
544+
if isinstance(new_fragment, RequirePosedge) and new_fragment._domain in self.domain_map:
545+
new_fragment._domain = self.domain_map[new_fragment._domain]
546+
return new_fragment
547+
538548

539549
class DomainLowerer(FragmentTransformer, ValueTransformer, StatementTransformer):
540550
def __init__(self, domains=None):

amaranth/lib/cdc.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import warnings
22

33
from .. import *
4+
from ..hdl._ir import RequirePosedge
45

56

67
__all__ = ["FFSynchronizer", "AsyncFFSynchronizer", "ResetSynchronizer", "PulseSynchronizer"]
@@ -187,6 +188,7 @@ def elaborate(self, platform):
187188
ClockSignal("async_ff").eq(ClockSignal(self._o_domain)),
188189
self.o.eq(flops[-1])
189190
]
191+
m.submodules += RequirePosedge(self._o_domain)
190192

191193
return m
192194

amaranth/vendor/_altera.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from ..hdl import *
44
from ..hdl import _ast
5+
from ..hdl._ir import RequirePosedge
56
from ..lib import io, wiring
67
from ..build import *
78

@@ -152,6 +153,7 @@ def elaborate(self, platform):
152153
inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert))
153154

154155
if self.direction is not io.Direction.Output:
156+
m.submodules += RequirePosedge(self.i_domain)
155157
i0_reg = Signal(len(self.port))
156158
i0_inv = Signal(len(self.port))
157159
i1_inv = Signal(len(self.port))
@@ -167,6 +169,7 @@ def elaborate(self, platform):
167169
m.d.comb += self.i[1].eq(i1_inv ^ inv_mask)
168170

169171
if self.direction is not io.Direction.Input:
172+
m.submodules += RequirePosedge(self.o_domain)
170173
m.submodules.o_ddr = Instance("altddio_out",
171174
p_width=len(self.port),
172175
o_dataout=buf.o,
@@ -507,6 +510,7 @@ def get_ff_sync(self, ff_sync):
507510
def get_async_ff_sync(self, async_ff_sync):
508511
m = Module()
509512
sync_output = Signal()
513+
m.submodules += RequirePosedge(async_ff_sync._o_domain)
510514
if async_ff_sync._edge == "pos":
511515
m.submodules += Instance("altera_std_synchronizer",
512516
p_depth=async_ff_sync._stages,

amaranth/vendor/_gowin.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import re
55

66
from ..hdl import *
7+
from ..hdl._ir import RequirePosedge
78
from ..lib import io, wiring
89
from ..lib.cdc import ResetSynchronizer
910
from ..build import *
@@ -131,6 +132,7 @@ def elaborate(self, platform):
131132
inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert))
132133

133134
if self.direction is not io.Direction.Output:
135+
m.submodules += RequirePosedge(self.i_domain)
134136
i0_inv = Signal(len(self.port))
135137
i1_inv = Signal(len(self.port))
136138
for bit in range(len(self.port)):
@@ -144,6 +146,7 @@ def elaborate(self, platform):
144146
m.d.comb += self.i[1].eq(i1_inv ^ inv_mask)
145147

146148
if self.direction is not io.Direction.Input:
149+
m.submodules += RequirePosedge(self.o_domain)
147150
o0_inv = self.o[0] ^ inv_mask
148151
o1_inv = self.o[1] ^ inv_mask
149152
for bit in range(len(self.port)):

amaranth/vendor/_lattice.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from abc import abstractmethod
22

33
from ..hdl import *
4+
from ..hdl._ir import RequirePosedge
45
from ..lib import io, wiring
56
from ..build import *
67

@@ -107,6 +108,7 @@ def elaborate(self, platform):
107108
inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert))
108109

109110
if self.direction is not io.Direction.Output:
111+
m.submodules += RequirePosedge(self.i_domain)
110112
i_inv = Signal.like(self.i)
111113
for bit in range(len(self.port)):
112114
m.submodules[f"i_ff{bit}"] = Instance("IFS1P3DX",
@@ -119,6 +121,7 @@ def elaborate(self, platform):
119121
m.d.comb += self.i.eq(i_inv ^ inv_mask)
120122

121123
if self.direction is not io.Direction.Input:
124+
m.submodules += RequirePosedge(self.o_domain)
122125
o_inv = Signal.like(self.o)
123126
m.d.comb += o_inv.eq(self.o ^ inv_mask)
124127
for bit in range(len(self.port)):
@@ -142,6 +145,7 @@ def elaborate(self, platform):
142145
inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert))
143146

144147
if self.direction is not io.Direction.Output:
148+
m.submodules += RequirePosedge(self.i_domain)
145149
i_inv = Signal.like(self.i)
146150
for bit in range(len(self.port)):
147151
m.submodules[f"i_ff{bit}"] = Instance("IFD1P3DX",
@@ -154,6 +158,7 @@ def elaborate(self, platform):
154158
m.d.comb += self.i.eq(i_inv ^ inv_mask)
155159

156160
if self.direction is not io.Direction.Input:
161+
m.submodules += RequirePosedge(self.o_domain)
157162
o_inv = Signal.like(self.o)
158163
m.d.comb += o_inv.eq(self.o ^ inv_mask)
159164
for bit in range(len(self.port)):
@@ -177,6 +182,7 @@ def elaborate(self, platform):
177182
inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert))
178183

179184
if self.direction is not io.Direction.Output:
185+
m.submodules += RequirePosedge(self.i_domain)
180186
i0_inv = Signal(len(self.port))
181187
i1_inv = Signal(len(self.port))
182188
for bit in range(len(self.port)):
@@ -191,6 +197,7 @@ def elaborate(self, platform):
191197
m.d.comb += self.i[1].eq(i1_inv ^ inv_mask)
192198

193199
if self.direction is not io.Direction.Input:
200+
m.submodules += RequirePosedge(self.o_domain)
194201
o0_inv = Signal(len(self.port))
195202
o1_inv = Signal(len(self.port))
196203
m.d.comb += [
@@ -218,6 +225,7 @@ def elaborate(self, platform):
218225
inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert))
219226

220227
if self.direction is not io.Direction.Output:
228+
m.submodules += RequirePosedge(self.i_domain)
221229
i0_inv = Signal(len(self.port))
222230
i1_inv = Signal(len(self.port))
223231
for bit in range(len(self.port)):
@@ -232,6 +240,7 @@ def elaborate(self, platform):
232240
m.d.comb += self.i[1].eq(i1_inv ^ inv_mask)
233241

234242
if self.direction is not io.Direction.Input:
243+
m.submodules += RequirePosedge(self.o_domain)
235244
o0_inv = Signal(len(self.port))
236245
o1_inv = Signal(len(self.port))
237246
m.d.comb += [
@@ -259,6 +268,7 @@ def elaborate(self, platform):
259268
inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert))
260269

261270
if self.direction is not io.Direction.Output:
271+
m.submodules += RequirePosedge(self.i_domain)
262272
i0_inv = Signal(len(self.port))
263273
i1_inv = Signal(len(self.port))
264274
for bit in range(len(self.port)):
@@ -273,6 +283,7 @@ def elaborate(self, platform):
273283
m.d.comb += self.i[1].eq(i1_inv ^ inv_mask)
274284

275285
if self.direction is not io.Direction.Input:
286+
m.submodules += RequirePosedge(self.o_domain)
276287
o0_inv = Signal(len(self.port))
277288
o1_inv = Signal(len(self.port))
278289
m.d.comb += [

amaranth/vendor/_siliconblue.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from abc import abstractmethod
55

66
from ..hdl import *
7+
from ..hdl._ir import RequirePosedge
78
from ..lib.cdc import ResetSynchronizer
89
from ..lib import io
910
from ..build import *
@@ -506,10 +507,12 @@ def get_inv(y, a):
506507
else:
507508
io_args.append(("o", "D_IN_0", i[bit]))
508509
elif isinstance(buffer, io.FFBuffer):
510+
m.submodules += RequirePosedge(self.i_domain)
509511
i_type = 0b00 # PIN_INPUT_REGISTERED aka PIN_INPUT_DDR
510512
io_args.append(("i", "INPUT_CLK", ClockSignal(buffer.i_domain)))
511513
io_args.append(("o", "D_IN_0", i[bit]))
512514
elif isinstance(buffer, io.DDRBuffer):
515+
m.submodules += RequirePosedge(self.i_domain)
513516
i_type = 0b00 # PIN_INPUT_REGISTERED aka PIN_INPUT_DDR
514517
io_args.append(("i", "INPUT_CLK", ClockSignal(buffer.i_domain)))
515518
io_args.append(("o", "D_IN_0", i0[bit]))
@@ -521,10 +524,12 @@ def get_inv(y, a):
521524
o_type = 0b1010 # PIN_OUTPUT_TRISTATE
522525
io_args.append(("i", "D_OUT_0", o[bit]))
523526
elif isinstance(buffer, io.FFBuffer):
527+
m.submodules += RequirePosedge(self.o_domain)
524528
o_type = 0b1101 # PIN_OUTPUT_REGISTERED_ENABLE_REGISTERED
525529
io_args.append(("i", "OUTPUT_CLK", ClockSignal(buffer.o_domain)))
526530
io_args.append(("i", "D_OUT_0", o[bit]))
527531
elif isinstance(buffer, io.DDRBuffer):
532+
m.submodules += RequirePosedge(self.o_domain)
528533
o_type = 0b1100 # PIN_OUTPUT_DDR_ENABLE_REGISTERED
529534
io_args.append(("i", "OUTPUT_CLK", ClockSignal(buffer.o_domain)))
530535
io_args.append(("i", "D_OUT_0", o0[bit]))

0 commit comments

Comments
 (0)