Reproduce the reference implementation's output - #47
Conversation
fpga-as is meant to be a drop-in replacement for the chain of fasm2frames and
xc7frames2bit, but on a database with the frame annotation work the two
disagree -- sometimes by not agreeing at all, in the form of an abort.
The differences were found by assembling 23 designs of five families (artix7,
kintex7, spartan7, zynq7 and virtex7) with both chains and comparing the
configuration frame by frame: fasm2frames prints its frames as text, fpga-as
now reproduces that dump via --dump_frames_file, and the two are diffed by
frame address. With the changes below all 23 designs assemble to identical
configuration, and the one binary replaces two processes: 8x to 112x faster
in wall clock (both ends measured on an idle machine, 60s to 0.5s on the
largest design). The 12 unit tests pass, three of them added here.
Feature resolution:
* A feature range wider than 64 bits reported a chunk width taken from the
range instead of the value. With a range of 83 bits (GTPE2_CHANNEL's
RXCDR_CFG[82:0]) the masking shift then wrapped, discarding every value
bit above the wrapped count, and the reported width above 64 made the
assembler shift past its 64 bit value, so it also set bits at addresses
the design never asked for (68 and 76 for a value that only sets 4 and
12). The chunk width now follows the value, and the guard added to the
caller keeps a wrapped shift from turning into a wrong address.
* A pseudo pip is documented in the tile type's own pips database, which is
not the database the tile's bits may alias to. RIOI_SING documents pips
that RIOI does not, and looking them up in the aliased database ended in
the .at() abort below.
* A tile type with its own segbits database is fuzzed natively, and
following its alias instead moves the bits into the aliased type's
window. On the virtex7 LIOB18_SING tiles the alias metadata is wrong to
begin with. The tile's own database now wins, which is what the
reference does.
* A feature no database defines is reported as an error instead of being
looked up with a container .at(). This build has exceptions disabled, so
that .at() on a missing key terminated the process: two designs in the
corpus (a LiteX SoC's PCIe/SATA and DDR variants) could not be assembled
at all.
* The bank anchor tile of a bank is HCLK_IOI3 where the bank uses HR
IOLOGIC and HCLK_IOI on the HP-only parts, so the anchor is now taken
from whichever the grid has instead of being assumed.
Injected configuration, in the order the reference applies it:
* The PUDC_B pullup is opt-in behind --emit_pudc_b_pullup and only applied
when the design leaves the pin unused. It used to be injected into every
design, changing the configuration of all of them, and into designs that
drive the pin themselves.
* The STEPDOWN feature's tag keeps its dots. Splitting the feature name
into four pieces put the marker in the discarded one, so DDR designs --
whose pins carry LVCMOS12_LVCMOS15_LVCMOS18_SSTL135_SSTL15.STEPDOWN --
never got their bank wide stepdown fill.
* HP bank IOBs get the input buffer, output buffer and inout partner bits
Vivado programs implicitly, and the X32 column indicator the LIOB18 X81
column needs when its Y1 half drives out.
* An output buffer tied to GND through general routing gets the tie root
marker of its column.
* An HCLK_L tile gets one "channel active" bit per BUFR channel in use.
* The features a part requires regardless of the design are read from
<part>/required_features.fasm. Without them a zynq7 bitstream is missing
the processing system's configuration, and the feature is a documented
TODO in the assembler.
These injected features are probed first: they only exist in databases
annotated with them, and injecting one a database does not carry cannot be
undone after the fact.
Also:
* --dump_frames_file writes the assembled frames in the text format
fasm2frames uses, which is what makes the comparison above possible.
* The bitstream header's part field carries the part name. It was passed
the string "fasm", while the source field next to it already carried the
generator.
Assisted-by: deepseek/deepseek-flash
hzeller
left a comment
There was a problem hiding this comment.
Nice! I've added a few comments, but I leave the review to @lromor
You mention the comparison between fasm2frames + xc7frames2bit and fpga-as. I think it would be super-cool to have this as a shell script (download the .fasm files at a particular github has upstream, run both toolchains over it and compare), then we can run this as a smoke test in the CI. That way we make sure not regressions are introduced.
Maybe test/smoke-tests.sh ?
Review of lromor#47 asked for the platform specific pieces to leave the main assembler file, for the ABSL_FLAGs to sit at the top, and for a regular string where a raw one is not needed: * The configuration the reference implementation injects around a design (PUDC_B pullup, STEPDOWN bank fill, HP bank glue, GFAN tie root, BUFRCLK channel markers) moves to fpga/injected-features.{h,cc} behind InjectConfigurationFeatures(), so assembler.cc keeps main(), the flags and the pipeline. * The four flags move below the includes, and their help strings become regular strings; the multi-line usage text keeps its raw string. * TileGridInfoAndSegbits was unused and goes. No behaviour change: the moved code is verbatim, the injection order and the probing are unchanged, and the demo designs assemble to byte-identical frames before and after the split (20 designs over artix7, kintex7, spartan7 and zynq7, compared frame by frame; the bitstreams differ only in the date/time field of the .bit header). The 12 unit tests pass. Assisted-by: deepseek/deepseek-flash
|
Thanks @hzeller — pushed 2368bab addressing the three comments. Flags to the top ( Raw strings ( Platform-specific code ( No behaviour change: same database, same inputs, the pre- and post-split binaries assemble the demo designs to byte-identical frames (20 designs over artix7, kintex7, spartan7 and zynq7, compared frame by frame; the On the smoke test (
Tell me the answers on the last two points (or point me at an existing CI job that does this) and I will write and wire it up. |
It's ok for now.
Sounds good. For now I would prefer to just have the "gold standard" files and ensure we match them. Especially whenever we encounter an edge case that doesn't seem to work as expected. |
|
Reopening isn't available for a merged PR (GitHub only reopens closed-unmerged ones), so here is the material you asked for as a gist: https://gist.github.com/hansfbaier/20a7b7da75d2bf8fc28ad92936d620e2 It follows your preference — the "gold standard" files, with a pointer at each edge case that didn't behave:
Case 05 is the one that still does not match: the design's own Each case carries: the smallest input ( Method note: the pre-fix build has no On the repository state: |
|
Amazing, thank you @hansfbaier ! |
|
Two additions to the gist, now that the corpus sweep has finished. The sweep is complete (
New case 06 narrows the It is a pseudo PIP: the reference and the fixed assembler both accept it, and it maps to no configuration bits at all — the frames agree (and are empty). Before the fix that same line dies in |
This makes fpga-as produce the same configuration as the chain it replaces on a
database that carries the openXC7 frame annotation work, and fixes the cases
where it produced no bitstream at all. The commit message has the full
write-up; this is the evidence behind it.
Verification
The two chains are not byte-comparable: the .bit header names the tool that
wrote it, and the two writers lay the stream out differently even when the
configuration is identical. So both sides were dumped as frames text --
fasm2frames prints that natively, fpga-as now reproduces it with
--dump_frames_file-- and compared frame by frame, by address, ignoring theframes each side pads with zeros:
The set is the 22 designs in openXC7/demo-projects that carry a .fasm (the
blinkies, the LiteX DDR SoCs, the DSP/LUTRAM/DDR3 tests) plus
vc707-johnsonfrom this repository's examples, which forces the HP bankpaths -- differential and single-ended HP IOBs, a
LIOB18_SINGtile, BUFG.Wall clock, assembler only, idle machine:
bazel test //fpga/...passes, 12 tests, three of them added here (a valuerange wider than the value over a range wider than 64 bits, and the HP bank
anchor tile naming).
What was wrong
Feature resolution:
the masking shift then wrapped:
RXCDR_CFG[82:0]on the GTP tiles lostevery value bit above 19, and the reported width above 64 made the caller
shift past its value and set bits at addresses the design never asked for.
tile's bits alias to aborted on tiles such as
RIOI_SING.virtex7
LIOB18_SINGtiles that alias is wrong to begin with.the process through a container
.at()(this build has exceptions off).HCLK_IOIon the HP-only parts, notHCLK_IOI3.Injected configuration: the PUDC_B pullup was injected into every design
instead of being opt-in, the STEPDOWN tag was split on the dots that are part
of it so DDR designs never filled their bank, the HP bank glue / GFAN tie
root / BUFRCLK channel markers were missing, and
required_features.fasmwasnever read (a zynq7 bitstream missed the processing system's configuration).
Each injected feature is probed before use: they only exist in databases
annotated with them.
Context
This came out of retiring openXC7/nextpnr-xilinx: the toolchain wants one
assembler instead of
fasm2frames+xc7frames2bitplus their Pythonenvironment, and
fpga-ashas been sitting in the devshell unused.