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 <admin@t27.ai>
This commit is contained in:
Vasilev Dmitrii 2026-06-20 16:35:53 +07:00 committed by GitHub
parent 77db4dae64
commit 2735ee5d6e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 68 additions and 11 deletions

View File

@ -3,7 +3,7 @@ XILINX_PARTS := xc3s500evq100 \
xc6slx16ftg256 xc6slx16csg324 xc6slx25csg324 xc6slx45csg324 xc6slx100fgg484 \ xc6slx16ftg256 xc6slx16csg324 xc6slx25csg324 xc6slx45csg324 xc6slx100fgg484 \
xc6slx25tcsg324 xc6slx45tfgg484 xc6slx150tfgg484 xc6slx150tcsg484 \ xc6slx25tcsg324 xc6slx45tfgg484 xc6slx150tfgg484 xc6slx150tcsg484 \
xc6vlx130tff784 \ xc6vlx130tff784 \
xc7a12t xc7a15t xc7a25t xc7a35t xc7a50t xc7a75t xc7a100t xc7a200t \ xc7a12t xc7a15t xc7a25t xc7a35t xc7a50t xc7a75t xc7a100t xc7a100tfgg676 xc7a200t \
xc7s6 xc7s15 xc7s25 xc7s50 xc7s75 xc7s100 \ xc7s6 xc7s15 xc7s25 xc7s50 xc7s75 xc7s100 \
xc7k70tfbg484 xc7k70tfbg676 \ xc7k70tfbg484 xc7k70tfbg676 \
xc7k160tffg676 \ xc7k160tffg676 \

View File

@ -59,6 +59,24 @@ Clean temporary/build files:
make clean 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 ## Add support for a new device
### 1. Register the part in `Makefile` ### 1. Register the part in `Makefile`

View File

@ -120,7 +120,21 @@ else:
os.sys.exit() os.sys.exit()
if model in ["xc7a", "xc7s"]: if model in ["xc7a", "xc7s"]:
# 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] 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}" pkg_name = f"{model}_{pkg}"
if model in ["xc7k"]: if model in ["xc7k"]:
m = re.match(r"(xc7k\d+t)(\w+)", part) m = re.match(r"(xc7k\d+t)(\w+)", part)
@ -193,7 +207,7 @@ if tool in ["ise", "vivado"]:
cst_type = "xdc" cst_type = "xdc"
# Artix/Spartan 7 Specific use case: # Artix/Spartan 7 Specific use case:
if family in ["Artix", "Spartan 7"]: if family in ["Artix", "Spartan 7"]:
tool_options = {'part': f"{part}{pkg}-1"} tool_options = {'part': f"{device}{pkg}-1"}
elif family == "Xilinx UltraScale": elif family == "Xilinx UltraScale":
if part in ["xcvu9p-flga2104", "xcku5p-ffvb676"]: if part in ["xcvu9p-flga2104", "xcku5p-ffvb676"]:
tool_options = {'part': part + '-1-e'} 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: with gzip.open(f"{flash_type}OverJtag_{part}.bit.gz", 'wb', compresslevel=9) as bit_gz:
shutil.copyfileobj(bit, bit_gz) shutil.copyfileobj(bit, bit_gz)
# Create Symbolic links for all supported packages. # Create Symbolic links for all supported packages — only when building
if family in ["Artix", "Spartan 7"]: # 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" in_file = f"{flash_type}OverJtag_{part}.bit.gz"
for pkg in packages[family][part]: for pkg in packages[family][part]:
out_file = f"{flash_type}OverJtag_{part}{pkg}.bit.gz" 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]) subprocess.run(["ln", "-s", in_file, out_file])

View File

@ -3,11 +3,29 @@ set_property CONFIG_VOLTAGE 3.3 [current_design]
set_property BITSTREAM.CONFIG.SPI_BUSWIDTH {4} [current_design] set_property BITSTREAM.CONFIG.SPI_BUSWIDTH {4} [current_design]
set_property BITSTREAM.GENERAL.COMPRESS TRUE [current_design] set_property BITSTREAM.GENERAL.COMPRESS TRUE [current_design]
set_property -dict {PACKAGE_PIN P18 IOSTANDARD LVTTL} [get_ports {csn}] # Drive the startup sequencer from JTAG TCK so the bitstream reaches EOS
set_property -dict {PACKAGE_PIN R14 IOSTANDARD LVTTL} [get_ports {sdi_dq0}] # (End-Of-Startup, i.e. DONE=HIGH) when loaded over JTAG via DLC10.
set_property -dict {PACKAGE_PIN R15 IOSTANDARD LVTTL} [get_ports {sdo_dq1}] # Without this Vivado defaults to Cclk (CCLK) — but CCLK is only driven
set_property -dict {PACKAGE_PIN P14 IOSTANDARD LVTTL} [get_ports {wpn_dq2}] # during SelectMAP/SPI configuration, not during JTAG. The result is a
set_property -dict {PACKAGE_PIN N14 IOSTANDARD LVTTL} [get_ports {hldn_dq3}] # 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 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}] create_clock -period 33.000 -name vers_tck -waveform {0.000 16.500} [get_pins {bscane2_version/DRCK}]