Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
268 changes: 268 additions & 0 deletions contrib/devfile-connectivity/README.md

Large diffs are not rendered by default.

68 changes: 68 additions & 0 deletions contrib/devfile-connectivity/docs/cff_serialization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# CFF / flat-offset → .rbf serialization map and method

Device-general (die `cycloneive1`, EP4CE6 == EP4CE10, 368011-B uncompressed
`.rbf`). Code: `scripts/bitpos_to_rbf.py` (main CRAM plane),
`scripts/cff_to_rbf.py` (aux/CFF planes). Data: `results/cff_offset_rbf_map.json`.

## The internal bit position

During assembly every config cell is written by
`ASM_BITFIELD::set_bits(vector<DB_BIT_SETTING>)` (`libcomp_asmcc.so` @0x1b5a0).
There is exactly one global `ASM_BITFIELD` for the whole device; each
`DB_BIT_SETTING.first` is the **global flat CRAM bit index**. `set_bits` pokes it
straight into the PGMIO config-image bitmap, so the "internal bitpos" is a flat
index into the image that is later serialized (preamble + per-frame CRC) into the
`.rbf`. Flat indices with bit31 set are region-tagged aux settings, routed to
separate planes (below), not the main CRAM image.

## Main-plane transform (closed form, no per-bit table)

The PGMIO image is a bit bitmap organized as `flat = row_byte*COLW + col`, which
inverts to fixed device geometry:

```
col = 1751 - F (F = rbf frame index; data cols F 25..1751)
row_byte = 207*(b+1) - Y (Y = data-byte-in-frame 0..207 ; b = bit-in-byte 0..7)
flat = (207*(b+1) - Y) * 1727 + (1751 - F)
rbf_byte(F,Y) = 32 + F*210 + Y ; rbf_bit = b

PREAMBLE=32 FRAME_STRIDE=210 N_FRAMES=1752 HEADER_FRAMES=25
DATA_COLS=1727 DATA_BYTES_PER_FRAME=208 CRC_BYTES=2 BITPLANE_STRIDE=207
```

These are fixed geometry constants, not lookups. Validated: 99.9964% of a whole
config image reproduces the real `.rbf` (the 104 misses are all at the `Y=0`
plane-boundary tie point); exact for every `Y` in 1..207 and for all HW-verified
LUT cells. Method of derivation: a gdb log-space sweep of
`PGMIO_F2P::make_cff_frame` (`libpgm_pgmio.so` @0x387f00), inverted to the closed
form above and wired in `bitpos_to_rbf.py`.

## Aux-plane routing (proven, exact decompile)

Every `DB_BIT_SETTING.first` with bit31 set is a region-tagged virtual address
routed to a separate in-RAM bitmap, then serialized into the **leading** frames
(F 0..24) that the main transform deliberately excludes:

```
(first & 0xC0000000)==0xC0000000 -> DB_ILLEGAL_* ; NOT written anywhere
(first & 0xA0000000)==0xA0000000 -> CFF plane ; offset = first & 0x5FFFFFFF
(first & 0x90000000)==0x90000000 -> UNVM plane ; offset = first & 0x6FFFFFFF
(first & 0x88000000)==0x88000000 -> Option Register ; offset = first & 0x77FFFFFF
else (bit31 clear) -> main CRAM image (closed form above)
```

Empirical serialization evidence (differential compiles, uncompressed `.rbf`):
the entire PLL-multiply (CFF) delta between two PLL carriers lands in header
frames `{0,5,6,7,8}` with zero bits in the data region; clk / M9K / LE-LAB aux
deltas likewise land in header frames only. So the aux/CFF plane **is** in the
`.rbf`, in the header band — not absent.

## Honest open gap (decode-or-refuse)

The CFF-offset → (header F, Y, bit) **permutation** is a device-file-driven
scramble we have not reduced to closed form; `cff_offset_rbf_map.json` records
the partial map recovered so far. Until a diverse walking-ones CFF sweep resolves
the full permutation, `cff_to_rbf.py` exposes no complete aux bit map and
**refuses** rather than guess. The aux/CFF band is a real but minor lever (the
whole vendor header region F 0..24 holds only ~3.3% of programmed bits); the
dominant unowned mass is data-region routing, not aux/CFF.
70 changes: 70 additions & 0 deletions contrib/devfile-connectivity/docs/compiler_model_extraction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Compiler-model-extraction method (harvest → invert → decode-or-refuse)

**What it is.** A general technique for building bit-exact CRAM codecs for a
Cyclone IV E family part *without* re-deriving the opaque enum-keyed device
file (`.ddb`) and *without* pair-diff fuzzing. Instead of guessing the encoding,
you let Quartus resolve it and read the answer out of the assembler's own memory.

**Why device-general.** The tables harvested are the assembler's *resolved
device model* — `{block / mux / setting / codeword → DB_BIT_SETTING flat-CRAM
bits}` — which is a property of the silicon device file, not of any one design.
The same `quartus_asm` binary and the same `cycloneive1` die database serve every
EP4CE6/EP4CE10 design, so the harvested setting↔bit tables hold for any target on
that die. Nothing here is specific to a particular bitstream.

## The four steps

1. **Harvest.** `quartus_asm`'s ASMDB reader parses the device file and
materializes the complete setting↔bit tables in RAM (every enum, mux, IOE
codeword, config bit already resolved). Break on the emit boundary
(`ASM_BITFIELD::set_bits`, `libcomp_asmcc.so`, and the per-class
`get_bits_*` resolvers) with a read-only gdb intercept and log every
`{setting, codeword → DB_BIT_SETTING bits}` the compiler emits, over a small
set of carrier compiles that walk the codewords of each block/mux.

