Skip to content

Instantly share code, notes, and snippets.

@gHashTag
Created August 11, 2026 18:18
Show Gist options
  • Select an option

  • Save gHashTag/329e200eeabb2c2fe430d2c952e83891 to your computer and use it in GitHub Desktop.

Select an option

Save gHashTag/329e200eeabb2c2fe430d2c952e83891 to your computer and use it in GitHub Desktop.
openXC7/nextpnr-xilinx#114 — IDDR probe designs for a Vivado build (A: direct, B: via IDELAYE2)

Two builds of the same design. The only intended difference between the two bitstreams is the position of the ILOGIC IFFDELMUXE3 mux.

Part

xc7a35tcsg324-1        (Digilent Arty A7-35T)

Any artix7 part works — the ILOGIC segbits are family-level, so an xc7a200t is equally good. If you retarget, please keep the pins identical between A and B.

What to build

build files design IFFDELMUXE3 position
A A_direct_top.v + A_direct_top.xdc IDDR with D straight from the pad P1 ("direct")
B B_idelay_top.v + B_idelay_top.xdc identical, but D through an IDELAYE2 P0 ("idelay")

Both modules are named top. The two .xdc files are byte-identical — same pins: clk=E3, d=A8, q1=H5, q2=J5 — taken from prjxray's own arty-a7 harness, so they are known good.

What I need back

Just the two .bit files. .dcps are equally welcome — I can pull top_late.xdc out of those myself.

Why

IFFDELMUXE3 is a three-position mux — Vivado's own site model says so (nextpnr-xilinx-meta/artix7/site_type_ILOGICE3.json lists from_pin 0/1/2) — and prjxray's fuzzer states what the positions mean (fuzzers/035-iob-ilogic/generate.py:216-227):

'direct' -> P1=1 ;  'idelay' -> P0=1 ;  'none' -> all 0

segbits_lioi3.db documents only P0 (28_116). P1, the position a plain IDDR needs, has no bit we can name — so nextpnr writes nothing, the mux sits in none, the IFF has no data path selected, and that is #114.

Diffing A against B isolates exactly the missing bit. I have bitread/bit2fasm and the artix7 database here and will do the disassembly and the differencing.

The database-side root cause is filed upstream as f4pga/prjxray#2569fuzzers/035-iob-ilogic/bits.dbf is an empty file, which is why the all-zero enum members never reach segbits.

Two extras, only if you have Vivado open anyway

Neither is needed for #114; both are things I cannot check without a vendor bitstream, and none of the four harnesses in prjxray-db contains a CMT.

  1. For #79 / PR #129 — an MMCME2_ADV with RST driven by an ordinary input port (not a constant). I found we emit ZINV_RST clear in that case, which per the fuzzer means the reset is inverted, i.e. the MMCM is held in reset for ever.
  2. For #65 / PR #121 — an IDDR at DDR_CLK_EDGE("SAME_EDGE_PIPELINED"). I claimed the encoding is both DDR_CLK_EDGE bits clear, from a prjxray-db fork plus the fuzzer rather than from vendor output. This is one parameter value away from build A.
// Build A -- IDDR with D taken STRAIGHT FROM THE PAD (no IDELAYE2).
// This is the IFFDELMUXE3 "direct" position, i.e. P1.
module top (
input wire clk,
input wire d,
output wire q1,
output wire q2
);
IDDR #(
.DDR_CLK_EDGE("SAME_EDGE"),
.INIT_Q1(1'b0), .INIT_Q2(1'b0),
.SRTYPE("SYNC")
) u_iddr (
.C(clk), .CE(1'b1), .D(d), .R(1'b0), .S(1'b0),
.Q1(q1), .Q2(q2)
);
endmodule
# Digilent Arty A7-35T. Pins transcribed from prjxray's own harness
# (prjxray-db/artix7/harness/arty-a7/swbut/design.txt), so they are known good.
# IDENTICAL in build A and build B -- that is the point.
set_property PACKAGE_PIN E3 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
create_clock -period 10.000 -name sys_clk [get_ports clk]
set_property PACKAGE_PIN A8 [get_ports d]
set_property IOSTANDARD LVCMOS33 [get_ports d]
set_property PACKAGE_PIN H5 [get_ports q1]
set_property IOSTANDARD LVCMOS33 [get_ports q1]
set_property PACKAGE_PIN J5 [get_ports q2]
set_property IOSTANDARD LVCMOS33 [get_ports q2]
// Build B -- IDENTICAL to A except D reaches the IDDR through an IDELAYE2.
// This is the IFFDELMUXE3 "idelay" position, i.e. P0.
// Same pins as A on purpose: the ONLY intended difference between the two
// bitstreams is the mux position.
module top (
input wire clk,
input wire d,
output wire q1,
output wire q2
);
wire d_dly;
(* IODELAY_GROUP = "iddr_probe" *)
IDELAYE2 #(
.IDELAY_TYPE("FIXED"),
.IDELAY_VALUE(0),
.DELAY_SRC("IDATAIN"),
.HIGH_PERFORMANCE_MODE("FALSE"),
.SIGNAL_PATTERN("DATA"),
.REFCLK_FREQUENCY(200.0)
) u_dly (
.C(1'b0), .CE(1'b0), .INC(1'b0), .LD(1'b0), .LDPIPEEN(1'b0),
.REGRST(1'b0), .CINVCTRL(1'b0), .CNTVALUEIN(5'b0),
.DATAIN(1'b0), .IDATAIN(d),
.DATAOUT(d_dly), .CNTVALUEOUT()
);
(* IODELAY_GROUP = "iddr_probe" *)
IDELAYCTRL u_idelayctrl (.REFCLK(clk), .RST(1'b0), .RDY());
IDDR #(
.DDR_CLK_EDGE("SAME_EDGE"),
.INIT_Q1(1'b0), .INIT_Q2(1'b0),
.SRTYPE("SYNC")
) u_iddr (
.C(clk), .CE(1'b1), .D(d_dly), .R(1'b0), .S(1'b0),
.Q1(q1), .Q2(q2)
);
endmodule
# Digilent Arty A7-35T. Pins transcribed from prjxray's own harness
# (prjxray-db/artix7/harness/arty-a7/swbut/design.txt), so they are known good.
# IDENTICAL in build A and build B -- that is the point.
set_property PACKAGE_PIN E3 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
create_clock -period 10.000 -name sys_clk [get_ports clk]
set_property PACKAGE_PIN A8 [get_ports d]
set_property IOSTANDARD LVCMOS33 [get_ports d]
set_property PACKAGE_PIN H5 [get_ports q1]
set_property IOSTANDARD LVCMOS33 [get_ports q1]
set_property PACKAGE_PIN J5 [get_ports q2]
set_property IOSTANDARD LVCMOS33 [get_ports q2]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment