From 2735ee5d6efb8f9820c90801ef609ed00fba51e2 Mon Sep 17 00:00:00 2001 From: Vasilev Dmitrii Date: Sat, 20 Jun 2026 16:35:53 +0700 Subject: [PATCH] fix(spiOverJtag): package-specific FGG676 bitstream build for QMTech XC7A100T (#682) * fix(spiOverJtag): package-specific FGG676 bitstream build for QMTech XC7A100T - Makefile: add xc7a100tfgg676 target - build.py: parse device+package form (e.g. xc7a100tfgg676), stop aliasing a package-specific bitstream to other packages via symlink - constr_xc7a_fgg676.xdc: set BITSTREAM.STARTUP.STARTUPCLK JtagClk (camelCase; all-caps JTAGCLK is silently rejected and falls back to Cclk, leaving EOS=0 over JTAG), correct SPI pins to LVCMOS33 per QMTech schematic - README.md: document package-specific bitstream build - remove stale spiOverJtag_xc7a100tfgg676.bit.gz (rebuilt by above) * spiOverJtag: reword FGG676 SPI-flash pin comment as package-level These pins are fixed by the XC7A*T-FGG676 package, not by the QMTech board. Reword the comment to reflect that any XC7A35T/50T/75T/100T/200T in the FGG676 package routes the SPI-flash signals to the same balls. Keep the QMTech schematic only as the cross-check reference. No pin values changed. Addresses trabucayre review on constr_xc7a_fgg676.xdc. --------- Co-authored-by: gHashTag --- spiOverJtag/Makefile | 2 +- spiOverJtag/README.md | 18 ++++++++++ spiOverJtag/build.py | 31 +++++++++++++++--- spiOverJtag/constr_xc7a_fgg676.xdc | 28 +++++++++++++--- spiOverJtag/spiOverJtag_xc7a100tfgg676.bit.gz | Bin 30332 -> 0 bytes 5 files changed, 68 insertions(+), 11 deletions(-) delete mode 100644 spiOverJtag/spiOverJtag_xc7a100tfgg676.bit.gz diff --git a/spiOverJtag/Makefile b/spiOverJtag/Makefile index f34c4bf..f45236a 100644 --- a/spiOverJtag/Makefile +++ b/spiOverJtag/Makefile @@ -3,7 +3,7 @@ XILINX_PARTS := xc3s500evq100 \ xc6slx16ftg256 xc6slx16csg324 xc6slx25csg324 xc6slx45csg324 xc6slx100fgg484 \ xc6slx25tcsg324 xc6slx45tfgg484 xc6slx150tfgg484 xc6slx150tcsg484 \ xc6vlx130tff784 \ - xc7a12t xc7a15t xc7a25t xc7a35t xc7a50t xc7a75t xc7a100t xc7a200t \ + xc7a12t xc7a15t xc7a25t xc7a35t xc7a50t xc7a75t xc7a100t xc7a100tfgg676 xc7a200t \ xc7s6 xc7s15 xc7s25 xc7s50 xc7s75 xc7s100 \ xc7k70tfbg484 xc7k70tfbg676 \ xc7k160tffg676 \ diff --git a/spiOverJtag/README.md b/spiOverJtag/README.md index ca26d1d..89b8f49 100644 --- a/spiOverJtag/README.md +++ b/spiOverJtag/README.md @@ -59,6 +59,24 @@ Clean temporary/build files: make clean ``` +## Package-specific bitstreams + +Some boards within the same FPGA family/size use different SPI flash pin +mappings depending on the device package. When the SPI pins differ between +packages, `build.py` must produce a distinct bitstream per package rather +than aliasing one build to every package via symlinks. + +To build a package-specific bitstream, pass the full device name +(device + package) to `make`. Example for the QMTech XC7A100T (FGG676) +core board: + +```bash +make spiOverJtag_xc7a100tfgg676.bit.gz +``` + +This consumes `constr_xc7a_fgg676.xdc` and produces a bitstream wired to +the FGG676 SPI dedicated configuration pins (C8/B19/A18/B18/A19). + ## Add support for a new device ### 1. Register the part in `Makefile` diff --git a/spiOverJtag/build.py b/spiOverJtag/build.py index 7a42fb8..595df7f 100755 --- a/spiOverJtag/build.py +++ b/spiOverJtag/build.py @@ -120,7 +120,21 @@ else: os.sys.exit() if model in ["xc7a", "xc7s"]: - pkg = packages[family][part][0] + # Accept either a bare device (e.g. "xc7a100t" -> first package) or a + # device suffixed with a package (e.g. "xc7a100tfgg676" -> "fgg676"). + if part in packages[family]: + pkg = packages[family][part][0] + device = part + else: + m = re.match(r"(xc7[as]\d+t?)([a-z]+\d+)", part) + if not m or m.group(1) not in packages[family]: + print(f"Error: cannot parse part/package from '{part}'") + os.sys.exit() + device = m.group(1) + pkg = m.group(2) + if pkg not in packages[family][device]: + print(f"Error: package '{pkg}' not listed for device '{device}'") + os.sys.exit() pkg_name = f"{model}_{pkg}" if model in ["xc7k"]: m = re.match(r"(xc7k\d+t)(\w+)", part) @@ -193,7 +207,7 @@ if tool in ["ise", "vivado"]: cst_type = "xdc" # Artix/Spartan 7 Specific use case: if family in ["Artix", "Spartan 7"]: - tool_options = {'part': f"{part}{pkg}-1"} + tool_options = {'part': f"{device}{pkg}-1"} elif family == "Xilinx UltraScale": if part in ["xcvu9p-flga2104", "xcku5p-ffvb676"]: tool_options = {'part': part + '-1-e'} @@ -285,10 +299,17 @@ if tool in ["vivado", "ise"]: with gzip.open(f"{flash_type}OverJtag_{part}.bit.gz", 'wb', compresslevel=9) as bit_gz: shutil.copyfileobj(bit, bit_gz) - # Create Symbolic links for all supported packages. - if family in ["Artix", "Spartan 7"]: + # Create Symbolic links for all supported packages โ€” only when building + # the bare device target (e.g. "xc7a100t"). When the explicit + # device+package form is used (e.g. "xc7a100tfgg676"), the bitstream is + # package-specific and must not be aliased to other packages. + if family in ["Artix", "Spartan 7"] and part in packages[family]: in_file = f"{flash_type}OverJtag_{part}.bit.gz" for pkg in packages[family][part]: out_file = f"{flash_type}OverJtag_{part}{pkg}.bit.gz" - if not os.path.exists(out_file): + # Don't overwrite a package-specific bitstream with a symlink to + # the first-package build (this was the original bug). + if os.path.islink(out_file) or not os.path.exists(out_file): + if os.path.islink(out_file): + os.unlink(out_file) subprocess.run(["ln", "-s", in_file, out_file]) diff --git a/spiOverJtag/constr_xc7a_fgg676.xdc b/spiOverJtag/constr_xc7a_fgg676.xdc index 00375a5..5f4034e 100644 --- a/spiOverJtag/constr_xc7a_fgg676.xdc +++ b/spiOverJtag/constr_xc7a_fgg676.xdc @@ -3,11 +3,29 @@ set_property CONFIG_VOLTAGE 3.3 [current_design] set_property BITSTREAM.CONFIG.SPI_BUSWIDTH {4} [current_design] set_property BITSTREAM.GENERAL.COMPRESS TRUE [current_design] -set_property -dict {PACKAGE_PIN P18 IOSTANDARD LVTTL} [get_ports {csn}] -set_property -dict {PACKAGE_PIN R14 IOSTANDARD LVTTL} [get_ports {sdi_dq0}] -set_property -dict {PACKAGE_PIN R15 IOSTANDARD LVTTL} [get_ports {sdo_dq1}] -set_property -dict {PACKAGE_PIN P14 IOSTANDARD LVTTL} [get_ports {wpn_dq2}] -set_property -dict {PACKAGE_PIN N14 IOSTANDARD LVTTL} [get_ports {hldn_dq3}] +# Drive the startup sequencer from JTAG TCK so the bitstream reaches EOS +# (End-Of-Startup, i.e. DONE=HIGH) when loaded over JTAG via DLC10. +# Without this Vivado defaults to Cclk (CCLK) โ€” but CCLK is only driven +# during SelectMAP/SPI configuration, not during JTAG. The result is a +# bitstream whose COR0 register has STARTUPCLK=00 (Cclk); we verified the +# previous CI build had COR0=0x02003fe5 (bits[27:26]=00). UG470 ยง6.3 +# Table 6-3 lists the valid Vivado literal as `JtagClk` (camelCase) โ€” +# all-caps `JTAGCLK` is silently rejected and Vivado falls back to Cclk. +set_property BITSTREAM.STARTUP.STARTUPCLK JtagClk [current_design] + +# SPI flash pins for the XC7A*T-FGG676 package (Bank 14 dual-purpose config IO: +# D00..D03, FCS_B). These pins are package-level: any XC7A35T/50T/75T/100T/200T +# in the FGG676 package routes the dedicated SPI-flash signals to the same balls, +# so this constraint is not specific to the QMTech board. +# Pinout cross-checked against the QMTech XC7A75T/100T/200T core board schematic +# (QMTECH_XC7A75T_100T_200T-CORE-BOARD-V01-20210109.pdf, on-board N25Q064A, +# JEDEC 0x20BA17). CCLK is driven internally via the STARTUPE2 primitive +# instantiated in xilinx_spiOverJtag.v. +set_property -dict {PACKAGE_PIN P18 IOSTANDARD LVCMOS33} [get_ports {csn}] +set_property -dict {PACKAGE_PIN R14 IOSTANDARD LVCMOS33} [get_ports {sdi_dq0}] +set_property -dict {PACKAGE_PIN R15 IOSTANDARD LVCMOS33} [get_ports {sdo_dq1}] +set_property -dict {PACKAGE_PIN P14 IOSTANDARD LVCMOS33} [get_ports {wpn_dq2}] +set_property -dict {PACKAGE_PIN N14 IOSTANDARD LVCMOS33} [get_ports {hldn_dq3}] create_clock -period 33.000 -name jtag_tck -waveform {0.000 16.500} [get_pins {bscane2_inst/DRCK}] create_clock -period 33.000 -name vers_tck -waveform {0.000 16.500} [get_pins {bscane2_version/DRCK}] diff --git a/spiOverJtag/spiOverJtag_xc7a100tfgg676.bit.gz b/spiOverJtag/spiOverJtag_xc7a100tfgg676.bit.gz deleted file mode 100644 index 6910300f729d60d1004c1e0fa4e34e5336135733..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30332 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