hdl._ir, lib, vendor: add RequirePosedge, use it whenever required.

This commit is contained in:
Wanda 2024-04-28 20:04:16 +02:00 committed by Catherine
parent d3c312cf96
commit 028d5d8073
9 changed files with 110 additions and 2 deletions

View file

@ -10,8 +10,9 @@ from . import _ast, _cd, _ir, _nir
__all__ = [
"AlreadyElaborated", "UnusedElaboratable", "Elaboratable", "DuplicateElaboratable",
"DriverConflict", "Fragment", "Instance", "IOBufferInstance", "PortDirection", "Design",
"build_netlist",
"DomainRequirementFailed", "DriverConflict",
"Fragment", "Instance", "IOBufferInstance", "RequirePosedge", "PortDirection",
"Design", "build_netlist",
]
@ -40,6 +41,11 @@ class DuplicateElaboratable(Exception):
pass
class DomainRequirementFailed(Exception):
"""Raised when a module has unsatisfied requirements about a clock domain, such as getting
a negedge domain when only posedge domains are supported."""
class Fragment:
@staticmethod
def get(obj, platform):
@ -385,6 +391,19 @@ class IOBufferInstance(Fragment):
self.src_loc = src_loc or tracer.get_src_loc(src_loc_at)
class RequirePosedge(Fragment):
"""A special fragment that requires a given domain to have :py:`clk_edge="pos"`, failing
elaboration otherwise.
This is a private interface, without a stability guarantee.
"""
def __init__(self, domain, *, src_loc_at=0, src_loc=None):
super().__init__()
self._domain = domain
self.src_loc = src_loc or tracer.get_src_loc(src_loc_at)
def _add_name(assigned_names, name):
if name in assigned_names:
name = f"{name}${len(assigned_names)}"
@ -429,6 +448,7 @@ class Design:
else:
self._use_signal(fragment, conn)
self._assign_names(fragment, hierarchy)
self._check_domain_requires()
def _compute_fragment_depth_parent(self, fragment: Fragment, parent: "Fragment | None", depth: int):
"""Recursively computes every fragment's depth and parent."""
@ -576,6 +596,8 @@ class Design:
self._use_signal(fragment, domain.clk)
if domain.rst is not None:
self._use_signal(fragment, domain.rst)
elif isinstance(fragment, _ir.RequirePosedge):
pass
else:
for domain_name, statements in fragment.statements.items():
if domain_name != "comb":
@ -662,6 +684,18 @@ class Design:
subfragment_name = _add_name(frag_info.assigned_names, subfragment_name)
self._assign_names(subfragment, hierarchy=(*hierarchy, subfragment_name))
def _check_domain_requires(self):
for fragment, fragment_info in self.fragments.items():
if isinstance(fragment, RequirePosedge):
domain = fragment.domains[fragment._domain]
if domain.clk_edge != "pos":
if fragment.src_loc is None:
src_loc = "<unknown>:0"
else:
src_loc = f"{fragment.src_loc[0]}:{fragment.src_loc[1]}"
fragment_name = ".".join(fragment_info.name)
raise DomainRequirementFailed(f"Domain {domain.name} has a negedge clock, but posedge clock is required by {fragment_name} at {src_loc}")
def lookup_domain(self, domain, context):
if domain == "comb":
raise KeyError("comb")
@ -1491,6 +1525,8 @@ class NetlistEmitter:
assert parent_module_idx is not None
self.emit_iobuffer(parent_module_idx, fragment)
self.fragment_module_idx[fragment] = parent_module_idx
elif isinstance(fragment, _ir.RequirePosedge):
pass
elif type(fragment) is _ir.Fragment:
module_idx = self.netlist.add_module(parent_module_idx, fragment_name, src_loc=fragment.src_loc, cell_src_loc=cell_src_loc)
self.fragment_module_idx[fragment] = module_idx

View file

