bayerlink-fpga 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. bayerlink_fpga-0.3.0/.github/workflows/ci.yml +149 -0
  2. bayerlink_fpga-0.3.0/.github/workflows/publish.yml +73 -0
  3. bayerlink_fpga-0.3.0/.gitignore +32 -0
  4. bayerlink_fpga-0.3.0/CHANGELOG.md +235 -0
  5. bayerlink_fpga-0.3.0/LICENSE +45 -0
  6. bayerlink_fpga-0.3.0/LICENSE-APACHE +202 -0
  7. bayerlink_fpga-0.3.0/PKG-INFO +234 -0
  8. bayerlink_fpga-0.3.0/README.md +218 -0
  9. bayerlink_fpga-0.3.0/bayerlink_fpga/__init__.py +25 -0
  10. bayerlink_fpga-0.3.0/bayerlink_fpga/ddc.py +107 -0
  11. bayerlink_fpga-0.3.0/bayerlink_fpga/frontends/__init__.py +17 -0
  12. bayerlink_fpga-0.3.0/bayerlink_fpga/frontends/hdmi_rx.json +73 -0
  13. bayerlink_fpga-0.3.0/bayerlink_fpga/hdl/axis_spy.v +46 -0
  14. bayerlink_fpga-0.3.0/bayerlink_fpga/hdl/axis_unpack.v +37 -0
  15. bayerlink_fpga-0.3.0/bayerlink_fpga/hdl/bayerlink_in.v +843 -0
  16. bayerlink_fpga-0.3.0/bayerlink_fpga/hdl/ddc_phy.v +21 -0
  17. bayerlink_fpga-0.3.0/bayerlink_fpga/hdl/link_reset.v +93 -0
  18. bayerlink_fpga-0.3.0/bayerlink_fpga/hdl/rx_axis.v +50 -0
  19. bayerlink_fpga-0.3.0/bayerlink_fpga/hdl/stream_switch.v +58 -0
  20. bayerlink_fpga-0.3.0/bayerlink_fpga/hdl/tmds_align.v +291 -0
  21. bayerlink_fpga-0.3.0/bayerlink_fpga/hdl/tmds_bond.v +163 -0
  22. bayerlink_fpga-0.3.0/bayerlink_fpga/hdl/tmds_rx.v +203 -0
  23. bayerlink_fpga-0.3.0/bayerlink_fpga/hdl/tmds_rx.xdc +22 -0
  24. bayerlink_fpga-0.3.0/bayerlink_fpga/hdl/tmds_rx_lane.v +89 -0
  25. bayerlink_fpga-0.3.0/bayerlink_fpga/modules.json +245 -0
  26. bayerlink_fpga-0.3.0/bayerlink_fpga/receiver.py +257 -0
  27. bayerlink_fpga-0.3.0/pyproject.toml +30 -0
  28. bayerlink_fpga-0.3.0/scripts/checklicence.py +84 -0
  29. bayerlink_fpga-0.3.0/scripts/judge.py +180 -0
  30. bayerlink_fpga-0.3.0/sim/tb_link_reset.v +91 -0
  31. bayerlink_fpga-0.3.0/tests/receiver_proof.py +599 -0
  32. bayerlink_fpga-0.3.0/tests/sim/tb_tmds_rx.v +353 -0
  33. bayerlink_fpga-0.3.0/tests/sim/xilinx_models.v +155 -0
  34. bayerlink_fpga-0.3.0/tests/test_frontends.py +69 -0
  35. bayerlink_fpga-0.3.0/tests/test_receiver.py +40 -0
  36. bayerlink_fpga-0.3.0/tests/test_tmds_rx_sim.py +36 -0
