diff --git a/apps/website/public/term/index.html b/apps/website/public/term/index.html index 8954843c0f..2b457d3a2b 100644 --- a/apps/website/public/term/index.html +++ b/apps/website/public/term/index.html @@ -44,7 +44,7 @@
Trinity S³AI

Recordings

-

Terminal sessions recorded from real commands: the output is what the commands printed. 30 so far.

+

Terminal sessions recorded from real commands: the output is what the commands printed. 32 so far.

tri x7-part · x7-refs · x7-bench: the references the bitstream path is checked on2026-10-04 04:52 UTC · 7.3 s real
tri fpga-tools · 14 bench tools test themselves2026-10-04 04:52 UTC · 5.8 s real
@@ -54,6 +54,8 @@

Recordings

tri fpga-batch-rehearsal · the batched wire protocol, rehearsed off-board2026-10-04 04:52 UTC · 4.7 s real
tri x7-mutate · plant a bug, the tests must catch it2026-10-04 04:48 UTC · 81.6 s real
tri x7-audit · the whole bitstream path, re-checked2026-10-04 03:38 UTC · 208.3 s real
+
Place and route to a loaded bitstream, timed2026-10-03 19:54 UTC · 22.6 s real
+
Three PERF2 bits, and one knockout bitstream per bit2026-10-03 19:53 UTC · 26.2 s real
tri x7-fasm · a bitstream assembled without Vivado2026-10-03 19:49 UTC · 21.7 s real
tri fpga-wire · the baud rate actually on the wire2026-10-03 19:49 UTC · 6.5 s real
tri fpga-txhold · RGMII margins from nextpnr's own SDF2026-10-03 19:49 UTC · 4.7 s real
diff --git a/apps/website/public/term/perf2-knockouts/card.png b/apps/website/public/term/perf2-knockouts/card.png new file mode 100644 index 0000000000..7f9ce24c62 Binary files /dev/null and b/apps/website/public/term/perf2-knockouts/card.png differ diff --git a/apps/website/public/term/perf2-knockouts/index.html b/apps/website/public/term/perf2-knockouts/index.html new file mode 100644 index 0000000000..a9fa74b137 --- /dev/null +++ b/apps/website/public/term/perf2-knockouts/index.html @@ -0,0 +1,95 @@ + + + + + +Three PERF2 bits, and one knockout bitstream per bit · Trinity S³AI + + + + + + + + + + + + + + + + + + + + +
+ +

Three PERF2 bits, and one knockout bitstream per bit

+

The A/B bitstreams differ in three bits. Each knockout clears one: 3 bytes from the working .bit.

+
+
recorded 2026-10-03 19:53 UTCreal 26.2 sshown 15.9 sexit codes 0 0
+
$ python3 perf2.py diff bufr-old.frames bufr-new.frames +$ python3 perf2.py knockout bufr-new.frames --out rec
+

Staged: the prompt and the typing. Real: every byte the commands printed, at the time they printed it. Any silence longer than 2 s is shown for 2 s, and the title bar says so while it happens. Edited: home directory shown as ~.

