product datasheet in progress

This commit is contained in:
Matthew Venn
2026-08-02 16:45:46 +02:00
parent ff75e344cd
commit 8efe741709
12 changed files with 1139 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
# Build outputs — regenerate with build.py
tt-datasheet.html
tt-datasheet.pdf
.tt-datasheet.chrome.html
+93
View File
@@ -0,0 +1,93 @@
Copyright 2020 The Archivo Project Authors (https://github.com/Omnibus-Type/Archivo)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
+13
View File
@@ -0,0 +1,13 @@
# Datasheet assets
Both files are embedded as data URIs by `build.py`, so the generated HTML is
self-contained. Swap either one by dropping in a replacement and updating the
`assets:` block in `datasheet.yaml`.
| File | Source | Licence |
| ---- | ------ | ------- |
| `archivo.woff2` | [Archivo](https://github.com/Omnibus-Type/Archivo) via Google Fonts, latin subset, variable weight 100–900 | SIL Open Font License 1.1 — see `OFL.txt` |
| `demoboard.jpg` | Tiny Tapeout ETR demoboard quickstart guide, downscaled to 1000px | © 2024 Pat Deegan, psychogenic.com — **not** covered by this repo's Apache-2.0 licence |
The demoboard photo is credited in the datasheet caption. Confirm redistribution
is agreed with the copyright holder before publishing the datasheet externally.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

+257
View File
@@ -0,0 +1,257 @@
#!/usr/bin/env python3
"""Build the two-page Tiny Tapeout product datasheet.
All copy, numbers and colours live in datasheet.yaml. This script renders them
through datasheet.html.mustache, inlining the font and photo as data URIs so the
result is a single self-contained file that prints to two A4 pages.
python3 docs/product/build.py # write tt-datasheet.html
python3 docs/product/build.py --pdf # also render tt-datasheet.pdf
The sheets are a fixed 297mm tall with overflow hidden, so copy that grows too
long is silently clipped rather than spilling onto a third page. Every build
therefore measures the rendered sheets in headless Chrome and fails if anything
overflows. Pass --no-check to skip that (or set CHROME if it isn't found).
Requires chevron and pyyaml, both already pinned in requirements.txt.
"""
import argparse
import base64
import logging
import os
import re
import shutil
import subprocess
import sys
from pathlib import Path
import chevron
import yaml
HERE = Path(__file__).parent
# `style` tokens in datasheet.yaml -> CSS classes in the template.
STYLE_CLASSES = {
"name": "t-name",
"mono": "t-mono",
"num": "t-num",
"price": "t-price",
"dim": "t-dim",
"center": "t-center",
}
# Tables are rendered through partials/table.mustache and need preparing first.
TABLE_KEYS = ["pricing", "process", "ratings", "gpio", "analog", "ordering"]
EM_DASH = "—"
CHROME_CANDIDATES = [
os.environ.get("CHROME"),
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
"google-chrome",
"chromium",
]
def cell_classes(style: str, value: str) -> str:
"""Map a column's style tokens to CSS classes for one cell."""
classes = []
for token in style.split():
try:
classes.append(STYLE_CLASSES[token])
except KeyError:
raise SystemExit(
f"unknown style '{token}' — expected one of {', '.join(STYLE_CLASSES)}"
)
# A lone em dash means "not applicable"; grey it out wherever it appears.
if value.strip() == EM_DASH and "t-dim" not in classes:
classes.append("t-dim")
return " ".join(classes)
def prepare_table(name: str, table: dict) -> None:
"""Expand a table's columns and rows into what the template expects."""
columns = table.get("columns") or []
for column in columns:
width = column.get("width")
column["width_attr"] = f' style="width:{width}"' if width else ""
column.setdefault("label", "")
styles = [column.get("style", "") for column in columns]
rows = []
for index, row in enumerate(table.get("rows") or [], start=1):
if len(row) != len(columns):
raise SystemExit(
f"{name}: row {index} has {len(row)} cells "
f"but there are {len(columns)} columns"
)
rows.append(
{
"cells": [
{"v": value, "cls": cell_classes(style, value)}
for style, value in zip(styles, row)
]
}
)
table["rows"] = rows
table["has_note"] = bool(table.get("note"))
table["has_footnote"] = bool(table.get("footnote"))
def as_data_uri_payload(path: Path) -> str:
if not path.is_file():
raise SystemExit(f"missing asset: {path}")
return base64.b64encode(path.read_bytes()).decode()
def find_chrome() -> str:
for candidate in CHROME_CANDIDATES:
if candidate and (Path(candidate).is_file() or shutil.which(candidate)):
return candidate
raise SystemExit(
"could not find Chrome — set the CHROME environment variable to its path"
)
MEASURE_SCRIPT = """
<div id="fit-report">pending</div>
<script>
window.addEventListener("load", () => setTimeout(() => {
const mm = 25.4 / 96;
const report = [...document.querySelectorAll(".sheet")].map((sheet, i) => {
const body = sheet.querySelector(".body");
return [i + 1, ((body.scrollHeight - body.clientHeight) * mm).toFixed(1)].join(":");
});
document.getElementById("fit-report").textContent = report.join(",");
}, 500));
</script>
"""
def run_chrome(*flags: str, page: Path) -> bytes:
result = subprocess.run(
[
find_chrome(),
"--headless",
"--disable-gpu",
"--no-sandbox",
*flags,
page.resolve().as_uri(),
],
check=True,
capture_output=True,
)
return result.stdout
def as_document(html_path: Path, extra: str = "") -> Path:
"""Chrome needs a complete document; the published artifact is wrapped for us."""
wrapped = html_path.with_name("." + html_path.stem + ".chrome.html")
wrapped.write_text(
"<!doctype html><html><head><meta charset='utf-8'>"
"<style>*{margin:0;padding:0}</style></head><body>"
+ html_path.read_text(encoding="utf-8")
+ extra
+ "</body></html>",
encoding="utf-8",
)
return wrapped
def check_fit(html_path: Path) -> bool:
"""Measure each sheet in Chrome; report how far any of them overflows."""
page = as_document(html_path, MEASURE_SCRIPT)
try:
dom = run_chrome("--virtual-time-budget=5000", "--dump-dom", page=page)
finally:
page.unlink(missing_ok=True)
found = re.search(rb'<div id="fit-report">([^<]*)</div>', dom)
if not found or found.group(1) == b"pending":
logging.warning("could not measure sheet fit — skipping check")
return True
overflowed = False
for entry in found.group(1).decode().split(","):
sheet, overflow = entry.split(":")
if float(overflow) > 0:
overflowed = True
logging.error(
f"page {sheet} overflows by {overflow}mm — content past the bottom "
"of the sheet is CLIPPED, not moved to another page"
)
else:
logging.info(f"page {sheet} fits")
if overflowed:
logging.error("trim copy in datasheet.yaml, or shorten a table")
return not overflowed
def render_pdf(html_path: Path, pdf_path: Path) -> None:
page = as_document(html_path)
try:
run_chrome("--no-pdf-header-footer", f"--print-to-pdf={pdf_path}", page=page)
finally:
page.unlink(missing_ok=True)
pages = len(re.findall(rb"/Type\s*/Page[^s]", pdf_path.read_bytes()))
logging.info(f"wrote {pdf_path} ({pages} pages)")
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--config", default=HERE / "datasheet.yaml", type=Path, help="content YAML"
)
parser.add_argument(
"--out", default=HERE / "tt-datasheet.html", type=Path, help="output HTML"
)
parser.add_argument(
"--pdf", action="store_true", help="also render a PDF next to the HTML"
)
parser.add_argument(
"--no-check",
dest="check",
action="store_false",
help="skip the headless-Chrome overflow check",
)
args = parser.parse_args()
logging.basicConfig(
level=logging.INFO, format="%(levelname)-8s %(message)s", stream=sys.stdout
)
with open(args.config, encoding="utf-8") as fh:
content = yaml.safe_load(fh)
for key in TABLE_KEYS:
if key not in content:
raise SystemExit(f"{args.config}: missing '{key}' section")
prepare_table(key, content[key])
assets = HERE / "assets"
content["font_b64"] = as_data_uri_payload(assets / content["assets"]["font"])
content["photo_b64"] = as_data_uri_payload(assets / content["assets"]["photo"])
with open(HERE / "datasheet.html.mustache", encoding="utf-8") as fh:
rendered = chevron.render(
fh.read(),
content,
partials_path=str(HERE / "partials"),
partials_ext="mustache",
)
args.out.write_text(rendered, encoding="utf-8")
logging.info(f"wrote {args.out} ({len(rendered) / 1024:.0f} KB)")
if args.pdf:
render_pdf(args.out, args.out.with_suffix(".pdf"))
if args.check and not check_fit(args.out):
sys.exit(1)
if __name__ == "__main__":
main()
+486
View File
@@ -0,0 +1,486 @@
<title>{{&doc.title}}</title>
<style>
@font-face {
font-family: "Archivo";
src: url(data:font/woff2;base64,{{&font_b64}}) format("woff2");
font-weight: 100 900;
font-style: normal;
font-display: block;
}
:root {
--navy: {{theme.navy}};
--red: {{theme.red}};
--ink: {{theme.ink}};
--ink-mid: {{theme.ink_mid}};
--ink-faint: {{theme.ink_faint}};
--ground: {{theme.ground}};
--rule: {{theme.rule}};
--rule-hard: {{theme.rule_hard}};
--paper: #ffffff;
--chrome: #d8d7dd;
--t-micro: 6.2pt;
--t-fine: 7.1pt;
--t-body: {{theme.body_size}};
--t-lead: 9.6pt;
--t-sub: 13pt;
--t-h2: 15pt;
--t-h1: {{theme.heading_size}};
}
@media (prefers-color-scheme: dark) { :root { --chrome: #17171c; } }
:root[data-theme="dark"] { --chrome: #17171c; }
:root[data-theme="light"] { --chrome: #d8d7dd; }
* { box-sizing: border-box; }
body {
margin: 0;
background: var(--chrome);
font-family: "Archivo", "Helvetica Neue", Arial, sans-serif;
color: var(--ink);
-webkit-font-smoothing: antialiased;
}
.deck {
display: flex;
flex-direction: column;
align-items: center;
gap: 8mm;
padding: 8mm 4mm;
}
.sheet {
width: 210mm;
height: 297mm;
background: var(--paper);
position: relative;
display: flex;
flex-direction: column;
overflow: hidden;
flex: none;
}
/* ---------- masthead ---------- */
.bar {
background: var(--navy);
color: #fff;
display: flex;
align-items: center;
gap: 5mm;
padding: 4.2mm 12mm;
flex: none;
}
.bar__logo { width: 11mm; height: 11mm; flex: none; display: block; }
.bar__word {
font-size: var(--t-sub);
font-weight: 700;
letter-spacing: -0.01em;
line-height: 1;
}
.bar__word span { display: block; font-size: var(--t-micro); font-weight: 600; letter-spacing: 0.14em; color: #9a99d6; margin-top: 1.1mm; }
.bar__tag {
margin-left: auto;
background: var(--red);
color: #fff;
font-size: var(--t-fine);
font-weight: 700;
letter-spacing: 0.16em;
padding: 1.4mm 3mm;
}
.bar__ref { font-size: var(--t-micro); font-weight: 500; letter-spacing: 0.1em; color: #9a99d6; font-variant-numeric: tabular-nums; }
.bar--p2 .bar__title { margin-left: auto; font-size: var(--t-fine); font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; }
/* ---------- shared structure ---------- */
.body {
flex: 1 1 auto;
padding: 0 12mm;
display: flex;
flex-direction: column;
min-height: 0;
}
.label {
font-size: var(--t-micro);
font-weight: 600;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--red);
text-align: left;
}
.label--dim { color: var(--ink-faint); }
.secthead {
display: flex;
align-items: baseline;
gap: 3mm;
border-bottom: 2px solid var(--rule-hard);
padding-bottom: 1.1mm;
margin-bottom: 2.1mm;
}
.secthead h2 {
margin: 0;
font-size: var(--t-h2);
font-weight: 700;
letter-spacing: -0.015em;
line-height: 1;
}
.secthead .note { margin-left: auto; font-size: var(--t-micro); font-weight: 500; letter-spacing: 0.06em; color: var(--ink-faint); }
/* ---------- hero ---------- */
.hero {
display: grid;
grid-template-columns: 1.32fr 1fr;
gap: 7mm;
padding: 4.6mm 0 4.2mm;
border-bottom: 2px solid var(--rule-hard);
}
.hero h1 {
margin: 2.2mm 0 0;
font-size: var(--t-h1);
font-weight: 700;
line-height: 0.97;
letter-spacing: -0.03em;
text-wrap: balance;
}
.hero p {
margin: 3mm 0 0;
font-size: var(--t-lead);
line-height: 1.4;
color: var(--ink);
max-width: 62ch;
}
.hero .rule { height: 2px; background: var(--red); width: 22mm; margin-top: 3.2mm; }
.hero figure { margin: 0; display: flex; flex-direction: column; }
.hero img { width: 100%; height: 44mm; object-fit: cover; display: block; }
.hero figcaption {
font-size: var(--t-micro);
color: var(--ink-faint);
letter-spacing: 0.05em;
margin-top: 1.4mm;
border-top: 1px solid var(--rule);
padding-top: 1.2mm;
}
/* ---------- feature grid ---------- */
.features {
display: grid;
grid-template-columns: repeat(3, 1fr);
border-bottom: 2px solid var(--rule-hard);
}
.feature {
padding: 2.8mm 4mm 2.9mm 0;
border-right: 1px solid var(--rule);
}
.feature:nth-child(3n) { border-right: 0; padding-right: 0; }
.feature:nth-child(n + 4) { border-top: 1px solid var(--rule); }
.feature h3 {
margin: 1.6mm 0 1.4mm;
font-size: var(--t-body);
font-weight: 700;
letter-spacing: -0.005em;
line-height: 1.12;
}
.feature p { margin: 0; font-size: var(--t-fine); line-height: 1.36; color: var(--ink-mid); }
/* ---------- tables ---------- */
table { width: 100%; border-collapse: collapse; }
th {
background: var(--navy);
color: #fff;
font-size: var(--t-micro);
font-weight: 600;
letter-spacing: 0.13em;
text-transform: uppercase;
text-align: left;
padding: 1.35mm 2.4mm;
}
td {
font-size: var(--t-fine);
line-height: 1.3;
padding: 1.35mm 2.4mm;
border-bottom: 1px solid var(--rule);
vertical-align: top;
}
tbody tr:last-child td { border-bottom: 0; }
.t-num { font-variant-numeric: tabular-nums; }
.t-name { font-weight: 600; white-space: nowrap; }
.t-mono { font-weight: 600; font-variant-numeric: tabular-nums; letter-spacing: 0.01em; white-space: nowrap; }
.t-price { font-weight: 700; color: var(--red); font-variant-numeric: tabular-nums; white-space: nowrap; }
.t-dim { color: var(--ink-faint); }
.t-center { text-align: center; }
.t-sub { color: var(--ink-faint); font-weight: 500; }
.zebra tbody tr:nth-child(odd) td { background: var(--ground); }
.tnote {
font-size: var(--t-micro);
line-height: 1.4;
color: var(--ink-mid);
border-left: 2px solid var(--red);
padding: 0 0 0 2.6mm;
margin-top: 1.9mm;
}
.tnote b { color: var(--ink); font-weight: 700; }
/* ---------- pricing / faq blocks ---------- */
.block { padding: 3.2mm 0; border-bottom: 2px solid var(--rule-hard); }
.block:last-of-type { border-bottom: 0; }
.faq {
display: grid;
grid-template-columns: 1fr 1fr;
column-gap: 8mm;
row-gap: 2.6mm;
}
.faq > div { border-top: 1px solid var(--rule); padding-top: 1.8mm; }
.faq h3 {
margin: 0 0 1.2mm;
font-size: var(--t-body);
font-weight: 700;
line-height: 1.15;
letter-spacing: -0.005em;
}
.faq p { margin: 0; font-size: var(--t-fine); line-height: 1.36; color: var(--ink-mid); }
/* ---------- page 2 two-column band ---------- */
.band { display: grid; grid-template-columns: 1.05fr 1fr; gap: 8mm; }
.band__col { display: flex; flex-direction: column; min-width: 0; }
.linkline {
font-size: var(--t-micro);
letter-spacing: 0.05em;
color: var(--ink-faint);
margin-top: auto;
padding-top: 1.4mm;
border-top: 1px solid var(--rule);
}
.linkline b { color: var(--red); font-weight: 700; }
a { color: inherit; text-decoration: none; }
a:focus-visible { outline: 2px solid var(--red); outline-offset: 2px; }
@media screen { .tnote a, .note a { text-decoration: underline; text-underline-offset: 1.5px; } }
/* ---------- footers ---------- */
.foot {
flex: none;
padding: 3.4mm 12mm 5mm;
border-top: 2px solid var(--rule-hard);
display: flex;
align-items: flex-end;
gap: 6mm;
}
.foot__body { flex: 1 1 auto; min-width: 0; }
.orgs {
margin-top: 1.8mm;
font-size: var(--t-fine);
font-weight: 600;
line-height: 1.5;
color: var(--ink);
column-count: 2;
column-gap: 8mm;
}
.orgs span { display: block; white-space: nowrap; }
.orgs span::before { content: "▪"; color: var(--red); margin-right: 1.6mm; font-size: 0.8em; }
.disclaimer { margin-top: 1.8mm; font-size: var(--t-micro); line-height: 1.45; color: var(--ink-mid); max-width: 150mm; }
.pageno {
flex: none;
font-size: var(--t-h2);
font-weight: 700;
font-variant-numeric: tabular-nums;
letter-spacing: -0.02em;
line-height: 0.9;
text-align: right;
}
.pageno span { display: block; font-size: var(--t-micro); font-weight: 600; letter-spacing: 0.14em; color: var(--ink-faint); margin-bottom: 1mm; }
/* ---------- print ---------- */
@page { size: A4; margin: 0; }
@media print {
body { background: #fff; }
.deck { display: block; gap: 0; padding: 0; }
.sheet { break-after: page; page-break-after: always; box-shadow: none; }
.sheet:last-child { break-after: auto; page-break-after: auto; }
th, .bar, .bar__tag, .zebra tbody tr:nth-child(odd) td {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}
@media screen {
.sheet { border: 1px solid var(--rule); }
}
</style>
<div class="deck">
<!-- ========================= PAGE 1 ========================= -->
<section class="sheet">
<header class="bar">
{{> logo}}
<div class="bar__word">{{&doc.wordmark}}<span>{{&doc.wordmark_sub}}</span></div>
<div class="bar__tag">{{&doc.tag}}</div>
<div class="bar__ref">{{&doc.ref}}</div>
</header>
<div class="body">
<div class="hero">
<div>
<div class="label">{{&hero.eyebrow}}</div>
<h1>{{&hero.heading}}</h1>
<p>{{&hero.body}}</p>
<div class="rule"></div>
</div>
<figure>
<img src="data:image/jpeg;base64,{{&photo_b64}}" alt="{{hero.photo_alt}}">
<figcaption>{{&hero.photo_caption}}</figcaption>
</figure>
</div>
<div class="features">
{{#features}}
<div class="feature">
<div class="label label--dim">{{&kicker}}</div>
<h3>{{&title}}</h3>
<p>{{&body}}</p>
</div>
{{/features}}
</div>
<div class="block">
{{#pricing}}{{> secthead}}{{> table}}{{> footnote}}{{/pricing}}
</div>
<div class="block">
{{#faq}}
{{> secthead}}
<div class="faq">
{{#items}}
<div>
<h3>{{&q}}</h3>
<p>{{&a}}</p>
</div>
{{/items}}
</div>
{{/faq}}
</div>
</div>
<footer class="foot">
<div class="foot__body">
<div class="label label--dim">{{&institutions.label}}</div>
<div class="orgs">
{{#institutions.items}}<span>{{&.}}</span>
{{/institutions.items}}
</div>
</div>
<div class="pageno"><span>PAGE</span>01</div>
</footer>
</section>
<!-- ========================= PAGE 2 ========================= -->
<section class="sheet">
<header class="bar bar--p2">
{{> logo}}
<div class="bar__word">{{&doc.wordmark}}<span>{{&doc.ref}}</span></div>
<div class="bar__title">{{&doc.page2_title}}</div>
</header>
<div class="body">
<div class="block">
{{#process}}{{> secthead}}{{> table}}{{> footnote}}{{/process}}
</div>
<div class="block">
{{#ratings}}{{> secthead}}{{> table}}{{> footnote}}{{/ratings}}
</div>
<div class="block">
<div class="band">
<div class="band__col">
{{#gpio}}
{{> secthead}}
{{> table}}
<div class="linkline">{{&linkline}}</div>
{{/gpio}}
</div>
<div class="band__col">
{{#analog}}
{{> secthead}}
{{> table}}
<div class="linkline">{{&linkline}}</div>
{{/analog}}
</div>
</div>
</div>
<div class="block">
{{#ordering}}{{> secthead}}{{> table}}{{> footnote}}{{/ordering}}
</div>
</div>
<footer class="foot">
<div class="foot__body">
<div class="label label--dim">{{&disclaimer.label}}</div>
<p class="disclaimer">{{&disclaimer.body}}</p>
</div>
<div class="pageno"><span>PAGE</span>02</div>
</footer>
</section>
</div>
+265
View File
@@ -0,0 +1,265 @@
# ---------------------------------------------------------------------------
# Tiny Tapeout product datasheet — all copy, numbers and colours.
#
# Edit this file, then rebuild:
# python3 docs/product/build.py
#
# Notes for editing:
# - Every text field is rendered as raw HTML, so <b>, <br> and <a href="...">
# work anywhere. Write a literal ampersand as &amp;.
# - Table `style` tokens set cell formatting, space separated:
# name bold, no wrapping (first column of most tables)
# mono bold + tabular figures (dimensions, values, signal names)
# num tabular figures (aligned digits)
# price bold red + no wrapping (pricing column)
# dim greyed out (em dashes, secondary text)
# center centre aligned
# - Table rows must have the same number of cells as there are columns.
# ---------------------------------------------------------------------------
doc:
title: Tiny Tapeout — Datasheet
wordmark: Tiny Tapeout
wordmark_sub: SHARED SILICON ASIC SHUTTLE
tag: DATASHEET
ref: DOC TT-DS-2026 · REV A
page2_title: Electrical &amp; Mechanical Specifications
# Colours and the two page-level type sizes. Everything else scales from these.
theme:
navy: "#040371" # header bars and table header rows
red: "#ec3013" # accent: labels, rules, prices
ink: "#141414" # body text
ink_mid: "#55545f" # secondary text
ink_faint: "#86858f" # captions, dashes
ground: "#f2f1f4" # zebra stripe on tables
rule: "#c9c8d0" # thin rules
rule_hard: "#141414" # 2px section rules
heading_size: 30pt # page 1 headline
body_size: 8.3pt # feature and FAQ body text
assets:
# Relative to docs/product/assets/. Swap either file and rebuild.
photo: demoboard.jpg
font: archivo.woff2
# --------------------------------------------------------------------------
# PAGE 1
# --------------------------------------------------------------------------
hero:
eyebrow: Shared multi-project chip · SKY130 / SG13G2 / GF180MCU
heading: From idea to chip design in minutes
body: >-
Tiny Tapeout makes it more accessible than ever to get your designs manufactured on a real chip.
Submit from a
GitHub repository and we handle integration, tapeout, packaging and delivery.
photo_alt: Tiny Tapeout ETR demoboard with a QFN-64 packaged ASIC on a breakout board
photo_caption: Demoboard with QFN-64 packaged die on breakout carrier.
features:
- kicker: 01 · Toolchain
title: Local tool install optional
body: >-
The whole flow can run in GitHub Actions. Push your repository and your design
is hardened in the cloud — nothing to install, no licences to buy.
- kicker: 02 · Languages
title: HDL agnostic
body: >-
Any language that can produce Verilog RTL. Project templates and CI are
supplied for Verilog, with community provided VHDL and Chisel.
- kicker: 03 · I/O
title: 26 pins, plus analog
body: >-
Clock, reset, 8 dedicated inputs, 8 dedicated outputs and 8 bidirectional
pins — plus up to 6 analog pins where the process allows.
- kicker: 04 · Process
title: Three PDKs
body: >-
SkyWater SKY130, IHP sg13g2 and GlobalFoundries gf180mcu. Open-source PDKs
running multiple times per year.
- kicker: 05 · Hardware
title: Demoboard available
body: >-
An optional RP2350-based demoboard carries your packaged chip and selects
any project on the die over USB.
- kicker: 06 · Flexibility
title: Bring your own flow
body: >-
Harden with the supplied open-source LibreLane flow, or use your own tools
entirely — the minimum submission is GDS + LEF.
pricing:
heading: Pricing
note: INDICATIVE FROM-PRICES · EUR
columns:
- {label: Item, width: "27%", style: name}
- {label: What it buys you}
- {label: From, width: "26%", style: price}
rows:
- ["1 tile",
"Roughly 1,000 standard cells of silicon area. Physical dimensions vary by PDK — see page 2.",
"€70"]
- ["Analog pin",
"Multiplexed analog pad access for mixed signal designs. Minimum project size of 2 tiles applies.",
"€100 <span class=\"t-sub\">/ pin</span>"]
- ["Demoboard PCB",
"Optional add-on: your packaged chip, an RP2350 demoboard and a breakout board.",
"€100 <span class=\"t-sub\">first 80 boards for individuals are subsidised</span><br>€300 <span class=\"t-sub\">academic &amp; industry</span>"]
footnote: >-
<b>Silicon area and the demoboard are billed separately.</b> Buying area gets
your design manufactured on the wafer — on its own it does not get you a
physical chip. To hold your chip in your hand, add the demoboard PCB. Confirm
current figures at
<b><a href="https://app.tinytapeout.com/calculator">app.tinytapeout.com/calculator</a></b>.
faq:
heading: Frequently asked Questions
note: FULL FAQ AT TINYTAPEOUT.COM/FAQ
items:
- q: How long until I get my chip?
a: >-
Fabrication takes 6–9 months, then boards are assembled, tested and
shipped. Check <a href="https://app.tinytapeout.com/chips">tinytapeout.com/chips</a> for the schedule.</b>.
- q: Do I need chip design experience?
a: >-
No. Getting started documentation on the website and help on the Discord community helps beginners get started.
Thousands of past projects are public to learn from.
- q: What do I actually receive?
a: >-
Area alone gets your design onto the wafer. Add the demoboard to receive a
packaged chip, an RP2350 demoboard and a breakout board. Bare chips are not available separately.
- q: Which processes can I use?
a: >-
SkyWater SKY130 (130 nm), IHP sg13g2 (130 nm BiCMOS) and GlobalFoundries
gf180mcu (180 nm), each on its own shuttle schedule.
- q: Is it only open to individuals?
a: >-
No. Universities, schools and companies run group submissions, and dozens
of institutions already teach with Tiny Tapeout.
- q: What are the licence terms?
a: >-
Every design is published publicly under Apache-2.0. The minimum
submission is GDS + LEF, produced with any toolchain you like.
institutions:
label: Taught and used at
items:
- Carnegie Mellon University
- New York University
- UC Santa Cruz
- UC San Diego
- San Jose State University
- Oklahoma State University
- Technical University of Denmark
- Linköping University
- Graz University of Technology
# --------------------------------------------------------------------------
# PAGE 2
# --------------------------------------------------------------------------
process:
heading: Process &amp; fabrication
note: TILE DIMENSIONS PER PDK
columns:
- {label: PDK, width: "19%", style: name}
- {label: Foundry, width: "21%"}
- {label: Node, width: "17%", style: num}
- {label: 1×1 tile area, width: "25%", style: mono}
- {label: Package}
rows:
- ["SKY130A", "SkyWater", "130 nm", "161.00 × 111.52 µm", "QFN-64"]
- ["ihp-sg13g2", "IHP", "130 nm BiCMOS", "202.08 × 154.98 µm", "QFN-64"]
- ["gf180mcuD", "GlobalFoundries", "180 nm", "346.64 × 160.72 µm", "QFN-64"]
footnote: >-
One tile holds approximately <b>1,000 standard cells</b>. Larger designs are
built from multi-tile blocks from 1×2 up to 8×4; the available sizes differ
per PDK. Analog projects require a minimum height of 2 tiles.
ratings:
heading: Absolute maximum ratings
note: DIGITAL I/O
columns:
- {label: Parameter, width: "40%", style: name}
- {label: Min, width: "13%", style: num}
- {label: Typ, width: "13%", style: num}
- {label: Max, width: "13%", style: num}
- {label: Unit}
rows:
- ["I/O supply voltage", "1.71", "3.30", "5.50", "V"]
- ["Input frequency", "—", "—", "66", "MHz"]
- ["Output toggle frequency", "—", "—", "33", "MHz"]
- ["Output drive strength, source/sink", "—", "4", "—", "mA"]
- ["Demoboard clock generator", "1", "—", "66.5 M", "Hz"]
- ["Pad-to-tile round-trip latency", "—", "—", "20", "ns"]
- ["Clock insertion delay, pad to logic", "—", "—", "10", "ns"]
- ["Pin-to-pin latency variance", "—", "—", "2", "ns"]
footnote: >-
Characterised on <b>SKY130A</b> using the <b>sky130_ef_io_gpiov2_pad</b> cell;
ratings differ per PDK. Inputs are <b>not 5 V tolerant</b> despite the supply
range. Full per-process figures at
<b><a href="https://tinytapeout.com/specs/">tinytapeout.com/specs</a></b>.
gpio:
heading: GPIO pin map
columns:
- {label: Signal, width: "34%", style: mono}
- {label: Width, width: "14%", style: num center}
- {label: Direction, width: "22%"}
- {label: Function}
rows:
- ["clk", "1", "Input", "Clock"]
- ["rst_n", "1", "Input", "Reset, active low"]
- ["ui_in[7:0]", "8", "Input", "General purpose"]
- ["uo_out[7:0]", "8", "Output", "General purpose"]
- ["uio[7:0]", "8", "Bidirectional", "Per-pin direction control"]
linkline: >-
26 I/O PINS TOTAL ·
<b><a href="https://tinytapeout.com/specs/gpio/">TINYTAPEOUT.COM/SPECS/GPIO</a></b>
analog:
heading: Analog I/O
note: OPTIONAL
columns:
- {label: Parameter, style: name}
- {label: Value, width: "34%", style: mono}
rows:
- ["Analog pins, maximum", "6 · ua[0:5]"]
- ["Series resistance, pad to project", "&lt; 500 Ω"]
- ["Parasitic capacitance", "&lt; 5 pF"]
- ["Current per pin, maximum", "4 mA"]
- ["VDPWR digital supply", "1.8 V"]
- ["VAPWR analog supply", "3.3 V"]
linkline: >-
ALLOCATED IN ORDER ·
<b><a href="https://tinytapeout.com/specs/analog/">TINYTAPEOUT.COM/SPECS/ANALOG</a></b>
ordering:
heading: Ordering information
note: APP.TINYTAPEOUT.COM/CALCULATOR
headless: true # no header row on this table
columns:
- {width: "27%", style: name}
- {}
rows:
- ["Shuttle cadence",
"Several shuttles per year across the three processes. Check <a href="https://app.tinytapeout.com/chips">tinytapeout.com/chips</a> for the schedule"]
- ["Lead time",
"6–9 months in fabrication, then assembly, test and fulfilment."]
- ["Deliverable",
"Silicon area only for remote or shared test, or area plus the demoboard kit: packaged chip, RP2350 demoboard and breakout board."]
- ["Design tools",
"Supplied open-source LibreLane flow running in GitHub Actions, or any toolchain of your own."]
- ["Minimum submission",
"GDS + LEF in a public repository, licensed Apache-2.0."]
disclaimer:
label: Disclaimer
body: >-
Specifications are indicative and subject to change between shuttles.
Electrical characteristics are quoted for the SKY130A process unless stated
otherwise — consult tinytapeout.com/specs for the process you intend to use.
Pricing is indicative; confirm current figures with the calculator before
ordering. Tiny Tapeout devices are educational and prototyping parts, and are
not qualified for safety-critical, medical or automotive use.
+1
View File
@@ -0,0 +1 @@
{{#has_footnote}}<p class="tnote">{{&footnote}}</p>{{/has_footnote}}
+5
View File
@@ -0,0 +1,5 @@
<svg class="bar__logo" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Tiny Tapeout logo">
<g transform="matrix(0.75075075,0,0,0.7513148,-156.00601,-72.426747)" fill="#ffffff">
<path d="m 341,96.4 c -44.5,0 -83.9,21.9 -108.1,55.5 -7.9,11 -14.2,23.2 -18.5,36.4 h 21.6 52.1 v 90.1 h 39.3 v -24.3 h 22.7 v 87.8 c -3,0.3 -6.1,0.4 -9.2,0.4 -62.2,0 -112.8,-50.6 -112.8,-112.8 0,-10.3 1.4,-20.2 4,-29.6 h -21 c -2.2,9.5 -3.3,19.4 -3.3,29.6 0,73.4 59.7,133.1 133.1,133.1 3.1,0 6.1,-0.1 9.2,-0.3 1.7,-0.1 3.4,-0.3 5.1,-0.5 0.1,0 0.3,0 0.4,0 11.8,-1.3 23.1,-4.1 33.8,-8.3 v 0 c 0,0 0,0 0,0 V 254.1 H 428 V 217.7 H 327.5 V 188.2 H 366 V 151.8 H 259.3 c 20.5,-21.6 49.6,-35.1 81.7,-35.1 62.2,0 112.8,50.6 112.8,112.8 0,40.1 -21,75.4 -52.6,95.4 v 23.4 c 43.3,-22 73,-67 73,-118.8 C 474.1,156.1 414.4,96.4 341,96.4 Z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 859 B

+4
View File
@@ -0,0 +1,4 @@
<div class="secthead">
<h2>{{&heading}}</h2>
{{#has_note}}<div class="note">{{&note}}</div>{{/has_note}}
</div>
+11
View File
@@ -0,0 +1,11 @@
<table class="zebra">
<colgroup>{{#columns}}<col{{&width_attr}}>{{/columns}}</colgroup>
{{^headless}}
<thead><tr>{{#columns}}<th>{{&label}}</th>{{/columns}}</tr></thead>
{{/headless}}
<tbody>
{{#rows}}
<tr>{{#cells}}<td class="{{cls}}">{{&v}}</td>{{/cells}}</tr>
{{/rows}}
</tbody>
</table>