@@ -0,0 +1,149 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-24.04
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ python-version: ["3.11", "3.12", "3.13", "3.14"]
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - uses: actions/setup-python@v5
19
+ with:
20
+ python-version: ${{ matrix.python-version }}
21
+ - name: Install iverilog
22
+ run: sudo apt-get update && sudo apt-get install -y iverilog
23
+ - name: Install Verilator
24
+ run: |
25
+ curl -fsSL -o /tmp/oss-cad.tgz \
26
+ https://github.com/YosysHQ/oss-cad-suite-build/releases/download/2026-08-08/oss-cad-suite-linux-x64-20260808.tgz
27
+ tar -xzf /tmp/oss-cad.tgz -C /opt
28
+ echo "/opt/oss-cad-suite/bin" >> "$GITHUB_PATH"
29
+ /opt/oss-cad-suite/bin/verilator --version
30
+ - run: |
31
+ python -m pip install --upgrade pip
32
+ pip install -e . pytest
33
+ - name: Every source file states this repository's licence
34
+ run: python scripts/checklicence.py
35
+ - name: Dependency licences are permissive
36
+ # An ALLOW-LIST, checked against the resolved environment. A deny-list
37
+ # only catches the licences somebody thought to name; the one that
38
+ # causes trouble is the one nobody anticipated, and an allow-list
39
+ # catches that by construction.
40
+ #
41
+ # A new dependency under an unlisted licence fails here. If the licence
42
+ # is genuinely permissive, add it to PERMITTED with a one-line reason.
43
+ # The full dependency audit lives in revela's docs/THIRD-PARTY.md,
44
+ # which covers this package transitively.
45
+ run: |
46
+ python - <<'PY'
47
+ import re, sys
48
+ from importlib.metadata import distributions
49
+
50
+ PERMITTED = {
51
+ "MIT", "MIT-0", "BSD-2-Clause", "BSD-3-Clause", "0BSD",
52
+ "Apache-2.0", "PSF-2.0", "ISC", "Zlib", "CC0-1.0", "HPND",
53
+ "Python-2.0", "Unlicense", "MPL-2.0",
54
+ "Apache-2.0 WITH SHL-2.1", # bayerlink-fpga itself
55
+ }
56
+ # MPL-2.0 is permitted only where it appears: a build-time transitive
57
+ # of the build backend, never imported, never redistributed in the
58
+ # wheel.
59
+
60
+ def terms(expression):
61
+ """The identifiers an SPDX expression names.
62
+
63
+ Split on AND/OR only: `WITH` binds an exception to its licence
64
+ ("Apache-2.0 WITH SHL-2.1") and the pair is one identifier.
65
+ Every branch of an OR must be permitted, which is stricter than
66
+ SPDX requires and is the right way round for this check.
67
+ """
68
+ parts = re.split(r"\s+(?:AND|OR)\s+|[()]", expression)
69
+ return {p.strip() for p in parts if p.strip()}
70
+
71
+ # Three metadata generations are in the wild at once, so all three are
72
+ # read: the PEP 639 `License-Expression`, the `License ::` trove
73
+ # classifiers, and the legacy free-text `License` field. A package
74
+ # that declares its licence only in the oldest form is not an
75
+ # undeclared package, and reporting it as one would train everyone to
76
+ # ignore this check.
77
+ LEGACY = {
78
+ "mit": "MIT", "mit license": "MIT", "expat": "MIT",
79
+ "bsd": "BSD-3-Clause", "bsd license": "BSD-3-Clause",
80
+ "bsd-3-clause": "BSD-3-Clause", "bsd 3-clause": "BSD-3-Clause",
81
+ "bsd-2-clause": "BSD-2-Clause", "apache-2.0": "Apache-2.0",
82
+ "apache 2.0": "Apache-2.0", "apache software license": "Apache-2.0",
83
+ "isc": "ISC", "psf-2.0": "PSF-2.0", "mpl-2.0": "MPL-2.0",
84
+ }
85
+ CLASSIFIER = {
86
+ "MIT License": "MIT", "MIT No Attribution License (MIT-0)": "MIT-0",
87
+ "BSD License": "BSD-3-Clause",
88
+ "Apache Software License": "Apache-2.0",
89
+ "Python Software Foundation License": "PSF-2.0",
90
+ "Mozilla Public License 2.0 (MPL 2.0)": "MPL-2.0",
91
+ "ISC License (ISCL)": "ISC", "The Unlicense (Unlicense)": "Unlicense",
92
+ "Zope Public License": None,
93
+ }
94
+
95
+ def declared(dist):
96
+ """(identifiers, how it was declared) for one distribution."""
97
+ expression = (dist.metadata.get("License-Expression") or "").strip()
98
+ if expression:
99
+ return terms(expression), expression
100
+ classifiers = [c for c in dist.metadata.get_all("Classifier") or []
101
+ if c.startswith("License ::")]
102
+ if classifiers:
103
+ names = [c.rsplit(" :: ", 1)[-1] for c in classifiers]
104
+ return ({CLASSIFIER.get(n, n) for n in names},
105
+ "classifier: " + "; ".join(names))
106
+ legacy = (dist.metadata.get("License") or "").strip()
107
+ if legacy and "\n" not in legacy and len(legacy) < 60:
108
+ return ({LEGACY.get(legacy.lower(), legacy)},
109
+ f"legacy License field: {legacy}")
110
+ return set(), "no declared licence"
111
+
112
+ # setuptools 78/79 dropped its `License ::` classifiers, and its
113
+ # metadata predates `License-Expression`, so it declares its licence
114
+ # in none of the three places read above. It is MIT; it is a build
115
+ # tool, never imported and never redistributed in a wheel; and pip
116
+ # brings it whether or not anything asked. Named here rather than
117
+ # waved through: anything ELSE that declares nothing still fails.
118
+ UNDECLARED_UPSTREAM = {"setuptools": "MIT"}
119
+
120
+ problems, checked = [], 0
121
+ for dist in distributions():
122
+ name = dist.metadata["Name"]
123
+ if not name:
124
+ continue
125
+ identifiers, how = declared(dist)
126
+ if not identifiers and name.lower() in UNDECLARED_UPSTREAM:
127
+ identifiers = {UNDECLARED_UPSTREAM[name.lower()]}
128
+ how = "known upstream packaging gap"
129
+ checked += 1
130
+ if not identifiers:
131
+ problems.append(f"{name} {dist.version}: no declared licence")
132
+ continue
133
+ unlisted = identifiers - PERMITTED
134
+ if unlisted:
135
+ problems.append(f"{name} {dist.version}: {how} "
136
+ f"-> not permitted: {sorted(unlisted)}")
137
+
138
+ print(f"checked {checked} distributions against the allow-list")
139
+ if problems:
140
+ print("\nNOT PERMITTED:")
141
+ for p in problems:
142
+ print(" " + p)
143
+ print("\nA dependency whose licence is not on the allow-list "
144
+ "cannot be added without a decision.")
145
+ sys.exit(1)
146
+ print("every dependency licence is on the allow-list")
147
+ PY
148
+
149
+ - run: pytest -q
@@ -0,0 +1,73 @@
1
+ # Publish bayerlink-fpga to PyPI via Trusted Publishing (OIDC) -- no stored token.
2
+ #
3
+ # One-time setup on PyPI:
4
+ # https://pypi.org/manage/account/publishing/ -> add a pending publisher
5
+ # PyPI project name : bayerlink-fpga
6
+ # Owner : bayerlink
7
+ # Repository : bayerlink-fpga
8
+ # Workflow : publish.yml
9
+ # Environment : pypi
10
+ # Then in this repository: Settings -> Environments -> create `pypi`,
11
+ # with a required reviewer so an accidental tag cannot publish.
12
+ #
13
+ # To release: bump version in pyproject.toml, tag vX.Y.Z, publish a GitHub
14
+ # Release from the tag.
15
+ name: Publish
16
+
17
+ on:
18
+ release:
19
+ types: [published]
20
+ workflow_dispatch:
21
+
22
+ permissions:
23
+ contents: read
24
+
25
+ jobs:
26
+ build:
27
+ runs-on: ubuntu-24.04
28
+ steps:
29
+ - uses: actions/checkout@v4
30
+ - uses: actions/setup-python@v5
31
+ with:
32
+ python-version: "3.12"
33
+ - name: Build and check
34
+ run: |
35
+ python -m pip install --upgrade pip build twine packaging
36
+ python -m build
37
+ twine check --strict dist/*
38
+ - name: Verify the tag matches the declared version
39
+ if: github.event_name == 'release'
40
+ run: |
41
+ python - <<'PY'
42
+ import os, pathlib, sys, tomllib
43
+ from packaging.version import Version
44
+ declared = tomllib.loads(
45
+ pathlib.Path("pyproject.toml").read_text())["project"]["version"]
46
+ tag = os.environ["GITHUB_REF_NAME"].lstrip("v")
47
+ if Version(declared) != Version(tag):
48
+ sys.exit(f"tag {tag!r} != pyproject version {declared!r}")
49
+ print("tag and version agree:", declared)
50
+ PY
51
+ - name: Install the wheel into a clean environment
52
+ run: |
53
+ python -m venv /tmp/clean
54
+ /tmp/clean/bin/pip install dist/*.whl
55
+ /tmp/clean/bin/python -c "import bayerlink_fpga.receiver, bayerlink_fpga.ddc; print('bayerlink_fpga imports')"
56
+ - uses: actions/upload-artifact@v4
57
+ with:
58
+ name: distributions
59
+ path: dist/
60
+
61
+ pypi:
62
+ needs: build
63
+ if: github.event_name == 'release'
64
+ runs-on: ubuntu-24.04
65
+ environment: pypi
66
+ permissions:
67
+ id-token: write
68
+ steps:
69
+ - uses: actions/download-artifact@v4
70
+ with:
71
+ name: distributions
72
+ path: dist/
73
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,32 @@
1
+ # GENERATED, and so not source. Every number in these is read from the
2
+ # file that owns it -- the board, the design, or the ISP's own published
3
+ # contract -- and build.sh regenerates all of them before Vivado starts.
4
+ # Committed, they would be a second copy of facts that already have
5
+ # owners, going stale in silence the first time someone edits an owner
6
+ # and does not rebuild. That disagreement does not surface as an error:
7
+ # a width mismatch in a block design is a CRITICAL WARNING and a quiet
8
+ # connection of the low bits.
9
+ hdl/generated/
10
+ boards/*/generated/
11
+ board-files
12
+ vivado-library
13
+ build/
14
+ out/
15
+ *.jou
16
+ *.log
17
+ *.bit
18
+ *.hwh
19
+ .Xil/
20
+ NA/
21
+ # dvi2rgb writes its timing tables beside the board while it elaborates
22
+ # and removes them again. They exist only during a build, which is
23
+ # exactly when someone is most likely to stage everything.
24
+ boards/*/dgl_*.data
25
+ clockInfo.txt
26
+ tight_setup_hold_pins.txt
27
+ .DS_Store
28
+ __pycache__/
29
+
30
+ # Packaging
31
+ dist/
32
+ *.egg-info/
@@ -0,0 +1,235 @@
1
+ # Changelog
2
+
3
+ Notable changes to bayerlink-fpga — the bayerlink receiver on real
4
+ silicon: the receiver and its TMDS front ends. (Through 0.2.1 it also
5
+ carried the PYNQ-Z2 board design: a generated ISP and a display raster.)
6
+
7
+ Up to 0.2.1 this repository published no package: a tag said which
8
+ **generator versions** the recorded bitstream was built from, because the
9
+ claim was reproducibility. From the next release it publishes
10
+ `bayerlink-fpga`, the receiver's package, and the board design lives in
11
+ revela.
12
+
13
+ ## 0.3.0
14
+
15
+ **An HDMI receiver of our own, `tmds_rx`** (`hdl/tmds_rx.v`), as a
16
+ second implementation of `frontends/hdmi_rx`: an MMCM at five times the
17
+ link into BUFIO and BUFR, an IDELAYE2 and an ISERDESE2 pair a lane, and
18
+ an alignment made for links that blank every line. Every delay tap of
19
+ every bit slip is watched for tens of lines, for whether it sees blanks
20
+ at all and how many control tokens it misreads; a lane samples at the
21
+ middle of the taps that see blanks -- the walls of its eye, not its
22
+ error-free taps, which sit off the middle -- at the least delay, so every
23
+ lock lands on the same taps, and a mode change costs ~150 ms where
24
+ dvi2rgb's 50 ms per tap cost seconds. The lanes are bonded where their
25
+ blanking ends, held bonded through a stray misread token, and a line's
26
+ data is what two of the three lanes say; `locked` is the clock locked and
27
+ the lanes delivering, falling at once when the cable is pulled. What it
28
+ saw is the block's optional account of the link, as named ports: the
29
+ bond, each lane's alignment, eye and bit slip, and a survey of every
30
+ lane's misread tokens at every tap of every slip, an entry at a time.
31
+ Proven in simulation against a DVI source skewed by bits and whole
32
+ words, with stray bit errors in its blanking, through a cable pull, at
33
+ 148.5, 74.25 and 27 MHz links (`tests/test_tmds_rx_sim.py`), and on the
34
+ PYNQ-Z2 at 148.5 MHz: twenty locks bit-exact, and a 1080p30 camera's
35
+ stream for minutes without a dropped lock.
36
+
37
+ **The EDID's range limits are its link mode's.** The monitor range
38
+ descriptor stated 29-31 Hz and 30-40 kHz whatever the build advertised;
39
+ it is derived from the container's own timing now, a step either side,
40
+ with its pixel clock as the ceiling.
41
+
42
+ **The board design moved to revela; this repository became the receiver's
43
+ package.** It keeps what any FPGA receiving bayerlink needs, and nothing
44
+ that belongs to one design:
45
+
46
+ - `bayerlink_fpga`, importable, in two layers: the receiver
47
+ (`receiver.py`, `ddc.py`, `hdl/`) and front ends (`frontends/hdmi_rx`).
48
+ The receiver and the DDC slave are leaves a design names --
49
+ `bayerlink_fpga.receiver:bayerlink_in`, `bayerlink_fpga.ddc:ddc_slave`
50
+ -- and its build sets them from the design's settings. Nothing in
51
+ it knows a board: a
52
+ board's pins and its front-end configuration live in that board's
53
+ library.
54
+ - Front ends are one block, `frontends/hdmi_rx`, described once with its
55
+ implementations; a design names the block, and a parameter chooses the
56
+ implementation. The lane map moved from the board to the front end: it
57
+ is a fact of the dvi2rgb implementation, following that IP's bus
58
+ order, so any board receiving through dvi2rgb inherits it, and the
59
+ receiver's lanes are assigned from it.
60
+ - The PYNQ-Z2 design -- block design, datapath, ISP pipeline, raster,
61
+ tee, bench scripts, clocking notes -- is `revela/boards/pynq_z2` in
62
+ revela, and builds there. Its generated Verilog is byte-identical to
63
+ this repository's 0.2.1 flow on the same generators.
64
+
65
+ **The judge finds registers through the build's address map.** revela's
66
+ design now has its own register bus, which Vivado cannot see through, so
67
+ the `.hwh` no longer lists the slaves; `scripts/judge.py` reads
68
+ `address_map.json`, written beside the bitstream, instead of PYNQ's
69
+ `ip_dict`. Judged 4/4 on that build.
70
+
71
+ **The receiver is this package's Verilog.** It was generated by np2hw,
72
+ which is a generic library; it is `bayerlink_fpga/hdl/bayerlink_in.v` now,
73
+ beside the protocol it implements -- its structure fixed, its capacity,
74
+ output depth, lane order and vsync polarity parameters, which
75
+ `bayerlink_fpga.receiver` works out from a design's settings and its
76
+ front end. The protocol's fourcc table and magic are generated from the
77
+ reference codec as `bayerlink_formats`, so the file restates none of
78
+ them. `tests/` proves it bit-exact against that codec and holds its
79
+ published ports to the file. The `python3 -m bayerlink_fpga` step and
80
+ `frontend.tcl` are gone: what dvi2rgb needs beside a batch run -- its
81
+ EDID data files -- a run copies for every IP that has them.
82
+
83
+ **The receiver is a library a design composes.** `modules.json` describes
84
+ each hand-written module as a block in revela's composite format, by its
85
+ sources, parameters and every port it has; the generated ones publish
86
+ their ports themselves. Each module's clock names the streams it drives
87
+ and its reset, so a block design need not guess either.
88
+ `judge.py` reads the GPIO fields by name from the `gpio_layout.json` built
89
+ beside the bitstream, and refuses a layout whose checksum, built into the
90
+ fabric, does not read back. `vid_probe`, whose word no design read, is
91
+ retired.
92
+
93
+ **The EDID reads bayerlink's rasters.** The advertised timing is the link
94
+ mode's raster from `bayerlink.rasters`, the table the source reads too,
95
+ rather than np2hw's; the bytes are unchanged.
96
+
97
+ ## 0.2.1
98
+
99
+ Built with `np2hw==0.6.1`, `bayerlink==0.5.0`, `revela==0.2.0`.
100
+
101
+ **The pinned install line resolves again.** 0.2.0 pinned `np2hw==0.6.0`,
102
+ which was withdrawn from PyPI before it was announced; a burned version
103
+ does not come back, so the documented `pip install` could not complete
104
+ and the reproducible build this repository promises was not reproducible
105
+ by anyone.
106
+
107
+ The same generator ships as np2hw 0.6.1. **The pin moved and the design
108
+ did not**: generating this pipeline under 0.6.0 and under 0.6.1 produces
109
+ a byte-identical pack — Verilog, register map, SystemRDL, FuseSoC core
110
+ and build manifest all compare equal. Between the two releases the only
111
+ code that changed is `ipxact.py`, which emits IP-XACT XML rather than
112
+ Verilog, plus one docstring. So the bitstream measured for 0.2.0 is the
113
+ bitstream this pin builds; nothing about the board result below is
114
+ restated on weaker evidence.
115
+
116
+ ## 0.2.0
117
+
118
+ Built with `np2hw==0.6.0`, `bayerlink==0.5.0`, `revela==0.2.0`.
119
+
120
+ Measured on the board: **WNS +0.134 ns MET, WHS +0.050 ns**, TNS and THS
121
+ zero — more margin on both than 0.1.0 had. `rx.bit` and `rx.hwh`
122
+ written, the as-built audit passes on seven addressable slaves, and no
123
+ critical-warning class appears that an earlier build's log did not.
124
+
125
+ **And it ran.** Loaded on the PYNQ-Z2, the bridge restored fifty
126
+ registers over the display cable, the link locked and genlocked, and the
127
+ board produced a correct 1920x1080 picture from a live sensor — the
128
+ first tagged build here proven on glass in the same pass that built it.
129
+
130
+ ### Line buffers moved behind a module boundary
131
+
132
+ np2hw 0.6.0 emits each line buffer as its own module rather than an
133
+ inline array, so an integrator targeting silicon can bind a memory macro
134
+ to it by name. On this board it is inert, and that is the point:
135
+ **block RAM 23.5 tiles (22 x RAMB36 + 3 x RAMB18), identical to the tile
136
+ against the previous build**, with LUTs and flip-flops within twenty of
137
+ where they were. A claim about inference that hardware could refute, and
138
+ did not.
139
+
140
+ ### The RTL says what its ports are
141
+
142
+ Every AXI4-Lite port and every elastic-stream port now names the bus it
143
+ belongs to, and the block design is given the bus definition that makes
144
+ those names resolvable. `gen/netlist.py` reads those declarations
145
+ instead of matching port names with a regex — the grouping is stated by
146
+ the module that owns the ports rather than inferred by everything
147
+ downstream — and refuses a module that carries a handshake while
148
+ declaring nothing.
149
+
150
+ The six hand-written board modules that speak the protocol declare it
151
+ now. The emitted wiring is unchanged: `netlist.tcl` is byte-identical
152
+ across the whole change.
153
+
154
+ ### The build says how much of the part it used
155
+
156
+ `report_utilization` runs after routing and lands beside the bitstream.
157
+ For its whole history this build reported whether it met timing and
158
+ never once how full the device was, which is not a question that can be
159
+ answered later.
160
+
161
+ ### Known gaps
162
+
163
+ Unchanged from 0.1.0: nothing runs `sim/`, and the link-judge capture
164
+ path is off by default.
165
+
166
+ ## 0.1.0 — first tagged build
167
+
168
+ Built with `np2hw==0.5.1`, `bayerlink==0.5.0`, `revela==0.2.0`.
169
+
170
+ Measured on the board: **WNS +0.047 ns MET, WHS +0.013 ns**, TNS and THS
171
+ zero, `rx.bit` and `rx.hwh` written, and no critical-warning class absent
172
+ from an earlier build's log.
173
+
174
+ ### The design
175
+
176
+ - **Three of the four sources are generated and none is committed** — the
177
+ receiver, the ISP and the display raster all come from their models.
178
+ There is no hand-edited Verilog to drift, which is why a build that
179
+ skips a generator fails on a missing file rather than quietly using a
180
+ stale one.
181
+
182
+ - **The ISP is a description.** `gen/pipeline.json` says what the
183
+ pipeline is, independent of any board; `revela` builds it and publishes
184
+ what it traced. The last scripted pipeline is gone.
185
+
186
+ - **The block design's numbers are generated from the files that own
187
+ them.** It used to learn them three ways — environment variables, Tcl
188
+ defaults beside them, and constants in the script — which agreed until
189
+ the depth moved and then did not. What a disagreement produces here is
190
+ not an error but a picture, because Vivado answers a width mismatch
191
+ with a CRITICAL WARNING and a silent connection of the low bits.
192
+
193
+ Three owners now: `gen/pipeline.json` (the design), `board.json` (the
194
+ target) and `<name>_build.json` (what the ISP actually traced, which
195
+ nobody may restate). `bd.tcl` reads no environment and declares no
196
+ defaults.
197
+
198
+ - **The datapath is described, not scripted.** `design.json` names the
199
+ blocks, their widths and their connections; `gen/netlist.py` emits the
200
+ Tcl and checks every width against the modules' own ports first. Every
201
+ instance declares its clock domain, and an undeclared crossing is
202
+ refused.
203
+
204
+ - **Two clock territories**: the cable's, quarantined because it stops
205
+ when the cable is pulled, and the board's. Nothing addressable lives on
206
+ a clock that can stop — a slave with no clock never answers, AXI has no
207
+ timeout, and the first software access after that takes the processor
208
+ with it.
209
+
210
+ - **Display placement is a register**, taken in vertical blanking behind
211
+ an arm/ack handshake, so a frame is drawn with one coherent set or the
212
+ previous one. A window that does not fit is refused rather than
213
+ clipped.
214
+
215
+ - **A line wider than the pack was built for is cropped, not
216
+ reinterpreted** — visibly, with a sticky `truncated`. A narrower
217
+ picture can be recognised and tuned; a refused one is a black screen
218
+ indistinguishable from a dead cable.
219
+
220
+ ### Checks that run before Vivado does
221
+
222
+ `checkbd`, `checklicence` and `checknets` run in seconds, against twenty
223
+ minutes of synthesis. `checknets` compares every wire drawn between our
224
+ own blocks against the widths those blocks actually have — every bug of
225
+ that shape this repository has had lived in exactly that gap. `checkhwh`
226
+ then audits the design **as built**, because the intent and the
227
+ automation's result are different documents.
228
+
229
+ ### Known gaps
230
+
231
+ - Nothing runs `sim/`: the four testbenches are launched by hand from a
232
+ `// Run:` comment in each. Two had rotted unnoticed before this
233
+ release and are fixed here.
234
+ - The link-judge capture path is off by default (`capture: false`); it is
235
+ an episodic instrument, not a daily tax.
@@ -0,0 +1,45 @@
1
+ SOLDERPAD HARDWARE LICENSE VERSION 2.1
2
+
3
+ This license operates as a wraparound license to the Apache License Version 2.0 (the "Apache License") and incorporates the terms and conditions of the Apache License (which can be found here: http://apache.org/licenses/LICENSE-2.0), with the following additions and modifications. It must be read in conjunction with the Apache License. Section 1 below modifies definitions and terminology in the Apache License and Section 2 below replaces Section 2 of the Apache License. The Appendix replaces the Appendix in the Apache License. You may, at your option, choose to treat any Work released under this license as released under the Apache License (thus ignoring all sections written below entirely).
4
+
5
+ 1. Terminology in the Apache License is supplemented or modified as follows:
6
+
7
+ "Authorship": any reference to 'authorship' shall be taken to read "authorship or design".
8
+
9
+ "Copyright owner": any reference to 'copyright owner' shall be taken to read "Rights owner".
10
+
11
+ "Copyright statement": the reference to 'copyright statement' shall be taken to read 'copyright or other statement pertaining to Rights'
12
+
13
+ The following new definition shall be added to the Definitions section of the Apache License:
14
+
15
+ "Rights" means copyright and any similar right including design right (whether registered or unregistered), rights in semiconductor topographies (mask works) and database rights (but excluding Patents and Trademarks).
16
+
17
+ The following definitions shall replace the corresponding definitions in the Apache License:
18
+
19
+ "License" shall mean this Solderpad Hardware License version 2.1, being the terms and conditions for use, manufacture, instantiation, adaptation, reproduction, and distribution as defined by Sections 1 through 9 of this document.
20
+
21
+ "Licensor" shall mean the Rights owner or entity authorized by the Rights owner that is granting the License.
22
+
23
+ "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship or design. For the purposes of this License, Derivative Works shall not include works that remain reversibly separable from, or merely link (or bind by name) or physically connect to or interoperate with the Work and Derivative Works thereof.
24
+
25
+ "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form or the application of a Source form to physical material, including but not limited to compiled object code, generated documentation, the instantiation of a hardware design or physical object or material and conversions to other media types, including intermediate forms such as bytecodes, FPGA bitstreams, moulds, artwork and semiconductor topographies (mask works).
26
+
27
+ "Source" form shall mean the preferred form for making modifications, including but not limited to source code, net lists, board layouts, CAD files, documentation source, and configuration files.
28
+
29
+ "Work" shall mean the work of authorship or design, whether in Source or Object form, made available under the License, as indicated by a notice relating to Rights that is included in or attached to the work (an example is provided in the Appendix below).
30
+
31
+ 2. Grant of License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable license under the Rights to reproduce, prepare Derivative Works of, make, adapt, repair, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form and do anything in relation to the Work as if the Rights did not exist.
32
+
33
+
34
+ APPENDIX
35
+
36
+ Copyright [yyyy] [name of copyright owner]
37
+ SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1
38
+
39
+ Licensed under the Solderpad Hardware License v 2.1 (the "License"); you may not use this file except in compliance with the License, or, at your option, the Apache License version 2.0.
40
+ You may obtain a copy of the License at
41
+
42
+ https://solderpad.org/licenses/SHL-2.1/
43
+
44
+ Unless required by applicable law or agreed to in writing, any work distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
45
+ See the License for the specific language governing permissions and limitations under the License.