+ +
+ + + diff --git a/apps/website/public/term/perf2-knockouts/meta.json b/apps/website/public/term/perf2-knockouts/meta.json new file mode 100644 index 0000000000..f816405f8a --- /dev/null +++ b/apps/website/public/term/perf2-knockouts/meta.json @@ -0,0 +1,23 @@ +{ + "id": "perf2-knockouts", + "title": "Three PERF2 bits, and one knockout bitstream per bit", + "desc": "The A/B bitstreams differ in three bits. Each knockout clears one: 3 bytes from the working .bit.", + "url": "https://t27.ai/term/perf2-knockouts/", + "gif": "https://t27.ai/term/perf2-knockouts/session.gif", + "post": "https://t27.ai/#/blog/three-bits-each-necessary", + "recorded": "2026-10-03 19:53 UTC", + "real_s": 26.2, + "shown_s": 15.9, + "commands": [ + "python3 perf2.py diff bufr-old.frames bufr-new.frames", + "python3 perf2.py knockout bufr-new.frames --out rec" + ], + "exit_codes": [ + "0", + "0" + ], + "redacted": [ + "home directory shown as ~" + ], + "published": "2026-10-03" +} diff --git a/apps/website/public/term/perf2-knockouts/session.cast b/apps/website/public/term/perf2-knockouts/session.cast new file mode 100644 index 0000000000..70bb098d91 --- /dev/null +++ b/apps/website/public/term/perf2-knockouts/session.cast @@ -0,0 +1,131 @@ +{"version": 2, "width": 104, "height": 30, "timestamp": 1791057239, "title": "Three PERF2 bits: which ones, and one knockout bitstream per bit", "commands": ["python3 perf2.py diff bufr-old.frames bufr-new.frames", "python3 perf2.py knockout bufr-new.frames --out rec"], "redacted": ["home directory shown as ~"]} +[0.0001, "o", "\u001b[38;2;255;215;0mboard $ \u001b[0m"] +[0.4101, "o", "p"] +[0.4552, "o", "y"] +[0.4935, "o", "t"] +[0.5337, "o", "h"] +[0.5705, "o", "o"] +[0.6059, "o", "n"] +[0.6437, "o", "3"] +[0.6889, "o", " "] +[0.7285, "o", "p"] +[0.7696, "o", "e"] +[0.8108, "o", "r"] +[0.8471, "o", "f"] +[0.8926, "o", "2"] +[0.9305, "o", "."] +[0.9696, "o", "p"] +[1.0097, "o", "y"] +[1.0466, "o", " "] +[1.09, "o", "d"] +[1.1359, "o", "i"] +[1.1718, "o", "f"] +[1.2162, "o", "f"] +[1.2612, "o", " "] +[1.3021, "o", "b"] +[1.3442, "o", "u"] +[1.3887, "o", "f"] +[1.4239, "o", "r"] +[1.4683, "o", "-"] +[1.5137, "o", "o"] +[1.5536, "o", "l"] +[1.5972, "o", "d"] +[1.6371, "o", "."] +[1.686, "o", "f"] +[1.7268, "o", "r"] +[1.7702, "o", "a"] +[1.8153, "o", "m"] +[1.8568, "o", "e"] +[1.8963, "o", "s"] +[1.9354, "o", " "] +[1.9806, "o", "b"] +[2.0263, "o", "u"] +[2.0682, "o", "f"] +[2.1071, "o", "r"] +[2.1487, "o", "-"] +[2.1885, "o", "n"] +[2.2394, "o", "e"] +[2.2784, "o", "w"] +[2.3236, "o", "."] +[2.3596, "o", "f"] +[2.4071, "o", "r"] +[2.4582, "o", "a"] +[2.4942, "o", "m"] +[2.5393, "o", "e"] +[2.5812, "o", "s"] +[2.9287, "o", "\r\n"] +[10.3484, "o", "bufr-old.frames -> bufr-new.frames: 3 bit(s) differ, frame ECC aside\r\n"] +[10.3781, "o", "CMT_TOP_R_LOWER_B_X8Y217\r\n"] +[10.3937, "o", " 28_994 0 -> 1 rows using it: c030ed6 CLK_PERF2.{CLKOUT2,CLKOUT3}\r\n master none\r\n"] +[10.3944, "o", " 29_1022 0 -> 1 rows using it: c030ed6 CLK_PERF2.{CLKFBOUT,CLKOUT0,CLKOUT1,CLKOUT2,CLKOUT3}\r\n master none\r\nHCLK_CMT_X8Y234\r\n"] +[10.4074, "o", " 28_180 0 -> 1 rows using it: c030ed6 PERFCLK2.{MUXED2,MUXED3}\r\n master none\r\n"] +[10.5448, "x", "0"] +[11.5525, "o", "\u001b[38;2;255;215;0mboard $ \u001b[0m"] +[12.0129, "o", "p"] +[12.0829, "o", "y"] +[12.1199, "o", "t"] +[12.168, "o", "h"] +[12.2249, "o", "o"] +[12.2633, "o", "n"] +[12.3251, "o", "3"] +[12.3746, "o", " "] +[12.4185, "o", "p"] +[12.4633, "o", "e"] +[12.5089, "o", "r"] +[12.5579, "o", "f"] +[12.5933, "o", "2"] +[12.6418, "o", "."] +[12.6884, "o", "p"] +[12.7322, "o", "y"] +[12.7918, "o", " "] +[12.8343, "o", "k"] +[12.8755, "o", "n"] +[12.9257, "o", "o"] +[12.9724, "o", "c"] +[13.009, "o", "k"] +[13.0481, "o", "o"] +[13.0835, "o", "u"] +[13.1302, "o", "t"] +[13.1687, "o", " "] +[13.208, "o", "b"] +[13.2464, "o", "u"] +[13.2831, "o", "f"] +[13.3195, "o", "r"] +[13.3663, "o", "-"] +[13.4064, "o", "n"] +[13.4442, "o", "e"] +[13.4908, "o", "w"] +[13.5373, "o", "."] +[13.5741, "o", "f"] +[13.6169, "o", "r"] +[13.6582, "o", "a"] +[13.7006, "o", "m"] +[13.7433, "o", "e"] +[13.7841, "o", "s"] +[13.8276, "o", " "] +[13.9087, "o", "-"] +[13.9597, "o", "-"] +[14.0158, "o", "o"] +[14.0726, "o", "u"] +[14.1191, "o", "t"] +[14.1685, "o", " "] +[14.2135, "o", "r"] +[14.2697, "o", "e"] +[14.3138, "o", "c"] +[14.6649, "o", "\r\n"] +[21.5975, "o", "\r\n== CMT_TOP_R_LOWER_B_X8Y217 28_994: frame 0x0002009C word 31 bit 2, 1 -> 0\r\n"] +[21.6137, "o", " rec/ko-28_994.frames == ko-28_994.frames (the file flashed on 2026-10-04)\r\n$ xc7frames2bit --part_file db-new/artix7/xc7a200tfbg484-2/part.yaml --part_name xc7a200tfbg484-2 --frm_file rec/ko-28_994.frames --output_file rec/ko-28_994.bit\r\n"] +[22.2006, "o", " rc=0 0.59 s\r\n"] +[22.7853, "o", " past the sync word, rec/ko-28_994.bit differs from bufr-new.bit in 3 bytes\r\n"] +[22.8778, "o", "\r\n== CMT_TOP_R_LOWER_B_X8Y217 29_1022: frame 0x0002009D word 31 bit 30, 1 -> 0\r\n"] +[22.8885, "o", " rec/ko-29_1022.frames == ko-29_1022.frames (the file flashed on 2026-10-04)\r\n"] +[22.8885, "o", "$ xc7frames2bit --part_file db-new/artix7/xc7a200tfbg484-2/part.yaml --part_name xc7a200tfbg484-2 --frm_file rec/ko-29_1022.frames --output_file rec/ko-29_1022.bit\r\n"] +[23.7913, "o", " rc=0 0.90 s\r\n"] +[24.1817, "o", " past the sync word, rec/ko-29_1022.bit differs from bufr-new.bit in 3 bytes\r\n"] +[24.2635, "o", "\r\n== HCLK_CMT_X8Y234 28_180: frame 0x0002009C word 50 bit 20, 1 -> 0"] +[24.2643, "o", "\r\n"] +[24.28, "o", " rec/ko-28_180.frames == ko-28_180.frames (the file flashed on 2026-10-04)\r\n$ xc7frames2bit --part_file db-new/artix7/xc7a200tfbg484-2/part.yaml --part_name xc7a200tfbg484-2 --frm_file rec/ko-28_180.frames --output_file rec/ko-28_180.bit\r\n"] +[24.8309, "o", " rc=0 0.55 s\r\n"] +[25.1509, "o", " past the sync word, rec/ko-28_180.bit differs from bufr-new.bit in 3 bytes\r\n"] +[25.2216, "x", "0"] +[26.2266, "o", "\u001b[38;2;255;215;0mboard $ \u001b[0m"] diff --git a/apps/website/public/term/perf2-knockouts/session.gif b/apps/website/public/term/perf2-knockouts/session.gif new file mode 100644 index 0000000000..2060e9ff9c Binary files /dev/null and b/apps/website/public/term/perf2-knockouts/session.gif differ diff --git a/apps/website/public/term/perf2-loop/card.png b/apps/website/public/term/perf2-loop/card.png new file mode 100644 index 0000000000..e5b715174f Binary files /dev/null and b/apps/website/public/term/perf2-loop/card.png differ diff --git a/apps/website/public/term/perf2-loop/index.html b/apps/website/public/term/perf2-loop/index.html new file mode 100644 index 0000000000..8b298891b0 --- /dev/null +++ b/apps/website/public/term/perf2-loop/index.html @@ -0,0 +1,94 @@ + + + + + +Place and route to a loaded bitstream, timed · Trinity S³AI + + + + + + + + + + + + + + + + + + + + +
+ +

Place and route to a loaded bitstream, timed

+

router1, bitwalk, xc7frames2bit, SRAM load: 19.06 s here, on a laptop at load average ~110.

+
+
recorded 2026-10-03 19:54 UTCreal 22.6 sshown 18.6 sexit codes 0
+
$ python3 perf2.py loop --out rec --flash
+

Staged: the prompt and the typing. Real: every byte the commands printed, at the time they printed it. Any silence longer than 2 s is shown for 2 s, and the title bar says so while it happens. Edited: home directory shown as ~.

