diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 097c364b8a..71d17565fe 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,8 @@ blank_issues_enabled: true contact_links: - - name: Documentation - url: https://ghashtag.github.io/trinity/docs/ - about: Read the documentation before opening an issue - - name: Cloud Dev Workflow - url: https://github.com/gHashTag/trinity/blob/main/CLAUDE.md#default-development-workflow - about: Every issue spawns an agent container automatically + - name: License an arithmetic core + url: https://t27.ai/ip + about: Cores that have already been through silicon — GF-T, GF16 matmul, BPSK modem. + - name: The course + url: https://t27.ai/course + about: Train a neural network on an FPGA, on a fully open toolchain. diff --git a/.github/ISSUE_TEMPLATE/verification-request.yml b/.github/ISSUE_TEMPLATE/verification-request.yml new file mode 100644 index 0000000000..1d590d032c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/verification-request.yml @@ -0,0 +1,76 @@ +name: Verification request +description: Have your RTL checked on real hardware. The first module is free. +title: "[verify] " +labels: ["verification-request"] +body: + - type: markdown + attributes: + value: | + Opening this issue starts the run. Nothing else is needed from you — no + email thread, no scheduling. A bot picks it up, checks your design and + posts the report back into this issue, usually within the hour for the + automated part. + + Everything the bot does is public and reproducible. If the automated + stage finds something, you will see exactly which check failed and how to + re-run it yourself. + + - type: input + id: repo + attributes: + label: Where is the RTL? + description: A public repository, or a branch and path inside one. Private work needs an NDA first — say so below and skip this. + placeholder: https://github.com/you/your-design + validations: + required: false + + - type: input + id: top + attributes: + label: Top module name + placeholder: my_mac_unit + validations: + required: true + + - type: textarea + id: spec + attributes: + label: What does it do, and what does "correct" mean for it? + description: | + This is the important field. The reference model is written from *this* + description rather than from your code — that is the whole point, since a + model derived from your RTL would agree with your RTL's bugs. + An algorithm, a paper, a table of expected outputs, or plain prose all work. + placeholder: | + 8x8 signed multiply-accumulate. acc <= acc + A*B, two's complement, + 32-bit accumulator, overflow wraps. Opcodes on uio_in[2:0]: ... + validations: + required: true + + - type: input + id: target + attributes: + label: Target device and frequency, if any + placeholder: SKY130 via Tiny Tapeout / 50 MHz + validations: + required: false + + - type: input + id: deadline + attributes: + label: Deadline + description: Shuttle dates are real deadlines. Say if you have one. + placeholder: TTSKY26c freeze + validations: + required: false + + - type: dropdown + id: publish + attributes: + label: May the report be published as a public example? + description: Either answer is fine. The report is yours regardless. + options: + - "Yes — publish it" + - "No — keep it in this issue only" + validations: + required: true diff --git a/.github/workflows/verify-request.yml b/.github/workflows/verify-request.yml new file mode 100644 index 0000000000..2c1647cbaa --- /dev/null +++ b/.github/workflows/verify-request.yml @@ -0,0 +1,193 @@ +name: Verification request + +# Runs the automated stage of a verification request and posts the result back +# into the issue, so a request needs no correspondence to get started. +# +# What this can decide on its own: whether the design elaborates, whether it +# synthesises, whether it infers latches, what it costs in cells, and — where the +# requester supplied a testbench — whether that passes. Those are the checks that +# do not need a human, and they are also the ones that most often fail. +# +# What it deliberately does not do: write the independent reference model. That is +# the part with the value in it, it has to be derived from the description of what +# the design should do rather than from the design, and no bot can do it. The +# comment says so plainly rather than implying the whole report is automatic. + +on: + issues: + types: [opened, labeled] + +permissions: + contents: read + issues: write + +jobs: + triage: + if: contains(github.event.issue.labels.*.name, 'verification-request') + runs-on: ubuntu-latest + timeout-minutes: 25 + + steps: + - name: Acknowledge, so nobody is left wondering + uses: actions/github-script@v7 + with: + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: [ + '**Picked up.** The automated stage is running now: elaboration, synthesis,', + 'a latch check and a cell count, plus your own testbench if the repository', + 'has one. Results land in this issue in a few minutes.', + '', + 'The part that follows is the part that matters and is not automated —', + 'an independent reference model written from your description of what the', + 'design should do, never from your RTL. A model derived from your code', + 'would agree with your code, including where it is wrong.', + ].join('\n') + }) + + - name: Read the request + id: parse + uses: actions/github-script@v7 + with: + script: | + const body = context.payload.issue.body || '' + // Issue-form bodies are "### Label\n\nvalue" blocks. + const field = (label) => { + const m = body.match(new RegExp('### ' + label + '\\s*\\n+([\\s\\S]*?)(?=\\n### |$)')) + const v = m ? m[1].trim() : '' + return v === '_No response_' ? '' : v + } + const repo = field('Where is the RTL\\?') + const top = field('Top module name') + core.setOutput('repo', repo) + core.setOutput('top', top) + // Only clone what is plainly a public GitHub URL. Anything else is + // left for a human rather than guessed at. + const ok = /^https:\/\/github\.com\/[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+\/?$/.test(repo) + core.setOutput('clonable', ok ? 'yes' : 'no') + + - name: Install the open toolchain + if: steps.parse.outputs.clonable == 'yes' + run: | + set -euo pipefail + sudo apt-get update -qq + sudo apt-get install -y -qq yosys iverilog + yosys -V + iverilog -V | head -1 + + - name: Fetch the design + if: steps.parse.outputs.clonable == 'yes' + env: + DESIGN_REPO: ${{ steps.parse.outputs.repo }} + run: | + set -euo pipefail + # Passed through the environment rather than interpolated into the + # shell, so an issue body cannot inject a command. + git clone --depth 1 "$DESIGN_REPO" design + find design -name '*.v' -o -name '*.sv' | head -40 + + - name: Check it + if: steps.parse.outputs.clonable == 'yes' + id: check + continue-on-error: true + env: + TOP: ${{ steps.parse.outputs.top }} + run: | + set -uo pipefail + SRC=$(find design -name '*.v' -not -name '*_tb.v' -not -path '*/test/*' | tr '\n' ' ') + if [ -z "$SRC" ]; then + echo "verdict=no Verilog found" >> "$GITHUB_OUTPUT" + exit 0 + fi + echo "### Sources" > report.md + echo '```' >> report.md + echo "$SRC" | tr ' ' '\n' | sed '/^$/d' >> report.md + echo '```' >> report.md + + echo "" >> report.md + echo "### Elaboration" >> report.md + if iverilog -g2012 -o /dev/null $SRC 2> elab.txt; then + echo "PASS — elaborates cleanly." >> report.md + else + echo "FAIL:" >> report.md + echo '```' >> report.md; head -25 elab.txt >> report.md; echo '```' >> report.md + fi + + echo "" >> report.md + echo "### Latch check" >> report.md + if yosys -q -p "read_verilog $SRC; hierarchy -top $TOP; proc; opt; select -assert-none t:\$_DLATCH_* t:\$dlatch t:\$_DLATCHSR_*" 2> latch.txt; then + echo "PASS — no latches inferred." >> report.md + else + echo "FAIL — a latch was inferred. This is the classic bug that only shows up on silicon:" >> report.md + echo '```' >> report.md; head -20 latch.txt >> report.md; echo '```' >> report.md + fi + + echo "" >> report.md + echo "### Synthesis, generic mapping" >> report.md + if yosys -q -p "read_verilog $SRC; synth -top $TOP; stat" > stat.txt 2>&1; then + echo '```' >> report.md + sed -n '/Printing statistics/,/^$/p' stat.txt | head -30 >> report.md + echo '```' >> report.md + else + echo "FAIL:" >> report.md + echo '```' >> report.md; tail -20 stat.txt >> report.md; echo '```' >> report.md + fi + + echo "" >> report.md + echo "### Registers" >> report.md + POSEDGE=$(grep -rc "posedge" $SRC 2>/dev/null | awk -F: '{s+=$2} END {print s+0}') + if [ "$POSEDGE" = "0" ]; then + echo "This design holds no registers, so it has no clock domain and no achieved frequency can belong to it. For a combinational block the comparable figure is propagation delay, not a clock rate." >> report.md + else + echo "Sequential design — $POSEDGE clocked blocks. Achieved frequency is measurable and comes in the hardware stage." >> report.md + fi + + - name: Post the result + if: always() + uses: actions/github-script@v7 + env: + CLONABLE: ${{ steps.parse.outputs.clonable }} + with: + script: | + const fs = require('fs') + let body + if (process.env.CLONABLE !== 'yes') { + body = [ + '**No public repository given, so the automated stage was skipped.**', + '', + 'That is not a problem — it just means the run starts by hand. If the', + 'sources are public, edit the issue with a plain', + '`https://github.com/owner/repo` URL and the checks will run on their own.', + 'If the work is private, say so and an NDA comes first.', + ].join('\n') + } else { + let r = '' + try { r = fs.readFileSync('report.md', 'utf8') } catch (e) { r = '_The automated stage produced no report; picking this up by hand._' } + body = [ + '## Automated stage', + '', + r, + '', + '---', + '', + '**What is still to come, and why it is not automated.** The checks above', + 'tell you whether the design builds and whether it is structurally sound.', + 'They cannot tell you whether it computes the right answer. That needs a', + 'reference model written from your description of the operation rather than', + 'from your RTL, known-answer vectors at each pipeline stage, and a replay', + 'on the board — because agreement in simulation does not prove agreement', + 'on silicon.', + '', + 'Toolchain: `yosys`, `iverilog`. Every command above is in', + '`.github/workflows/verify-request.yml`, so you can re-run all of it.', + ].join('\n') + } + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body, + }) diff --git a/apps/website/src/pages/HardwareVerification.tsx b/apps/website/src/pages/HardwareVerification.tsx index 0c966b088e..5710bc845b 100644 --- a/apps/website/src/pages/HardwareVerification.tsx +++ b/apps/website/src/pages/HardwareVerification.tsx @@ -7,6 +7,8 @@ import Navigation from '../components/Navigation' import Footer from '../components/Footer' import QuantumBackground from '../components/QuantumBackground' +const REQUEST_URL = 'https://github.com/gHashTag/trinity/issues/new?template=verification-request.yml' + const CONTACT = { email: 'admin@t27.ai', github: 'https://github.com/gHashTag', @@ -102,6 +104,13 @@ const STEPS = [ 'You get a signed report — measured numbers, vectors, bitstream, and every command needed to reproduce it.', ] +const AUTO_STEPS: [string, string, string][] = [ + ['minute 0', 'You open one issue', 'Where the RTL is, the top module, and what "correct" means for it. Nothing else.'], + ['minute 1', 'A bot acknowledges', 'So nobody is left wondering whether it arrived.'], + ['minute 5', 'Automated checks post back', 'Elaboration, latch check, synthesis, cell counts — publicly, with every command shown.'], + ['then', 'The part a bot cannot do', 'An independent reference model, per-stage vectors, and a replay on the board.'], +] + // Measured facts, not badges. Nothing here is a claim I cannot show the working for. const SIGNALS: [string, string][] = [ ['170,068', 'cycles in the last run'], @@ -124,6 +133,15 @@ const RELATED_RU = [ // Russian copy. Other locales fall back to English rather than showing gaps. const RU = { + autoTitle: 'Начать — без переписки', + autoLede: 'Открываете одну заявку. Робот подхватывает её, прогоняет проверки, которым человек не нужен, и в считаные минуты публикует результат — со всеми командами, чтобы вы могли перепроверить сами.', + autoSteps: [ + ['минута 0', 'Вы открываете заявку', 'Где RTL, какой топ-модуль и что для него значит «правильно». Больше ничего.'], + ['минута 1', 'Робот подтверждает', 'Чтобы не оставалось сомнений, дошло ли.'], + ['минута 5', 'Автопроверки публикуются', 'Элаборация, защёлки, синтез, счёт ячеек — открыто, со всеми командами.'], + ['дальше', 'То, что робот не может', 'Независимая эталонная модель, векторы по ступеням и повтор на плате.'], + ] as [string, string, string][], + autoCta: 'Открыть заявку', diagramTitle: 'Почему внешняя проверка может с вами не согласиться', signals: [ ['170 068', 'циклов в последнем прогоне'], @@ -223,7 +241,9 @@ export default function HardwareVerification() {

+ {/* How a request actually starts. Named plainly because "get in touch" + is the step most people never take. */} + +

+ {c ? c.autoTitle : 'No email thread to start it'} +

+

+ {c ? c.autoLede : 'Open one request. A bot picks it up, runs the checks that do not need a human, and posts the result back within minutes — publicly, with every command shown, so you can re-run it yourself.'} +

+
+ {(c ? c.autoSteps : AUTO_STEPS).map(([when, what, note], i) => ( +
+

+ {String(i + 1).padStart(2, '0')} · {when} +

+

{what}

+

{note}

+
+ ))} +
+ + {c ? c.autoCta : 'Open a request'} + +
+ {/* What you get */} 6) begin + checks = checks + 1; + if (pipe_result !== expect_d3) begin + errors = errors + 1; + if (errors <= 15) + $display("MISMATCH at %0d: pipelined=%h combinational(delayed)=%h", i, pipe_result, expect_d3); + end + end + end + + $display(""); + $display("compared %0d cycles, %0d mismatches", checks, errors); + if (errors == 0) $display("RESULT: EQUIVALENT"); + else $display("RESULT: NOT EQUIVALENT"); + $finish; + end + +endmodule