@ -314,6 +314,8 @@ class FragmentTransformer:
oe=fragment.oe,
src_loc=fragment.src_loc,
)
elif isinstance(fragment, RequirePosedge):
new_fragment = RequirePosedge(fragment._domain, src_loc=fragment.src_loc)
else:
new_fragment = Fragment(src_loc=fragment.src_loc)
new_fragment.attrs = OrderedDict(fragment.attrs)
@ -445,6 +447,8 @@ class DomainCollector(ValueVisitor, StatementVisitor):
self.on_value(port._data)
self.on_value(port._en)
self._add_used_domain(port._domain)
if isinstance(fragment, RequirePosedge):
self._add_used_domain(fragment._domain)
if isinstance(fragment, Instance):
for name, (value, dir) in fragment.ports.items():
@ -535,6 +539,12 @@ class DomainRenamer(FragmentTransformer, ValueTransformer, StatementTransformer)
if port._domain in self.domain_map:
port._domain = self.domain_map[port._domain]
def on_fragment(self, fragment):
new_fragment = super().on_fragment(fragment)
if isinstance(new_fragment, RequirePosedge) and new_fragment._domain in self.domain_map:
new_fragment._domain = self.domain_map[new_fragment._domain]
return new_fragment
class DomainLowerer(FragmentTransformer, ValueTransformer, StatementTransformer):
def __init__(self, domains=None):

View file

@ -1,6 +1,7 @@
import warnings
from .. import *
from ..hdl._ir import RequirePosedge
__all__ = ["FFSynchronizer", "AsyncFFSynchronizer", "ResetSynchronizer", "PulseSynchronizer"]
@ -187,6 +188,7 @@ class AsyncFFSynchronizer(Elaboratable):
ClockSignal("async_ff").eq(ClockSignal(self._o_domain)),
self.o.eq(flops[-1])
]
m.submodules += RequirePosedge(self._o_domain)
return m

View file

@ -2,6 +2,7 @@ from abc import abstractmethod
from ..hdl import *
from ..hdl import _ast
from ..hdl._ir import RequirePosedge
from ..lib import io, wiring
from ..build import *
@ -152,6 +153,7 @@ class DDRBuffer(io.DDRBuffer):
inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert))
if self.direction is not io.Direction.Output:
m.submodules += RequirePosedge(self.i_domain)
i0_reg = Signal(len(self.port))
i0_inv = Signal(len(self.port))
i1_inv = Signal(len(self.port))
@ -167,6 +169,7 @@ class DDRBuffer(io.DDRBuffer):
m.d.comb += self.i[1].eq(i1_inv ^ inv_mask)
if self.direction is not io.Direction.Input:
m.submodules += RequirePosedge(self.o_domain)
m.submodules.o_ddr = Instance("altddio_out",
p_width=len(self.port),
o_dataout=buf.o,
@ -507,6 +510,7 @@ class AlteraPlatform(TemplatedPlatform):
def get_async_ff_sync(self, async_ff_sync):
m = Module()
sync_output = Signal()
m.submodules += RequirePosedge(async_ff_sync._o_domain)
if async_ff_sync._edge == "pos":
m.submodules += Instance("altera_std_synchronizer",
p_depth=async_ff_sync._stages,

View file

@ -4,6 +4,7 @@ import math
import re
from ..hdl import *
from ..hdl._ir import RequirePosedge
from ..lib import io, wiring
from ..lib.cdc import ResetSynchronizer
from ..build import *
@ -131,6 +132,7 @@ class DDRBuffer(io.DDRBuffer):
inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert))
if self.direction is not io.Direction.Output:
m.submodules += RequirePosedge(self.i_domain)
i0_inv = Signal(len(self.port))
i1_inv = Signal(len(self.port))
for bit in range(len(self.port)):
@ -144,6 +146,7 @@ class DDRBuffer(io.DDRBuffer):
m.d.comb += self.i[1].eq(i1_inv ^ inv_mask)
if self.direction is not io.Direction.Input:
m.submodules += RequirePosedge(self.o_domain)
o0_inv = self.o[0] ^ inv_mask
o1_inv = self.o[1] ^ inv_mask
for bit in range(len(self.port)):

