xilinx: use FDPE instances to implement get_async_ff_sync()

This closes #721 by implementing get_async_ff_sync() using FDPE
primitives to obtain exactly the netlist that we want. This consits
of a chain of N FPDEs (by default N = 2) with all their PRE pins
connected to the reset for a positive edge reset or to the ~reset
for a negative edge reset. The D pin of the first FDPE in the chain
is connected to GND.

To make timing analysis work correctly, two new attributes are
introduced: amaranth.vivado.false_path_pre and
amaranth.vivado.max_delay_pre. These work similarly to
amaranth.vivado.false_path and amaranth.vivado.max_delay, but affect
only the PRE pin, which is what is needed for this synchronizer.
The TCL has been modified to generate constraints using these
attributes, and there are comments explaining how to use the attributes
directly in an XDC file in case the user wants to manage their XDC
file manually instead of using the TCL.
This commit is contained in:
Daniel Estévez 2024-02-08 11:53:23 +01:00 committed by Catherine
parent 9e75962c35
commit d8f70be4d9

View file

@ -172,6 +172,11 @@ class XilinxPlatform(TemplatedPlatform):
foreach cell [get_cells -quiet -hier -filter {amaranth.vivado.false_path == "TRUE"}] { foreach cell [get_cells -quiet -hier -filter {amaranth.vivado.false_path == "TRUE"}] {
set_false_path -to $cell set_false_path -to $cell
} }
foreach pin [get_pins -of \
[get_cells -quiet -hier -filter {amaranth.vivado.false_path_pre == "TRUE"}] \
-filter {REF_PIN_NAME == PRE}] {
set_false_path -to $pin
}
foreach cell [get_cells -quiet -hier -filter {amaranth.vivado.max_delay != ""}] { foreach cell [get_cells -quiet -hier -filter {amaranth.vivado.max_delay != ""}] {
set clock [get_clocks -of_objects \ set clock [get_clocks -of_objects \
[all_fanin -flat -startpoints_only [get_pin $cell/D]]] [all_fanin -flat -startpoints_only [get_pin $cell/D]]]
@ -180,6 +185,11 @@ class XilinxPlatform(TemplatedPlatform):
-to [get_cells $cell] [get_property amaranth.vivado.max_delay $cell] -to [get_cells $cell] [get_property amaranth.vivado.max_delay $cell]
} }
} }
foreach cell [get_cells -quiet -hier -filter {amaranth.vivado.max_delay_pre != ""}] {
set_max_delay \
-to [get_pins -of [get_cells $cell] -filter {REF_PIN_NAME == PRE}] \
[get_property amaranth.vivado.max_delay_pre $cell]
}
{{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}} {{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}}
report_timing_summary -file {{name}}_timing_synth.rpt report_timing_summary -file {{name}}_timing_synth.rpt
report_utilization -hierarchical -file {{name}}_utilization_hierarchical_synth.rpt report_utilization -hierarchical -file {{name}}_utilization_hierarchical_synth.rpt
@ -1191,29 +1201,53 @@ class XilinxPlatform(TemplatedPlatform):
def get_async_ff_sync(self, async_ff_sync): def get_async_ff_sync(self, async_ff_sync):
m = Module() m = Module()
m.domains += ClockDomain("async_ff", async_reset=True, local=True) m.domains += ClockDomain("async_ff", async_reset=True, local=True)
flops = [Signal(1, name=f"stage{index}", reset=1, # Instantiate a chain of async_ff_sync._stages FDPEs with all
attrs={"ASYNC_REG": "TRUE"}) # their PRE pins connected to either async_ff_sync.i or
for index in range(async_ff_sync._stages)] # ~async_ff_sync.i. The D of the first FDPE in the chain is
# connected to GND.
flops_q = Signal(async_ff_sync._stages, reset_less=True)
flops_d = Signal(async_ff_sync._stages, reset_less=True)
flops_pre = Signal(reset_less=True)
for i in range(async_ff_sync._stages):
flop = Instance("FDPE", p_INIT=1, o_Q=flops_q[i],
i_C=ClockSignal(async_ff_sync._o_domain),
i_CE=Const(1), i_PRE=flops_pre, i_D=flops_d[i],
a_ASYNC_REG="TRUE")
m.submodules[f"stage{i}"] = flop
if self.toolchain == "Vivado": if self.toolchain == "Vivado":
if async_ff_sync._max_input_delay is None: if async_ff_sync._max_input_delay is None:
flops[0].attrs["amaranth.vivado.false_path"] = "TRUE" # This attribute should be used with a constraint of the form
#
# set_false_path -to [ \
# get_pins -of [get_cells -hier -filter {amaranth.vivado.false_path_pre == "TRUE"}] \
# -filter { REF_PIN_NAME == PRE } ]
#
flop.attrs["amaranth.vivado.false_path_pre"] = "TRUE"
else: else:
flops[0].attrs["amaranth.vivado.max_delay"] = str(async_ff_sync._max_input_delay * 1e9) # This attributed should be used with a constraint of the form
#
# set_max_delay -to [ \
# get_pins -of [get_cells -hier -filter {amaranth.vivado.max_delay_pre == "3.0"}] \
# -filter { REF_PIN_NAME == PRE } ] \
# 3.0
#
# A different constraint must be added for each different _max_input_delay value
# used. The same value should be used in the second parameter of set_max_delay
# and in the -filter.
flop.attrs["amaranth.vivado.max_delay_pre"] = str(async_ff_sync._max_input_delay * 1e9)
elif async_ff_sync._max_input_delay is not None: elif async_ff_sync._max_input_delay is not None:
raise NotImplementedError("Platform '{}' does not support constraining input delay " raise NotImplementedError("Platform '{}' does not support constraining input delay "
"for AsyncFFSynchronizer" "for AsyncFFSynchronizer"
.format(type(self).__name__)) .format(type(self).__name__))
for i, o in zip((0, *flops), flops):
m.d.async_ff += o.eq(i) for i, o in zip((0, *flops_q), flops_d):
m.d.comb += o.eq(i)
if async_ff_sync._edge == "pos": if async_ff_sync._edge == "pos":
m.d.comb += ResetSignal("async_ff").eq(async_ff_sync.i) m.d.comb += flops_pre.eq(async_ff_sync.i)
else: else:
m.d.comb += ResetSignal("async_ff").eq(~async_ff_sync.i) m.d.comb += flops_pre.eq(~async_ff_sync.i)
m.d.comb += [ m.d.comb += async_ff_sync.o.eq(flops_q[-1])
ClockSignal("async_ff").eq(ClockSignal(async_ff_sync._o_domain)),
async_ff_sync.o.eq(flops[-1])
]
return m return m