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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -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.
76 changes: 76 additions & 0 deletions .github/ISSUE_TEMPLATE/verification-request.yml
Original file line number Diff line number Diff line change
@@ -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
193 changes: 193 additions & 0 deletions .github/workflows/verify-request.yml
Original file line number Diff line number Diff line change
@@ -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,
})
54 changes: 53 additions & 1 deletion apps/website/src/pages/HardwareVerification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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'],
Expand All @@ -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', 'циклов в последнем прогоне'],
Expand Down Expand Up @@ -223,7 +241,9 @@ export default function HardwareVerification() {
</p>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.75rem', marginTop: '1.75rem' }}>
<motion.a
href={mailto('Hardware verification request')}
href={REQUEST_URL}
target="_blank"
rel="noopener noreferrer"
className="btn"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
Expand Down Expand Up @@ -277,6 +297,38 @@ export default function HardwareVerification() {
<VerificationDiagram />
</motion.div>

{/* How a request actually starts. Named plainly because "get in touch"
is the step most people never take. */}
<motion.div
className="premium-card"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
style={{ marginBottom: '2rem' }}
>
<h2 style={{ fontSize: 'clamp(1.3rem, 3.5vw, 1.7rem)', marginTop: 0, marginBottom: '0.6rem' }}>
{c ? c.autoTitle : 'No email thread to start it'}
</h2>
<p style={{ fontSize: '0.95rem', lineHeight: 1.65, opacity: 0.9, maxWidth: '62ch', margin: '0 auto 1.5rem' }}>
{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.'}
</p>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(190px, 1fr))', gap: '0.9rem', marginBottom: '1.5rem' }}>
{(c ? c.autoSteps : AUTO_STEPS).map(([when, what, note], i) => (
<div key={what} style={{ borderTop: '2px solid var(--accent)', paddingTop: '0.8rem' }}>
<p style={{ margin: 0, fontSize: '0.72rem', letterSpacing: '0.1em', textTransform: 'uppercase', opacity: 0.6 }}>
{String(i + 1).padStart(2, '0')} · {when}
</p>
<p style={{ margin: '0.3rem 0 0.2rem', fontSize: '0.98rem', fontWeight: 700, color: 'var(--accent)' }}>{what}</p>
<p style={{ margin: 0, fontSize: '0.86rem', lineHeight: 1.5, opacity: 0.85 }}>{note}</p>
</div>
))}
</div>
<a href={REQUEST_URL} target="_blank" rel="noopener noreferrer" className="btn" style={{ padding: '12px 28px', fontSize: '0.9rem' }}>
{c ? c.autoCta : 'Open a request'}
</a>
</motion.div>

{/* What you get */}
<motion.div
initial={{ opacity: 0, y: 20 }}
Expand Down
Loading
Loading