Skip to content

Commit eef6679

Browse files
committed
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.
1 parent 9e75962 commit eef6679

File tree

1 file changed

+50
-13
lines changed

1 file changed

+50
-13
lines changed

amaranth/vendor/_xilinx.py

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,11 @@ def vendor_toolchain(self):
172172
foreach cell [get_cells -quiet -hier -filter {amaranth.vivado.false_path == "TRUE"}] {
173173
set_false_path -to $cell
174174
}
175+
foreach pin [get_pins -of \
176+
[get_cells -quiet -hier -filter {amaranth.vivado.false_path_pre == "TRUE"}] \
177+
-filter {REF_PIN_NAME == PRE}] {
178+
set_false_path -to $pin
179+
}
175180
foreach cell [get_cells -quiet -hier -filter {amaranth.vivado.max_delay != ""}] {
176181
set clock [get_clocks -of_objects \
177182
[all_fanin -flat -startpoints_only [get_pin $cell/D]]]
@@ -180,6 +185,11 @@ def vendor_toolchain(self):
180185
-to [get_cells $cell] [get_property amaranth.vivado.max_delay $cell]
181186
}
182187
}
188+
foreach cell [get_cells -quiet -hier -filter {amaranth.vivado.max_delay_pre != ""}] {
189+
set_max_delay \
190+
-to [get_pins -of [get_cells $cell] -filter {REF_PIN_NAME == PRE}] \
191+
[get_property amaranth.vivado.max_delay_pre $cell]
192+
}
183193
{{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}}
184194
report_timing_summary -file {{name}}_timing_synth.rpt
185195
report_utilization -hierarchical -file {{name}}_utilization_hierarchical_synth.rpt
@@ -1191,29 +1201,56 @@ def get_ff_sync(self, ff_sync):
11911201
def get_async_ff_sync(self, async_ff_sync):
11921202
m = Module()
11931203
m.domains += ClockDomain("async_ff", async_reset=True, local=True)
1194-
flops = [Signal(1, name=f"stage{index}", reset=1,
1195-
attrs={"ASYNC_REG": "TRUE"})
1196-
for index in range(async_ff_sync._stages)]
1204+
# Instantiate a chain of async_ff_sync._stages FDPEs with all
1205+
# their PRE pins connected to either async_ff_sync.i or
1206+
# ~async_ff_sync.i. The D of the first FDPE in the chain is
1207+
# connected to GND.
1208+
flops_q = Signal(async_ff_sync._stages, reset_less=True)
1209+
flops_d = Signal(async_ff_sync._stages, reset_less=True)
1210+
flops_pre = Signal(reset_less=True)
1211+
for i in range(async_ff_sync._stages):
1212+
flops = [Instance("FDPE", p_INIT=1, o_Q=flops_q[i],
1213+
i_C=ClockSignal(async_ff_sync._o_domain),
1214+
i_CE=Const(1), i_PRE=flops_pre, i_D=flops_d[i],
1215+
a_ASYNC_REG="TRUE")
1216+
for i in range(async_ff_sync._stages)]
1217+
for i, f in enumerate(flops):
1218+
setattr(m.submodules, f"stage{i}", f)
11971219
if self.toolchain == "Vivado":
11981220
if async_ff_sync._max_input_delay is None:
1199-
flops[0].attrs["amaranth.vivado.false_path"] = "TRUE"
1221+
# This attribute should be used with a constraint of the form
1222+
#
1223+
# set_false_path -to [ \
1224+
# get_pins -of [get_cells -hier -filter {amaranth.vivado.false_path_pre == "TRUE"}] \
1225+
# -filter { REF_PIN_NAME == PRE } ]
1226+
#
1227+
for f in flops:
1228+
f.attrs["amaranth.vivado.false_path_pre"] = "TRUE"
12001229
else:
1201-
flops[0].attrs["amaranth.vivado.max_delay"] = str(async_ff_sync._max_input_delay * 1e9)
1230+
# This attributed should be used with a constraint of the form
1231+
#
1232+
# set_max_delay -to [ \
1233+
# get_pins -of [get_cells -hier -filter {amaranth.vivado.max_delay_pre == "3.0"}] \
1234+
# -filter { REF_PIN_NAME == PRE } ] \
1235+
# 3.0
1236+
#
1237+
# A different constraint must be added for each different _max_input_delay value
1238+
# used. The same value should be used in the second parameter of set_max_delay
1239+
# and in the -filter.
1240+
for f in flops:
1241+
f.attrs["amaranth.vivado.max_delay_pre"] = str(async_ff_sync._max_input_delay * 1e9)
12021242
elif async_ff_sync._max_input_delay is not None:
12031243
raise NotImplementedError("Platform '{}' does not support constraining input delay "
12041244
"for AsyncFFSynchronizer"
12051245
.format(type(self).__name__))
1206-
for i, o in zip((0, *flops), flops):
1207-
m.d.async_ff += o.eq(i)
1246+
for i, o in zip((0, *flops_q), flops_d):
1247+
m.d.comb += o.eq(i)
12081248

12091249
if async_ff_sync._edge == "pos":
1210-
m.d.comb += ResetSignal("async_ff").eq(async_ff_sync.i)
1250+
m.d.comb += flops_pre.eq(async_ff_sync.i)
12111251
else:
1212-
m.d.comb += ResetSignal("async_ff").eq(~async_ff_sync.i)
1252+
m.d.comb += flops_pre.eq(~async_ff_sync.i)
12131253

1214-
m.d.comb += [
1215-
ClockSignal("async_ff").eq(ClockSignal(async_ff_sync._o_domain)),
1216-
async_ff_sync.o.eq(flops[-1])
1217-
]
1254+
m.d.comb += async_ff_sync.o.eq(flops_q[-1])
12181255

12191256
return m

0 commit comments

Comments
 (0)