View file

@ -1,6 +1,7 @@
from abc import abstractmethod
from ..hdl import *
from ..hdl._ir import RequirePosedge
from ..lib import io, wiring
from ..build import *
@ -107,6 +108,7 @@ class FFBufferECP5(io.FFBuffer):
inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert))
if self.direction is not io.Direction.Output:
m.submodules += RequirePosedge(self.i_domain)
i_inv = Signal.like(self.i)
for bit in range(len(self.port)):
m.submodules[f"i_ff{bit}"] = Instance("IFS1P3DX",
@ -119,6 +121,7 @@ class FFBufferECP5(io.FFBuffer):
m.d.comb += self.i.eq(i_inv ^ inv_mask)
if self.direction is not io.Direction.Input:
m.submodules += RequirePosedge(self.o_domain)
o_inv = Signal.like(self.o)
m.d.comb += o_inv.eq(self.o ^ inv_mask)
for bit in range(len(self.port)):
@ -142,6 +145,7 @@ class FFBufferNexus(io.FFBuffer):
inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert))
if self.direction is not io.Direction.Output:
m.submodules += RequirePosedge(self.i_domain)
i_inv = Signal.like(self.i)
for bit in range(len(self.port)):
m.submodules[f"i_ff{bit}"] = Instance("IFD1P3DX",
@ -154,6 +158,7 @@ class FFBufferNexus(io.FFBuffer):
m.d.comb += self.i.eq(i_inv ^ inv_mask)
if self.direction is not io.Direction.Input:
m.submodules += RequirePosedge(self.o_domain)
o_inv = Signal.like(self.o)
m.d.comb += o_inv.eq(self.o ^ inv_mask)
for bit in range(len(self.port)):
@ -177,6 +182,7 @@ class DDRBufferECP5(io.DDRBuffer):
inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert))
if self.direction is not io.Direction.Output:
m.submodules += RequirePosedge(self.i_domain)
i0_inv = Signal(len(self.port))
i1_inv = Signal(len(self.port))
for bit in range(len(self.port)):
@ -191,6 +197,7 @@ class DDRBufferECP5(io.DDRBuffer):
m.d.comb += self.i[1].eq(i1_inv ^ inv_mask)
if self.direction is not io.Direction.Input:
m.submodules += RequirePosedge(self.o_domain)
o0_inv = Signal(len(self.port))
o1_inv = Signal(len(self.port))
m.d.comb += [
@ -218,6 +225,7 @@ class DDRBufferMachXO2(io.DDRBuffer):
inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert))
if self.direction is not io.Direction.Output:
m.submodules += RequirePosedge(self.i_domain)
i0_inv = Signal(len(self.port))
i1_inv = Signal(len(self.port))
for bit in range(len(self.port)):
@ -232,6 +240,7 @@ class DDRBufferMachXO2(io.DDRBuffer):
m.d.comb += self.i[1].eq(i1_inv ^ inv_mask)
if self.direction is not io.Direction.Input:
m.submodules += RequirePosedge(self.o_domain)
o0_inv = Signal(len(self.port))
o1_inv = Signal(len(self.port))
m.d.comb += [
@ -259,6 +268,7 @@ class DDRBufferNexus(io.DDRBuffer):
inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert))
if self.direction is not io.Direction.Output:
m.submodules += RequirePosedge(self.i_domain)
i0_inv = Signal(len(self.port))
i1_inv = Signal(len(self.port))
for bit in range(len(self.port)):
@ -273,6 +283,7 @@ class DDRBufferNexus(io.DDRBuffer):
m.d.comb += self.i[1].eq(i1_inv ^ inv_mask)
if self.direction is not io.Direction.Input:
m.submodules += RequirePosedge(self.o_domain)
o0_inv = Signal(len(self.port))
o1_inv = Signal(len(self.port))
m.d.comb += [

View file

@ -4,6 +4,7 @@
from abc import abstractmethod
from ..hdl import *
from ..hdl._ir import RequirePosedge
from ..lib.cdc import ResetSynchronizer
from ..lib import io
from ..build import *
@ -506,10 +507,12 @@ class SiliconBluePlatform(TemplatedPlatform):
else:
io_args.append(("o", "D_IN_0", i[bit]))
elif isinstance(buffer, io.FFBuffer):
m.submodules += RequirePosedge(self.i_domain)
i_type = 0b00 # PIN_INPUT_REGISTERED aka PIN_INPUT_DDR
io_args.append(("i", "INPUT_CLK", ClockSignal(buffer.i_domain)))
io_args.append(("o", "D_IN_0", i[bit]))
elif isinstance(buffer, io.DDRBuffer):
m.submodules += RequirePosedge(self.i_domain)
i_type = 0b00 # PIN_INPUT_REGISTERED aka PIN_INPUT_DDR
io_args.append(("i", "INPUT_CLK", ClockSignal(buffer.i_domain)))
io_args.append(("o", "D_IN_0", i0[bit]))
@ -521,10 +524,12 @@ class SiliconBluePlatform(TemplatedPlatform):
o_type = 0b1010 # PIN_OUTPUT_TRISTATE
io_args.append(("i", "D_OUT_0", o[bit]))
elif isinstance(buffer, io.FFBuffer):
m.submodules += RequirePosedge(self.o_domain)
o_type = 0b1101 # PIN_OUTPUT_REGISTERED_ENABLE_REGISTERED
io_args.append(("i", "OUTPUT_CLK", ClockSignal(buffer.o_domain)))
io_args.append(("i", "D_OUT_0", o[bit]))
elif isinstance(buffer, io.DDRBuffer):
m.submodules += RequirePosedge(self.o_domain)
o_type = 0b1100 # PIN_OUTPUT_DDR_ENABLE_REGISTERED
io_args.append(("i", "OUTPUT_CLK", ClockSignal(buffer.o_domain)))
io_args.append(("i", "D_OUT_0", o0[bit]))

View file

@ -2,6 +2,7 @@ import re
from abc import abstractmethod
from ..hdl import *
from ..hdl._ir import RequirePosedge
from ..lib.cdc import ResetSynchronizer
from ..lib import io, wiring
from ..build import *
@ -125,11 +126,13 @@ class FFBuffer(io.FFBuffer):
inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert))
if self.direction is not io.Direction.Output:
m.submodules += RequirePosedge(self.i_domain)
i_inv = Signal.like(self.i)
_make_dff(m, "i", self.i_domain, buf.i, i_inv, iob=True)
m.d.comb += self.i.eq(i_inv ^ inv_mask)
if self.direction is not io.Direction.Input:
m.submodules += RequirePosedge(self.o_domain)
o_inv = Signal.like(self.o)
m.d.comb += o_inv.eq(self.o ^ inv_mask)
_make_dff(m, "o", self.o_domain, o_inv, buf.o, iob=True)
@ -146,6 +149,7 @@ class DDRBufferVirtex2(io.DDRBuffer):
inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert))
if self.direction is not io.Direction.Output:
m.submodules += RequirePosedge(self.i_domain)
i0_inv = Signal(len(self.port))
i1_inv = Signal(len(self.port))
# First-generation input DDR register: basically just two FFs with opposite
@ -161,6 +165,7 @@ class DDRBufferVirtex2(io.DDRBuffer):
m.d.comb += self.i[1].eq(i1_inv ^ inv_mask)
if self.direction is not io.Direction.Input:
m.submodules += RequirePosedge(self.o_domain)
o0_inv = Signal(len(self.port))
o1_inv = Signal(len(self.port))
o1_ff = Signal(len(self.port))
@ -210,6 +215,7 @@ class DDRBufferSpartan3E(io.DDRBuffer):
}
if self.direction is not io.Direction.Output:
m.submodules += RequirePosedge(self.i_domain)
i0_inv = Signal(len(self.port))
i1_inv = Signal(len(self.port))
if platform.family == "spartan6" or isinstance(self.port, io.DifferentialPort):
@ -257,6 +263,7 @@ class DDRBufferSpartan3E(io.DDRBuffer):
m.d.comb += self.i[1].eq(i1_inv ^ inv_mask)
if self.direction is not io.Direction.Input:
m.submodules += RequirePosedge(self.o_domain)
o0_inv = Signal(len(self.port))
o1_inv = Signal(len(self.port))
m.d.comb += [
@ -333,6 +340,7 @@ class DDRBufferVirtex4(io.DDRBuffer):
inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert))
if self.direction is not io.Direction.Output:
m.submodules += RequirePosedge(self.i_domain)
i0_inv = Signal(len(self.port))
i1_inv = Signal(len(self.port))
for bit in range(len(self.port)):
@ -352,6 +360,7 @@ class DDRBufferVirtex4(io.DDRBuffer):
m.d.comb += self.i[1].eq(i1_inv ^ inv_mask)
if self.direction is not io.Direction.Input:
m.submodules += RequirePosedge(self.o_domain)
o0_inv = Signal(len(self.port))
o1_inv = Signal(len(self.port))
m.d.comb += [
@ -384,6 +393,7 @@ class DDRBufferUltrascale(io.DDRBuffer):
inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert))
if self.direction is not io.Direction.Output:
m.submodules += RequirePosedge(self.i_domain)
i0_inv = Signal(len(self.port))
i1_inv = Signal(len(self.port))
for bit in range(len(self.port)):
@ -402,6 +412,7 @@ class DDRBufferUltrascale(io.DDRBuffer):
m.d.comb += self.i[1].eq(i1_inv ^ inv_mask)
if self.direction is not io.Direction.Input:
m.submodules += RequirePosedge(self.o_domain)
o0_inv = Signal(len(self.port))
o1_inv = Signal(len(self.port))
m.d.comb += [
@ -1234,6 +1245,7 @@ class XilinxPlatform(TemplatedPlatform):
def get_async_ff_sync(self, async_ff_sync):
m = Module()
m.submodules += RequirePosedge(async_ff_sync._o_domain)
m.domains += ClockDomain("async_ff", async_reset=True, local=True)
# Instantiate a chain of async_ff_sync._stages FDPEs with all
# their PRE pins connected to either async_ff_sync.i or

View file

@ -3598,3 +3598,28 @@ class DomainLookupTestCase(FHDLTestCase):
self.assertIs(design.lookup_domain("sync", xm5), m1_c)
self.assertIs(design.lookup_domain("d", xm4), m4_d)
self.assertIs(design.lookup_domain("d", xm5), m5_d)
class RequirePosedgeTestCase(FHDLTestCase):
def test_require_ok(self):
m = Module()
m.domains.sync = ClockDomain()
m.submodules += RequirePosedge("sync")
Fragment.get(m, None).prepare()
def test_require_fail(self):
m = Module()
m.domains.sync = ClockDomain(clk_edge="neg")
m.submodules += RequirePosedge("sync")
with self.assertRaisesRegex(DomainRequirementFailed,
r"^Domain sync has a negedge clock, but posedge clock is required by top.U\$0 at .*$"):
Fragment.get(m, None).prepare()
def test_require_renamed(self):
m = Module()
m.domains.sync = ClockDomain(clk_edge="pos")
m.domains.test = ClockDomain(clk_edge="neg")
m.submodules += DomainRenamer("test")(RequirePosedge("sync"))
with self.assertRaisesRegex(DomainRequirementFailed,
r"^Domain test has a negedge clock, but posedge clock is required by top.U\$0 at .*$"):
Fragment.get(m, None).prepare()