-
Notifications
You must be signed in to change notification settings - Fork 57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Vivado CDC constraints #227
Conversation
Vivado was inferring an SRL16 from a MultiReg in some cases
This tags the first register in each `MultiReg` or `ResetSynchronizer` with the attribute `nmigen_async_ff` and then applies a false path and max delay constraint to all registers tagged with that attribute in the `.xdc` file. The max delay defaults to 5 ns and has an override, `max_delay` where it can be changed for the > whole project. It's possible to make this an argument to `MultiReg` instead, but is more complex. > git commit -m "add clock domain crossing constraints on Vivado This tags the first register in each `MultiReg` or `ResetSynchronizer` with the attribute `nmigen_async_ff` and then applies a false path and max delay constraint to all registers tagged with that attribute in the `.xdc` file. The max delay defaults to 5 ns and has an override, `max_delay` where it can be changed for the whole project. It's possible to make this an optional argument to `MultiReg` instead, but is more complex. It would probably work to set `nmigen_async_ff` to the desired delay rather than just `TRUE`. I'm not sure how hard it would be to extract that in the TCL or if it would be easier to keep a dict of all used delay values and put a line for each into the `.xdc` file.
…_cdc_constraints
Codecov Report
@@ Coverage Diff @@
## master #227 +/- ##
==========================================
- Coverage 82.38% 82.32% -0.06%
==========================================
Files 33 33
Lines 5483 5489 +6
Branches 1172 1174 +2
==========================================
+ Hits 4517 4519 +2
- Misses 834 836 +2
- Partials 132 134 +2
Continue to review full report at Codecov.
|
References: I expect a few more constraint requirements once #40 is merged due to a timing path between the write clock and async outputs on the LUT RAMs. It's a challenge to deal with: https://forums.xilinx.com/t5/Vivado-TCL-Community/set-disable-timing-for-hierarchical-cell/td-p/495808 Alternately, set the clocks to be asynchronous, but getting correct clock names is a challenge. |
Xilinx UG903 is self contradictory and the |
the false path had precedence over the max_delay despite the docs the large negative min delay effectively disables hold path timing which was the intended goal of the false path.
Ideally, we would use I also noticed that there is a "critical warning" generated when the added lines are present in the Direction would be appreciated. |
This isn't directly related to the PR at hand (I'll look more into how Vivado works before commenting here), but I have a question you might answer regarding attributes. nMigen emits some internal attributes like |
Can you elaborate on the exact nature of complication here? |
Actually, I was wrong. Vivado doesn't mind. ISE complains:
|
We would either need an I did find that instead of the I think I'll need to add a dict keyed with |
As you found, Vivado does seem to ignore any attribute it doesn't use. It seems safe enough to assume it won't use anything starting with I do see Vivado complaining about the |
We'll have this information once #4 is implemented, but right now this isn't viable.
I don't understand this statement. We already have
I would personally opt for a simpler solution: grab the attribute with Tcl and set it, again, with Tcl. I'll throw together a snippet soon so you don't have to do it yourself. |
Sounds good.
This is #220. Unfortunately an upstream issue. |
I was referring to The LUTRAM on 7series has a timing path from the write clock to the async read data. I want to make that a false path / max delay while keeping the path from read address to read data. Having the from clock makes that possible. |
My knowledge of Tcl is quite limited, so I'll leave that to you. |
MultiReg renamed to FFSynchronizer
This is effectively equivalent without the ugly constant There is now no hold path in the timing report, but it retains the max_delay.
@dlharmon I think I understand how to do this in Tcl, but could you please explain what |
This one is good and what I based the migen async reset synchronizer constraints on. There are more good posts by that guy in the xilinx forums: http://www.colmryan.org/avrums-clock-domain-crossing-widsom.html |
The first link mentioned by @jordens does a decent job explaining it. Simply adding a false path to the flip flop is the easiest, but allows the place and route unlimited delay in that net which is problematic in some cases. For instance, I have a bunch of 250 MHz same frequency, unknown phase shift crossings that need to come out of reset within a few cycles of each other, so a 5 ns max delay works well there. |
Do I understand it correctly that |
This seems to work, but does generate a warning for each input with no clock. foreach cell [get_cells -hier -filter {nmigen.vivado.max_delay != ""}] {
set clock [get_clocks -quiet -of_objects [all_fanin -flat -startpoints_only [get_pin $cell/D]]]
if {[llength $clock] != 0} {
set_max_delay -datapath_only -from $clock \
-to [get_cells $cell] [get_property nmigen.vivado.max_delay $cell]
}
} Edit: add I suppose we could add an else with a simple max delay if there is no source clock. |
Yep, I was about to suggest something like that. I think you want But isn't this behavior correct? If you set |
Not necessarily. What about the case of something like a reset pushbutton on an input? I'm not sure that should trigger a warning. |
What would be the purpose of setting |
Nothing whatsoever. I'd forgotten about the plan to make the default max delay |
I like the idea of removing the -quiet, not adding an else. |
Are you covering this use case by adding Also, I understand that |
Ah, I missed your second comment somehow. Yes. Removing |
Good idea on doing I need to rework the PR and could have mis-rebased too. |
I pushed what I think we agreed on. It does add -quiet to the initial find cells lines so that if there are no I added the |
Thanks for the update. It looks like I did not post my work-in-progress patch for diff --git a/nmigen/lib/cdc.py b/nmigen/lib/cdc.py
index d21df79..3c62b8f 100644
--- a/nmigen/lib/cdc.py
+++ b/nmigen/lib/cdc.py
@@ -29,15 +29,15 @@ class FFSynchronizer(Elaboratable):
Signal connected to synchroniser output.
o_domain : str
Name of output clock domain.
- stages : int
- Number of synchronization stages between input and output. The lowest safe number is 2,
- with higher numbers reducing MTBF further, at the cost of increased latency.
reset : int
Reset value of the flip-flops. On FPGAs, even if ``reset_less`` is True,
the :class:`FFSynchronizer` is still set to this value during initialization.
reset_less : bool
- If True (the default), this :class:`FFSynchronizer` is unaffected by ``o_domain`` reset.
- See "Note on Reset" below.
+ If ``True`` (the default), this :class:`FFSynchronizer` is unaffected by ``o_domain``
+ reset. See "Note on Reset" below.
+ stages : int
+ Number of synchronization stages between input and output. The lowest safe number is 2,
+ with higher numbers reducing MTBF further, at the cost of increased latency.
Platform override
-----------------
@@ -61,7 +61,8 @@ class FFSynchronizer(Elaboratable):
:class:`FFSynchronizer` is reset by the ``o_domain`` reset only.
"""
- def __init__(self, i, o, *, o_domain="sync", reset=0, reset_less=True, stages=2):
+ def __init__(self, i, o, *, o_domain="sync", reset=0, reset_less=True, stages=2,
+ max_input_delay=None):
_check_stages(stages)
self.i = i
@@ -72,10 +73,16 @@ class FFSynchronizer(Elaboratable):
self._o_domain = o_domain
self._stages = stages
+ self._max_input_delay = max_input_delay
+
def elaborate(self, platform):
if hasattr(platform, "get_ff_sync"):
return platform.get_ff_sync(self)
+ if self._max_input_delay is not None:
+ raise NotImplementedError("Platform {!r} does not support constraining input delay "
+ "for FFSynchronizer".format(platform))
+
m = Module()
flops = [Signal(self.i.shape(), name="stage{}".format(index),
reset=self._reset, reset_less=self._reset_less)
@@ -117,7 +124,7 @@ class ResetSynchronizer(Elaboratable):
Define the ``get_reset_sync`` platform method to override the implementation of
:class:`ResetSynchronizer`, e.g. to instantiate library cells directly.
"""
- def __init__(self, arst, *, domain="sync", stages=2):
+ def __init__(self, arst, *, domain="sync", stages=2, max_input_delay=None):
_check_stages(stages)
self.arst = arst
@@ -125,10 +132,16 @@ class ResetSynchronizer(Elaboratable):
self._domain = domain
self._stages = stages
+ self._max_input_delay = None
+
def elaborate(self, platform):
if hasattr(platform, "get_reset_sync"):
return platform.get_reset_sync(self)
+ if self._max_input_delay is not None:
+ raise NotImplementedError("Platform {!r} does not support constraining input delay "
+ "for ResetSynchronizer".format(platform))
+
m = Module()
m.domains += ClockDomain("reset_sync", async_reset=True, local=True)
flops = [Signal(1, name="stage{}".format(index), reset=1)
diff --git a/nmigen/vendor/lattice_ecp5.py b/nmigen/vendor/lattice_ecp5.py
index f18e87c..89143ab 100644
--- a/nmigen/vendor/lattice_ecp5.py
+++ b/nmigen/vendor/lattice_ecp5.py
@@ -533,3 +533,6 @@ class LatticeECP5Platform(TemplatedPlatform):
io_B=p_port[bit],
)
return m
+
+ # CDC primitives are not currently specialized for ECP5. While Diamond supports the necessary
+ # attributes (TBD); nextpnr-ecp5 does not.
diff --git a/nmigen/vendor/lattice_ice40.py b/nmigen/vendor/lattice_ice40.py
index bc1df8f..ffbbd0c 100644
--- a/nmigen/vendor/lattice_ice40.py
+++ b/nmigen/vendor/lattice_ice40.py
@@ -564,3 +564,6 @@ class LatticeICE40Platform(TemplatedPlatform):
# Tristate and bidirectional buffers are not supported on iCE40 because it requires external
# termination, which is incompatible for input and output differential I/Os.
+
+ # CDC primitives are not currently specialized for iCE40. It is not known if iCECube2 supports
+ # the necessary attributes; nextpnr-ice40 does not.
diff --git a/nmigen/vendor/xilinx_7series.py b/nmigen/vendor/xilinx_7series.py
index b25c4e9..f0a4f19 100644
--- a/nmigen/vendor/xilinx_7series.py
+++ b/nmigen/vendor/xilinx_7series.py
@@ -78,6 +78,10 @@ class Xilinx7SeriesPlatform(TemplatedPlatform):
{% endfor %}
{{get_override("script_after_read")|default("# (script_after_read placeholder)")}}
synth_design -top {{name}} -part {{platform.device}}{{platform.package}}-{{platform.speed}}
+ set_false_path -hold -to [get_cells -hier -filter {nmigen.vivado.false_path == TRUE}]
+ foreach cell [get_cells -hier -filter {nmigen.vivado.max_delay != ""}] {
+ set_max_delay [get_property nmigen.vivado.max_delay $cell] -to $cell
+ }
{{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}}
report_timing_summary -file {{name}}_timing_synth.rpt
report_utilization -hierarchical -file {{name}}_utilization_hierachical_synth.rpt
@@ -367,7 +371,28 @@ class Xilinx7SeriesPlatform(TemplatedPlatform):
reset=ff_sync._reset, reset_less=ff_sync._reset_less,
attrs={"ASYNC_REG": "TRUE"})
for index in range(ff_sync._stages)]
+ flops[0].attrs["nmigen.vivado.false_path"] = "TRUE"
+ if ff_sync.max_input_delay is not None:
+ flops[0].attrs["nmigen.vivado.max_delay"] = str(ff_sync.max_input_delay)
for i, o in zip((ff_sync.i, *flops), flops):
m.d[ff_sync._o_domain] += o.eq(i)
m.d.comb += ff_sync.o.eq(flops[-1])
return m
+
+ def get_reset_sync(self, reset_sync):
+ m = Module()
+ m.domains += ClockDomain("reset_sync", async_reset=True, local=True)
+ flops = [Signal(1, name="stage{}".format(index), reset=1,
+ attrs={"ASYNC_REG": "TRUE"})
+ for index in range(reset_sync._stages)]
+ flops[0].attrs["nmigen.vivado.false_path"] = "TRUE"
+ if reset_sync.max_input_delay is not None:
+ flops[0].attrs["nmigen.vivado.max_delay"] = str(reset_sync.max_input_delay)
+ for i, o in zip((0, *flops), flops):
+ m.d.reset_sync += o.eq(i)
+ m.d.comb += [
+ ClockSignal("reset_sync").eq(ClockSignal(reset_sync._domain)),
+ ResetSignal("reset_sync").eq(reset_sync.arst),
+ ResetSignal(reset_sync._domain).eq(flops[-1])
+ ]
+ return m
diff --git a/nmigen/vendor/xilinx_spartan_3_6.py b/nmigen/vendor/xilinx_spartan_3_6.py
index 284cfab..eb0aac0 100644
--- a/nmigen/vendor/xilinx_spartan_3_6.py
+++ b/nmigen/vendor/xilinx_spartan_3_6.py
@@ -412,6 +412,10 @@ class XilinxSpartan3Or6Platform(TemplatedPlatform):
return m
def get_ff_sync(self, ff_sync):
+ if ff_sync._max_input_delay is not None:
+ raise NotImplementedError("Platform {!r} does not support constraining input delay "
+ "for FFSynchronizer".format(self))
+
m = Module()
flops = [Signal(ff_sync.i.shape(), name="stage{}".format(index),
reset=ff_sync._reset, reset_less=ff_sync._reset_less,
@@ -423,6 +427,10 @@ class XilinxSpartan3Or6Platform(TemplatedPlatform):
return m
def get_reset_sync(self, reset_sync):
+ if reset_sync._max_input_delay is not None:
+ raise NotImplementedError("Platform {!r} does not support constraining input delay "
+ "for ResetSynchronizer".format(self))
+
m = Module()
m.domains += ClockDomain("reset_sync", async_reset=True, local=True)
flops = [Signal(1, name="stage{}".format(index), reset=1, |
error messages and comments did not merge xilinx_7series.py
First commit reverts I think we want to not apply a false path if there is a max delay set as it overrides the max delay in Vivado. I have |
I very much appreciate assistance with CDC primitives, especially on platforms I haven't worked with much. Speaking of which, I tried to add false path support for ISE. I came up with this patch: diff --git a/nmigen/vendor/xilinx_spartan_3_6.py b/nmigen/vendor/xilinx_spartan_3_6.py
index 284cfab..ba3d53f 100644
--- a/nmigen/vendor/xilinx_spartan_3_6.py
+++ b/nmigen/vendor/xilinx_spartan_3_6.py
@@ -127,9 +127,12 @@ class XilinxSpartan3Or6Platform(TemplatedPlatform):
{% endfor %}
{% endfor %}
{% for signal, frequency in platform.iter_clock_constraints() -%}
- NET "{{signal|hierarchy("/")}}" TNM_NET="PRD{{signal|hierarchy("/")}}";
- TIMESPEC "TS{{signal|hierarchy("/")}}"=PERIOD "PRD{{signal|hierarchy("/")}}" {{1000000000/frequency}} ns HIGH 50%;
+ NET "{{signal|hierarchy("/")}}" TNM_NET = "PERIOD_{{signal|hierarchy("/")}}";
+ TIMESPEC "TS_clk_{{signal|hierarchy("/")}}" = PERIOD
+ "PERIOD_{{signal|hierarchy("/")}}" {{1000000000/frequency}} ns HIGH 50%;
{% endfor %}
+ NET "*/async_ff$stage1$*" TPSYNC = "TPSYNC_async_ff";
+ TIMESPEC "TS_async_ff" = TO "TPSYNC_async_ff" TIG;
{{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
"""
}
@@ -413,7 +416,7 @@ class XilinxSpartan3Or6Platform(TemplatedPlatform):
def get_ff_sync(self, ff_sync):
m = Module()
- flops = [Signal(ff_sync.i.shape(), name="stage{}".format(index),
+ flops = [Signal(ff_sync.i.shape(), name="async_ff$stage{}$".format(index),
reset=ff_sync._reset, reset_less=ff_sync._reset_less,
attrs={"ASYNC_REG": "TRUE"})
for index in range(ff_sync._stages)]
@@ -425,7 +428,7 @@ class XilinxSpartan3Or6Platform(TemplatedPlatform):
def get_reset_sync(self, reset_sync):
m = Module()
m.domains += ClockDomain("reset_sync", async_reset=True, local=True)
- flops = [Signal(1, name="stage{}".format(index), reset=1,
+ flops = [Signal(1, name="async_ff$stage{}$".format(index), reset=1,
attrs={"ASYNC_REG": "TRUE"})
for index in range(reset_sync._stages)]
for i, o in zip((0, *flops), flops): Would you be able to confirm that it works? I'm also not entirely sure about using |
I'm far less confident with ISE than Vivado. It's been a while since I've done more than minor changes to old projects. I made this test case: from nmigen import *
from nmigen.lib.cdc import FFSynchronizer
from nmigen.build import *
from nmigen.vendor.xilinx_spartan_3_6 import *
class TestPlatform(XilinxSpartan6Platform):
device = "xc6slx9"
package = "tqg144"
speed = "2"
default_clk = "clk100"
resources = [
Resource("clk100", 0, Pins("P126", dir="i"), Clock(100e6), Attrs(IOSTANDARD="LVCMOS33")),
Resource("clk97", 0, Pins("P123", dir="i"), Clock(97e6), Attrs(IOSTANDARD="LVCMOS33")),
Resource("led", 0, Pins("P119", dir='o'), Attrs(IOSTANDARD="LVCMOS33")),
]
connectors = []
class Blinky(Elaboratable):
def elaborate(self, platform):
clk97 = platform.request("clk97")
user_led = platform.request("led", 0)
counter = Signal(20)
blink_97 = Signal()
m = Module()
m.domains.clk97 = ClockDomain("clk97")
m.d.comb += ClockSignal("clk97").eq(clk97.i)
m.submodules.mreg = FFSynchronizer(i=counter[-1], o=blink_97, o_domain="clk97", stages=2, reset=1)
m.d.sync += counter.eq(counter + 1)
m.d.comb += user_led.o.eq(blink_97)
return m
if __name__ == "__main__":
platform = TestPlatform()
platform.build(Blinky(), do_program=False) With or without your patch, it passes timing. I'd forgotten that ISE by default ignores timing between unrelated clocks. I think just the I ran |
Thanks. Then I think it is fine as it is. |
Related: #212
This tags the first register in each
MultiReg
orResetSynchronizer
with the attribute
nmigen_async_ff
and then applies a false path andmax delay constraint to all registers tagged with that attribute in
the
.xdc
file.The max delay defaults to 5 ns and has an override,
max_delay
whereit can be changed for the > whole project. It's possible to make this
an argument to
MultiReg
instead, but is more complex. > git commit-m "add clock domain crossing constraints on Vivado This tags the
first register in each
MultiReg
orResetSynchronizer
with theattribute
nmigen_async_ff
and then applies a false path and maxdelay constraint to all registers tagged with that attribute in the
.xdc
file.The max delay defaults to 5 ns and has an override,
max_delay
whereit can be changed for the whole project. It's possible to make this an
optional argument to
MultiReg
instead, but is more complex. It wouldprobably work to set
nmigen_async_ff
to the desired delay ratherthan just
TRUE
. I'm not sure how hard it would be to extract that inthe TCL or if it would be easier to keep a dict of all used delay
values and put a line for each into the
.xdc
file.