From b051216f6f33990bdc69088365ed4ba2bf532ebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Est=C3=A9vez?= Date: Thu, 8 Feb 2024 11:53:23 +0100 Subject: [PATCH] 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. --- amaranth/vendor/_xilinx.py | 74 +++++++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 20 deletions(-) diff --git a/amaranth/vendor/_xilinx.py b/amaranth/vendor/_xilinx.py index 0f033e21f..d847bedac 100644 --- a/amaranth/vendor/_xilinx.py +++ b/amaranth/vendor/_xilinx.py @@ -172,6 +172,11 @@ def vendor_toolchain(self): foreach cell [get_cells -quiet -hier -filter {amaranth.vivado.false_path == "TRUE"}] { 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 != ""}] { set clock [get_clocks -of_objects \ [all_fanin -flat -startpoints_only [get_pin $cell/D]]] @@ -180,6 +185,11 @@ def vendor_toolchain(self): -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)")}} report_timing_summary -file {{name}}_timing_synth.rpt report_utilization -hierarchical -file {{name}}_utilization_hierarchical_synth.rpt @@ -1191,29 +1201,53 @@ def get_ff_sync(self, ff_sync): def get_async_ff_sync(self, async_ff_sync): m = Module() m.domains += ClockDomain("async_ff", async_reset=True, local=True) - flops = [Signal(1, name=f"stage{index}", reset=1, - attrs={"ASYNC_REG": "TRUE"}) - for index in range(async_ff_sync._stages)] - if self.toolchain == "Vivado": - if async_ff_sync._max_input_delay is None: - flops[0].attrs["amaranth.vivado.false_path"] = "TRUE" - else: - flops[0].attrs["amaranth.vivado.max_delay"] = str(async_ff_sync._max_input_delay * 1e9) - elif async_ff_sync._max_input_delay is not None: - raise NotImplementedError("Platform '{}' does not support constraining input delay " - "for AsyncFFSynchronizer" - .format(type(self).__name__)) - for i, o in zip((0, *flops), flops): - m.d.async_ff += o.eq(i) + # Instantiate a chain of async_ff_sync._stages FDPEs with all + # their PRE pins connected to either async_ff_sync.i or + # ~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 async_ff_sync._max_input_delay is None: + # 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: + # 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: + raise NotImplementedError("Platform '{}' does not support constraining input delay " + "for AsyncFFSynchronizer" + .format(type(self).__name__)) + + for i, o in zip((0, *flops_q), flops_d): + m.d.comb += o.eq(i) 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: - m.d.comb += ResetSignal("async_ff").eq(~async_ff_sync.i) + m.d.comb += flops_pre.eq(~async_ff_sync.i) - m.d.comb += [ - ClockSignal("async_ff").eq(ClockSignal(async_ff_sync._o_domain)), - async_ff_sync.o.eq(flops[-1]) - ] + m.d.comb += async_ff_sync.o.eq(flops_q[-1]) return m