+ +
+ + + diff --git a/apps/website/public/term/perf2-loop/meta.json b/apps/website/public/term/perf2-loop/meta.json new file mode 100644 index 0000000000..ff44aacfa9 --- /dev/null +++ b/apps/website/public/term/perf2-loop/meta.json @@ -0,0 +1,21 @@ +{ + "id": "perf2-loop", + "title": "Place and route to a loaded bitstream, timed", + "desc": "router1, bitwalk, xc7frames2bit, SRAM load: 19.06 s here, on a laptop at load average ~110.", + "url": "https://t27.ai/term/perf2-loop/", + "gif": "https://t27.ai/term/perf2-loop/session.gif", + "post": "https://t27.ai/#/blog/three-bits-each-necessary", + "recorded": "2026-10-03 19:54 UTC", + "real_s": 22.6, + "shown_s": 18.6, + "commands": [ + "python3 perf2.py loop --out rec --flash" + ], + "exit_codes": [ + "0" + ], + "redacted": [ + "home directory shown as ~" + ], + "published": "2026-10-03" +} diff --git a/apps/website/public/term/perf2-loop/session.cast b/apps/website/public/term/perf2-loop/session.cast new file mode 100644 index 0000000000..80aa064627 --- /dev/null +++ b/apps/website/public/term/perf2-loop/session.cast @@ -0,0 +1,73 @@ +{"version": 2, "width": 104, "height": 30, "timestamp": 1791057298, "title": "Place and route to a loaded bitstream: the CLK_PERF2 probe, timed", "commands": ["python3 perf2.py loop --out rec --flash"], "redacted": ["home directory shown as ~"]} +[0.0001, "o", "\u001b[38;2;255;215;0mboard $ \u001b[0m"] +[0.4034, "o", "p"] +[0.4485, "o", "y"] +[0.4876, "o", "t"] +[0.5232, "o", "h"] +[0.5635, "o", "o"] +[0.6075, "o", "n"] +[0.6437, "o", "3"] +[0.6881, "o", " "] +[0.727, "o", "p"] +[0.7716, "o", "e"] +[0.8074, "o", "r"] +[0.8445, "o", "f"] +[0.8899, "o", "2"] +[0.9349, "o", "."] +[0.9757, "o", "p"] +[1.011, "o", "y"] +[1.0547, "o", " "] +[1.0979, "o", "l"] +[1.1369, "o", "o"] +[1.1726, "o", "o"] +[1.2096, "o", "p"] +[1.2521, "o", " "] +[1.297, "o", "-"] +[1.3357, "o", "-"] +[1.3782, "o", "o"] +[1.4151, "o", "u"] +[1.4552, "o", "t"] +[1.4948, "o", " "] +[1.5339, "o", "r"] +[1.569, "o", "e"] +[1.6104, "o", "c"] +[1.6496, "o", " "] +[1.6873, "o", "-"] +[1.7225, "o", "-"] +[1.7608, "o", "f"] +[1.7987, "o", "l"] +[1.8424, "o", "a"] +[1.8883, "o", "s"] +[1.926, "o", "h"] +[2.2717, "o", "\r\n"] +[2.46, "o", "NEXTPNR_PIP_BLACKLIST=perf2-only.blacklist (forces CLK_PERF2 <- CLKOUT3, PERFCLK2 <- MUXED2)\r\n$ ./nextpnr-xilinx.bufrclk --chipdb ~/openxc7-src/chipdb/xc7a200tfbg484-2.bin --xdc perf_probe_classic.xdc --json perf_probe_adv.json --fasm rec/r1.fasm --router router1 > rec/r1.log 2>&1\r\n"] +[8.4128, "o", " rc=0 5.95 s\r\n"] +[8.4129, "o", " Info: Router1 time 0.78s\r\n"] +[8.4133, "o", " HCLK_IOI3_X1Y234.HCLK_IOI_CK_BUFRCLK2.HCLK_IOI_RCLK2RCLK2\r\n HCLK_L_X15Y234.HCLK_LEAF_CLK_B_TOPL0.HCLK_CK_BUFRCLK2\r\n"] +[8.4137, "o", " CMT_TOP_R_LOWER_B_X8Y217.CMT_R_LOWER_B_CLK_PERF2.CMT_LR_LOWER_B_MMCM_CLKOUT3\r\n HCLK_CMT_X8Y234.HCLK_CMT_MUX_PHSR_PERFCLK2.HCLK_CMT_MUX_MMCM_MUXED2\r\n"] +[8.4139, "o", " HCLK_L_X15Y234.ENABLE_BUFFER.HCLK_CK_BUFRCLK2\r\n HCLK_CMT_X8Y234.HCLK_CMT_CK_BUFRCLK2_USED\r\n HCLK_CMT_L_X256Y234.HCLK_CMT_CK_BUFRCLK2_USED\r\n"] +[8.4147, "o", "$ ~/t27-xilinx7/specs/xilinx7/bitwalk --fasm ~/.cache/x7/fasm/db-artix7-xc7a200tfbg484-2-db-new-v2.txt rec/r1.fasm rec/r1.frames\r\n"] +[9.3643, "o", "rec/r1.frames: 20230 frames, 3848 bits, required 0, stepdown 96, ppip lines 370, outside the tile 0 (wrapped below the frame 0, dropped above it 0), own bits outside the frame 0 (db 394.4 ms, total 933.5 ms)\r\n"] +[9.553, "o", " rc=0 1.14 s\r\n$ xc7frames2bit --part_file db-new/artix7/xc7a200tfbg484-2/part.yaml --part_name xc7a200tfbg484-2 --frm_file rec/r1.frames --output_file rec/r1.bit\r\n"] +[11.6681, "o", " rc=0 2.11 s\r\n"] +[11.7356, "o", " past the sync word, rec/r1.bit == r1-new.bit (the build flashed on 2026-10-04)\r\n$ openFPGALoader -c digilent_hs2 --freq 30000000 rec/r1.bit\r\n"] +[12.1365, "o", "empty\r\n"] +[12.7243, "o", "\u001b[94mJtag frequency : requested 30.00MHz -> real 30.00MHz \u001b[0m\r\n"] +[12.8394, "o", "\u001b[94mOpen file \u001b[0m"] +[12.8888, "o", "\u001b[32mDONE\u001b[0m\r\n\u001b[94mParse file \u001b[0m"] +[13.2887, "o", "\u001b[32mDONE\u001b[0m"] +[13.2896, "o", "\r\n"] +[13.2896, "o", "load program\r\n"] +[14.4714, "o", "\u001b[94m\rLoad SRAM: [\u001b[0m= \u001b[94m] 2.00%\u001b[0m"] +[15.5252, "o", "\u001b[94m\rLoad SRAM: [\u001b[0m===== \u001b[94m] 9.00%\u001b[0m"] +[16.5248, "o", "\u001b[94m\rLoad SRAM: [\u001b[0m=========== \u001b[94m] 21.00%\u001b[0m"] +[17.5621, "o", "\u001b[94m\rLoad SRAM: [\u001b[0m================= \u001b[94m] 34.00%\u001b[0m"] +[18.619, "o", "\u001b[94m\rLoad SRAM: [\u001b[0m=========================== \u001b[94m] 54.00%\u001b[0m"] +[19.6717, "o", "\u001b[94m\rLoad SRAM: [\u001b[0m================================== \u001b[94m] 67.00%\u001b[0m"] +[20.6862, "o", "\u001b[94m\rLoad SRAM: [\u001b[0m=========================================== \u001b[94m] 85.00%\u001b[0m"] +[21.5533, "o", "\u001b[94m\rLoad SRAM: [\u001b[0m==================================================\u001b[94m] 100.00%\u001b[0m\u001b[32m\r\nDone\u001b[0m\r\n"] +[21.5583, "o", "Shift IR 35\r\nir: 1 isc_done 1 isc_ena 0 init 1 done 1\r\n"] +[21.5878, "o", " rc=0 9.85 s\r\n"] +[21.5884, "o", "\r\n place and route 5.95 s\r\n FASM to frames 1.14 s\r\n frames to bitstream 2.11 s\r\n flash to SRAM 9.85 s\r\n total 19.06 s\r\n"] +[21.6344, "x", "0"] +[22.6465, "o", "\u001b[38;2;255;215;0mboard $ \u001b[0m"] diff --git a/apps/website/public/term/perf2-loop/session.gif b/apps/website/public/term/perf2-loop/session.gif new file mode 100644 index 0000000000..d5e5e43db7 Binary files /dev/null and b/apps/website/public/term/perf2-loop/session.gif differ diff --git a/apps/website/src/data/blog/bodies/three-bits-each-necessary.ts b/apps/website/src/data/blog/bodies/three-bits-each-necessary.ts new file mode 100644 index 0000000000..966230a482 --- /dev/null +++ b/apps/website/src/data/blog/bodies/three-bits-each-necessary.ts @@ -0,0 +1,383 @@ +import type { Block } from '../types' +import { PERF2_KNOCKOUTS, PERF2_LOOP } from '../../casts' + +// Every number here is from one session on 2026-10-04 with one ALINX AX7203 board. +// The LEDs were read by eye by the maintainer; the bitstreams and logs are local. + +export const body: Block[] = [ + { + kind: 'h', + text: 'What was measured', + }, + { + kind: 'p', + text: "An MMCM in a Xilinx 7-series clock-management tile can hand an output to the regional clock buffers through a performance-clock path. The path is two muxes: one in the CMT tile (`CLK_PERF0` to `CLK_PERF3`) and one in the HCLK_CMT tile above it (`PERFCLK0` to `PERFCLK3`). Project X-Ray's database has rows that say which configuration bits select each input. For the path used here, the rows on prjxray-db master are short. `CLK_PERF2.CLKOUT3` is `29_994` alone, and `PERFCLK2.MUXED2` is `28_176 29_133`. The rows we proposed in openXC7/prjxray-db#30, at commit `c030ed6`, add `28_994` and `29_1022` to the first and `28_180` to the second.", + }, + { + kind: 'p', + text: "Those rows came from a population. cavearr ran a campaign of 1083 Vivado specimens, and we re-derived its numbers with our own bit reader before writing the rows. A population says which bits move together, but it does not say a design runs. This post is the board half.", + }, + { + kind: 'h', + text: 'The probe', + }, + { + kind: 'p', + text: "The board is an ALINX AX7203 (`xc7a200tfbg484-2`). The 200 MHz board clock goes through IBUFDS and BUFG into an MMCM. The VCO runs at 1000 MHz and `CLKOUT3_DIVIDE = 10`, so `CLKOUT3` is 100 MHz. `CLKOUT3` goes through `CLK_PERF2` and `PERFCLK2 <- MUXED2` into a BUFR in bypass mode, and the BUFR clocks a 27-bit counter. A 28-bit counter on the BUFG is the reference. The top bit of each counter drives an LED, so both LEDs should toggle every 1.34 s, since 2^27 / 100 MHz = 2^28 / 200 MHz. A third LED blinks fast while the MMCM reports `LOCKED`.", + }, + { + kind: 'p', + text: "The counter sits in ordinary fabric, not in the I/O column, so the clock has to leave the BUFR through the horizontal clock spine. An earlier post used the same trick for its one-bit A/B, and this probe also depends on the `ENABLE_BUFFER` bit that post verified.", + }, + { + kind: 'h', + text: 'The A/B: one route, two databases, three bits', + }, + { + kind: 'p', + text: "One place-and-route run produced one FASM file, and `fasm2frames` assembled it twice. The first time was against prjxray-db master `517d66a`, and the second against the same checkout with the eight files of `c030ed6` copied in. Past the sync word, the two bitstreams differ in exactly three bits, and all three are PERF bits: `28_994` and `29_1022` in the CMT tile, and `28_180` in the HCLK_CMT tile.", + }, + { + kind: 'table', + head: ['rows', 'reference LED', 'BUFR counter LED', 'LOCKED'], + rows: [ + ['master `517d66a`', 'blinks, ~1.3 s', 'steady', 'locked'], + ['`c030ed6`', 'blinks, ~1.3 s', 'blinks in step with the reference', 'locked'], + ], + }, + { + kind: 'p', + text: "The LEDs on this board are active-low, so \"steady\" means a counter frozen at its reset value. With the master rows, the MMCM locks and the reference runs, but no clock reaches the BUFR.", + }, + { + kind: 'h', + text: 'Knockouts: each bit is necessary', + }, + { + kind: 'p', + text: "The counter runs with all three bits set, but that does not show each one is needed, since one could be a passenger. So we took the working frames, cleared exactly one of the three bits, and wrote a bitstream for each, with no new place-and-route. Past the sync word, each knockout differs from the working bitstream in three bytes: the bit itself and the frame's ECC word.", + }, + { + kind: 'table', + head: ['bit cleared', 'row it belongs to', 'BUFR counter LED'], + rows: [ + ['none (`c030ed6` rows)', '', 'blinks in step with the reference'], + ['`28_994`', '`CLK_PERF2.CLKOUT3`', 'steady'], + ['`29_1022`', '`CLK_PERF2.CLKOUT3`', 'steady'], + ['`28_180`', '`PERFCLK2.MUXED2` (used bit)', 'steady'], + ['none, re-flashed after the knockouts', '', 'blinks in step with the reference'], + ], + }, + { + kind: 'p', + text: "Clearing any one bit stops the counter, and the working bitstream still ran when flashed again afterwards, so the board did not change state in between. `28_180` is a bit that our own earlier version of the rows left out. It is the HCLK \"used\" bit, `(26+p)_180` for `PERFCLKp`. We found it was missing only when our check started counting every bit in a fixed set, instead of only the bits that some row mentioned.", + }, + { + kind: 'terminal', + src: PERF2_KNOCKOUTS.src, + share: PERF2_KNOCKOUTS.share, + title: PERF2_KNOCKOUTS.title, + caption: PERF2_KNOCKOUTS.caption.en, + }, + { + kind: 'h', + text: 'The decode agrees with the board', + }, + { + kind: 'p', + text: "The database is read in two directions: `fasm2frames` encodes features into bits, and `bit2fasm` decodes bits back into features. We decoded both bitstreams with both databases and kept the PERF features of the two tiles.", + }, + { + kind: 'table', + head: ['bitstream', 'decoded with master', 'decoded with `c030ed6`'], + rows: [ + ['master rows (counter dead)', '`CLK_PERF2.CLKOUT3`, `PERFCLK2.MUXED2`', 'none'], + ['`c030ed6` rows (counter running)', '`CLK_PERF2.CLKOUT3`, `PERFCLK2.MUXED2`', '`CLK_PERF2.CLKOUT3`, `PERFCLK2.MUXED2`'], + ], + }, + { + kind: 'p', + text: "With the master rows, the dead bitstream decodes as if the path were configured. Each master row is a subset of the real one, and all of its bits are present. A decoder built on those rows therefore reports a working clock path that the silicon does not have. With `c030ed6`, the decode matches the board in both cases.", + }, + { + kind: 'h', + text: 'What the open toolchain ships today', + }, + { + kind: 'p', + text: "prjxray-db#30 is open. nextpnr-xilinx main pins prjxray-db at `6b8695e`, which is cavearr's prjxray-db#13, merged on 7 September. At that commit, `CLK_PERF2.CLKOUT3` is `29_994` and `PERFCLK2.MUXED2` is `28_176 29_133`, the same rows as master `517d66a`, which left the counter dead here. We read the rows at that commit. We did not build nextpnr-xilinx main or run the probe through it.", + }, + { + kind: 'h', + text: 'How the build was set up, and what was stale', + }, + { + kind: 'p', + text: "The router did not choose this path. Left alone, nextpnr routes `CLKOUT3` through `CLK_PERF0`, which has no rows on master at all. A pip blacklist removed the alternatives, so the route had to take `CLK_PERF2 <- CLKOUT3` and `PERFCLK2 <- MUXED2`.", + }, + { + kind: 'p', + text: "We placed and routed with classic nextpnr-xilinx. The local himbaechel chip database for the xc7a200t had been generated from a db without the PERF rows, so those pips had no configuration bits and were dropped.", + }, + { + kind: 'p', + text: "The classic binary was stale. It was built from `b608fd2c`, which is 115 commits behind openXC7/nextpnr-xilinx main. We found that out only after the first board runs, through two symptoms:", + }, + { + kind: 'ul', + items: [ + "**The MMCM.** With `MMCME2_BASE`, it never locked, because the FASM had no `ZINV_RST` or `ZINV_PWRDWN`. AssassinK786 fixed this in nextpnr-xilinx#191, merged on 9 September. We instantiated `MMCME2_ADV` instead, which writes both bits.", + "**The BUFR's clock enable.** The counter was dead with both row sets, because the regional clock enable in the HCLK tile, `00_31`, was never written. That is our own nextpnr-xilinx#205, merged on 24 September and missing from this build. We applied it locally. Routing stayed identical, the FASM gained three lines, and both A/B bitstreams carry them.", + ], + }, + { + kind: 'p', + text: "The lesson we wrote down is to check `git log HEAD..origin/main` before blaming a tool. An earlier draft of this report said classic nextpnr-xilinx had never received a fix that it had in fact received, and the fix was ours.", + }, + { + kind: 'h', + text: 'A faster loop', + }, + { + kind: 'p', + text: "Each knockout needs a flash and a look at the LEDs, so the time from an edit to the board matters. At the start, one cycle took about 104–111 s. By the end, it took about 19–28 s. Synthesis is not in the table, because it was not timed here.", + }, + { + kind: 'table', + head: ['step', 'before', 'after', 'what changed'], + rows: [ + ['place and route', '49 s', '13.2 s', '`--router router1` instead of router2'], + ['FASM to frames', '37–39 s', '0.43–1.72 s', 'bitwalk, the t27-spec assembler, instead of `fasm2frames`'], + ['frames to bitstream', '0.8 s', '0.8 s', 'unchanged (`xc7frames2bit`)'], + ['flash to SRAM', '17.5–22.6 s', '4.5–12.2 s', '`openFPGALoader --freq 30000000`'], + ['**total**', '**~104–111 s**', '**~19–28 s**', ''], + ], + }, + { + kind: 'p', + text: "Router2's log says it spent 38.1 s, but the per-net times it reports add up to about 1.5 s, so most of that time went to something other than routing nets. Router1 finished routing in 2.53 s. The two routes differ in 225 lines of FASM but take the same PERF path. We flashed the router1 bitstream, and the two LEDs blink together. Both routes report a maximum frequency above 270 MHz on both clocks, against the default 12 MHz target, so timing was never under pressure.", + }, + { + kind: 'p', + text: "bitwalk wrote frames byte-identical to `fasm2frames` for both databases. The knockouts skipped place-and-route entirely: a frame edit, frames to bitstream, then a flash, which takes about 6–14 s. At 30 MHz, the flash took anywhere from 4.5 to 12.2 s for bitstreams of the same size, and we do not know why.", + }, + { + kind: 'p', + text: "One more cycle was recorded after the board work. Its place-and-route took 5.95 s, not 13.2 s, and its frames-to-bitstream step took 2.11 s, not 0.8 s. Other jobs kept the laptop at a load average of about 110 the whole time, so a single timing here moves by that much, and neither number is a better estimate than the other.", + }, + { + kind: 'terminal', + src: PERF2_LOOP.src, + share: PERF2_LOOP.share, + title: PERF2_LOOP.title, + caption: PERF2_LOOP.caption.en, + }, + { + kind: 'p', + text: "The previous post said nothing there pointed to a faster place-and-route. This is not one either, in the sense that post meant: it is a router flag, measured on a design with a few hundred wires. We did not measure whether router1 holds up on the 121,587-line design from that post, which takes 70.1 s to place and route.", + }, + { + kind: 'h', + text: 'What this does not show', + }, + { + kind: 'ul', + items: [ + "**Sufficiency.** On this path the three bits are enough, together with the bits nextpnr already wrote. The knockouts show only that each one is necessary here.", + "**Any other path.** That includes the other `CLK_PERF` muxes, the other MMCM outputs, the other `PERFCLK`s, the left side of the die, and other parts and families.", + "**The frequency beyond what the eye sees.** The two LEDs stay in step over a few periods, and there is no counter readback.", + "**himbaechel nextpnr.** Its chip database would have to be regenerated from a db with the PERF rows.", + "**The full round trip.** The decode was compared on the PERF features of two tiles only.", + "**Published artefacts.** The two recordings show the commands and their real output. The script they run, the bitstreams, the frames and the logs are on one laptop and are not published.", + ], + }, +] + +export const ruBody: Block[] = [ + { + kind: 'h', + text: 'Что измерено', + }, + { + kind: 'p', + text: "MMCM в тайле управления тактами Xilinx 7-series может отдать выход региональным тактовым буферам через тракт performance clock. Тракт состоит из двух мультиплексоров: один в тайле CMT (`CLK_PERF0`–`CLK_PERF3`), второй в тайле HCLK_CMT над ним (`PERFCLK0`–`PERFCLK3`). В базе Project X-Ray есть строки, которые говорят, какие конфигурационные биты выбирают каждый вход. Для тракта из этого поста строки на master prjxray-db короткие. `CLK_PERF2.CLKOUT3` — это один `29_994`, а `PERFCLK2.MUXED2` — `28_176 29_133`. Строки, которые мы предложили в openXC7/prjxray-db#30 на коммите `c030ed6`, добавляют `28_994` и `29_1022` к первой и `28_180` ко второй.", + }, + { + kind: 'p', + text: "Эти строки выведены из популяции. cavearr провёл кампанию из 1083 образцов Vivado, и прежде чем писать строки, мы заново получили его числа своим считывателем битов. Популяция говорит, какие биты меняются вместе, но не говорит, что дизайн работает. Этот пост — половина с платой.", + }, + { + kind: 'h', + text: 'Пробник', + }, + { + kind: 'p', + text: "Плата — ALINX AX7203 (`xc7a200tfbg484-2`). Тактовый сигнал платы 200 МГц идёт через IBUFDS и BUFG в MMCM. VCO работает на 1000 МГц, `CLKOUT3_DIVIDE = 10`, так что `CLKOUT3` — 100 МГц. `CLKOUT3` идёт через `CLK_PERF2` и `PERFCLK2 <- MUXED2` в BUFR в режиме bypass, а BUFR тактирует 27-битный счётчик. Опорный сигнал — 28-битный счётчик на BUFG. Старший бит каждого счётчика зажигает светодиод, так что оба должны переключаться каждые 1,34 с: 2^27 / 100 МГц = 2^28 / 200 МГц. Третий светодиод часто мигает, пока MMCM выдаёт `LOCKED`.", + }, + { + kind: 'p', + text: "Счётчик стоит в обычной логике, а не в столбце ввода-вывода, поэтому такт должен выйти из BUFR через горизонтальный тактовый хребет. Тот же приём использовался для однобитного A/B в одном из прошлых постов, и этот пробник тоже зависит от бита `ENABLE_BUFFER`, который тот пост проверил.", + }, + { + kind: 'h', + text: 'A/B: одна разводка, две базы, три бита', + }, + { + kind: 'p', + text: "Один прогон размещения и разводки дал один FASM-файл, и `fasm2frames` собрал его дважды. Первый раз против master prjxray-db `517d66a`, второй — против того же checkout с восемью файлами `c030ed6`, скопированными поверх. После sync word два битстрима различаются ровно в трёх битах, и все три относятся к PERF: `28_994` и `29_1022` в тайле CMT и `28_180` в тайле HCLK_CMT.", + }, + { + kind: 'table', + head: ['строки', 'опорный светодиод', 'светодиод счётчика BUFR', 'LOCKED'], + rows: [ + ['master `517d66a`', 'мигает, ~1,3 с', 'не меняется', 'есть'], + ['`c030ed6`', 'мигает, ~1,3 с', 'мигает в такт с опорным', 'есть'], + ], + }, + { + kind: 'p', + text: "Светодиоды на этой плате активны низким уровнем, так что «не меняется» значит, что счётчик застыл в начальном значении. Со строками master MMCM захватывает частоту и опорный счётчик идёт, но до BUFR такт не доходит.", + }, + { + kind: 'h', + text: 'Нокауты: нужен каждый бит', + }, + { + kind: 'p', + text: "Со всеми тремя битами счётчик идёт, но это не показывает, что нужен каждый: один может оказаться попутчиком. Поэтому мы взяли рабочие кадры, сбросили ровно один из трёх битов и для каждого случая записали битстрим, без новой разводки. После sync word каждый нокаут отличается от рабочего битстрима в трёх байтах: сам бит и ECC-слово кадра.", + }, + { + kind: 'table', + head: ['сброшенный бит', 'чья это строка', 'светодиод счётчика BUFR'], + rows: [ + ['никакой (строки `c030ed6`)', '', 'мигает в такт с опорным'], + ['`28_994`', '`CLK_PERF2.CLKOUT3`', 'не меняется'], + ['`29_1022`', '`CLK_PERF2.CLKOUT3`', 'не меняется'], + ['`28_180`', '`PERFCLK2.MUXED2` (бит used)', 'не меняется'], + ['никакой, повторная прошивка после нокаутов', '', 'мигает в такт с опорным'], + ], + }, + { + kind: 'p', + text: "Сброс любого одного бита останавливает счётчик, а рабочий битстрим, прошитый повторно после нокаутов, снова работает, так что состояние платы между прошивками не менялось. `28_180` — бит, который наша собственная ранняя версия строк пропустила. Это бит «used» тайла HCLK, `(26+p)_180` для `PERFCLKp`. Мы заметили, что его нет, только когда проверка начала считать каждый бит из фиксированного набора, а не только биты, которые упоминает какая-нибудь строка.", + }, + { + kind: 'terminal', + src: PERF2_KNOCKOUTS.src, + share: PERF2_KNOCKOUTS.share, + title: PERF2_KNOCKOUTS.title, + caption: PERF2_KNOCKOUTS.caption.ru, + }, + { + kind: 'h', + text: 'Декодирование сходится с платой', + }, + { + kind: 'p', + text: "Базу читают в двух направлениях: `fasm2frames` кодирует фичи в биты, а `bit2fasm` декодирует биты обратно в фичи. Мы декодировали оба битстрима обеими базами и оставили PERF-фичи двух тайлов.", + }, + { + kind: 'table', + head: ['битстрим', 'декодирован с master', 'декодирован с `c030ed6`'], + rows: [ + ['строки master (счётчик стоит)', '`CLK_PERF2.CLKOUT3`, `PERFCLK2.MUXED2`', 'ничего'], + ['строки `c030ed6` (счётчик идёт)', '`CLK_PERF2.CLKOUT3`, `PERFCLK2.MUXED2`', '`CLK_PERF2.CLKOUT3`, `PERFCLK2.MUXED2`'], + ], + }, + { + kind: 'p', + text: "Со строками master мёртвый битстрим декодируется так, будто тракт настроен. Каждая строка master — подмножество настоящей, и все её биты на месте. Поэтому декодер на этих строках сообщает о рабочем тактовом тракте, которого в кремнии нет. С `c030ed6` декодирование совпадает с платой в обоих случаях.", + }, + { + kind: 'h', + text: 'Что открытый тулчейн поставляет сегодня', + }, + { + kind: 'p', + text: "prjxray-db#30 открыт. main nextpnr-xilinx фиксирует prjxray-db на `6b8695e` — это prjxray-db#13 от cavearr, влитый 7 сентября. На этом коммите `CLK_PERF2.CLKOUT3` — это `29_994`, а `PERFCLK2.MUXED2` — `28_176 29_133`: те же строки, что на master `517d66a`, с которыми счётчик здесь стоял. Мы прочитали строки на этом коммите. Сам main nextpnr-xilinx мы не собирали и пробник через него не прогоняли.", + }, + { + kind: 'h', + text: 'Как была устроена сборка и что в ней устарело', + }, + { + kind: 'p', + text: "Этот тракт выбрал не роутер. Если ничего не делать, nextpnr ведёт `CLKOUT3` через `CLK_PERF0`, для которого на master строк нет вообще. Чёрный список pip убрал альтернативы, так что разводке пришлось пройти через `CLK_PERF2 <- CLKOUT3` и `PERFCLK2 <- MUXED2`.", + }, + { + kind: 'p', + text: "Размещали и разводили классическим nextpnr-xilinx. Локальная chip database himbaechel для xc7a200t была сгенерирована из базы без PERF-строк, поэтому у этих pip не было конфигурационных битов, и они выпадали.", + }, + { + kind: 'p', + text: "Классический бинарник был устаревшим: собран из `b608fd2c`, на 115 коммитов позади main openXC7/nextpnr-xilinx. Мы узнали это только после первых прогонов на плате, по двум симптомам:", + }, + { + kind: 'ul', + items: [ + "**MMCM.** С `MMCME2_BASE` он не захватывал частоту, потому что в FASM не было `ZINV_RST` и `ZINV_PWRDWN`. AssassinK786 исправил это в nextpnr-xilinx#191, влитом 9 сентября. Мы поставили вместо него `MMCME2_ADV`, который пишет оба бита.", + "**Разрешение такта BUFR.** Счётчик стоял с обоими наборами строк, потому что разрешение регионального такта в тайле HCLK, `00_31`, не записывалось. Это наш собственный nextpnr-xilinx#205, влитый 24 сентября, которого в этой сборке не было. Мы применили его локально. Разводка не изменилась, в FASM добавились три строки, и они есть в обоих битстримах A/B.", + ], + }, + { + kind: 'p', + text: "Урок, который мы записали: прежде чем винить инструмент, проверить `git log HEAD..origin/main`. Ранний черновик этого отчёта утверждал, что классический nextpnr-xilinx так и не получил исправление, которое он на самом деле получил, и исправление было нашим.", + }, + { + kind: 'h', + text: 'Цикл стал быстрее', + }, + { + kind: 'p', + text: "Каждый нокаут требует прошивки и взгляда на светодиоды, так что время от правки до платы важно. В начале один цикл занимал около 104–111 с. К концу — около 19–28 с. Синтеза в таблице нет: здесь его не засекали.", + }, + { + kind: 'table', + head: ['шаг', 'до', 'после', 'что изменилось'], + rows: [ + ['размещение и разводка', '49 с', '13,2 с', '`--router router1` вместо router2'], + ['FASM в кадры', '37–39 с', '0,43–1,72 с', 'bitwalk, ассемблер из t27-спеков, вместо `fasm2frames`'], + ['кадры в битстрим', '0,8 с', '0,8 с', 'без изменений (`xc7frames2bit`)'], + ['прошивка в SRAM', '17,5–22,6 с', '4,5–12,2 с', '`openFPGALoader --freq 30000000`'], + ['**итого**', '**~104–111 с**', '**~19–28 с**', ''], + ], + }, + { + kind: 'p', + text: "По логу router2 потратил 38,1 с, но времена по отдельным цепям, которые он сообщает, в сумме дают около 1,5 с, так что большая часть времени ушла не на разводку цепей. Router1 закончил разводку за 2,53 с. Две разводки различаются на 225 строк FASM, но идут через один и тот же PERF-тракт. Битстрим от router1 мы прошили, и два светодиода мигают вместе. Обе разводки показывают максимальную частоту выше 270 МГц на обоих тактах при целевых 12 МГц по умолчанию, так что тайминг ни разу не был под давлением.", + }, + { + kind: 'p', + text: "bitwalk записал кадры, байт в байт совпадающие с `fasm2frames`, для обеих баз. Нокауты вовсе обходились без разводки: правка кадров, кадры в битстрим, прошивка — около 6–14 с. На 30 МГц прошивка занимала от 4,5 до 12,2 с для битстримов одного размера, и мы не знаем почему.", + }, + { + kind: 'p', + text: "После работы с платой записан ещё один цикл. Размещение и разводка в нём заняли 5,95 с, а не 13,2 с, а шаг из кадров в битстрим — 2,11 с, а не 0,8 с. Другие задачи всё это время держали ноутбук на load average около 110, так что отдельный замер здесь гуляет на столько, и ни одно из чисел не точнее другого.", + }, + { + kind: 'terminal', + src: PERF2_LOOP.src, + share: PERF2_LOOP.share, + title: PERF2_LOOP.title, + caption: PERF2_LOOP.caption.ru, + }, + { + kind: 'p', + text: "Прошлый пост говорил, что ничто в нём не указывает путь к более быстрой разводке. Это тоже не он, в том смысле, который имел в виду тот пост: это флаг роутера, измеренный на дизайне из нескольких сотен проводов. Выдержит ли router1 дизайн из того поста на 121 587 строк, где размещение и разводка занимают 70,1 с, мы не измеряли.", + }, + { + kind: 'h', + text: 'Чего это не показывает', + }, + { + kind: 'ul', + items: [ + "**Достаточность.** На этом тракте трёх битов хватает вместе с битами, которые nextpnr уже записал. Нокауты показывают только, что здесь нужен каждый из них.", + "**Любой другой тракт.** Сюда входят другие мультиплексоры `CLK_PERF`, другие выходы MMCM, другие `PERFCLK`, левая сторона кристалла, другие кристаллы и семейства.", + "**Частоту точнее, чем видит глаз.** Два светодиода идут в такт несколько периодов, а считывания счётчика нет.", + "**nextpnr himbaechel.** Его chip database пришлось бы пересобрать из базы с PERF-строками.", + "**Полный круг.** Декодирование сравнивалось только по PERF-фичам двух тайлов.", + "**Опубликованные артефакты.** Две записи показывают команды и их настоящий вывод. Скрипт, который они запускают, битстримы, кадры и логи лежат на одном ноутбуке и не опубликованы.", + ], + }, +] diff --git a/apps/website/src/data/blog/index.ts b/apps/website/src/data/blog/index.ts index f60204ceff..23e48e6508 100644 --- a/apps/website/src/data/blog/index.ts +++ b/apps/website/src/data/blog/index.ts @@ -2,6 +2,42 @@ import type { PostMeta } from './types' /** Индекс блога: список и метаданные без тяжёлых тел публикаций. */ export const postsIndex: PostMeta[] = [ + { + slug: "three-bits-each-necessary", + title: "Three bits, each one necessary: the MMCM's performance clock on silicon", + summary: "[one clock path on one AX7203 board; LEDs read by eye, no counter readback; built with a nextpnr-xilinx 115 commits behind main, with one merged fix applied locally; the faster loop was timed on one small design] An MMCM output can reach a regional clock buffer through a performance-clock path, and two rows in Project X-Ray's database say which bits switch that path on. With the rows on prjxray-db master, a counter clocked through it does not run on an XC7A200T. With the rows we proposed in openXC7/prjxray-db#30, it runs, and the two bitstreams differ in exactly three bits. Clearing any one of the three stops the counter again, so each is necessary. nextpnr-xilinx main still pins a database with the old rows. Along the way, the edit-to-board loop went from about 104–111 s to about 19–28 s, mostly from a router flag, the t27-spec assembler and a faster JTAG clock.", + date: "2026-10-04", + readingMinutes: 6, + tags: ["FPGA", "Open toolchain", "Verification"], + receipts: [ + { label: "openXC7/prjxray-db#30: the CLK_PERF and PERFCLK rows, c030ed6 (open)", href: "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/openXC7/prjxray-db/pull/30" }, + { label: "openXC7/nextpnr#24: where the rows are discussed", href: "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/openXC7/nextpnr/issues/24" }, + { label: "The verification scripts behind the rows", href: "https://gist.github.com/gHashTag/3c9216880b77946385dfc5bea317d927" }, + { label: "cavearr's perfclk campaign, 1083 Vivado specimens", href: "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/cavearr/prjxray/releases/tag/perfclk-campaign-2026-10" }, + { label: "openXC7/prjxray-db#13: the rows nextpnr-xilinx main pins · MERGED 2026-09-07", href: "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/openXC7/prjxray-db/pull/13" }, + { label: "openXC7/nextpnr-xilinx#191: the MMCM RST/PWRDWN fix · MERGED 2026-09-09", href: "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/openXC7/nextpnr-xilinx/pull/191" }, + { label: "openXC7/nextpnr-xilinx#205: the BUFR regional clock enables · MERGED 2026-09-24", href: "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/openXC7/nextpnr-xilinx/pull/205" }, + { label: "Two bitstreams, one bit apart (the earlier one-bit A/B)", href: "https://t27.ai/#/blog/two-bitstreams-one-bit-apart" }, + { label: "The FPGA flow, layer by layer (the previous timing post)", href: "https://t27.ai/#/blog/the-fpga-flow-layer-by-layer" }, + { label: "The recording: the three bits and the knockout bitstreams", href: "https://t27.ai/term/perf2-knockouts/" }, + { label: "The recording: one cycle from place-and-route to SRAM, timed", href: "https://t27.ai/term/perf2-loop/" }, + ], + openQuestions: [ + "One clock path on one board, with LEDs read by eye. The knockouts show that each bit is necessary here, not that the three are sufficient on other paths.", + "nextpnr-xilinx main pins rows that left this counter dead. Whether main routes a design through this path at all was not tested.", + "router1 was timed on a design with a few hundred wires. On a large design it may be slower than router2 or fail to route.", + ], + published: true, + ru: { + title: "Три бита, и каждый нужен: performance clock у MMCM на кремнии", + summary: "[один тактовый тракт на одной плате AX7203; светодиоды читались глазом, считывания счётчика нет; сборка nextpnr-xilinx на 115 коммитов позади main, с одним влитым исправлением, применённым локально; быстрый цикл засекался на одном маленьком дизайне] Выход MMCM может дойти до регионального тактового буфера через тракт performance clock, и две строки в базе Project X-Ray говорят, какие биты этот тракт включают. Со строками master prjxray-db счётчик, тактируемый через него, на XC7A200T не идёт. Со строками, которые мы предложили в openXC7/prjxray-db#30, идёт, а два битстрима различаются ровно в трёх битах. Сброс любого одного из трёх снова останавливает счётчик, так что нужен каждый. main nextpnr-xilinx всё ещё фиксирует базу со старыми строками. Попутно цикл от правки до платы сократился с примерно 104–111 с до примерно 19–28 с, в основном за счёт флага роутера, ассемблера из t27-спеков и более быстрого такта JTAG.", + openQuestions: [ + "Один тактовый тракт на одной плате, светодиоды читались глазом. Нокауты показывают, что здесь нужен каждый бит, а не что трёх хватит на других трактах.", + "main nextpnr-xilinx фиксирует строки, с которыми этот счётчик стоял. Разводит ли main дизайн через этот тракт вообще, не проверялось.", + "router1 засекался на дизайне из нескольких сотен проводов. На большом дизайне он может оказаться медленнее router2 или не развести его.", + ], + }, + }, { slug: "tri-cast-from-command-to-card", title: "tri cast: record a command, check the recording, publish it as a page", diff --git a/apps/website/src/data/blog/posts.ts b/apps/website/src/data/blog/posts.ts index 9ea1005f04..f43c9b3831 100644 --- a/apps/website/src/data/blog/posts.ts +++ b/apps/website/src/data/blog/posts.ts @@ -1,3 +1,4 @@ +import { body as body_three_bits_each_necessary, ruBody as ruBody_three_bits_each_necessary } from './bodies/three-bits-each-necessary' import { body as body_tri_cast_from_command_to_card, ruBody as ruBody_tri_cast_from_command_to_card } from './bodies/tri-cast-from-command-to-card' import { body as body_the_fpga_flow_layer_by_layer, ruBody as ruBody_the_fpga_flow_layer_by_layer } from './bodies/the-fpga-flow-layer-by-layer' import { body as body_the_back_half_of_openxc7_from_a_spec, ruBody as ruBody_the_back_half_of_openxc7_from_a_spec } from './bodies/the-back-half-of-openxc7-from-a-spec' @@ -85,6 +86,7 @@ import { body as body_real_value_in_integer_container, ruBody as ruBody_real_val import { body as body_one_commit_nine_workflow_outcomes, ruBody as ruBody_one_commit_nine_workflow_outcomes } from './bodies/one-commit-nine-workflow-outcomes' const bodies: Record = { + 'three-bits-each-necessary': { body: body_three_bits_each_necessary, ruBody: ruBody_three_bits_each_necessary }, 'tri-cast-from-command-to-card': { body: body_tri_cast_from_command_to_card, ruBody: ruBody_tri_cast_from_command_to_card }, 'the-fpga-flow-layer-by-layer': { body: body_the_fpga_flow_layer_by_layer, ruBody: ruBody_the_fpga_flow_layer_by_layer }, 'the-back-half-of-openxc7-from-a-spec': { body: body_the_back_half_of_openxc7_from_a_spec, ruBody: ruBody_the_back_half_of_openxc7_from_a_spec }, diff --git a/apps/website/src/data/casts.ts b/apps/website/src/data/casts.ts index 975b60e0f9..e1801c2546 100644 --- a/apps/website/src/data/casts.ts +++ b/apps/website/src/data/casts.ts @@ -27,3 +27,23 @@ export const DEVKIT_FLOW: Cast = { ru: 'Записанный прогон: `tri devkit flow --build` и `tri devkit impact` на одном дизайне XC7A200T, 2026-10-03. Каждый напечатанный байт настоящий и появляется тогда, когда появился; приглашение и набор команд постановочные, паузы длиннее 2 с сокращены, и пока это происходит, в заголовке окна стоит пометка. Сама запись на английском.', }, } + +export const PERF2_KNOCKOUTS: Cast = { + src: 'term/perf2-knockouts/session.cast', + share: 'https://t27.ai/term/perf2-knockouts/', + title: 'perf2.py diff · knockout · the three CLK_PERF2 bits', + caption: { + en: 'The recorded run, 2026-10-04 at 02:53 UTC+7, after the board runs: `perf2.py diff` names the three bits that separate the two A/B bitstreams, and no master row uses any of them. `perf2.py knockout` rebuilds the three knockout frames files, each byte-identical to the one that was flashed, and each knockout .bit differs from the working one in 3 bytes past the sync word. The LEDs are not in the recording. Every byte printed is real; the prompt and the typing are staged, and silences over 2 s are shortened.', + ru: 'Записанный прогон, 2026-10-04 в 02:53 UTC+7, после прогонов на плате: `perf2.py diff` называет три бита, которыми различаются два битстрима A/B, и ни одна строка master не использует ни одного из них. `perf2.py knockout` заново собирает три файла кадров для нокаутов, каждый байт в байт равен прошитому, и каждый .bit нокаута отличается от рабочего в 3 байтах после sync word. Светодиодов в записи нет. Каждый напечатанный байт настоящий; приглашение и набор команд постановочные, паузы длиннее 2 с сокращены. Сама запись на английском.', + }, +} + +export const PERF2_LOOP: Cast = { + src: 'term/perf2-loop/session.cast', + share: 'https://t27.ai/term/perf2-loop/', + title: 'perf2.py loop --flash · place and route to SRAM, timed', + caption: { + en: 'The recorded run, 2026-10-04 at 02:54 UTC+7: `perf2.py loop --flash` places and routes the probe with router1, assembles it with bitwalk and `xc7frames2bit`, checks that the result is identical past the sync word to the bitstream that ran on the board, and loads it into SRAM. In this recording: place and route 5.95 s (router1 reports 0.78 s of it), FASM to frames 1.14 s, frames to bitstream 2.11 s, SRAM load 9.85 s, 19.06 s in total, with other jobs holding the laptop at a load average of about 110 on 8 cores. Synthesis is not in it. Every byte printed is real; the prompt and the typing are staged, and silences over 2 s are shortened.', + ru: 'Записанный прогон, 2026-10-04 в 02:54 UTC+7: `perf2.py loop --flash` размещает и разводит пробник через router1, собирает его bitwalk и `xc7frames2bit`, проверяет, что результат после sync word совпадает с битстримом, который работал на плате, и загружает его в SRAM. В этой записи: размещение и разводка 5,95 с (из них router1 сообщает 0,78 с), FASM в кадры 1,14 с, кадры в битстрим 2,11 с, загрузка в SRAM 9,85 с, всего 19,06 с, при этом другие задачи держали ноутбук на load average около 110 на 8 ядрах. Синтеза в ней нет. Каждый напечатанный байт настоящий; приглашение и набор команд постановочные, паузы длиннее 2 с сокращены. Сама запись на английском.', + }, +}