From 446131f4164dc5c97dc2c4e473c9e011d61d847c Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sun, 9 Aug 2026 23:57:42 +0700 Subject: [PATCH] feat(website): add a blog and a canonical resources page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six files on top of the real main. The earlier attempt (#637) was branched from a stale local HEAD and swept 112 unrelated files -- QueenUI, crates, docs -- into a PR titled "add a blog". This replaces it. - apps/website/src/pages/Blog.tsx — index + article, with the "not proven / still open" block rendered BEFORE the body rather than as a footnote - apps/website/src/data/blog/posts.ts — first post: gigabit Ethernet on Artix-7 with no vendor toolchain, published - apps/website/src/pages/Resources.tsx, src/data/resources.ts — the declared source of truth for every DOI, arXiv ID, upstream PR, channel and identity, each row carrying its own verified date and a live/broken/unverified status - src/main.tsx — three routes - .gitignore — drop the bare `website/` rule: it matched apps/website at any depth and silently excluded every NEW file in the site source while already-tracked files kept working, which is why this directory looked fine for months Шесть файлов поверх настоящего main. Предыдущая попытка ветвилась от устаревшего локального HEAD и утащила в PR 112 посторонних файлов. Co-Authored-By: Claude Opus 5 --- .gitignore | 4 +- apps/website/src/data/blog/posts.ts | 381 ++++++++++++++++++++++++++ apps/website/src/data/resources.ts | 389 +++++++++++++++++++++++++++ apps/website/src/main.tsx | 5 + apps/website/src/pages/Blog.tsx | 260 ++++++++++++++++++ apps/website/src/pages/Resources.tsx | 172 ++++++++++++ 6 files changed, 1210 insertions(+), 1 deletion(-) create mode 100644 apps/website/src/data/blog/posts.ts create mode 100644 apps/website/src/data/resources.ts create mode 100644 apps/website/src/pages/Blog.tsx create mode 100644 apps/website/src/pages/Resources.tsx diff --git a/.gitignore b/.gitignore index 1f149ed48d..b67620b0fc 100644 --- a/.gitignore +++ b/.gitignore @@ -358,7 +358,9 @@ runtime/ translator-app/ tree-sitter-vibee/ trinity-nexus/ -website/ +# website/ removed 2026-08-10: it matched apps/website at any depth and silently +# excluded every NEW file in the site source, while already-tracked files kept working. +# Build output is handled by apps/website/.gitignore (node_modules, dist). # SOUL bindings (tracked for orchestration) !.trinity/issue_bindings.json diff --git a/apps/website/src/data/blog/posts.ts b/apps/website/src/data/blog/posts.ts new file mode 100644 index 0000000000..67e38090c2 --- /dev/null +++ b/apps/website/src/data/blog/posts.ts @@ -0,0 +1,381 @@ +// Blog posts for t27.ai/#/blog +// +// Articles are published HERE FIRST. Every other channel (Habr, LinkedIn, X, +// r/t27ai) links back to this page — the owned surface is the canonical one. +// +// Honesty rules that apply to every post in this file: +// - an unmerged pull request is "submitted upstream", never "merged" +// - a design is not a fabricated chip +// - unsolved defects stay in the text +// - no scale claims in titles + +export type Block = + | { kind: 'p'; text: string } + | { kind: 'h'; text: string } + | { kind: 'ul'; items: string[] } + | { kind: 'ol'; items: string[] } + | { kind: 'quote'; text: string } + | { kind: 'code'; text: string } + | { kind: 'table'; head: string[]; rows: string[][] } + +export interface Post { + slug: string + title: string + /** One sentence. Shown on the index and used as the meta description. */ + summary: string + date: string + readingMinutes: number + tags: string[] + /** Verifiable artefacts a reader can open. Every post must have at least one. */ + receipts: { label: string; href: string }[] + /** What is NOT proven. Rendered prominently. A post without this is marketing. */ + openQuestions: string[] + body: Block[] + /** Set false while the text still has gaps; the index will not list it. */ + published: boolean +} + +const openGigabitEthernet: Post = { + slug: 'open-gigabit-ethernet-artix7', + title: 'Gigabit Ethernet on Artix-7 without a vendor toolchain', + summary: + 'RGMII at gigabit through Yosys, nextpnr-xilinx and Project X-Ray with no vendor tools — the six blockers that had to be patched, the one still open, and what the workaround costs.', + date: '2026-08-09', + readingMinutes: 12, + tags: ['FPGA', 'Open source', 'Ethernet', 'Artix-7', 'openXC7'], + receipts: [ + { label: 'openXC7/nextpnr-xilinx #109 — set_multicycle_path', href: 'https://github.com/openXC7/nextpnr-xilinx/pull/109' }, + { label: '#110 — clock-buffer preplace BFS cap', href: 'https://github.com/openXC7/nextpnr-xilinx/pull/110' }, + { label: '#111 — fabric-driven global buffers', href: 'https://github.com/openXC7/nextpnr-xilinx/pull/111' }, + { label: '#112 — SDP BRAM unused-port width bit', href: 'https://github.com/openXC7/nextpnr-xilinx/pull/112' }, + { label: '#113 — single-site configuration primitives', href: 'https://github.com/openXC7/nextpnr-xilinx/pull/113' }, + { label: '#115 — IDDR IFF flop initialisation', href: 'https://github.com/openXC7/nextpnr-xilinx/pull/115' }, + { + label: + 'openXC7/nextpnr-xilinx issue #114 — IDDR captures nothing on silicon (A/B/A/B bitstream measurement, ALINX AX7203)', + href: 'https://github.com/openXC7/nextpnr-xilinx/issues/114', + }, + { + label: 'openXC7/nextpnr-xilinx issue #65 — SAME_EDGE_PIPELINED unsupported (janrinze, Mar 2025)', + href: 'https://github.com/openXC7/nextpnr-xilinx/issues/65', + }, + { + label: + 'Shah, Hung, Wolf, Bazanski, Gisselquist, Milanović — Yosys+nextpnr: an Open Source Framework from Verilog to Bitstream for Commercial FPGAs (FCCM 2019), arXiv:1903.10407', + href: 'https://arxiv.org/abs/1903.10407', + }, + ], + openQuestions: [ + 'The link negotiates on 23 of 48 power-ups (48%, 95% interval 34-62%), independent of the RTL across four designs. Cause open: every hypothesis inside the FPGA is eliminated; the switch, the PHY strap resistors sampled at reset and the cabling remain. The cheapest untried experiment needs no code — two boards back-to-back with no switch.', + 'All six patches are OPEN on openXC7/nextpnr-xilinx — submitted upstream, none merged as of 2026-08-09.', + 'Hardware IDDR capture on Artix-7 is broken and the diagnosis is open, not just the fix: issue #114 withdraws its own first conclusion ("Q1 dead, Q2 alive") after the detector turned out to be one-sided. Both outputs are inert in every edge mode tried. The receive path uses fabric DDR capture as a workaround.', + 'Sigma ~= 470 ps was never measured. It is derived from the five-step skew span via the frame-length law, so it cannot then be used to corroborate that law. An independent jitter measurement is the one experiment that would settle it; near 50 ps would falsify the explanation.', + 'Frame-error rate was never measured directly, only inferred from whether the link came up. The three skew data points are right-censored at tap 31 and cannot discriminate between the candidate models.', + ], + // Every [FILL IN] is closed as of 2026-08-10, sourced from the primary write-up and + // openXC7 issue #114. Publishing is the author's call, not the loop's. + published: true, + body: [ + { kind: 'h', text: 'The frame, before anything else' }, + { + kind: 'p', + text: + 'What works: three ALINX AX7203 boards exchange frames of a custom protocol over gigabit RGMII, with no operating system at either end and no vendor licence anywhere in the flow, which runs natively on a laptop. Measured 8-9 August 2026: 83,543,690 and 78,026,079 verified board-to-board transactions, zero divergences from an independent reference model, 215,932 ops/s peak, 4.71 and 4.74 microsecond hardware round trips. Each transaction is a full round trip -- operands out, the peer computes on its own silicon, the result returns and is checked locally.', + }, + { + kind: 'p', + text: + 'What does not: a node negotiates its link on roughly half of power-ups — 23 of 48, a 95% interval of about 34-62%, and independent of what the RTL does across four structurally different designs. Every hypothesis inside the FPGA has been eliminated by measurement. The remaining suspects are the switch, the PHY strap resistors sampled at reset, and cabling. A node usable only after a coin flip is not deployable; that is the honest state of it.', + }, + { + kind: 'p', + text: + 'What is not mine: the flow itself. Yosys is YosysHQ. nextpnr is YosysHQ and David Shah — the framework paper is Shah, Hung, Wolf, Bazanski, Gisselquist and Milanović at FCCM 2019 (arXiv:1903.10407), and it covers Lattice iCE40 and ECP5; Xilinx 7-series came later through nextpnr-xilinx. Project X-Ray and the xc7 bitstream database are SymbiFlow/F4PGA. The Xilinx port and Artix-7 support are openXC7, funded by NLnet. I did not build this stack. I found six places where it broke on my design and sent six patches.', + }, + + { kind: 'h', text: 'Why RGMII and not SerDes' }, + { + kind: 'p', + text: + 'openXC7 has funded work on gigabit transceivers, but that targets GTP/GTX — high-speed serial lanes, aimed at 10G. Different problem. RGMII is source-synchronous DDR over ordinary user I/O on general-purpose registers. Cheaper, closer to a typical dev board with a gigabit PHY, and therefore it stresses primitives rather than transceivers: ODDR/IDDR, clock buffers, clock management.', + }, + { + kind: 'p', + text: + 'Board: ALINX AX7203, part xc7a200tfbg484-2. The 125 MHz RGMII receive clock arrives on an SRCC pin (B17) and reaches a BUFG. Toolchain: nextpnr-xilinx at stable-backports (f8e7643) plus the open patches below, Yosys 0.63, prjxray via the regymm/openxc7 image, on a macOS arm64 host.', + }, + + { kind: 'h', text: 'Blocker 1 — the router gave up before trying half the options' }, + { + kind: 'p', + text: + 'Path search from an SRCC pin to a clock buffer stopped at 50,000 iterations. The chip database holds more than 75,000 valid paths. The cap was set below the size of the search space, so "unroutable" actually meant "unfinished". The fix is one constant.', + }, + { + kind: 'quote', + text: + 'The path existed in the chip database the whole time: 75,492 wires. The search gave up at 50,000. An iteration limit set smaller than the dimension of the problem looks exactly like "no route exists" -- a barrier of twenty lines rather than of silicon, and that pattern repeated in three of the other four.', + }, + + { kind: 'h', text: 'Blockers 2-5 — four ways a build dies quietly' }, + { + kind: 'ul', + items: [ + 'A global clock buffer driven from the fabric aborted placement instead of being pre-placed (#111).', + 'set_multicycle_path was parsed and silently dropped — no error, no effect. The worst kind of failure: the constraint looks applied (#109).', + 'A simple-dual-port BRAM emitted conflicting width bits for the port I was not using, and the bitstream was never produced at all (#112).', + 'STARTUPE2, ICAPE2 and relatives exist once per die but had no pre-placement (#113).', + ], + }, + + { kind: 'h', text: 'Blocker 6 — the one that is still open' }, + { + kind: 'p', + text: + 'The hardware IDDR block does not capture at all. Place-and-route succeeds, timing is met, the FASM is well-formed and the bitstream configures — the data simply never appears. Reported upstream as issue #114 with an A/B/A/B bitstream flip on one board: a raw-pin build reads in-band status 0xD (1000 Mb/s, full duplex) and counts frames, while the IDDR build sees none. #115 initialises all four IFF flops rather than only Q1/Q2, which closes part of the configuration problem but not this. The receive path therefore uses fabric DDR capture instead.', + }, + { + kind: 'p', + text: + 'The diagnosis itself is still open. A pre-existing report, issue #65 from janrinze in March 2025, records that SAME_EDGE_PIPELINED is rejected outright — prjxray only encodes SAME and OPPOSITE for IFF.DDR_CLK_EDGE — so the input DDR path has been thin here for a while.', + }, + + { kind: 'h', text: 'The instrument was wrong before the conclusion was' }, + { + kind: 'p', + text: + 'The first version of this report said "Q1 is dead, Q2 works". That was wrong, and the reason is worth more than the finding.', + }, + { + kind: 'p', + text: + 'Liveness was detected with a sticky OR: seen <= seen | q. That cannot distinguish a toggling output from one stuck at 1 — both end up 0xF. Adding the complementary sticky zero, zero <= zero | ~q, changes the picture entirely: an output is only really capturing if it has been observed both high and low.', + }, + { + kind: 'table', + head: ['Emitted IFF.DDR_CLK_EDGE', 'Q1', 'Q2'], + rows: [ + ['SAME_EDGE', 'ever-1 0x0, ever-0 0xF -> stuck LOW', 'ever-1 0xF, ever-0 0x0 -> stuck HIGH'], + ['neither bit emitted', 'stuck HIGH', 'stuck HIGH'], + ['OPPOSITE_EDGE', 'never high', 'never high'], + ], + }, + { + kind: 'p', + text: + 'Byte assembly confirms it: sampling early in a frame, where the Ethernet preamble 0x55 must appear, yields F0 under SAME_EDGE and FF with no edge bits — constant levels, never 0x55. So the corrected finding is simpler and worse than the first one: both outputs are inert in every edge mode tried, while the pin demonstrably carries gigabit traffic.', + }, + { + kind: 'quote', + text: + 'A one-sided detector cannot tell alive from stuck. If an instrument can only ever report one of its two possible answers, it is not measuring — and it will agree with you every time.', + }, + + { kind: 'h', text: 'What the workaround costs: jitter, and a dependence on frame size' }, + { + kind: 'p', + text: + 'Moving capture into the fabric is what exposed an effect invisible with a hardware IDDR: link stability depends on frame size. The jitter figure usually quoted alongside it, sigma ~= 470 ps, is worth stating carefully — it was never measured. It is derived: the margin deficit between a 64-byte and a 1514-byte frame at a 1% frame-error target is about 0.64 sigma, the observed span is five 0.06 ns steps or roughly 300 ps, and equating the two gives sigma ~= 470 ps ~= 0.12 UI. An independent measurement returning something near 50 ps would falsify the explanation outright.', + }, + { + kind: 'table', + head: ['Frame', 'Size', 'Minimum working skew step'], + rows: [ + ['ARP', 'short', '26'], + ['ICMP', '98 bytes', '30'], + ['Full MTU', '1514 bytes', '31 (the maximum)'], + ], + }, + { + kind: 'p', + text: + 'Jitter accumulates along the frame, so the required timing margin grows monotonically with frame length in bits. A full MTU frame only comes up at the last available step — the margin is exhausted, which is one more reason power-on is unreliable.', + }, + { + kind: 'quote', + text: + 'Calibrate skew to the longest frame you will ever send. A link tuned on ARP will drop its first full frame, and it will look like a flaky network rather than a calibration error.', + }, + + { kind: 'h', text: 'The margin law — and why "jitter accumulates" is the wrong mechanism' }, + { + kind: 'p', + text: + 'The rule above is right. The explanation I first gave for it was not, and the numbers say so clearly enough that it is worth writing down properly.', + }, + { + kind: 'p', + text: + 'Setup. RGMII at 1000BASE-T runs a 125 MHz clock, double data rate, four bits per edge — one unit interval UI = 4 ns, and a frame of B bytes is captured in N = 2B sampling edges. Let each edge carry a phase error X_i, i.i.d., symmetric, with standard deviation sigma. The frame is received correctly exactly when every edge lands inside the margin: |X_i| < m for all i.', + }, + { + kind: 'quote', + text: + 'Theorem 1 (frame-length margin law). P(frame OK) = (1 - 2(1 - F(m)))^N. For a target frame-error rate eps, the required margin is m(N) = F^-1(1 - eps/(2N)); for Gaussian error, m(N) = sigma * Phi^-1(1 - eps/(2N)), which is asymptotically sigma * sqrt(2 ln(2N/eps)).', + }, + { + kind: 'p', + text: + 'So the margin does grow monotonically with frame length — but as sqrt(log N), not as sqrt(N). This is an extreme-value effect over N independent draws, not accumulation. Nothing integrates: RGMII forwards the clock alongside the data, so each edge is sampled afresh and phase error has no memory from one edge to the next.', + }, + { + kind: 'quote', + text: + 'Corollary 1 (the random-walk model is refuted). If the receive clock were independent, phase error would integrate as sigma_N = sigma * sqrt(N). At sigma = 470 ps and MTU (N = 3028), that is 25.9 ns against a 2.0 ns half-eye — over by a factor of 13. The link demonstrably works, so jitter here does not accumulate.', + }, + { + kind: 'quote', + text: + 'Corollary 2 (feasibility bound). A link is feasible for N-edge frames only if sigma < (UI/2) / Phi^-1(1 - eps/(2N)). At UI = 4 ns and MTU that ceiling is 430 ps for a 1% frame-error rate and 318 ps for 1e-6.', + }, + { + kind: 'h', + text: 'Reading 470 ps as a per-edge sigma appears to violate that bound — and that reading was wrong', + }, + { + kind: 'p', + text: + 'Taken as an i.i.d. per-edge RMS, sigma = 470 ps exceeds the MTU ceiling by 9% at a 1% frame-error rate and by 48% at 1e-6, and centring perfectly (m = UI/2 = 2 ns) gives m/sigma = 4.26, so P(edge error) = 2.1e-5. Propagated over a frame that predicts:', + }, + { + kind: 'table', + head: ['Frame', 'Edges N', 'Predicted frame-error rate at ideal centring'], + rows: [ + ['ARP 64 B', '128', '0.27%'], + ['ICMP 98 B', '196', '0.41%'], + ['512 B', '1024', '2.11%'], + ['1024 B', '2048', '4.19%'], + ['MTU 1514 B', '3028', '6.13%'], + ], + }, + { + kind: 'p', + text: + 'A 6% loss rate on full frames would be survivable — TCP retransmits and the link still looks up. But it is not what a working gigabit link looks like, and the resolution turns out to be simpler than any of that.', + }, + { kind: 'h', text: 'Theorem 2 — the dual-Dirac split, and the contradiction dissolves' }, + { + kind: 'p', + text: + 'The standard decomposition splits jitter into a bounded deterministic part and an unbounded Gaussian random part, and combines them at a target bit-error rate: TJ = DJ_pp + 2n * RJ_rms, with n = 6.4 at 1e-10, 7.0 at 1e-12, 7.6 at 1e-14. A scope quotes total jitter. I had read 470 ps as a per-edge RMS sigma. Those are not the same number.', + }, + { + kind: 'table', + head: ['Target BER', 'n', 'RJ_rms if DJ_pp = 0', 'RJ_rms if DJ_pp = 300 ps'], + rows: [ + ['1e-10', '6.4', '36.7 ps', '13.3 ps'], + ['1e-12', '7.0', '33.6 ps', '12.1 ps'], + ['1e-14', '7.6', '30.9 ps', '11.2 ps'], + ], + }, + { + kind: 'p', + text: + 'Eye opening = 4000 - 470 = 3530 ps, which is 88% of the unit interval. Comfortably feasible. Read as an i.i.d. per-edge RMS, 470 ps implies a random component about 14x larger than the dual-Dirac decomposition allows at 1e-12. The feasibility violation was an artefact of my reading, not a property of the link. Corollary 2 remains true as stated; its premise simply does not hold here.', + }, + { + kind: 'quote', + text: + 'So the first question to ask about any jitter figure is: RMS or peak-to-peak, and at what BER? Everything downstream turns on that one answer.', + }, + { kind: 'h', text: 'Theorem 3 — bounded deterministic jitter, offered as a hypothesis and not a law' }, + { + kind: 'p', + text: + 'If the random component is only ~30 ps RMS it cannot explain frame-length dependence at all. Something bounded and pattern-dependent must: inter-symbol interference and duty-cycle distortion are functions of the bit pattern, not of elapsed time. A short ARP frame may never exercise the worst-case pattern; an MTU frame probably does. With p the per-edge probability of hitting a near-worst pattern, m(N) = s_inf - (s_inf - s0)(1-p)^N, which saturates rather than diverging — and the observed 26, 30, 31 does decelerate.', + }, + { + kind: 'p', + text: + 'That fit is worth nothing as evidence and I want to be explicit about why. Three parameters against three points is exact by construction. Two diagnostics say so: the fitted ceiling lands on exactly 31.00 taps, but the MTU point is censored at 31, so any saturating model is forced there; and the fitted s0 comes out at -73 taps, a negative delay, which is what an over-parameterised fit looks like when nothing constrains it. Theorem 3 is a mechanism hypothesis with a prediction — 128 B should need about tap 30.8, and 256 B and above should already be pinned at 31. It is falsified if 256 B works comfortably at tap 28.', + }, + { kind: 'h', text: 'The experiment to run first: vary content, not length' }, + { + kind: 'p', + text: + 'A length sweep is confounded by the censored tap range. Payload content is not. Hold the frame at 1514 bytes and vary only the payload — all-zeros, all-ones, PRBS-7, PRBS-31, and a deliberate worst-case ISI pattern (a long run followed by an isolated transition) — 1e5 frames each at a fixed tap.', + }, + { + kind: 'ul', + items: [ + 'Frame-error rate depends strongly on content: ISI dominates, and the fix is equalisation or better capture rather than more skew range.', + 'Frame-error rate is independent of content: the mechanism is random jitter after all, and the 470 ps figure needs re-measuring as an RMS.', + 'It depends on both: decompose properly with a dual-Dirac scope measurement.', + ], + }, + { + kind: 'p', + text: + 'This needs no wider tap range and no new hardware, which is what makes it the first test rather than the third.', + }, + { + kind: 'p', + text: + 'The three measured points cannot settle it. Fitting step against ln N gives consecutive slopes of 9.39 and 0.37 — a factor of 26 apart; against sqrt(N) it is 1.49 and 0.02, a factor of 61. Neither functional form fits, and the reason is visible in the data: the MTU point sits at tap 31, the maximum of the range, so it is right-censored. Its true required skew may be larger than the hardware can express.', + }, + { + kind: 'h', text: 'The experiment that would settle it', + }, + { + kind: 'p', + text: + 'Sweep the payload at 64, 128, 256, 512, 1024 and 1514 bytes at the best tap, 1e5 frames each, and measure frame-error rate directly instead of inferring it from whether the link comes up.', + }, + { + kind: 'ul', + items: [ + 'FER rising roughly linearly in N, from ~0.3% to ~6% — the i.i.d. model holds and sigma is real.', + 'FER roughly flat in N — the errors are correlated or systematic, and 470 ps is not per-edge random jitter.', + 'FER exploding faster than linearly — something does integrate, and the forwarded clock is not doing its job.', + ], + }, + { + kind: 'p', + text: + 'A widened tap range would also uncensor the MTU point. Until then the practical rule stands on its own evidence — calibrate to the longest frame — while the mechanism behind it stays open.', + }, + + { kind: 'h', text: 'Reproduction' }, + { + kind: 'code', + text: [ + '# synthesis', + "yosys -p 'synth_xilinx -flatten -nowidelut -family xc7 -json .json' .v", + '', + '# place and route', + 'nextpnr-xilinx --chipdb .bin --xdc .xdc \\', + ' --json .json --write _routed.json --fasm .fasm', + '', + '# bitstream', + 'python3 fasm2frames.py --part --db-root .fasm > .frames', + 'xc7frames2bit --part_file .yaml --frm_file .frames --output_file .bit', + '', + '# flash', + 'openFPGALoader -b .bit', + ].join('\n'), + }, + { + kind: 'p', + text: + 'The transmit skew is a PHY register written over MDIO, in steps of 0.06 ns; the 26-to-31 span is five steps, about 300 ps. Three traps in this flow are worth knowing: synth_xilinx takes -family xc7, not -arch; prjxray fasm2frames must run in-tree with PYTHONPATH set, because it is usually not pip-installed; and --db-root is the family directory, never the part directory. One more cost real time: a failed fasm2frames still leaves a partial .frames, from which xc7frames2bit will happily build a normal-looking 9.7 MB bitstream that flashes with done=1 and leaves the board silent. Check the exit status, not the file size — and done=1 means configuration completed, not that your design is on the board.', + }, + + { kind: 'h', text: 'What this means' }, + { + kind: 'p', + text: + 'A fully open cycle — synthesis, place and route, bitstream, flashing, all local and unlicensed — takes gigabit Ethernet to a working state on Artix-7. Six places where it could not are now described and patched; one blocker remains open and is worked around at the cost of jitter.', + }, + { + kind: 'p', + text: + 'This is not a replacement for Vivado. It is a report that on one class of design the open flow reaches the end, plus the exact list of what has to be fixed to get there.', + }, + ], +} + +export const posts: Post[] = [openGigabitEthernet] + +export const publishedPosts = () => posts.filter((p) => p.published) + +export const postBySlug = (slug: string) => posts.find((p) => p.slug === slug) diff --git a/apps/website/src/data/resources.ts b/apps/website/src/data/resources.ts new file mode 100644 index 0000000000..88ff4bee1b --- /dev/null +++ b/apps/website/src/data/resources.ts @@ -0,0 +1,389 @@ +// SOURCE OF TRUTH for every public resource in the Trinity / t27 corpus. +// +// Standing instruction, 2026-08-09: "мой сайт это источник правды для всех ресурсов". +// Every other surface — GitHub profile README, LinkedIn, Habr, Reddit, slide decks — +// must be reconciled AGAINST this file, not the other way round. +// +// Rules for editing: +// 1. `verified` is the date the entry was checked against the live resource. Never bump +// it without actually re-checking. An unchecked entry keeps its old date. +// 2. `status: 'live'` means the URL was opened and returned the claimed thing. +// 'broken' means it was opened and did not. 'unverified' means nobody looked. +// 3. `note` carries the discrepancy when a resource is described wrongly elsewhere. +// 4. Never describe an unmerged pull request as merged, and never claim an arXiv ID +// that does not exist. + +export type ResourceKind = + | 'paper' + | 'dataset' + | 'repo' + | 'channel' + | 'identity' + | 'service' + +export type ResourceStatus = 'live' | 'broken' | 'unverified' + +export interface Resource { + kind: ResourceKind + title: string + href: string + /** Stable identifier: arXiv ID, DOI, handle, ORCID. */ + id?: string + /** ISO date this row was last checked against the live resource. */ + verified: string + status: ResourceStatus + /** A discrepancy, a caveat, or what makes this row load-bearing. */ + note?: string +} + +export const CORPUS_VERIFIED = '2026-08-09' + +export const papers: Resource[] = [ + { + kind: 'paper', + title: + 'GoldenFloat: A Phi-Derived Static-Split Floating-Point Family from GF4 to GF1024 with a Lucas-Exact Integer Identity', + href: 'https://arxiv.org/abs/2606.05017', + id: 'arXiv:2606.05017v3', + verified: '2026-08-09', + status: 'live', + note: 'v3, 22 Jun 2026, 20 pp, cs.AR primary + cs.MS. v1 = 19 pp, v2 = 20 pp.', + }, + { + kind: 'paper', + title: + 'An 83-Format Numeric Catalog with Bit-Exact Conformance Vectors: A Vendor-Neutral Reference for FP8, BF16, MXFP4, and Microscaling Formats', + href: 'https://arxiv.org/abs/2606.09686', + id: 'arXiv:2606.09686v2', + verified: '2026-08-09', + status: 'live', + note: '83 formats, not 84. The 84-format title is v1 only and is historical.', + }, + { + kind: 'paper', + title: 'Cognitive framework', + href: 'https://arxiv.org/abs/2605.28405', + id: 'arXiv:2605.28405v1', + verified: '2026-07-28', + status: 'unverified', + note: 'v1, 27 May 2026, 32 pp, cs.AI. Not re-opened in the 2026-08-09 pass.', + }, +] + +export const datasets: Resource[] = [ + { + kind: 'dataset', + title: 'GoldenFloat: φ-Optimal Floating-Point Formats for Ternary Computing (T27)', + href: 'https://doi.org/10.5281/zenodo.19456875', + id: '10.5281/zenodo.19456875', + verified: '2026-08-09', + status: 'live', + note: + 'v0.1.0, 2026-04-07, 22 views / 9 downloads. The GitHub profile README calls this "t27 language spec" — WRONG, it is a GoldenFloat record. Also overlaps arXiv:2606.05017 with a different version number and no visible cross-link.', + }, + { + kind: 'dataset', + title: 'Trinity S³AI Framework — Complete Research Collection v5.0', + href: 'https://doi.org/10.5281/zenodo.19227879', + id: '10.5281/zenodo.19227879', + verified: '2026-08-09', + status: 'live', + note: + '2026-03-26, 54 views / 12 downloads. The GitHub profile README calls this "Trinity v9.0" — WRONG, the record says v5.0.', + }, + { + kind: 'dataset', + title: 'gHashTag/trinity: Trinity v2.0.2 — FPGA Autoregressive Ternary LLM', + href: 'https://doi.org/10.5281/zenodo.18947017', + id: '10.5281/zenodo.18947017', + verified: '2026-08-09', + status: 'live', + note: + '2026-03-11, 141 views / 12 downloads. Description matches the README. ✓ Concept DOI 10.5281/zenodo.18939351.', + }, + + // The B-series: seven records the GitHub profile never mentioned. Found via the + // related_identifiers of the v5.0 collection, not from any list we maintained. + { + kind: 'dataset', + title: 'Trinity B001: Ternary Neural Networks — Complete Scientific Framework', + href: 'https://doi.org/10.5281/zenodo.19227865', + id: '10.5281/zenodo.19227865', + verified: '2026-08-09', + status: 'live', + note: 'v5.0, 2026-03-26, 69 views.', + }, + { + kind: 'dataset', + title: 'Trinity B002: Zero-DSP FPGA Architecture for Ternary Inference', + href: 'https://doi.org/10.5281/zenodo.19227867', + id: '10.5281/zenodo.19227867', + verified: '2026-08-09', + status: 'live', + note: + 'v5.0, 2026-03-26, 54 views. Overlaps the unpublished FPL paper "Zero-DSP Ternary Transformer Inference on a $30 FPGA" — check for self-overlap before submitting.', + }, + { + kind: 'dataset', + title: 'Trinity B003: TRI-27 ISA — Ternary Instruction Set with Coptic Alphabet', + href: 'https://doi.org/10.5281/zenodo.19227869', + id: '10.5281/zenodo.19227869', + verified: '2026-08-09', + status: 'live', + note: + 'v5.0, 2026-03-26, 64 views. This — not 19456875 — is the record closest to "t27 language spec".', + }, + { + kind: 'dataset', + title: 'Trinity B004: Queen Lotus Cycle — Autonomous Orchestration', + href: 'https://doi.org/10.5281/zenodo.19227871', + id: '10.5281/zenodo.19227871', + verified: '2026-08-09', + status: 'live', + note: 'v5.0, 2026-03-26, 54 views.', + }, + { + kind: 'dataset', + title: 'Trinity B005: Tri Language — Linear Types, Effects, Dual Targets', + href: 'https://doi.org/10.5281/zenodo.19227873', + id: '10.5281/zenodo.19227873', + verified: '2026-08-09', + status: 'live', + note: 'v5.0, 2026-03-26, 49 views.', + }, + { + kind: 'dataset', + title: 'Trinity B006: Sacred GF16/TF3 — Phi-Based Arithmetic for Ternary Computing', + href: 'https://doi.org/10.5281/zenodo.19227875', + id: '10.5281/zenodo.19227875', + verified: '2026-08-09', + status: 'live', + note: + 'v5.0, 2026-03-26, 54 views. Overlaps the unpublished arXiv draft "Sacred GF16/TF3-9 Arithmetic on Artix-7".', + }, + { + kind: 'dataset', + title: 'Trinity B007: VSA Operations for Ternary Computing v5.0', + href: 'https://doi.org/10.5281/zenodo.19227877', + id: '10.5281/zenodo.19227877', + verified: '2026-08-09', + status: 'live', + note: + 'v5.0, 2026-03-26, **228 views — the most-viewed record in the corpus**, and it appears on no profile, README or CV.', + }, +] + +export const upstream: Resource[] = [ + { + kind: 'repo', + title: 'openXC7/nextpnr-xilinx #109 — set_multicycle_path -setup (XDC parser + timing)', + href: 'https://github.com/openXC7/nextpnr-xilinx/pull/109', + verified: '2026-08-09', + status: 'live', + note: 'OPEN, not merged.', + }, + { + kind: 'repo', + title: '#110 — raise clock-buffer preplace BFS cap so SRCC clock pins can route', + href: 'https://github.com/openXC7/nextpnr-xilinx/pull/110', + verified: '2026-08-09', + status: 'live', + note: 'OPEN, not merged.', + }, + { + kind: 'repo', + title: '#111 — preplace fabric-driven global buffers instead of aborting placement', + href: 'https://github.com/openXC7/nextpnr-xilinx/pull/111', + verified: '2026-08-09', + status: 'live', + note: 'OPEN, not merged.', + }, + { + kind: 'repo', + title: "#112 — don't emit a conflicting width bit for the unused port of an SDP BRAM", + href: 'https://github.com/openXC7/nextpnr-xilinx/pull/112', + verified: '2026-08-09', + status: 'live', + note: 'OPEN, not merged.', + }, + { + kind: 'repo', + title: '#113 — preplace the single-site configuration primitives (STARTUPE2, ICAPE2, ...)', + href: 'https://github.com/openXC7/nextpnr-xilinx/pull/113', + verified: '2026-08-09', + status: 'live', + note: 'OPEN, not merged.', + }, + { + kind: 'repo', + title: '#115 — initialise all four IFF flops for an IDDR, not just Q1/Q2', + href: 'https://github.com/openXC7/nextpnr-xilinx/pull/115', + verified: '2026-08-09', + status: 'live', + note: 'OPEN, not merged. The underlying IDDR capture issue stays open in the toolchain.', + }, +] + +export const channels: Resource[] = [ + { + kind: 'channel', + title: 'Habr — @raoffonom', + href: 'https://habr.com/ru/users/raoffonom/', + verified: '2026-08-09', + status: 'live', + note: + 'Strongest social channel: 12 articles, best one at 7.8K reach / 19 bookmarks / 20 comments. Two further posts were pulled — one flagged as machine-written, one at -8 votes.', + }, + { + kind: 'channel', + title: 'LinkedIn — /in/neurocoder', + href: 'https://www.linkedin.com/in/neurocoder/', + verified: '2026-08-09', + status: 'live', + note: '612 followers. Last post: 461 impressions, 1 reaction.', + }, + { + kind: 'channel', + title: 'X — @t27_dev', + href: 'https://x.com/t27_dev', + verified: '2026-08-09', + status: 'live', + note: + '118 followers, 145 posts, joined Apr 2019. Formerly @koshasuperstar — renamed, not a separate account.', + }, + { + kind: 'channel', + title: 'X — @t27_lang', + href: 'https://x.com/t27_lang', + verified: '2026-08-09', + status: 'unverified', + note: + 'This is the handle the site footer links. The GitHub README links @t27_dev instead. Pick one.', + }, + { + kind: 'channel', + title: 'Telegram — t.me/t27_lang (project channel)', + href: 'https://t.me/t27_lang', + verified: '2026-08-09', + status: 'unverified', + note: 'Subscriber count not measured.', + }, + { + kind: 'channel', + title: 'Telegram — t.me/t27_dev (personal account, NOT a channel)', + href: 'https://t.me/t27_dev', + verified: '2026-08-09', + status: 'live', + note: + 't.me/s/t27_dev returns only a bio. The GitHub README Telegram badge points here, so it leads to a DM box rather than a broadcast channel.', + }, + { + kind: 'channel', + title: 'Reddit — r/t27ai', + href: 'https://www.reddit.com/r/t27ai/', + verified: '2026-08-09', + status: 'live', + note: + 'PUBLIC as of 2026-08-09 (verified: subreddit_type "public", 1 subscriber). Was restricted from creation on 27 Mar 2026 until a review request was approved. Two posts, both by the owner, silent four months — it now needs content, not permissions.', + }, + { + kind: 'channel', + title: 'Telegram — t.me/t27_lang (project channel)', + href: 'https://t.me/t27_lang', + verified: '2026-08-09', + status: 'live', + note: + '18 subscribers. Best post 166 views — the Russian Knuth/Setun piece; PhD monograph post 91. Same pattern as Habr: culturally-anchored Russian framing outperforms English announcements.', + }, + { + kind: 'channel', + title: 'X — @trinity_cli', + href: 'https://twitter.com/trinity_cli', + verified: '2026-08-09', + status: 'unverified', + note: + 'A third X handle, found only in the r/t27ai sidebar. X is now split three ways: @t27_dev (118 followers, the real history), @t27_lang (site footer), @trinity_cli (subreddit).', + }, + { + kind: 'channel', + title: 'Zenodo community — trinity', + href: 'https://zenodo.org/communities/trinity', + verified: '2026-08-09', + status: 'unverified', + note: 'Linked only from the r/t27ai sidebar; absent from the profile, the site and the CV.', + }, + { + kind: 'channel', + title: 'Reddit — u/Open-Elderberry699', + href: 'https://www.reddit.com/user/Open-Elderberry699/', + verified: '2026-08-09', + status: 'live', + note: + 'Auto-generated username carrying real FPGA posts in r/FPGA and r/Zig. Reputation cannot accrue to a name nobody recognises.', + }, + { + kind: 'channel', + title: 'dev.to — @serverlesskiy', + href: 'https://dev.to/serverlesskiy', + verified: '2026-08-09', + status: 'broken', + note: + '/ghashtag redirects here. The profile still links twitter.com/koshasuperstar — a dead handle.', + }, + { + kind: 'channel', + title: 'Medium — @raoffonom', + href: 'https://medium.com/@raoffonom', + verified: '2026-08-09', + status: 'live', + note: '56 followers, 7 posts, dormant since Nov 2021.', + }, + { + kind: 'channel', + title: 'GitHub — gHashTag', + href: 'https://github.com/gHashTag', + verified: '2026-08-09', + status: 'live', + note: + '89 followers, 217 repositories total / 187 public. The profile README still says 210 total / 186 public / 86 followers.', + }, +] + +export const identities: Resource[] = [ + { + kind: 'identity', + title: 'ORCID 0009-0008-4294-6159', + href: 'https://orcid.org/0009-0008-4294-6159', + id: '0009-0008-4294-6159', + verified: '2026-07-28', + status: 'unverified', + }, + { + kind: 'identity', + title: 'arXiv author identifier', + href: 'https://arxiv.org/a/vasilev_d_1', + verified: '2026-08-09', + status: 'broken', + note: + 'Returns "Not Found". The three arXiv papers do not resolve to one author page, so nothing can link to a single publication list.', + }, + { + kind: 'identity', + title: 'Contact — admin@t27.ai', + href: 'mailto:admin@t27.ai', + verified: '2026-08-09', + status: 'live', + }, +] + +export const allResources = (): Resource[] => [ + ...papers, + ...datasets, + ...upstream, + ...channels, + ...identities, +] + +/** Rows a reader should treat with suspicion. Rendered first, on purpose. */ +export const discrepancies = () => + allResources().filter((r) => r.status === 'broken' || /WRONG|dead|does not|Pick one/i.test(r.note ?? '')) diff --git a/apps/website/src/main.tsx b/apps/website/src/main.tsx index ea2bc5b386..c16eb632d9 100644 --- a/apps/website/src/main.tsx +++ b/apps/website/src/main.tsx @@ -17,6 +17,8 @@ import GFT from './pages/GFT.tsx' import Licensing from './pages/Licensing.tsx' import Proof from './pages/Proof.tsx' import AboutAuthor from './pages/AboutAuthor.tsx' +import { BlogIndex, BlogPost } from './pages/Blog.tsx' +import Resources from './pages/Resources.tsx' import { I18nProvider } from './i18n/context.tsx' createRoot(document.getElementById('root')!).render( @@ -34,6 +36,9 @@ createRoot(document.getElementById('root')!).render( } /> } /> } /> + } /> + } /> + } /> } /> } /> } /> diff --git a/apps/website/src/pages/Blog.tsx b/apps/website/src/pages/Blog.tsx new file mode 100644 index 0000000000..e17660f959 --- /dev/null +++ b/apps/website/src/pages/Blog.tsx @@ -0,0 +1,260 @@ +import { Link, useParams } from 'react-router-dom' +import Navigation from '../components/Navigation' +import Footer from '../components/Footer' +import { publishedPosts, postBySlug, type Block, type Post } from '../data/blog/posts' + +const wrap: React.CSSProperties = { + maxWidth: '760px', + margin: '0 auto', + padding: '120px 24px 80px', +} + +const meta: React.CSSProperties = { + color: 'var(--text-dim, #8a8a8a)', + fontSize: '0.85rem', + letterSpacing: '0.04em', + textTransform: 'uppercase', +} + +const card: React.CSSProperties = { + display: 'block', + padding: '24px', + border: '1px solid var(--border, #2a2a2a)', + borderRadius: '10px', + marginBottom: '20px', + textDecoration: 'none', + color: 'inherit', +} + +function renderBlock(b: Block, i: number) { + switch (b.kind) { + case 'h': + return ( +

+ {b.text} +

+ ) + case 'p': + return ( +

+ {b.text} +

+ ) + case 'ul': + return ( +
    + {b.items.map((it, j) => ( +
  • + {it} +
  • + ))} +
+ ) + case 'ol': + return ( +
    + {b.items.map((it, j) => ( +
  1. + {it} +
  2. + ))} +
+ ) + case 'quote': + return ( +
+ {b.text} +
+ ) + case 'code': + return ( +
+          {b.text}
+        
+ ) + case 'table': + return ( +
+ + + + {b.head.map((h, j) => ( + + ))} + + + + {b.rows.map((r, j) => ( + + {r.map((c, k) => ( + + ))} + + ))} + +
+ {h} +
+ {c} +
+
+ ) + } +} + +/** Renders before the body on purpose: what is not proven comes first, not last. */ +function OpenQuestions({ post }: { post: Post }) { + if (!post.openQuestions.length) return null + return ( +
+
Not proven / still open
+
    + {post.openQuestions.map((q, i) => ( +
  • + {q} +
  • + ))} +
+
+ ) +} + +function Receipts({ post }: { post: Post }) { + if (!post.receipts.length) return null + return ( +
+
Receipts
+ +
+ ) +} + +export function BlogIndex() { + const items = publishedPosts() + return ( +
+ +
+

Blog

+

+ Measured results and the methods behind them. Published here first; every other channel + links back to this page. +

+ + {items.length === 0 ? ( +

+ No posts published yet. Drafts exist but still have gaps — a post goes live when every + number in it can be traced to an artefact. +

+ ) : ( + items.map((p) => ( + +
+ {p.date} · {p.readingMinutes} min · {p.tags.join(' · ')} +
+

{p.title}

+

+ {p.summary} +

+ + )) + )} +
+
+
+ ) +} + +export function BlogPost() { + const { slug } = useParams<{ slug: string }>() + const post = slug ? postBySlug(slug) : undefined + + if (!post || !post.published) { + return ( +
+ +
+

Not published

+

+ This post either does not exist or is still a draft.{' '} + Back to the blog. +

+
+
+
+ ) + } + + return ( +
+ +
+
+ {post.date} · {post.readingMinutes} min · {post.tags.join(' · ')} +
+

{post.title}

+

+ {post.summary} +

+ + + {post.body.map(renderBlock)} + + +

+ ← All posts +

+
+
+
+ ) +} + +export default BlogIndex diff --git a/apps/website/src/pages/Resources.tsx b/apps/website/src/pages/Resources.tsx new file mode 100644 index 0000000000..80d077d715 --- /dev/null +++ b/apps/website/src/pages/Resources.tsx @@ -0,0 +1,172 @@ +import Navigation from '../components/Navigation' +import Footer from '../components/Footer' +import { + papers, + datasets, + upstream, + channels, + identities, + discrepancies, + CORPUS_VERIFIED, + type Resource, +} from '../data/resources' + +const wrap: React.CSSProperties = { + maxWidth: '900px', + margin: '0 auto', + padding: '120px 24px 80px', +} + +const dim = 'var(--text-dim, #8a8a8a)' +const border = 'var(--border, #2a2a2a)' +const accent = 'var(--accent, #d4af37)' + +const badge = (status: Resource['status']): React.CSSProperties => ({ + display: 'inline-block', + fontSize: '0.68rem', + letterSpacing: '0.06em', + textTransform: 'uppercase', + padding: '2px 7px', + borderRadius: '4px', + border: `1px solid ${status === 'broken' ? '#c0392b' : status === 'live' ? accent : border}`, + color: status === 'broken' ? '#e06055' : status === 'live' ? accent : dim, + whiteSpace: 'nowrap', +}) + +function Table({ title, rows, id }: { title: string; rows: Resource[]; id: string }) { + return ( +
+

{title}

+
+ + + + {['Resource', 'Identifier', 'Checked', 'State'].map((h) => ( + + ))} + + + + {rows.map((r) => ( + + + + + + + ))} + +
+ {h} +
+ + {r.title} + + {r.note && ( +
+ {r.note} +
+ )} +
+ {r.id ?? '—'} + + {r.verified} + + {r.status} +
+
+
+ ) +} + +export default function Resources() { + const bad = discrepancies() + return ( +
+ +
+

Resources

+

+ The canonical list of every public resource in this corpus — papers, DOIs, upstream + patches, channels, identities. Anything stated elsewhere (GitHub profile, LinkedIn, + slide decks) is reconciled against this page, not the other way round. +

+

+ Each row carries the date it was last checked against the live resource. A row is not + re-dated unless someone actually re-opened it. Corpus last swept:{' '} + {CORPUS_VERIFIED}. +

+ + {bad.length > 0 && ( +
+
+ Known discrepancies — {bad.length} +
+

+ Published here rather than quietly fixed, so that anyone who found the wrong + version elsewhere can see which one is right. +

+
    + {bad.map((r) => ( +
  • + + {r.title} + + {r.note && — {r.note}} +
  • + ))} +
+
+ )} + + +
+
+
+
+ +
+ + ) +}