2. **Invert.** Turn the forward table `{codeword → bits}` into a decode table
`{observed bits → codeword → named meaning}`. The inversion is *exact*, not
fuzzed, because within one setting/codeword the compiler's emit-order value
tuple over its cell set is identical across every independent compile, and the
per-mux signatures are provably unique (no two codewords of a mux share a
signature, and no codeword has an all-zero signature) — so an unused (all-zero)
or unobserved read naturally **refuses** rather than mislabels.

3. **Validate.** Confirm each codec reproduces its oracle bit-exact, then confirm
it decodes **held-out fresh compiles** (seeds/designs never in the harvest)
back to the intent the fitter chose, with zero mispredictions and zero
ambiguous refusals. Per-codec held-out results (all clean):
- `codec_le-lab-secondary` — leave-one-compile-out over 8 independent compiles.
- `codec_ioe-reg-and-inputmux` — 2 fresh seeds; the blockty6 input range was
independently re-probed to its true 18-input extent (sel16/17 held-out
round-trip bit-exact).
- `codec_pll-m9k-clock` — 5 fresh PLL carriers (varying multiply / duty /
phase); identical CLKOUT-select and clock block-mux signature every time.

4. **Integrate, decode-or-refuse.** Wire each inverted codec behind the unified
decoder (`decode_rbf.py`) with its own safe-reject. A codec claims cells only
for a setting whose full recorded signature is present bit-for-bit in the
target image; any mismatch, non-unique match, or unmappable bit → REFUSED.

## Honest scope of what is readable from a `.rbf`

Harvesting reveals *which plane* each resolved setting lands in. Only settings
whose config field lies in the main-CRAM plane (flat bit31 == 0) are serialized
into the frame data and therefore decodable from a shipped `.rbf`. Settings that
the fitter commits to the aux/CFF serializer planes (flat bit31 set, tag
`0xA…`/`0x9…`/`0x88…`) — e.g. the design-varying PLL numeric config
(charge-pump / loop-filter / VCO / per-counter multiply / duty / phase) — are
**not** in the frame CRAM and are refused wholesale, not bluffed. This is a real
open gap (the CFF offset→rbf permutation, see `cff_serialization.md`), not a
codec weakness: the encoding tables are exact and decode the moment their plane
becomes readable.

## Relation to this repo's pair-diff method

Pair-diff and model-extraction are complementary. Pair-diff proves a bit's
*location* from two real bitstreams; model-extraction proves the *whole encoding
table* (every codeword, every named field) from the compiler's own resolution.
Where both cover a feature they agree bit-exact (see `lut_codec_validation.md`,
where the model-extracted LUT geometry matches the golden pair-diff sweep 42/42).
Model-extraction supersedes re-deriving the enum format for the config classes;
pair-diff remains the ground-truth cross-check and the route/CRAM discovery path.
36 changes: 36 additions & 0 deletions contrib/devfile-connectivity/docs/coordinate_reconciliation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Coordinate reconciliation: routing (X,Y) == IOE/LUT (X,Y)

Device-general (die `cycloneive1`, EP4CE6 == EP4CE10).

The routing-node lattices in `routing_lattices_and_nodeclass.md` are expressed in
an `(X,Y)` from the DYGR device-file node locations
(`DYGR_DIE_INFO_BODY::get_location`). The LUT/IOE codecs
(`lut_sigma.py`, `lut_fullgrid.py`, `atom_first` block origins, the IOE
input-mux codec) are expressed in the placer/atom `(X,Y)`.

**These are the same coordinate system with the same origin.** A routing node at
`(X,Y)` and the LAB/IOE cell at `(X,Y)` refer to the same physical column/row of
the die; there is no offset, flip, or scale between them. Concretely:

- The LOCAL_INTERCONNECT / BLOCK_INPUT_MUX taps that a LUT input consumes at cell
`(X,Y)` carry routing node `(X,Y)` in the connectivity table — the join is a
direct coordinate equality, which is what lets a bound routing arc be chained
onto a LUT endpoint at the same `(X,Y)` (see `static_connectivity.py` /
`connectivity_codec.py`).
- The device column layout is identical in both views: LAB columns
`X ∈ {3,4,6,7,8,10,11,12,13,16,17,18,19,21,22,23,24,25,26,28,29,31}` for CE6,
extended by the CE10 jailbreak columns `{5,9,14,30,32,33}`; rows
`Y ∈ [2..21]` with gaps at `Y=15,20`. The routing lattices index the same
columns/rows (the C4/R4 `X` term ranges over the same die columns).

**Why this matters for the pipeline.** Because the two models share one origin,
a net can be traced from a LUT output (placer cell `(X,Y)`), through the routing
lattice (router node `(X,Y)`), to a LUT input (placer cell `(X,Y)`) with no
coordinate translation layer. Any consumer that already speaks the repo's
`config.py` `(X,Y)` convention consumes the connectivity table and the routing
lattices directly.

Validation: the coordinate identity is what makes the LUT-output-hop bind checks
in `nodeclass.md` and the LI/LEIM tap resolution in `static_connectivity.py`
round-trip bit-exact — a wrong offset would have produced systematic bind
failures rather than the observed clean round-trips.
Loading