mirror of
https://github.com/TinyTapeout/tt-support-tools.git
synced 2026-11-03 04:36:09 +01:00
Merge pull request #144 from krisjdev/feat/tt07-update-datasheet
feat(datasheet): backport datasheet to tt07
This commit is contained in:
@@ -1,4 +1,33 @@
|
|||||||
from typing import TypedDict
|
from typing import List, Literal, TypedDict
|
||||||
|
|
||||||
|
from tech import TechName
|
||||||
|
|
||||||
|
|
||||||
|
class ArtworkEntryType(TypedDict):
|
||||||
|
"""TypedDict for artwork, used by `config.DatasheetConfig`"""
|
||||||
|
|
||||||
|
id: str
|
||||||
|
rotate: str
|
||||||
|
|
||||||
|
|
||||||
|
class DatasheetConfig(TypedDict):
|
||||||
|
"""TypedDict for the datasheet config within Tiny Tapeout's config.yaml file"""
|
||||||
|
|
||||||
|
pinout: Literal[
|
||||||
|
"caravel",
|
||||||
|
"openframe",
|
||||||
|
"openframe_sky130",
|
||||||
|
"customframe_ihp_sg13g2",
|
||||||
|
"customframe_gf180mcud",
|
||||||
|
]
|
||||||
|
theme_override_colour: str
|
||||||
|
show_chip_viewer: bool
|
||||||
|
link_disable_colour: bool
|
||||||
|
link_override_colour: str
|
||||||
|
qrcode_follows_theme: bool
|
||||||
|
include: List[str]
|
||||||
|
disabled: List[str]
|
||||||
|
artwork: List[ArtworkEntryType]
|
||||||
|
|
||||||
|
|
||||||
class Config(TypedDict):
|
class Config(TypedDict):
|
||||||
@@ -9,3 +38,5 @@ class Config(TypedDict):
|
|||||||
project_dir: str
|
project_dir: str
|
||||||
end_date: str
|
end_date: str
|
||||||
openframe: bool
|
openframe: bool
|
||||||
|
pdk: TechName
|
||||||
|
datasheet_config: DatasheetConfig
|
||||||
|
|||||||
+16
-5
@@ -265,14 +265,23 @@ if __name__ == "__main__":
|
|||||||
"--dump-json", help="dump json of all project data to given file"
|
"--dump-json", help="dump json of all project data to given file"
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--dump-markdown", help="dump markdown of all project data to given file"
|
"--metrics", help="print some project metrics", action="store_const", const=True
|
||||||
)
|
)
|
||||||
parser.add_argument("--dump-pdf", help="create pdf from the markdown")
|
parser.add_argument(
|
||||||
|
"--build-datasheet",
|
||||||
|
help="build datasheet using the typst template",
|
||||||
|
action="store_const",
|
||||||
|
const=True,
|
||||||
|
)
|
||||||
|
parser.add_argument("--doc-tapeout-index", help="path to json tapeout index")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--build-hugo-content", help="directory to where to build hugo content"
|
"--build-hugo-content", help="directory to where to build hugo content"
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--metrics", help="print some project metrics", action="store_const", const=True
|
"--template-version",
|
||||||
|
help="set typst template version (default 1.0.0)",
|
||||||
|
default="1.0.0",
|
||||||
|
nargs="?",
|
||||||
)
|
)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
@@ -327,9 +336,11 @@ if __name__ == "__main__":
|
|||||||
if args.update_image:
|
if args.update_image:
|
||||||
docs.update_image()
|
docs.update_image()
|
||||||
|
|
||||||
if args.dump_markdown:
|
if args.build_datasheet:
|
||||||
shuttle.configure_mux()
|
shuttle.configure_mux()
|
||||||
docs.write_datasheet(args.dump_markdown, args.dump_pdf)
|
|
||||||
|
if args.build_datasheet:
|
||||||
|
docs.build_datasheet(args.template_version, args.doc_tapeout_index)
|
||||||
|
|
||||||
if args.build_hugo_content:
|
if args.build_hugo_content:
|
||||||
shuttle.configure_mux()
|
shuttle.configure_mux()
|
||||||
|
|||||||
+512
@@ -0,0 +1,512 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import chevron
|
||||||
|
import matplotlib as mpl
|
||||||
|
|
||||||
|
from config import Config, DatasheetConfig
|
||||||
|
|
||||||
|
|
||||||
|
class DocsHelper:
|
||||||
|
@staticmethod
|
||||||
|
def pretty_address(address: int, subtile_address: Optional[int] = None) -> str:
|
||||||
|
"""
|
||||||
|
Format the address and subtile address (if applicable) into an nice string for the typst template
|
||||||
|
"""
|
||||||
|
content = str(address).rjust(4, "-")
|
||||||
|
if subtile_address is not None:
|
||||||
|
content += f"/{subtile_address}"
|
||||||
|
return content
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def pretty_clock(clock: int) -> str:
|
||||||
|
"""
|
||||||
|
Format the clock with engineering notation
|
||||||
|
"""
|
||||||
|
if clock == 0:
|
||||||
|
return "No Clock"
|
||||||
|
else:
|
||||||
|
formatter = mpl.ticker.EngFormatter(sep=" ")
|
||||||
|
# [clock, SI suffix]
|
||||||
|
hz_as_eng = formatter(clock).split(" ")
|
||||||
|
|
||||||
|
if len(hz_as_eng) == 2:
|
||||||
|
return f"{hz_as_eng[0]} {hz_as_eng[1]}Hz"
|
||||||
|
elif len(hz_as_eng) == 1:
|
||||||
|
return f"{hz_as_eng[0]} Hz"
|
||||||
|
else:
|
||||||
|
raise RuntimeError(
|
||||||
|
"unexpected amount of entries when formatting clock for datasheet"
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_authors(authors: str) -> str:
|
||||||
|
"""
|
||||||
|
Format the authors properly
|
||||||
|
|
||||||
|
Split the authors with a set of delimiters, then repackage as a typst array for the template
|
||||||
|
"""
|
||||||
|
split_authors = re.split(r"[,|;|+]| and | & ", authors)
|
||||||
|
formatted_authors = []
|
||||||
|
for author in split_authors:
|
||||||
|
stripped = author.strip()
|
||||||
|
if stripped == "":
|
||||||
|
continue
|
||||||
|
|
||||||
|
# typst needs a trailing comma if len(array) == 1, so that it doesn't interpret it as an expression
|
||||||
|
# https://typst.app/docs/reference/foundations/array/
|
||||||
|
formatted_authors.append(f'"{stripped}",')
|
||||||
|
return "".join(formatted_authors)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _escape_characters_in_content(text: str) -> str:
|
||||||
|
"""
|
||||||
|
Helper function to escape certain characters in content blocks
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
text.replace("[", "\\[")
|
||||||
|
.replace("]", "\\]")
|
||||||
|
.replace("/", "\\/")
|
||||||
|
.replace("#", "\\#")
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _escape_characters_in_string(text: str) -> str:
|
||||||
|
"""
|
||||||
|
Helper function to escape characters in strings
|
||||||
|
|
||||||
|
- Escapes double quotation mark
|
||||||
|
- Removes newlines, carrige returns
|
||||||
|
"""
|
||||||
|
|
||||||
|
return text.replace('"', '\\"').replace("\n", "").replace("\r", "")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_digital_pins(pins: dict) -> str:
|
||||||
|
"""
|
||||||
|
Iterate through and create the digital pin table as a string
|
||||||
|
"""
|
||||||
|
pin_table = ""
|
||||||
|
for pin in pins:
|
||||||
|
ui_text = DocsHelper._escape_characters_in_content(pin["ui"])
|
||||||
|
uo_text = DocsHelper._escape_characters_in_content(pin["uo"])
|
||||||
|
uio_text = DocsHelper._escape_characters_in_content(pin["uio"])
|
||||||
|
pin_table += (
|
||||||
|
f"[`{pin['pin_index']}`], [{ui_text}], [{uo_text}], [{uio_text}],\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
return pin_table
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_analog_pins(pins: dict):
|
||||||
|
"""
|
||||||
|
Iterate through and create the analog pin table as a string
|
||||||
|
"""
|
||||||
|
pin_table = ""
|
||||||
|
for pin in pins:
|
||||||
|
desc_text = DocsHelper._escape_characters_in_content(pin["desc"])
|
||||||
|
pin_table += (
|
||||||
|
f"[`{pin['ua_index']}`], [`{pin['analog_index']}`], [{desc_text}],\n"
|
||||||
|
)
|
||||||
|
return pin_table
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_docs_as_typst(path: str) -> str:
|
||||||
|
"""
|
||||||
|
Run pandoc to convert a given file to typst
|
||||||
|
"""
|
||||||
|
# enable/disable certain extensions
|
||||||
|
# see all with `pandoc --list-extensions=markdown`
|
||||||
|
# documentation at https://pandoc.org/MANUAL.html
|
||||||
|
# in general, these extensions should offer some additional formatting options whilst still
|
||||||
|
# remaining similar to github flavoured markdown
|
||||||
|
pandoc_extensions = [
|
||||||
|
"-auto_identifiers",
|
||||||
|
"+autolink_bare_uris",
|
||||||
|
"-citations",
|
||||||
|
"-definition_lists",
|
||||||
|
"+emoji",
|
||||||
|
"-example_lists",
|
||||||
|
"-fancy_lists",
|
||||||
|
"+lists_without_preceding_blankline",
|
||||||
|
"-fenced_divs",
|
||||||
|
]
|
||||||
|
pandoc_command = [
|
||||||
|
"pandoc",
|
||||||
|
path,
|
||||||
|
"--shift-heading-level-by=-1",
|
||||||
|
"-f",
|
||||||
|
f"markdown{''.join(pandoc_extensions)}",
|
||||||
|
"-t",
|
||||||
|
"typst-citations",
|
||||||
|
"--wrap=preserve",
|
||||||
|
]
|
||||||
|
logging.info(pandoc_command)
|
||||||
|
|
||||||
|
result = subprocess.run(pandoc_command, capture_output=True)
|
||||||
|
|
||||||
|
if result.stderr != b"":
|
||||||
|
logging.warning(result.stderr.decode())
|
||||||
|
|
||||||
|
return result.stdout.decode()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_project_type(language: str, is_wokwi: bool, is_analog: bool) -> str:
|
||||||
|
if is_wokwi:
|
||||||
|
return "Wokwi"
|
||||||
|
elif is_analog:
|
||||||
|
return "Analog"
|
||||||
|
else:
|
||||||
|
return "HDL"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_project_info(yaml_info: dict, index_info: dict) -> dict:
|
||||||
|
"""
|
||||||
|
Merge two sources of project information into one dictionary
|
||||||
|
|
||||||
|
Accepts `info.yaml` from the project root, and project information from the tapeout index.
|
||||||
|
Using these two sources, the function will pick and merge the required information for the datasheet.
|
||||||
|
"""
|
||||||
|
info = {
|
||||||
|
"title": yaml_info["project"]["title"],
|
||||||
|
"author": yaml_info["project"]["author"],
|
||||||
|
"description": yaml_info["project"]["description"],
|
||||||
|
"address": index_info["address"],
|
||||||
|
"clock_hz": index_info["clock_hz"],
|
||||||
|
"git_url": index_info["repo"],
|
||||||
|
"language": yaml_info["project"]["language"],
|
||||||
|
"macro": index_info["macro"],
|
||||||
|
"is_analog": len(index_info["analog_pins"]) > 0,
|
||||||
|
"is_wokwi": True if "wokwi_id" in yaml_info["project"] else False,
|
||||||
|
"type": "project",
|
||||||
|
}
|
||||||
|
|
||||||
|
if info["is_wokwi"]:
|
||||||
|
info["wokwi_id"] = yaml_info["project"]["wokwi_id"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
info["pins"] = [
|
||||||
|
{
|
||||||
|
"pin_index": str(i),
|
||||||
|
"ui": index_info["pinout"][f"ui[{i}]"],
|
||||||
|
"uo": index_info["pinout"][f"uo[{i}]"],
|
||||||
|
"uio": index_info["pinout"][f"uio[{i}]"],
|
||||||
|
}
|
||||||
|
for i in range(8)
|
||||||
|
]
|
||||||
|
except KeyError as e:
|
||||||
|
logging.info(
|
||||||
|
f"project is missing a pin entry ({e}), falling back to info.yaml (is this a subtile?)"
|
||||||
|
)
|
||||||
|
|
||||||
|
info["pins"] = [
|
||||||
|
{
|
||||||
|
"pin_index": str(i),
|
||||||
|
"ui": yaml_info["pinout"][f"ui[{i}]"],
|
||||||
|
"uo": yaml_info["pinout"][f"uo[{i}]"],
|
||||||
|
"uio": yaml_info["pinout"][f"uio[{i}]"],
|
||||||
|
}
|
||||||
|
for i in range(8)
|
||||||
|
]
|
||||||
|
|
||||||
|
if info["is_analog"]:
|
||||||
|
info["analog_pins"] = [
|
||||||
|
{
|
||||||
|
"ua_index": str(i),
|
||||||
|
"analog_index": index_info["analog_pins"][i],
|
||||||
|
"desc": index_info["pinout"][f"ua[{i}]"],
|
||||||
|
}
|
||||||
|
for i in range(len(index_info["analog_pins"]))
|
||||||
|
]
|
||||||
|
|
||||||
|
if "type" in index_info:
|
||||||
|
if index_info["type"] == "subtile":
|
||||||
|
info["type"] = "subtile"
|
||||||
|
info["subtile_addr"] = index_info["subtile_addr"]
|
||||||
|
info["subtile_group"] = index_info["subtile_group"]
|
||||||
|
elif index_info["type"] == "group":
|
||||||
|
info["type"] = "group"
|
||||||
|
|
||||||
|
return info
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def normalise_project_info(project) -> dict:
|
||||||
|
"""
|
||||||
|
Similar to `DocHelper.format_project_info`, but this accepts a `Project` object and attempts to
|
||||||
|
normalise it to the tapeout index format that is expected by the `Docs.build_datasheet()` function.
|
||||||
|
|
||||||
|
To help visualise it, the output of this function would be fed to the `index_info` parameter of
|
||||||
|
the `DocsHelper.format_project_info()`.
|
||||||
|
"""
|
||||||
|
info = {
|
||||||
|
"title": project.info.title,
|
||||||
|
"author": project.info.author,
|
||||||
|
"description": project.info.description,
|
||||||
|
"address": project.mux_address,
|
||||||
|
"clock_hz": project.info.clock_hz,
|
||||||
|
"repo": project.git_url,
|
||||||
|
"language": project.info.language,
|
||||||
|
"macro": project.info.top_module,
|
||||||
|
"is_analog": project.is_analog_design(),
|
||||||
|
"is_wokwi": project.is_wokwi(),
|
||||||
|
"type": "project",
|
||||||
|
}
|
||||||
|
|
||||||
|
if project.is_wokwi():
|
||||||
|
info["wokwi_id"] = project.info.wokwi_id
|
||||||
|
|
||||||
|
info["pinout"] = {}
|
||||||
|
for i in range(8):
|
||||||
|
info["pinout"].update({f"ui[{i}]": project.info.pinout.ui[i]})
|
||||||
|
info["pinout"].update({f"uo[{i}]": project.info.pinout.uo[i]})
|
||||||
|
info["pinout"].update({f"uio[{i}]": project.info.pinout.uio[i]})
|
||||||
|
|
||||||
|
if project.is_analog_design():
|
||||||
|
info["analog_pins"] = project.analog_pins
|
||||||
|
for i, desc in enumerate(project.info.pinout.ua):
|
||||||
|
info["pinout"].update({f"ua[{i}]": desc})
|
||||||
|
else:
|
||||||
|
info["analog_pins"] = []
|
||||||
|
|
||||||
|
return info
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def write_doc(path: str, template: str, content: dict) -> None:
|
||||||
|
try:
|
||||||
|
with open(path, "w") as f:
|
||||||
|
f.write(chevron.render(template, content))
|
||||||
|
except FileNotFoundError:
|
||||||
|
logging.warning(
|
||||||
|
f"unable to write to {path}... the project exists in tapeout index, but not in local directory? skipping"
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def populate_template_tags(
|
||||||
|
info: dict, danger_info: dict, docs: str, template_version="1.0.0"
|
||||||
|
) -> dict:
|
||||||
|
"""
|
||||||
|
Populate the required template fields given information about the project and its danger level
|
||||||
|
"""
|
||||||
|
content = {
|
||||||
|
"template_version": template_version,
|
||||||
|
"project_title": DocsHelper._escape_characters_in_string(info["title"]),
|
||||||
|
"project_author": f"({DocsHelper.format_authors(info['author'])})",
|
||||||
|
"project_repo_link": info["git_url"],
|
||||||
|
"project_description": DocsHelper._escape_characters_in_string(
|
||||||
|
info["description"]
|
||||||
|
),
|
||||||
|
"project_address": DocsHelper.pretty_address(info["address"]),
|
||||||
|
"project_clock": DocsHelper.pretty_clock(info["clock_hz"]),
|
||||||
|
"project_type": DocsHelper.get_project_type(
|
||||||
|
info["language"], info["is_wokwi"], info["is_analog"]
|
||||||
|
),
|
||||||
|
"project_doc_body": docs,
|
||||||
|
"digital_pins": DocsHelper.format_digital_pins(info["pins"]),
|
||||||
|
}
|
||||||
|
|
||||||
|
if info["macro"] in danger_info:
|
||||||
|
content["is_dangerous"] = True
|
||||||
|
content["project_danger_level"] = danger_info[info["macro"]]["level"]
|
||||||
|
content["project_danger_reason"] = danger_info[info["macro"]]["reason"]
|
||||||
|
|
||||||
|
if info["type"] == "subtile":
|
||||||
|
content["project_address"] = DocsHelper.pretty_address(
|
||||||
|
info["address"], info["subtile_addr"]
|
||||||
|
)
|
||||||
|
|
||||||
|
if info["is_wokwi"]:
|
||||||
|
content["is_wokwi"] = True
|
||||||
|
content["project_wokwi_id"] = info["wokwi_id"]
|
||||||
|
|
||||||
|
if info["is_analog"]:
|
||||||
|
content["is_analog"] = True
|
||||||
|
content["analog_pins"] = DocsHelper.format_analog_pins(info["analog_pins"])
|
||||||
|
|
||||||
|
return content
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def configure_datasheet(
|
||||||
|
shuttle_config: Config, datasheet_template: str, template_version: str = "1.0.0"
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Prepare the datasheet.typ file with info from `config.yaml`
|
||||||
|
|
||||||
|
The following keys can be used to configure the datasheet:
|
||||||
|
- `pinout`: set what pinout table is shown in multiplexer chapter (see DatasheetConfig.pinout in config.py)
|
||||||
|
- `theme_override_colour`: set a custom colour for the datasheet (rgb object)
|
||||||
|
- `show_chip_viewer`*: toggle the chip viewer QR code (boolean)
|
||||||
|
- `link_disable_colour`: disable link colouring (boolean)
|
||||||
|
- `link_override_colour`: set a custom link colour (rgb object)
|
||||||
|
- `qrcode_follows_theme`: colour template-generated QR codes with the main theme colour (boolean)
|
||||||
|
- `include`*: a list of additional `.typ` files to include - they are copied into the body of `datasheet.typ`
|
||||||
|
|
||||||
|
The keys correspond to the arguments available in the template, except those marked with an asterisk.
|
||||||
|
|
||||||
|
Example usage (in `config.yaml`):
|
||||||
|
```yaml
|
||||||
|
datasheet_config:
|
||||||
|
pinout: caravel
|
||||||
|
theme_override_colour: tt.colours.THEME_TT06_PINK
|
||||||
|
link_override_colour: rgb("#a5415c")
|
||||||
|
include:
|
||||||
|
- doc/chip_map.typ
|
||||||
|
- doc/funding.typ
|
||||||
|
```
|
||||||
|
|
||||||
|
There are other keys which are *not* used to configure the datasheet, but instead control the content of it:
|
||||||
|
- `disabled`: a list of projects which are excluded from the datasheet, mainly due to compilation issues
|
||||||
|
- `artwork`: a list of key pairs that specify which artwork is added in the projects section (order sensitive)
|
||||||
|
|
||||||
|
The `artwork` key pairs consist of an `ID` and a `rotate` value that specify which image to use and its rotation:
|
||||||
|
```yaml
|
||||||
|
datasheet_config:
|
||||||
|
disabled:
|
||||||
|
- tt_um_project_1
|
||||||
|
- tt_um_project_2
|
||||||
|
artwork:
|
||||||
|
- {id: photo1, rotate: 90deg}
|
||||||
|
- {id: photo2, rotate: -90deg}
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
logging.info("configuring datasheet.typ")
|
||||||
|
content = {
|
||||||
|
"template_version": template_version,
|
||||||
|
"shuttle_name": shuttle_config["name"],
|
||||||
|
"shuttle_id": shuttle_config["id"],
|
||||||
|
"shuttle_id_upper": shuttle_config["id"].upper(),
|
||||||
|
"if_chip_viewer": True,
|
||||||
|
# themeing
|
||||||
|
"qrcode_follows_theme": "false",
|
||||||
|
"link_disable_colour": "false",
|
||||||
|
}
|
||||||
|
|
||||||
|
# get repo link
|
||||||
|
git_repo_link_cmd = ["git", "config", "--get", "remote.origin.url"]
|
||||||
|
logging.info(git_repo_link_cmd)
|
||||||
|
result = subprocess.run(git_repo_link_cmd, capture_output=True)
|
||||||
|
git_repo_link = result.stdout.decode().strip()
|
||||||
|
content["repo_link"] = git_repo_link
|
||||||
|
|
||||||
|
if "datasheet_config" in shuttle_config:
|
||||||
|
datasheet_config = shuttle_config["datasheet_config"]
|
||||||
|
|
||||||
|
if datasheet_config is None:
|
||||||
|
logging.warning(
|
||||||
|
"datasheet config specified in config.yaml but has no entries"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
content["if_pinout"] = True
|
||||||
|
if "pinout" in datasheet_config:
|
||||||
|
content["pinout"] = shuttle_config["datasheet_config"]["pinout"]
|
||||||
|
logging.info(
|
||||||
|
f"using pinout config verbatim ('{content['pinout']}')"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# determine appropriate pinout table from pdk first, otherwise fall back to shuttle id
|
||||||
|
if "pdk" in shuttle_config:
|
||||||
|
match shuttle_config["pdk"]:
|
||||||
|
case "sky130A":
|
||||||
|
content["pinout"] = "openframe_sky130"
|
||||||
|
case "ihp-sg13g2":
|
||||||
|
content["pinout"] = "customframe_ihp_sg13g2"
|
||||||
|
case "gf180mcuD":
|
||||||
|
content["pinout"] = "customframe_gf180mcud"
|
||||||
|
case _:
|
||||||
|
logging.warning(
|
||||||
|
f"unable to determine appropriate pinout from PDK, falling back to 'openframe_sky130'"
|
||||||
|
)
|
||||||
|
content["pinout"] = "openframe_sky130"
|
||||||
|
|
||||||
|
logging.info(f"PDK defined, using pinout '{content['pinout']}'")
|
||||||
|
|
||||||
|
else:
|
||||||
|
if shuttle_config["id"].startswith("ttsky"):
|
||||||
|
content["pinout"] = "openframe_sky130"
|
||||||
|
|
||||||
|
elif shuttle_config["id"].startswith("ttihp"):
|
||||||
|
content["pinout"] = "customframe_ihp_sg13g2"
|
||||||
|
|
||||||
|
elif shuttle_config["id"].startswith("ttgf"):
|
||||||
|
content["pinout"] = "customframe_gf180mcud"
|
||||||
|
else:
|
||||||
|
logging.warning(
|
||||||
|
f"unable to determine appropriate pinout, falling back to 'openframe_sky130'"
|
||||||
|
)
|
||||||
|
logging.warning(
|
||||||
|
f"either define 'pinout' or 'pdk' in shuttle config"
|
||||||
|
)
|
||||||
|
content["pinout"] = "openframe_sky130"
|
||||||
|
|
||||||
|
logging.info(
|
||||||
|
f"PDK not defined, using '{content['pinout']}' pinout"
|
||||||
|
)
|
||||||
|
|
||||||
|
if "theme_override_colour" in datasheet_config:
|
||||||
|
content["if_theme_override_colour"] = True
|
||||||
|
content["theme_override_colour"] = datasheet_config[
|
||||||
|
"theme_override_colour"
|
||||||
|
]
|
||||||
|
|
||||||
|
if "show_chip_viewer" in datasheet_config:
|
||||||
|
content["if_chip_viewer"] = datasheet_config["show_chip_viewer"]
|
||||||
|
|
||||||
|
if "link_disable_colour" in datasheet_config:
|
||||||
|
# convert to string and lowercase because python "False" != typst "false"
|
||||||
|
content["link_disable_colour"] = str(
|
||||||
|
datasheet_config["link_disable_colour"]
|
||||||
|
).lower()
|
||||||
|
|
||||||
|
if "link_override_colour" in datasheet_config:
|
||||||
|
content["if_link_override_colour"] = True
|
||||||
|
content["link_override_colour"] = datasheet_config[
|
||||||
|
"link_override_colour"
|
||||||
|
]
|
||||||
|
|
||||||
|
if "qrcode_follows_theme" in datasheet_config:
|
||||||
|
content["qrcode_follows_theme"] = str(
|
||||||
|
datasheet_config["qrcode_follows_theme"]
|
||||||
|
).lower()
|
||||||
|
|
||||||
|
if "include" in datasheet_config:
|
||||||
|
doc_body = []
|
||||||
|
for path in datasheet_config["include"]:
|
||||||
|
logging.info(f"including {os.path.abspath(path)}")
|
||||||
|
|
||||||
|
with open(os.path.abspath(path)) as f:
|
||||||
|
doc_body.append(f.read())
|
||||||
|
|
||||||
|
content["datasheet_body"] = "\n".join(doc_body)
|
||||||
|
|
||||||
|
with open("datasheet.typ", "w") as f:
|
||||||
|
logging.info("writing datasheet.typ")
|
||||||
|
f.write(chevron.render(datasheet_template, content))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def compile(path="datasheet.typ") -> None:
|
||||||
|
typst_cmd = [
|
||||||
|
"typst",
|
||||||
|
"compile",
|
||||||
|
path,
|
||||||
|
"datasheet.pdf",
|
||||||
|
"--root",
|
||||||
|
".",
|
||||||
|
"--font-path",
|
||||||
|
"./tt/docs/typst/resources/fonts",
|
||||||
|
]
|
||||||
|
logging.info(typst_cmd)
|
||||||
|
result = subprocess.call(typst_cmd)
|
||||||
|
|
||||||
|
if result != 0:
|
||||||
|
logging.error("running typst returned non-zero exit code")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def project_is_disabled(config: DatasheetConfig, macro: str) -> bool:
|
||||||
|
if config is not None:
|
||||||
|
if "disabled" in config and config["disabled"] is not None:
|
||||||
|
return macro in config["disabled"]
|
||||||
|
|
||||||
|
return False
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#import "/tt/docs/typst/src/tt.typ" as tt
|
||||||
|
|
||||||
|
#show: tt.datasheet.datasheet.with(
|
||||||
|
shuttle_id: "{{&shuttle_id_upper}}",
|
||||||
|
shuttle_name: "{{&shuttle_name}}",
|
||||||
|
repo_link: "{{&repo_link}}",
|
||||||
|
theme: "bold",
|
||||||
|
|
||||||
|
{{#if_theme_override_colour}}
|
||||||
|
theme_override_colour: {{&theme_override_colour}},
|
||||||
|
{{/if_theme_override_colour}}
|
||||||
|
|
||||||
|
projects: include "datasheet_manifest.typ",
|
||||||
|
|
||||||
|
{{#if_pinout}}
|
||||||
|
show_pinouts: "{{&pinout}}",
|
||||||
|
{{/if_pinout}}
|
||||||
|
|
||||||
|
{{#if_chip_viewer}}
|
||||||
|
chip_viewer_link: "https://tinytapeout.com/decap/{{shuttle_id}}",
|
||||||
|
{{/if_chip_viewer}}
|
||||||
|
|
||||||
|
link_disable_colour: {{link_disable_colour}},
|
||||||
|
|
||||||
|
{{#if_link_override_colour}}
|
||||||
|
link_override_colour: {{&link_override_colour}},
|
||||||
|
{{/if_link_override_colour}}
|
||||||
|
|
||||||
|
qrcode_follows_theme: {{qrcode_follows_theme}},
|
||||||
|
)
|
||||||
|
|
||||||
|
{{&datasheet_body}}
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
#import "../src/lib.typ": annotated_qrcode
|
||||||
|
|
||||||
|
#let call_to_action_page(qrcode_colour: black) = [
|
||||||
|
#align(center)[
|
||||||
|
#heading(level: 1)[Where is #emph(underline("your")) design?]
|
||||||
|
*Go from idea to chip design in minutes, without breaking the bank.*
|
||||||
|
]
|
||||||
|
|
||||||
|
Interested and want to get your own design manufactured? Visit our website and
|
||||||
|
check out our educational material and previous submissions!
|
||||||
|
|
||||||
|
#align(center, heading(level: 2, outlined: false)[How?])
|
||||||
|
New to this? Use our basic Wokwi template to see what's possible. If you're ready for more,
|
||||||
|
use our advanced Wokwi template and unlock some extra pins.
|
||||||
|
|
||||||
|
Know Verilog and CocoTB? Get stuck in with our HDL templates.
|
||||||
|
|
||||||
|
#align(center, heading(level: 2, outlined: false)[When?])
|
||||||
|
Multiple shuttles are run per year, meaning you've got an opportunity to manufacture your design
|
||||||
|
at any time.
|
||||||
|
|
||||||
|
#align(center, heading(level: 2, outlined: false)[Stuck? Need help? Want inspiration?])
|
||||||
|
Come chat to us and our community on Discord! Scan the QR code below.
|
||||||
|
|
||||||
|
#v(1cm)
|
||||||
|
#grid(
|
||||||
|
columns: (1fr, 1fr, 1fr),
|
||||||
|
rows: 1,
|
||||||
|
column-gutter: 1em,
|
||||||
|
align: center,
|
||||||
|
inset: 8pt,
|
||||||
|
fill: white,
|
||||||
|
|
||||||
|
annotated_qrcode(
|
||||||
|
"https://tinytapeout.com",
|
||||||
|
"Website",
|
||||||
|
tiaoma_args: ("scale": 2.0, "fg-color": qrcode_colour)
|
||||||
|
),
|
||||||
|
|
||||||
|
annotated_qrcode(
|
||||||
|
"https://tinytapeout.com/digital_design",
|
||||||
|
"Digital design guide",
|
||||||
|
tiaoma_args: ("scale": 1.75, "fg-color": qrcode_colour)
|
||||||
|
),
|
||||||
|
|
||||||
|
annotated_qrcode(
|
||||||
|
"https://tinytapeout.com/discord",
|
||||||
|
"Discord server",
|
||||||
|
tiaoma_args: ("scale": 2.0, "fg-color": qrcode_colour)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
@@ -0,0 +1,405 @@
|
|||||||
|
#let caravel = {
|
||||||
|
align(center,
|
||||||
|
table(
|
||||||
|
columns: 4,
|
||||||
|
align: left,
|
||||||
|
table.header(
|
||||||
|
[mprj_io_pin], [Function], [Signal], [QFN64 pin]
|
||||||
|
),
|
||||||
|
|
||||||
|
`0`, [Input], [`ui_in[0]`], [`31`],
|
||||||
|
`1`, [Input], [`ui_in[1]`], [`32`],
|
||||||
|
`2`, [Input], [`ui_in[2]`], [`33`],
|
||||||
|
`3`, [Input], [`ui_in[3]`], [`34`],
|
||||||
|
`4`, [Input], [`ui_in[4]`], [`35`],
|
||||||
|
`5`, [Input], [`ui_in[5]`], [`36`],
|
||||||
|
`6`, [Input], [`ui_in[6]`], [`37`],
|
||||||
|
`7`, [Analog], [`analog[0]`], [`41`],
|
||||||
|
`8`, [Analog], [`analog[1]`], [`42`],
|
||||||
|
`9`, [Analog], [`analog[2]`], [`43`],
|
||||||
|
`10`, [Analog], [`analog[3]`], [`44`],
|
||||||
|
`11`, [Analog], [`analog[4]`], [`45`],
|
||||||
|
`12`, [Analog], [`analog[5]`], [`46`],
|
||||||
|
`13`, [Input], [`ui_in[7]`], [48],
|
||||||
|
`14`, [Input], [`clk` $dagger$], [50],
|
||||||
|
`15`, [Input], [`rst_n` $dagger$], [`51`],
|
||||||
|
`16`, [Bidirectional], [`uio[0]`], [`53`],
|
||||||
|
`17`, [Bidirectional], [`uio[1]`], [`54`],
|
||||||
|
`18`, [Bidirectional], [`uio[2]`], [`55`],
|
||||||
|
`19`, [Bidirectional], [`uio[3]`], [`57`],
|
||||||
|
`20`, [Bidirectional], [`uio[4]`], [`58`],
|
||||||
|
`21`, [Bidirectional], [`uio[5]`], [`59`],
|
||||||
|
`22`, [Bidirectional], [`uio[6]`], [`60`],
|
||||||
|
`23`, [Bidirectional], [`uio[7]`], [`61`],
|
||||||
|
`24`, [Output], [`uo_out[0]`], [`62`],
|
||||||
|
`25`, [Output], [`uo_out[1]`], [`2`],
|
||||||
|
`26`, [Output], [`uo_out[2]`], [`3`],
|
||||||
|
`27`, [Output], [`uo_out[3]`], [`4`],
|
||||||
|
`28`, [Output], [`uo_out[4]`], [`5`],
|
||||||
|
`29`, [Output], [`uo_out[5]`], [`6`],
|
||||||
|
`30`, [Output], [`uo_out[6]`], [`7`],
|
||||||
|
`31`, [Output], [`uo_out[7]`], [`8`],
|
||||||
|
`32`, [Analog], [`analog[6]`], [`11`],
|
||||||
|
`33`, [Analog], [`analog[7]`], [`12`],
|
||||||
|
`34`, [Analog], [`analog[8]`], [`13`],
|
||||||
|
`35`, [Analog], [`analog[9]`], [`14`],
|
||||||
|
`36`, [Analog], [`analog[10]`], [`15`],
|
||||||
|
`37`, [Analog], [`analog[11]`], [`16`],
|
||||||
|
`38`, [Mux Control], [`ctrl_ena`], [`22`],
|
||||||
|
`39`, [Mux Control], [`ctrl_sel_inc`], [`24`],
|
||||||
|
`40`, [Mux Control], [`ctrl_sel_rst_n`], [`25`],
|
||||||
|
`41`, [Reserved], [(none)], [`26`],
|
||||||
|
`42`, [Reserved], [(none)], [`27`],
|
||||||
|
`43`, [Reserved], [(none)], [`28`],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
[
|
||||||
|
$dagger$ Internally, there's no difference between `clk`, `rst_n` and `ui_in` pins. They are all just bits in the
|
||||||
|
`pad_ui_in` bus. However, we use different names to make it easier to understand the purpose of each signal.
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#let openframe = {
|
||||||
|
align(center,
|
||||||
|
table(
|
||||||
|
columns: 3,
|
||||||
|
align: left,
|
||||||
|
|
||||||
|
table.header(
|
||||||
|
[QFN64 Pin], [Function], [Signal]
|
||||||
|
),
|
||||||
|
|
||||||
|
`1`, [Mux Control], [`ctrl_ena`],
|
||||||
|
`2`, [Mux Control], [`ctrl_sel_inc`],
|
||||||
|
`3`, [Mux Control], [`ctrl_sel_rst_n`],
|
||||||
|
`4`, [Reserved], [(none)],
|
||||||
|
`5`, [Reserved], [(none)],
|
||||||
|
`6`, [Reserved], [(none)],
|
||||||
|
`7`, [Reserved], [(none)],
|
||||||
|
`8`, [Reserved], [(none)],
|
||||||
|
`9`, [Output], [`uo_out[0]`],
|
||||||
|
`10`, [Output], [`uo_out[1]`],
|
||||||
|
`11`, [Output], [`uo_out[2]`],
|
||||||
|
`12`, [Output], [`uo_out[3]`],
|
||||||
|
`13`, [Output], [`uo_out[4]`],
|
||||||
|
`14`, [Output], [`uo_out[5]`],
|
||||||
|
`15`, [Output], [`uo_out[6]`],
|
||||||
|
`16`, [Output], [`uo_out[7]`],
|
||||||
|
`17`, [Power], [VDD IO],
|
||||||
|
`18`, [Ground], [GND IO],
|
||||||
|
`19`, [Analog], [`analog[0]`],
|
||||||
|
`20`, [Analog], [`analog[1]`],
|
||||||
|
`21`, [Analog], [`analog[2]`],
|
||||||
|
`22`, [Analog], [`analog[3]`],
|
||||||
|
`23`, [Power], [VAA Analog],
|
||||||
|
`24`, [Ground], [GND Analog],
|
||||||
|
`25`, [Analog], [`analog[4]`],
|
||||||
|
`26`, [Analog], [`analog[5]`],
|
||||||
|
`27`, [Analog], [`analog[6]`],
|
||||||
|
`28`, [Analog], [`analog[7]`],
|
||||||
|
`29`, [Ground], [VDD Core],
|
||||||
|
`30`, [Power], [VDD Core],
|
||||||
|
`31`, [Ground], [GND IO],
|
||||||
|
`32`, [Power], [VDD IO],
|
||||||
|
`33`, [Bidirectional], [`uio[0]`],
|
||||||
|
`34`, [Bidirectional], [`uio[1]`],
|
||||||
|
`35`, [Bidirectional], [`uio[2]`],
|
||||||
|
`36`, [Bidirectional], [`uio[3]`],
|
||||||
|
`37`, [Bidirectional], [`uio[4]`],
|
||||||
|
`38`, [Bidirectional], [`uio[5]`],
|
||||||
|
`39`, [Bidirectional], [`uio[6]`],
|
||||||
|
`40`, [Bidirectional], [`uio[7]`],
|
||||||
|
`41`, [Input], [`ui_in[0]`],
|
||||||
|
`42`, [Input], [`ui_in[1]`],
|
||||||
|
`43`, [Input], [`ui_in[2]`],
|
||||||
|
`44`, [Input], [`ui_in[3]`],
|
||||||
|
`45`, [Input], [`ui_in[4]`],
|
||||||
|
`46`, [Input], [`ui_in[5]`],
|
||||||
|
`47`, [Input], [`ui_in[6]`],
|
||||||
|
`48`, [Input], [`ui_in[7]`],
|
||||||
|
`49`, [Input], [`rst_n` $dagger$],
|
||||||
|
`50`, [Input], [`clk` $dagger$],
|
||||||
|
`51`, [Ground], [GND IO],
|
||||||
|
`52`, [Power], [VDD IO],
|
||||||
|
`53`, [Analog], [`analog[8]`],
|
||||||
|
`54`, [Analog], [`analog[9]`],
|
||||||
|
`55`, [Analog], [`analog[10]`],
|
||||||
|
`56`, [Analog], [`analog[11]`],
|
||||||
|
`57`, [Ground], [GND Analog],
|
||||||
|
`58`, [Power], [VDD Analog],
|
||||||
|
`59`, [Analog], [`analog[12]`],
|
||||||
|
`60`, [Analog], [`analog[13]`],
|
||||||
|
`61`, [Analog], [`analog[14]`],
|
||||||
|
`62`, [Analog], [`analog[15]`],
|
||||||
|
`63`, [Ground], [GND Core],
|
||||||
|
`64`, [Power], [VDD Core]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
[
|
||||||
|
$dagger$ Internally, there's no difference between `clk`, `rst_n` and `ui_in` pins. They are all just bits in the
|
||||||
|
`pad_ui_in` bus. However, we use different names to make it easier to understand the purpose of each signal.
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#let openframe_sky130 = {
|
||||||
|
align(center,
|
||||||
|
table(
|
||||||
|
columns: 4,
|
||||||
|
align: left,
|
||||||
|
|
||||||
|
table.header(
|
||||||
|
[QFN64 pin], [mprj_io_pin], [Function], [Signal]
|
||||||
|
),
|
||||||
|
|
||||||
|
`1`, `vssa2`, [Ground], [GND],
|
||||||
|
`2`, `25`, [Output], `uo[1]`,
|
||||||
|
`3`, `26`, [Output], `uo[2]`,
|
||||||
|
`4`, `27`, [Output], `uo[3]`,
|
||||||
|
`5`, `28`, [Output], `uo[4]`,
|
||||||
|
`6`, `29`, [Output], `uo[5]`,
|
||||||
|
`7`, `30`, [Output], `uo[6]`,
|
||||||
|
`8`, `31`, [Output], `uo[7]`,
|
||||||
|
`9`, `vdda2`, [Power], [VDD Analog],
|
||||||
|
`10`, `vssd2`, [Ground], [GND],
|
||||||
|
`11`, `32`, [Analog], `analog[6]`,
|
||||||
|
`12`, `33`, [Analog], `analog[7]`,
|
||||||
|
`13`, `34`, [Analog], `analog[8]`,
|
||||||
|
`14`, `35`, [Analog], `analog[9]`,
|
||||||
|
`15`, `36`, [Analog], `analog[10]`,
|
||||||
|
`16`, `37`, [Analog], `analog[11]`,
|
||||||
|
`17`, `vddio`, [Power], [VDD IO],
|
||||||
|
`18`, `vccd`, [Power], [VDD Core],
|
||||||
|
`19`, `NC`, [], [],
|
||||||
|
`20`, `vssa`, [Ground], [GND],
|
||||||
|
`21`, `resetb`, [], [],
|
||||||
|
`22`, `38`, [Mux], `ctrl_ena`,
|
||||||
|
`23`, `vssd`, [Ground], [GND],
|
||||||
|
`24`, `39`, [Mux], `ctrl_sel_inc`,
|
||||||
|
`25`, `40`, [Mux], `ctrl_sel_rst_n`,
|
||||||
|
`26`, `41`, [Reserved], [(none)],
|
||||||
|
`27`, `42`, [Reserved], [(none)],
|
||||||
|
`28`, `43`, [Reserved], [(none)],
|
||||||
|
`29`, `vssio`, [Ground], [GND],
|
||||||
|
`30`, `vdda`, [Power], [VDD Analog],
|
||||||
|
`31`, `0`, [Input], `ui[0]`,
|
||||||
|
`32`, `1`, [Input], `ui[1]`,
|
||||||
|
`33`, `2`, [Input], `ui[2]`,
|
||||||
|
`34`, `3`, [Input], `ui[3]`,
|
||||||
|
`35`, `4`, [Input], `ui[4]`,
|
||||||
|
`36`, `5`, [Input], `ui[5]`,
|
||||||
|
`37`, `6`, [Input], `ui[6]`,
|
||||||
|
`38`, `vssa1`, [Ground], [GND],
|
||||||
|
`39`, `vssd1`, [Ground], [GND],
|
||||||
|
`40`, `vdda1`, [Power], [VDD Analog],
|
||||||
|
`41`, `7`, [Analog], `analog[0]`,
|
||||||
|
`42`, `8`, [Analog], `analog[1]`,
|
||||||
|
`43`, `9`, [Analog], `analog[2]`,
|
||||||
|
`44`, `10`, [Analog], `analog[3]`,
|
||||||
|
`45`, `11`, [Analog], `analog[4]`,
|
||||||
|
`46`, `12`, [Analog], `analog[5]`,
|
||||||
|
`47`, `vdda1`, [Power], [VDD Analog],
|
||||||
|
`48`, `13`, [Input], `ui[7]`,
|
||||||
|
`49`, `vccd1`, [Power], [VDD Core],
|
||||||
|
`50`, `14`, [Input], [`u_clk` $dagger$],
|
||||||
|
`51`, `15`, [Input], [`u_rst_n` $dagger$],
|
||||||
|
`52`, `vssa1`, [Ground], [GND],
|
||||||
|
`53`, `16`, [Bidirectional], `io[0]`,
|
||||||
|
`54`, `17`, [Bidirectional], `io[1]`,
|
||||||
|
`55`, `18`, [Bidirectional], `io[2]`,
|
||||||
|
`56`, `vssio`, [Ground], [GND],
|
||||||
|
`57`, `19`, [Bidirectional], `io[3]`,
|
||||||
|
`58`, `20`, [Bidirectional], `io[4]`,
|
||||||
|
`59`, `21`, [Bidirectional], `io[5]`,
|
||||||
|
`60`, `22`, [Bidirectional], `io[6]`,
|
||||||
|
`61`, `23`, [Bidirectional], `io[7]`,
|
||||||
|
`62`, `24`, [Output], `uo[0]`,
|
||||||
|
`63`, `vccd2`, [Power], [VDD Core],
|
||||||
|
`64`, `vddio`, [Power], [VDD IO],
|
||||||
|
`EPAD`, [], [Ground], []
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
[
|
||||||
|
$dagger$ Internally, there's no difference between `u_clk`, `u_rst_n`, and `ui` pins. They are all just bits in the
|
||||||
|
`pad_ui_in` bus. However, we use different names to make it easier to understand the purpose of each signal.
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#let customframe_ihp_sg13g2 = {
|
||||||
|
align(center,
|
||||||
|
table(
|
||||||
|
columns: 4,
|
||||||
|
align: left,
|
||||||
|
|
||||||
|
table.header(
|
||||||
|
[Die Pad], [QFN64 pin], [Function], [Signal]
|
||||||
|
),
|
||||||
|
|
||||||
|
`0`, `1`, [Mux Control], `ctrl_ena`,
|
||||||
|
`1`, `2`, [Mux Control], `ctrl_sel_inc`,
|
||||||
|
`2`, `3`, [Mux Control], `ctrl_sel_rst_n`,
|
||||||
|
`3`, `4`, [Reserved], [],
|
||||||
|
`4`, `5`, [Reserved], [],
|
||||||
|
`5`, `6`, [Reserved], [],
|
||||||
|
`6`, `7`, [Reserved], [],
|
||||||
|
`7`, `8`, [Reserved], [],
|
||||||
|
`8`, `9`, [Output], `uo[0]`,
|
||||||
|
`9`, `10`, [Output], `uo[1]`,
|
||||||
|
`10`, `11`, [Output], `uo[2]`,
|
||||||
|
`11`, `12`, [Output], `uo[3]`,
|
||||||
|
`12`, `13`, [Output], `uo[4]`,
|
||||||
|
`13`, `14`, [Output], `uo[5]`,
|
||||||
|
`14`, `15`, [Output], `uo[6]`,
|
||||||
|
`15`, `16`, [Output], `uo[7]`,
|
||||||
|
`16`, `17`, [Power], [VDD IO],
|
||||||
|
`17`, `18`, [Ground], [GND IO],
|
||||||
|
`18`, `19`, [Analog], `analog[0]`,
|
||||||
|
`19`, `20`, [Analog], `analog[1]`,
|
||||||
|
`20`, `21`, [Analog], `analog[2]`,
|
||||||
|
`21`, `22`, [Analog], `analog[3]`,
|
||||||
|
`22`, `23`, [Power], [VDD Analog],
|
||||||
|
`23`, `24`, [Ground], [GND Analog],
|
||||||
|
`24`, `25`, [Analog], `analog[4]`,
|
||||||
|
`25`, `26`, [Analog], `analog[5]`,
|
||||||
|
`26`, `27`, [Analog], `analog[6]`,
|
||||||
|
`27`, `28`, [Analog], `analog[7]`,
|
||||||
|
`28`, `29`, [Ground], [GND Core],
|
||||||
|
`29`, `30`, [Power], [VDD Core],
|
||||||
|
`30`, `31`, [Ground], [GND IO],
|
||||||
|
`31`, `32`, [Power], [VDD IO],
|
||||||
|
`32`, `33`, [Bidirectional], `uio[0]`,
|
||||||
|
`33`, `34`, [Bidirectional], `uio[1]`,
|
||||||
|
`34`, `35`, [Bidirectional], `uio[2]`,
|
||||||
|
`35`, `36`, [Bidirectional], `uio[3]`,
|
||||||
|
`36`, `37`, [Bidirectional], `uio[4]`,
|
||||||
|
`37`, `38`, [Bidirectional], `uio[5]`,
|
||||||
|
`38`, `39`, [Bidirectional], `uio[6]`,
|
||||||
|
`39`, `40`, [Bidirectional], `uio[7]`,
|
||||||
|
`40`, `41`, [Input], `ui[0]`,
|
||||||
|
`41`, `42`, [Input], `ui[1]`,
|
||||||
|
`42`, `43`, [Input], `ui[2]`,
|
||||||
|
`43`, `44`, [Input], `ui[3]`,
|
||||||
|
`44`, `45`, [Input], `ui[4]`,
|
||||||
|
`45`, `46`, [Input], `ui[5]`,
|
||||||
|
`46`, `47`, [Input], `ui[6]`,
|
||||||
|
`47`, `48`, [Input], `ui[7]`,
|
||||||
|
`48`, `49`, [Input], [`u_rst_n` $dagger$],
|
||||||
|
`49`, `50`, [Input], [`u_clk` $dagger$],
|
||||||
|
`50`, `51`, [Ground], [GND IO],
|
||||||
|
`51`, `52`, [Power], [VDD IO],
|
||||||
|
`52`, `53`, [Analog], `analog[8]`,
|
||||||
|
`53`, `54`, [Analog], `analog[9]`,
|
||||||
|
`54`, `55`, [Analog], `analog[10]`,
|
||||||
|
`55`, `56`, [Analog], `analog[11]`,
|
||||||
|
`56`, `57`, [Ground], [GND Analog],
|
||||||
|
`57`, `58`, [Power], [VDD Analog],
|
||||||
|
`58`, `59`, [Analog], `analog[12]`,
|
||||||
|
`59`, `60`, [Analog], `analog[13]`,
|
||||||
|
`60`, `61`, [Analog], `analog[14]`,
|
||||||
|
`61`, `62`, [Analog], `analog[15]`,
|
||||||
|
`62`, `63`, [Ground], [GND Core],
|
||||||
|
`63`, `64`, [Power], [VDD Core],
|
||||||
|
`SUB`, `EPAD`, [Ground], [],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
[
|
||||||
|
$dagger$ Internally, there's no difference between `u_clk`, `u_rst_n`, and `ui` pins. They are all just bits in the
|
||||||
|
`pad_ui_in` bus. However, we use different names to make it easier to understand the purpose of each signal.
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#let customframe_gf180mcud = {
|
||||||
|
align(center,
|
||||||
|
table(
|
||||||
|
columns: 4,
|
||||||
|
align: left,
|
||||||
|
|
||||||
|
table.header(
|
||||||
|
[Die Pad], [QFN64 pin], [Function], [Signal]
|
||||||
|
),
|
||||||
|
|
||||||
|
`0`, `1`, [Mux Control], `ctrl_ena`,
|
||||||
|
`1`, `2`, [Mux Control], `ctrl_sel_inc`,
|
||||||
|
`2`, `3`, [Mux Control], `ctrl_sel_rst_n`,
|
||||||
|
`3`, `4`, [Reserved], [],
|
||||||
|
`4`, `5`, [Reserved], [],
|
||||||
|
`5`, `6`, [Reserved], [],
|
||||||
|
`6`, `7`, [Reserved], [],
|
||||||
|
`7`, `8`, [Reserved], [],
|
||||||
|
`8`, `EPAD`, [Ground], [GND IO],
|
||||||
|
`9`, `9`, [Output], `uo[0]`,
|
||||||
|
`10`, `10`, [Output], `uo[1]`,
|
||||||
|
`11`, `11`, [Output], `uo[2]`,
|
||||||
|
`12`, `12`, [Output], `uo[3]`,
|
||||||
|
`13`, `13`, [Output], `uo[4]`,
|
||||||
|
`14`, `14`, [Output], `uo[5]`,
|
||||||
|
`15`, `15`, [Output], `uo[6]`,
|
||||||
|
`16`, `16`, [Output], `uo[7]`,
|
||||||
|
`17`, `17`, [Power], [VDD IO],
|
||||||
|
`18`, `EPAD`, [Ground], [GND IO],
|
||||||
|
`19`, `18`, [Analog], `analog[0]`,
|
||||||
|
`20`, `19`, [Analog], `analog[1]`,
|
||||||
|
`21`, `20`, [Analog], `analog[2]`,
|
||||||
|
`22`, `21`, [Analog], `analog[3]`,
|
||||||
|
`23`, `22`, [Analog], `analog[4]`,
|
||||||
|
`24`, `23`, [Analog], `analog[5]`,
|
||||||
|
`25`, `24`, [Power], [PWR Analog],
|
||||||
|
`26`, `EPAD`, [Ground], [GND Analog],
|
||||||
|
`27`, `25`, [Analog], `analog[6]`,
|
||||||
|
`28`, `26`, [Analog], `analog[7]`,
|
||||||
|
`29`, `27`, [Analog], `analog[8]`,
|
||||||
|
`30`, `28`, [Analog], `analog[9]`,
|
||||||
|
`31`, `29`, [Analog], `analog[10]`,
|
||||||
|
`32`, `30`, [Analog], `analog[11]`,
|
||||||
|
`33`, `EPAD`, [Ground], [GND Core],
|
||||||
|
`34`, `31`, [Power], [VDD Core],
|
||||||
|
`35`, `EPAD`, [Ground], [GND IO],
|
||||||
|
`36`, `32`, [Power], [VDD IO],
|
||||||
|
`37`, `33`, [Bidirectional], `uio[0]`,
|
||||||
|
`38`, `34`, [Bidirectional], `uio[1]`,
|
||||||
|
`39`, `35`, [Bidirectional], `uio[2]`,
|
||||||
|
`40`, `36`, [Bidirectional], `uio[3]`,
|
||||||
|
`41`, `37`, [Bidirectional], `uio[4]`,
|
||||||
|
`42`, `38`, [Bidirectional], `uio[5]`,
|
||||||
|
`43`, `39`, [Bidirectional], `uio[6]`,
|
||||||
|
`44`, `40`, [Bidirectional], `uio[7]`,
|
||||||
|
`45`, `EPAD`, [Ground], [GND IO],
|
||||||
|
`46`, `41`, [Input], `ui[0]`,
|
||||||
|
`47`, `42`, [Input], `ui[1]`,
|
||||||
|
`48`, `43`, [Input], `ui[2]`,
|
||||||
|
`49`, `44`, [Input], `ui[3]`,
|
||||||
|
`50`, `45`, [Input], `ui[4]`,
|
||||||
|
`51`, `46`, [Input], `ui[5]`,
|
||||||
|
`52`, `47`, [Input], `ui[6]`,
|
||||||
|
`53`, `48`, [Input], `ui[7]`,
|
||||||
|
`54`, `49`, [Input], [`u_rst_n` $dagger$],
|
||||||
|
`55`, `50`, [Input], [`u_clk` $dagger$],
|
||||||
|
`56`, `EPAD`, [Ground], [GND IO],
|
||||||
|
`57`, `51`, [Power], [VDD IO],
|
||||||
|
`58`, `52`, [Analog], `analog[12]`,
|
||||||
|
`59`, `53`, [Analog], `analog[13]`,
|
||||||
|
`60`, `54`, [Analog], `analog[14]`,
|
||||||
|
`61`, `55`, [Analog], `analog[15]`,
|
||||||
|
`62`, `EPAD`, [Ground], [GND Analog],
|
||||||
|
`63`, `56`, [Power], [PWR Analog],
|
||||||
|
`64`, `57`, [Analog], `analog[16]`,
|
||||||
|
`65`, `58`, [Analog], `analog[17]`,
|
||||||
|
`66`, `59`, [Analog], `analog[18]`,
|
||||||
|
`67`, `60`, [Analog], `analog[19]`,
|
||||||
|
`68`, `61`, [Analog], `analog[20]`,
|
||||||
|
`69`, `62`, [Analog], `analog[21]`,
|
||||||
|
`70`, `EPAD`, [Ground], [GND Core],
|
||||||
|
`71`, `63`, [Power], [VDD Core],
|
||||||
|
`72`, `EPAD`, [Ground], [GND IO],
|
||||||
|
`73`, `64`, [Power], [VDD IO]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
[
|
||||||
|
$dagger$ Internally, there's no difference between `u_clk`, `u_rst_n`, and `ui` pins. They are all just bits in the
|
||||||
|
`pad_ui_in` bus. However, we use different names to make it easier to understand the purpose of each signal.
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
= Team
|
||||||
|
Tiny Tapeout would not be possible without a lot of people helping. We would especially like to thank:
|
||||||
|
- *Uri Shaked* for #link("https://wokwi.com")[Wokwi] development and lots more
|
||||||
|
- #link("https://psychogenic.com/")[*Patrick Deegan*] for PCBs, software, documentation and lots more
|
||||||
|
- #link("https://twitter.com/tnt")[*Sylvain Munaut*] for help with scan chain improvements
|
||||||
|
- #link("https://www.linkedin.com/in/michael-thompson-0a581a/")[*Mike Thompson*] and
|
||||||
|
#link("https://www.linkedin.com/in/mitch-bailey-8ba0b45/")[*Mitch Bailey*] for verification expertise
|
||||||
|
- #link("https://www.linkedin.com/in/tim-edwards-4376a18/")[*Tim Edwards*] and
|
||||||
|
#link("https://www.linkedin.com/in/harald-pretl-4911ba10/")[*Harald Pretl*] for ASIC expertise
|
||||||
|
- #link("https://twitter.com/jix_")[*Jix*] for formal verification support
|
||||||
|
- #link("https://twitter.com/proppy")[*Proppy*] for help with GitHub actions
|
||||||
|
- #link("https://twitter.com/maxiborga")[*Maximo Balestrini*] for all the amazing renders and the interactive GDS viewer
|
||||||
|
- *James Rosenthal* for coming up with digital design examples
|
||||||
|
- All the *people who took part in TinyTapeout 01* and volunteered time to improve docs and test the flow
|
||||||
|
- The #link("https://www.yosyshq.com/")[*team at YosysHQ*] and *all the other open source EDA tool makes*
|
||||||
|
- *Jeff* and the #link("https://efabless.com/")[*Efabless Team*] for running the shuttles and providing OpenLane and
|
||||||
|
sponsorship
|
||||||
|
- #link("https://www.youtube.com/watch?v=EczW2IWdnOM")[*Tim Ansell* and *Google*] for supporting the open source silicon
|
||||||
|
movement
|
||||||
|
- #link("https://zerotoasiccourse.com/")[*Zero to ASIC course community*] for all your support
|
||||||
|
- *Jeremy Birch* for help with STA
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
#import "../src/lib.typ": get_image_by_id
|
||||||
|
|
||||||
|
= The Tiny Tapeout Multiplexer
|
||||||
|
== Overview
|
||||||
|
The Tiny Tapeout Multiplexer distributes a single set of user IOs to multiple user designs. It is the backbone of the
|
||||||
|
Tiny Tapeout chip.
|
||||||
|
|
||||||
|
It has the following features:
|
||||||
|
- 10 dedicated inputs
|
||||||
|
- 8 dedicated outputs
|
||||||
|
- 8 bidirectional outputs
|
||||||
|
- Supports up to 512 user designs (32 mux units, each with up to 16 designs)
|
||||||
|
- Designs can have different sizes. The basic unit is called a tile, and each design can occupy up to 16 tiles.
|
||||||
|
|
||||||
|
== Operation
|
||||||
|
The multiplexer consists of three main units:
|
||||||
|
1. The controller $dash.em$ used to set the address of the active design
|
||||||
|
2. The spine $dash.em$ a bus that connects the controller with all the mux units
|
||||||
|
3. Mux units $dash.em$ connects the spine to individual user designs
|
||||||
|
|
||||||
|
=== The Controller
|
||||||
|
The mux controller has 3 input lines:
|
||||||
|
#align(center)[
|
||||||
|
#table(
|
||||||
|
columns: 2, rows: 4,
|
||||||
|
table.header(
|
||||||
|
[Input], [Description]
|
||||||
|
),
|
||||||
|
`ena`, [Sent as-is (buffered) to the downstream mux units],
|
||||||
|
`sel_rst_n`, [Resets the internal address counter to 0 (active low)],
|
||||||
|
`sel_inc`, [Increments the internal address counter by 1]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
It outputs the address of the currently selected design on the `si_sel` port of the spine (see below).
|
||||||
|
|
||||||
|
For instance, to select the design at address 12, you need to pulse `sel_rst_n` low, and then pulse `sel_inc` 12 times:
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
caption: [Mux signals for activating the design at address 12],
|
||||||
|
align(center, get_image_by_id("images", "mux_address_select_waveform"))
|
||||||
|
)
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
caption: [Mux Diagram],
|
||||||
|
align(center, get_image_by_id("images", "mux_diagram", height: 55%))
|
||||||
|
)
|
||||||
|
|
||||||
|
#figure(
|
||||||
|
caption: [Mux Controller Diagram],
|
||||||
|
align(center, get_image_by_id("images", "mux_controller"))
|
||||||
|
)
|
||||||
|
|
||||||
|
Internally, the controller is just a chain of 10 D-flip-flops. The `sel_inc` signal is connected to the clock of the
|
||||||
|
first flip-flop, and the output of each flip-flop is connected to the clock of the next flip-flop. The `sel_rst_n`
|
||||||
|
signal is connected to the reset of all flip-flops.
|
||||||
|
|
||||||
|
The following Wokwi project demonstrates this setup:
|
||||||
|
#link("https://wokwi.com/projects/36434780766")[wokwi.com/projects/36434780766].
|
||||||
|
It contains an Arduino Nano that decodes the currently selected mux address and displays it on a 7-segment display.
|
||||||
|
Click on the button labeled `RST_N` to reset the counter, and click on the button labeled `INC` to increment the counter.
|
||||||
|
|
||||||
|
=== The Spine
|
||||||
|
The controller and all muxes are connected together through the spine. The spine has the following signals going to it:
|
||||||
|
|
||||||
|
*From controller to mux*:
|
||||||
|
- `si_ena` $dash.em$ the `ena` input
|
||||||
|
- `si_sel` $dash.em$ selected design address (10 bits)
|
||||||
|
- `ui_in` $dash.em$ user clock, user `rst_n`, user_inputs (10 bits)
|
||||||
|
- `uio_in` $dash.em$ bidirectional I/O inputs (8 bits)
|
||||||
|
|
||||||
|
*From mux to controller*:
|
||||||
|
- `uo_out` $dash.em$ user outputs (8 bits)
|
||||||
|
- `uio_oe` $dash.em$ bidirectional I/O output enable (8 bits)
|
||||||
|
- `uio_out` $dash.em$ bidirectional I/O outputs (8 bits)
|
||||||
|
|
||||||
|
The only signal which is actually generated by the controller is `si_sel` (using `sel_rst_n` and `sel_inc`, as explained
|
||||||
|
above). The other signals are just going through from/to chip I/O pads.
|
||||||
|
|
||||||
|
=== The Multiplexer
|
||||||
|
Each mux branch is connected to up to 16 designs. It also has 5 bits of a hard-coded address (each unit gets assigned
|
||||||
|
a different address, based on its position on the die).
|
||||||
|
|
||||||
|
The mux implements the following logic:
|
||||||
|
If `si_ena` is 1, and `si_sel` matches the mux address, we know the mux is active. Then, it activates the specific
|
||||||
|
user design port that matches the remaining bits of `si_sel`.
|
||||||
|
|
||||||
|
*For the active design*:
|
||||||
|
- `clk`, `rst_n`, `ui_in`, `uio_in` are connected to the respective pins coming from the spine (through a buffer)
|
||||||
|
- `uo_out`, `uio_oe`, `uio_out` are connected to the respective pins going out to the spine (through a tristate buffer)
|
||||||
|
|
||||||
|
*For all others, inactive designs* (including all designs in inactive muxes):
|
||||||
|
- `clk`, `rst_n`, `ui_in`, `uio_in` are all tied to zero
|
||||||
|
- `uo_out`, `uio_oe`, `uio_out` are disconnected from the spine (the tristate buffer output enable is disabled)
|
||||||
|
|
||||||
|
== Pinout
|
||||||
|
// this pinout table must be populated from within the datasheet template itself
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
#import "../src/lib.typ": badge, callout
|
||||||
|
#import "../src/colours.typ"
|
||||||
|
|
||||||
|
= Using This Datasheet
|
||||||
|
|
||||||
|
== Structure
|
||||||
|
Projects are ordered by their mux address, in ascending order. Documentation is user-provided from their GitHub
|
||||||
|
repositories and are merged into the final shuttle once the deadline is reached.
|
||||||
|
|
||||||
|
In general, each project should contain:
|
||||||
|
- The user-provided title & a list of authors
|
||||||
|
- A link to the GitHub repository used for submission
|
||||||
|
- A link to the Wokwi project (if applicable)
|
||||||
|
- A "How it works" section
|
||||||
|
- A "How to test" section
|
||||||
|
- An "External hardware" section (if applicable)
|
||||||
|
- A pinout table for both digital & analog designs
|
||||||
|
|
||||||
|
== Badges
|
||||||
|
This datasheet uses "badges" to quickly convey some information about the content. These badges are explained in the
|
||||||
|
table below.
|
||||||
|
|
||||||
|
#let example_address_badge = box(baseline: 0.4em, badge(colours.BADGE_TEAL, strong(raw("-123"))))
|
||||||
|
#let example_microtile_addr_badge = box(baseline: 0.4em, badge(colours.BADGE_TEAL, strong(raw("-423/2"))))
|
||||||
|
#let example_clock_badge = box(baseline: 0.4em, badge(colours.BADGE_BLUE, strong(raw("25.175 MHz"))))
|
||||||
|
#let example_proj_wokwi = box(baseline: 0.4em, badge(colours.BADGE_PINK, strong(raw("Wokwi Project"))))
|
||||||
|
#let example_proj_hdl = box(baseline: 0.4em, badge(colours.BADGE_PINK, strong(raw("HDL Project"))))
|
||||||
|
#let example_proj_analog = box(baseline: 0.4em, badge(colours.BADGE_PINK, strong(raw("Analog Project"))))
|
||||||
|
#let example_proj_analog = box(baseline: 0.4em, badge(colours.BADGE_PINK, strong(raw("Analog Project"))))
|
||||||
|
#let example_medium_danger = box(baseline: 0.4em, badge(colours.WARNING_STRONG, strong(raw("Medium Danger"))))
|
||||||
|
#let example_high_danger = box(baseline: 0.4em, badge(colours.DANGER_STRONG, strong(raw("High Danger"))))
|
||||||
|
#let example_artwork = box(baseline: 0.4em, badge(colours.BADGE_PURPLE, strong(raw("Artwork"))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#table(
|
||||||
|
columns: 2,
|
||||||
|
inset: 6pt,
|
||||||
|
align: (center + horizon, left),
|
||||||
|
|
||||||
|
table.header(
|
||||||
|
[Badge], [Description]
|
||||||
|
),
|
||||||
|
|
||||||
|
example_artwork, [Used to showcase artwork from our community.],
|
||||||
|
|
||||||
|
[#example_address_badge \ #example_microtile_addr_badge], [Mux address of the project, in decimal. For microtile
|
||||||
|
designs, their sub-address is placed after the forward slash. In this example, it would be `2`.],
|
||||||
|
|
||||||
|
example_clock_badge, [Clock frequency of the project. May be truncated from actual value or omitted completely.],
|
||||||
|
|
||||||
|
[#example_proj_hdl \ #example_proj_wokwi \ #example_proj_analog], [Project type, indicating if it was made with a HDL,
|
||||||
|
Wokwi, or if it is analog.],
|
||||||
|
|
||||||
|
[#example_medium_danger \ #example_high_danger], [Indicates the risk that the project presents to the ASIC. Medium
|
||||||
|
danger projects can damage the ASIC under certain conditions, whilst high danger projects _will_ damage the ASIC.]
|
||||||
|
)
|
||||||
|
|
||||||
|
== Callouts
|
||||||
|
In addition to #example_medium_danger and #example_high_danger badges being used, a callout is placed before the project
|
||||||
|
documentation begins to alert the user.
|
||||||
|
|
||||||
|
A callout for #example_medium_danger may look something like:
|
||||||
|
#callout("warning",
|
||||||
|
`This project will damage the ASIC under certain conditions.`,
|
||||||
|
`There is an error in the schematic which may lead to ASIC failure under certain clocking conditions.`
|
||||||
|
)
|
||||||
|
|
||||||
|
Similarly, a callout for #example_high_danger may look something like:
|
||||||
|
#callout("danger",
|
||||||
|
`This project will damage the ASIC.`,
|
||||||
|
`There is an error in the schematic which may cause permanent damage when powered on in a certain configuration.`
|
||||||
|
)
|
||||||
|
|
||||||
|
Should there be a project that poses a danger, the callout will explain the reasoning behind the danger level.
|
||||||
|
|
||||||
|
Callouts may also provide some additional information, and look something like so:
|
||||||
|
#callout("info",
|
||||||
|
`Information`,
|
||||||
|
raw("Silicon melts at 1414°C, and boils at 3265°C. Don't let your chip get too hot!")
|
||||||
|
)
|
||||||
|
|
||||||
|
== Figures & Footnotes
|
||||||
|
Numbering for figures and footnotes within the "Project" chapter is formed by combining the address of the project with
|
||||||
|
the current figure number. For example, the second figure for a project with an address of 256 will be captioned with
|
||||||
|
"Figure 256.2". Likewise, the third footnote for a project of address 128 will be shown as "128.3".
|
||||||
|
|
||||||
|
The numbering outside of the "Project" chapter resumes as normal, being formatted with a simple number, e.g. "Figure 3".
|
||||||
|
|
||||||
|
== Updates
|
||||||
|
This datasheet is intended to be a living and breathing document. Please update your projects' datasheet with new
|
||||||
|
information if you have it, by creating a pull request against the shuttle repository.
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
Copyright 2021 The Martian Mono Project Authors (https://github.com/evilmartians/mono)
|
||||||
|
|
||||||
|
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:
|
||||||
|
https://openfontlicense.org
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
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.
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
Martian Mono Variable Font
|
||||||
|
==========================
|
||||||
|
|
||||||
|
This download contains Martian Mono as both a variable font and static fonts.
|
||||||
|
|
||||||
|
Martian Mono is a variable font with these axes:
|
||||||
|
wdth
|
||||||
|
wght
|
||||||
|
|
||||||
|
This means all the styles are contained in a single file:
|
||||||
|
Martian_Mono/MartianMono-VariableFont_wdth,wght.ttf
|
||||||
|
|
||||||
|
If your app fully supports variable fonts, you can now pick intermediate styles
|
||||||
|
that aren’t available as static fonts. Not all apps support variable fonts, and
|
||||||
|
in those cases you can use the static font files for Martian Mono:
|
||||||
|
Martian_Mono/static/MartianMono_Condensed-Thin.ttf
|
||||||
|
Martian_Mono/static/MartianMono_Condensed-ExtraLight.ttf
|
||||||
|
Martian_Mono/static/MartianMono_Condensed-Light.ttf
|
||||||
|
Martian_Mono/static/MartianMono_Condensed-Regular.ttf
|
||||||
|
Martian_Mono/static/MartianMono_Condensed-Medium.ttf
|
||||||
|
Martian_Mono/static/MartianMono_Condensed-SemiBold.ttf
|
||||||
|
Martian_Mono/static/MartianMono_Condensed-Bold.ttf
|
||||||
|
Martian_Mono/static/MartianMono_Condensed-ExtraBold.ttf
|
||||||
|
Martian_Mono/static/MartianMono_SemiCondensed-Thin.ttf
|
||||||
|
Martian_Mono/static/MartianMono_SemiCondensed-ExtraLight.ttf
|
||||||
|
Martian_Mono/static/MartianMono_SemiCondensed-Light.ttf
|
||||||
|
Martian_Mono/static/MartianMono_SemiCondensed-Regular.ttf
|
||||||
|
Martian_Mono/static/MartianMono_SemiCondensed-Medium.ttf
|
||||||
|
Martian_Mono/static/MartianMono_SemiCondensed-SemiBold.ttf
|
||||||
|
Martian_Mono/static/MartianMono_SemiCondensed-Bold.ttf
|
||||||
|
Martian_Mono/static/MartianMono_SemiCondensed-ExtraBold.ttf
|
||||||
|
Martian_Mono/static/MartianMono-Thin.ttf
|
||||||
|
Martian_Mono/static/MartianMono-ExtraLight.ttf
|
||||||
|
Martian_Mono/static/MartianMono-Light.ttf
|
||||||
|
Martian_Mono/static/MartianMono-Regular.ttf
|
||||||
|
Martian_Mono/static/MartianMono-Medium.ttf
|
||||||
|
Martian_Mono/static/MartianMono-SemiBold.ttf
|
||||||
|
Martian_Mono/static/MartianMono-Bold.ttf
|
||||||
|
Martian_Mono/static/MartianMono-ExtraBold.ttf
|
||||||
|
Martian_Mono/static/MartianMono_SemiExpanded-Thin.ttf
|
||||||
|
Martian_Mono/static/MartianMono_SemiExpanded-ExtraLight.ttf
|
||||||
|
Martian_Mono/static/MartianMono_SemiExpanded-Light.ttf
|
||||||
|
Martian_Mono/static/MartianMono_SemiExpanded-Regular.ttf
|
||||||
|
Martian_Mono/static/MartianMono_SemiExpanded-Medium.ttf
|
||||||
|
Martian_Mono/static/MartianMono_SemiExpanded-SemiBold.ttf
|
||||||
|
Martian_Mono/static/MartianMono_SemiExpanded-Bold.ttf
|
||||||
|
Martian_Mono/static/MartianMono_SemiExpanded-ExtraBold.ttf
|
||||||
|
|
||||||
|
Get started
|
||||||
|
-----------
|
||||||
|
|
||||||
|
1. Install the font files you want to use
|
||||||
|
|
||||||
|
2. Use your app's font picker to view the font family and all the
|
||||||
|
available styles
|
||||||
|
|
||||||
|
Learn more about variable fonts
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
|
||||||
|
https://variablefonts.typenetwork.com
|
||||||
|
https://medium.com/variable-fonts
|
||||||
|
|
||||||
|
In desktop apps
|
||||||
|
|
||||||
|
https://theblog.adobe.com/can-variable-fonts-illustrator-cc
|
||||||
|
https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
|
||||||
|
|
||||||
|
Online
|
||||||
|
|
||||||
|
https://developers.google.com/fonts/docs/getting_started
|
||||||
|
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
|
||||||
|
https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
|
||||||
|
|
||||||
|
Installing fonts
|
||||||
|
|
||||||
|
MacOS: https://support.apple.com/en-us/HT201749
|
||||||
|
Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
|
||||||
|
Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
|
||||||
|
|
||||||
|
Android Apps
|
||||||
|
|
||||||
|
https://developers.google.com/fonts/docs/android
|
||||||
|
https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
Please read the full license text (OFL.txt) to understand the permissions,
|
||||||
|
restrictions and requirements for usage, redistribution, and modification.
|
||||||
|
|
||||||
|
You can use them in your products & projects – print or digital,
|
||||||
|
commercial or otherwise.
|
||||||
|
|
||||||
|
This isn't legal advice, please consider consulting a lawyer and see the full
|
||||||
|
license for all details.
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,93 @@
|
|||||||
|
Copyright 2024 The Montserrat.Git Project Authors (https://github.com/JulietaUla/Montserrat.git)
|
||||||
|
|
||||||
|
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:
|
||||||
|
https://openfontlicense.org
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
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.
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
Montserrat Variable Font
|
||||||
|
========================
|
||||||
|
|
||||||
|
This download contains Montserrat as both variable fonts and static fonts.
|
||||||
|
|
||||||
|
Montserrat is a variable font with this axis:
|
||||||
|
wght
|
||||||
|
|
||||||
|
This means all the styles are contained in these files:
|
||||||
|
Montserrat/Montserrat-VariableFont_wght.ttf
|
||||||
|
Montserrat/Montserrat-Italic-VariableFont_wght.ttf
|
||||||
|
|
||||||
|
If your app fully supports variable fonts, you can now pick intermediate styles
|
||||||
|
that aren’t available as static fonts. Not all apps support variable fonts, and
|
||||||
|
in those cases you can use the static font files for Montserrat:
|
||||||
|
Montserrat/static/Montserrat-Thin.ttf
|
||||||
|
Montserrat/static/Montserrat-ExtraLight.ttf
|
||||||
|
Montserrat/static/Montserrat-Light.ttf
|
||||||
|
Montserrat/static/Montserrat-Regular.ttf
|
||||||
|
Montserrat/static/Montserrat-Medium.ttf
|
||||||
|
Montserrat/static/Montserrat-SemiBold.ttf
|
||||||
|
Montserrat/static/Montserrat-Bold.ttf
|
||||||
|
Montserrat/static/Montserrat-ExtraBold.ttf
|
||||||
|
Montserrat/static/Montserrat-Black.ttf
|
||||||
|
Montserrat/static/Montserrat-ThinItalic.ttf
|
||||||
|
Montserrat/static/Montserrat-ExtraLightItalic.ttf
|
||||||
|
Montserrat/static/Montserrat-LightItalic.ttf
|
||||||
|
Montserrat/static/Montserrat-Italic.ttf
|
||||||
|
Montserrat/static/Montserrat-MediumItalic.ttf
|
||||||
|
Montserrat/static/Montserrat-SemiBoldItalic.ttf
|
||||||
|
Montserrat/static/Montserrat-BoldItalic.ttf
|
||||||
|
Montserrat/static/Montserrat-ExtraBoldItalic.ttf
|
||||||
|
Montserrat/static/Montserrat-BlackItalic.ttf
|
||||||
|
|
||||||
|
Get started
|
||||||
|
-----------
|
||||||
|
|
||||||
|
1. Install the font files you want to use
|
||||||
|
|
||||||
|
2. Use your app's font picker to view the font family and all the
|
||||||
|
available styles
|
||||||
|
|
||||||
|
Learn more about variable fonts
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
|
||||||
|
https://variablefonts.typenetwork.com
|
||||||
|
https://medium.com/variable-fonts
|
||||||
|
|
||||||
|
In desktop apps
|
||||||
|
|
||||||
|
https://theblog.adobe.com/can-variable-fonts-illustrator-cc
|
||||||
|
https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
|
||||||
|
|
||||||
|
Online
|
||||||
|
|
||||||
|
https://developers.google.com/fonts/docs/getting_started
|
||||||
|
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
|
||||||
|
https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
|
||||||
|
|
||||||
|
Installing fonts
|
||||||
|
|
||||||
|
MacOS: https://support.apple.com/en-us/HT201749
|
||||||
|
Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
|
||||||
|
Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
|
||||||
|
|
||||||
|
Android Apps
|
||||||
|
|
||||||
|
https://developers.google.com/fonts/docs/android
|
||||||
|
https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
Please read the full license text (OFL.txt) to understand the permissions,
|
||||||
|
restrictions and requirements for usage, redistribution, and modification.
|
||||||
|
|
||||||
|
You can use them in your products & projects – print or digital,
|
||||||
|
commercial or otherwise.
|
||||||
|
|
||||||
|
This isn't legal advice, please consider consulting a lawyer and see the full
|
||||||
|
license for all details.
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,32 @@
|
|||||||
|
// callouts
|
||||||
|
#let WARNING_STRONG = rgb("#ff6b21")
|
||||||
|
#let WARNING_WEAK = rgb("#fd8448")
|
||||||
|
|
||||||
|
#let DANGER_STRONG = rgb("#ff0000")
|
||||||
|
#let DANGER_WEAK = rgb("#ff4747")
|
||||||
|
|
||||||
|
#let INFO_STRONG = rgb("#2766ef")
|
||||||
|
#let INFO_WEAK = rgb("#4b7be2")
|
||||||
|
|
||||||
|
// misc document items
|
||||||
|
#let LINK_DEFAULT = rgb("#4646cd")
|
||||||
|
#let TABLE_GREY = rgb("#2b2b2b")
|
||||||
|
#let ARTWORK_GREY_INFO = rgb("#2b2b2bda")
|
||||||
|
#let HORIZONTAL_RULE_GREY = rgb("#c4c4c4c4")
|
||||||
|
|
||||||
|
// badges
|
||||||
|
#let BADGE_TEAL = rgb("#009696")
|
||||||
|
#let BADGE_MUTED_TEAL = rgb("#007474")
|
||||||
|
#let BADGE_BLUE = rgb("#3e71e7")
|
||||||
|
#let BADGE_PINK = rgb("#db44b2")
|
||||||
|
#let BADGE_PURPLE = rgb("#a952ff")
|
||||||
|
|
||||||
|
// themes
|
||||||
|
#let THEME_PLUM = rgb("#9c6fb6")
|
||||||
|
#let THEME_TT06_PINK = rgb("#ec789d")
|
||||||
|
#let THEME_SKY_BLUE = rgb("#0c66ab")
|
||||||
|
|
||||||
|
// unused colours for safekeeping
|
||||||
|
#let UNUSED_GOLD = rgb("#ffc936")
|
||||||
|
#let UNUSED_PURPLE_1 = rgb("#9123ff")
|
||||||
|
#let UNUSED_PURPLE_2 = rgb("#a041ff")
|
||||||
@@ -0,0 +1,640 @@
|
|||||||
|
#import "@preview/tiaoma:0.3.0"
|
||||||
|
|
||||||
|
#import "lib.typ": badge, callout, annotated_qrcode, get_image_by_id
|
||||||
|
#import "colours.typ"
|
||||||
|
|
||||||
|
#import "../chapters/pinout-tables.typ" as pins
|
||||||
|
#import "../chapters/call-to-action.typ": call_to_action_page
|
||||||
|
|
||||||
|
#let _funding_content = state("_funding_content", [])
|
||||||
|
#let _funding_alt_title = state("_funding_alt_title", [])
|
||||||
|
#let _flip_footer_ordering = state("_flip_footer_ordering", false)
|
||||||
|
#let _chip_render_content = state("_chip_render_content", none)
|
||||||
|
#let _chip_render_sponsor_text = state("_chip_render_sponsor_text", none)
|
||||||
|
#let _chip_render_sponsor_logo = state("_chip_render_sponsor_logo", none)
|
||||||
|
|
||||||
|
#let styling(body) = {
|
||||||
|
set page(paper: "a4", margin: auto)
|
||||||
|
set text(font: "Montserrat", size: 12pt)
|
||||||
|
show raw: set text(font: "Martian Mono", weight: "light")
|
||||||
|
|
||||||
|
// style headings
|
||||||
|
show heading.where(level: 1): this => {
|
||||||
|
text(size: 28pt, this)
|
||||||
|
v(0.1cm)
|
||||||
|
}
|
||||||
|
show heading.where(level: 2): set text(size: 22pt)
|
||||||
|
show heading.where(level: 3): set text(size: 16pt)
|
||||||
|
|
||||||
|
// indent numbered and bullet point lists
|
||||||
|
set enum(indent: 1em)
|
||||||
|
set list(indent: 1em)
|
||||||
|
|
||||||
|
// format table so that empty cells show dark grey em dash
|
||||||
|
show table.cell: this => {
|
||||||
|
if this.body == [] {
|
||||||
|
align(center + horizon, text(fill: colours.TABLE_GREY)[$dash.em$])
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set quote styling
|
||||||
|
// used for project description
|
||||||
|
show quote: set align(center)
|
||||||
|
show quote: set pad(x: 1em)
|
||||||
|
show quote: set block(above: 1.75em)
|
||||||
|
|
||||||
|
body
|
||||||
|
}
|
||||||
|
|
||||||
|
#let styling_set_par(body) = {
|
||||||
|
// justify text, except for in tables
|
||||||
|
set par(justify: true)
|
||||||
|
show table: this => {
|
||||||
|
set par(justify: false)
|
||||||
|
this
|
||||||
|
}
|
||||||
|
body
|
||||||
|
}
|
||||||
|
|
||||||
|
#let _footer(shuttle, invert_text_colour: false, display_pg_as: "1", flip_ordering: false) = {
|
||||||
|
// setup
|
||||||
|
set text(size: 10pt)
|
||||||
|
set text(white) if invert_text_colour
|
||||||
|
|
||||||
|
let logo = box(baseline: 0.25em, {
|
||||||
|
if invert_text_colour {
|
||||||
|
get_image_by_id("logos", "tt_roundel_white", height: 25%)
|
||||||
|
} else {
|
||||||
|
get_image_by_id("logos", "tt_roundel_black", height: 25%)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// get the title of the current chapter
|
||||||
|
let title = context query(selector(heading.where(level: 1)).before(here())).last().body
|
||||||
|
let pg_num = context counter(page).display(display_pg_as)
|
||||||
|
|
||||||
|
let TITLE_SPACING = 0.5cm
|
||||||
|
let LOGO_SPACING = 0.25cm
|
||||||
|
|
||||||
|
let set_1 = {strong(pg_num); h(TITLE_SPACING); emph(title); h(1fr); emph(shuttle); h(LOGO_SPACING); logo}
|
||||||
|
let set_2 = {logo; h(LOGO_SPACING); emph(shuttle); h(1fr); emph(title); h(TITLE_SPACING); strong(pg_num)}
|
||||||
|
|
||||||
|
// actual styling
|
||||||
|
context {
|
||||||
|
// alternate odd-even pages
|
||||||
|
if calc.even(counter(page).get().first()) {
|
||||||
|
if flip_ordering {set_2} else {set_1}
|
||||||
|
} else {
|
||||||
|
if flip_ordering {set_1} else {set_2}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#let project(
|
||||||
|
title: "Title",
|
||||||
|
author: ("Author 1", "Author 2"),
|
||||||
|
repo_link: "!!! missing repository link !!!",
|
||||||
|
description: "!!! Missing description !!!",
|
||||||
|
address: "----",
|
||||||
|
clock: "No Clock",
|
||||||
|
type: "HDL",
|
||||||
|
wokwi_id: none,
|
||||||
|
danger_level: none,
|
||||||
|
danger_reason: none,
|
||||||
|
|
||||||
|
body
|
||||||
|
) = {
|
||||||
|
|
||||||
|
show: styling;
|
||||||
|
show: styling_set_par;
|
||||||
|
|
||||||
|
// make fake heading - this is the one that gets shown in the table of contents
|
||||||
|
// which has the nice address marker next to it
|
||||||
|
let fake_heading = block(
|
||||||
|
heading(level: 2, outlined: true, bookmarked: false)[
|
||||||
|
#box(baseline: 0.4em, badge(colours.BADGE_TEAL, strong(raw(address))))
|
||||||
|
#title
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
// move fake heading so it doesn't affect layout
|
||||||
|
place(top + left, hide(fake_heading))
|
||||||
|
|
||||||
|
// make heading thats seen on the project page
|
||||||
|
heading(level: 2, outlined: false, bookmarked: true, title)
|
||||||
|
|
||||||
|
// display author names
|
||||||
|
// if 1, it is displayed as is
|
||||||
|
// if 2, it is displayed as "a & b"
|
||||||
|
// if >2, it is displayed as "a, b & c"
|
||||||
|
let author_text = []
|
||||||
|
author_text += [by *#author.at(0)*]
|
||||||
|
|
||||||
|
if author.len() == 2 {
|
||||||
|
author_text += [ & *#author.at(1)*]
|
||||||
|
|
||||||
|
} else if author.len() > 2 {
|
||||||
|
let names = author.slice(1, author.len()-1)
|
||||||
|
|
||||||
|
for name in names {
|
||||||
|
author_text += [, *#name*]
|
||||||
|
}
|
||||||
|
|
||||||
|
author_text += [ & *#author.at(author.len()-1)*]
|
||||||
|
}
|
||||||
|
author_text
|
||||||
|
|
||||||
|
// makes badges array, add address first
|
||||||
|
let badges = (badge(colours.BADGE_TEAL, strong(raw(address))),)
|
||||||
|
if clock != "No Clock" {badges.push(badge(colours.BADGE_BLUE, strong(raw(clock))))}
|
||||||
|
badges.push(badge(colours.BADGE_PINK, strong(raw(type + " Project"))))
|
||||||
|
|
||||||
|
// add asic danger callout if necessary
|
||||||
|
let danger_callout
|
||||||
|
if danger_level != none {
|
||||||
|
if danger_level == "medium" {
|
||||||
|
badges.push(badge(colours.WARNING_STRONG, strong(raw("Medium Danger"))))
|
||||||
|
danger_callout = callout(
|
||||||
|
"warning",
|
||||||
|
raw("This project can damage the ASIC under certain conditions."),
|
||||||
|
raw(danger_reason)
|
||||||
|
)
|
||||||
|
|
||||||
|
} else if danger_level == "high" {
|
||||||
|
badges.push(badge(colours.DANGER_STRONG, strong(raw("High Danger"))))
|
||||||
|
danger_callout = callout(
|
||||||
|
"danger",
|
||||||
|
raw("This project will damage the ASIC."),
|
||||||
|
raw(danger_reason)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
panic([unexpected danger_level value (#danger_level)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add badges to page, unpack badge array
|
||||||
|
grid(columns: badges.len(), column-gutter: 1em, ..badges)
|
||||||
|
|
||||||
|
// hardcode these repo/wokwi links to be black and to not follow link-override-colour
|
||||||
|
link(repo_link, text(black, raw(repo_link.trim("https://", at: start))))
|
||||||
|
|
||||||
|
if wokwi_id != none {
|
||||||
|
let wokwi_link = "https://wokwi.com/projects/" + wokwi_id
|
||||||
|
parbreak()
|
||||||
|
link(wokwi_link, text(black, raw(wokwi_link.trim("https://", at: start))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// add description and danger callout to page
|
||||||
|
quote(block: true, quotes: true, emph(description))
|
||||||
|
danger_callout
|
||||||
|
|
||||||
|
set heading(offset: 2)
|
||||||
|
|
||||||
|
// ensure each figure and footnote is only counted within its project
|
||||||
|
// format numbering as "<project_address>.<figure_count>"
|
||||||
|
let project_numbering(count) = {address.trim("-", at: start) + "." + str(count)}
|
||||||
|
|
||||||
|
counter(figure.where(kind: image)).update(0)
|
||||||
|
counter(footnote).update(0)
|
||||||
|
set figure(numbering: project_numbering)
|
||||||
|
set footnote(numbering: project_numbering)
|
||||||
|
|
||||||
|
body
|
||||||
|
pagebreak(weak: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
#let splash_chapter_page(
|
||||||
|
title,
|
||||||
|
page_colour: white,
|
||||||
|
invert_text_colour: false,
|
||||||
|
footer_text: none,
|
||||||
|
additional_content: none
|
||||||
|
) = {
|
||||||
|
|
||||||
|
page(
|
||||||
|
fill: page_colour,
|
||||||
|
footer: context {
|
||||||
|
_footer(
|
||||||
|
footer_text,
|
||||||
|
invert_text_colour: invert_text_colour,
|
||||||
|
flip_ordering: _flip_footer_ordering.final()
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
// page body - center title and add additional content if necessary
|
||||||
|
{
|
||||||
|
set text(white) if invert_text_colour
|
||||||
|
|
||||||
|
// make headings
|
||||||
|
align(center + horizon, block(text(size: 100pt, strong(title))))
|
||||||
|
place(top + left, hide(heading(level: 1, title)))
|
||||||
|
|
||||||
|
// make info box if needed
|
||||||
|
if additional_content != none {
|
||||||
|
set text(black)
|
||||||
|
place(center + bottom, block(fill: white, inset: 10pt, additional_content))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make a tiling logo page
|
||||||
|
#let tiling_logo_page() = {
|
||||||
|
|
||||||
|
let pattern = tiling(
|
||||||
|
size: (3cm, 3cm),
|
||||||
|
spacing: (1cm, 1cm),
|
||||||
|
get_image_by_id("logos", "tt_roundel_light_grey")
|
||||||
|
)
|
||||||
|
|
||||||
|
page(
|
||||||
|
background: rect(width: 110%, height: 105%, inset: 0pt, outset: 0pt, stroke: none, fill: pattern),
|
||||||
|
[] // empty page
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#let datasheet(
|
||||||
|
shuttle_id: none,
|
||||||
|
shuttle_name: none,
|
||||||
|
repo_link: none,
|
||||||
|
theme: "classic",
|
||||||
|
projects: none,
|
||||||
|
show_pinouts: "openframe",
|
||||||
|
theme_override_colour: none,
|
||||||
|
date: datetime.today(),
|
||||||
|
link_disable_colour: false,
|
||||||
|
link_override_colour: none,
|
||||||
|
chip_viewer_link: none,
|
||||||
|
qrcode_follows_theme: false,
|
||||||
|
|
||||||
|
body
|
||||||
|
) = {
|
||||||
|
|
||||||
|
set document(
|
||||||
|
title: [#shuttle_name Datasheet],
|
||||||
|
author: "Tiny Tapeout & Contributors",
|
||||||
|
description: [The official Tiny Tapeout datasheet for #shuttle_id. Template by Kristaps Jurkans (bluesky/\@krisj.dev)]
|
||||||
|
)
|
||||||
|
|
||||||
|
show: styling;
|
||||||
|
|
||||||
|
let date_str = date.display("[month repr:long] [day padding:none], [year]")
|
||||||
|
|
||||||
|
// contains title, repo link and compilation date
|
||||||
|
let cover_text = align(center)[
|
||||||
|
#text(size: 32pt, strong[#shuttle_name Datasheet])
|
||||||
|
|
||||||
|
#link(
|
||||||
|
repo_link,
|
||||||
|
text(
|
||||||
|
size: 14pt,
|
||||||
|
font: "Martian Mono",
|
||||||
|
repo_link.trim("https://", at: start)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
#v(2.5cm)
|
||||||
|
#text(size: 16pt, weight: "medium", date_str)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
// configure theme variables
|
||||||
|
let selected_theme_colour = colours.THEME_PLUM // default fill
|
||||||
|
if theme_override_colour != none {selected_theme_colour = theme_override_colour}
|
||||||
|
|
||||||
|
let qrcode_colour = black
|
||||||
|
if qrcode_follows_theme and theme == "bold" {qrcode_colour = selected_theme_colour}
|
||||||
|
|
||||||
|
// make titlepage
|
||||||
|
if theme == "classic" {
|
||||||
|
get_image_by_id("logos", "tt_square_colourful")
|
||||||
|
cover_text
|
||||||
|
|
||||||
|
} else if theme == "bold" {
|
||||||
|
set page(
|
||||||
|
background: get_image_by_id("backgrounds", "gds_bw_transparent", height: 100%),
|
||||||
|
fill: selected_theme_colour
|
||||||
|
)
|
||||||
|
|
||||||
|
align(
|
||||||
|
center + horizon,
|
||||||
|
get_image_by_id("logos", "tt_roundel_white", height: 60%)
|
||||||
|
)
|
||||||
|
|
||||||
|
set text(white)
|
||||||
|
cover_text
|
||||||
|
|
||||||
|
} else if theme == "monochrome" {
|
||||||
|
align(
|
||||||
|
center + horizon,
|
||||||
|
get_image_by_id("logos", "tt_roundel_black", height: 60%)
|
||||||
|
)
|
||||||
|
cover_text
|
||||||
|
}
|
||||||
|
|
||||||
|
// style link colour
|
||||||
|
// moved after title page since theme-override-colour would cause it to blend in with the background
|
||||||
|
let link_colour = luma(0%)
|
||||||
|
if not link_disable_colour {
|
||||||
|
// if override colour specified, set it to that
|
||||||
|
// else fall back to default link colour
|
||||||
|
if link_override_colour != none {
|
||||||
|
link_colour = link_override_colour
|
||||||
|
} else {
|
||||||
|
link_colour = colours.LINK_DEFAULT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
show link: set text(link_colour)
|
||||||
|
|
||||||
|
// add tiling logo page (this is pg2 of the datasheet)
|
||||||
|
tiling_logo_page()
|
||||||
|
|
||||||
|
show: styling_set_par;
|
||||||
|
|
||||||
|
// setup for table of contents
|
||||||
|
set page(footer: _footer(shuttle_id, invert_text_colour: false, display_pg_as: "i"))
|
||||||
|
counter(page).update(1)
|
||||||
|
// add spacing for each H1, and make H1 bold
|
||||||
|
show outline.entry.where(level: 1): set block(above: 1em)
|
||||||
|
show outline.entry.where(level: 1): strong
|
||||||
|
|
||||||
|
// make actual table of contents
|
||||||
|
outline(
|
||||||
|
depth: 2,
|
||||||
|
title: heading(level: 1, outlined: false, bookmarked: true, "Table of Contents")
|
||||||
|
)
|
||||||
|
pagebreak(weak: true)
|
||||||
|
|
||||||
|
// determine whether we need to flip the footer ordering depending on if the table fo contents
|
||||||
|
// ended on an odd or even page
|
||||||
|
context {
|
||||||
|
_flip_footer_ordering.update(calc.even(counter(page).get().last()))
|
||||||
|
}
|
||||||
|
|
||||||
|
set page(footer: context {_footer(
|
||||||
|
shuttle_id, display_pg_as: "1", flip_ordering: _flip_footer_ordering.final()
|
||||||
|
)})
|
||||||
|
|
||||||
|
// reset page counter so that it starts counting actual content now
|
||||||
|
counter(page).update(1)
|
||||||
|
|
||||||
|
// make chip render pages
|
||||||
|
context {
|
||||||
|
let chip_renders = _chip_render_content.final()
|
||||||
|
|
||||||
|
if chip_renders != none {
|
||||||
|
let qr
|
||||||
|
let qr_grid
|
||||||
|
|
||||||
|
if chip_viewer_link != none {
|
||||||
|
qr_grid = annotated_qrcode(
|
||||||
|
chip_viewer_link, "Online chip viewer",
|
||||||
|
tiaoma_args: ("scale": 2.0, "fg-color": qrcode_colour)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let sponsor_grid
|
||||||
|
if _chip_render_sponsor_text.final() != none and _chip_render_sponsor_logo.final() != none {
|
||||||
|
sponsor_grid = grid(
|
||||||
|
columns: 1,
|
||||||
|
rows: 3,
|
||||||
|
align: center + horizon,
|
||||||
|
row-gutter: 1em,
|
||||||
|
|
||||||
|
_chip_render_sponsor_text.final(),
|
||||||
|
_chip_render_sponsor_logo.final(),
|
||||||
|
[] // added for padding
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let content
|
||||||
|
let content_arr = ()
|
||||||
|
|
||||||
|
if sponsor_grid != none {
|
||||||
|
content_arr.push(sponsor_grid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if qr_grid != none {
|
||||||
|
content_arr.push(qr_grid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if content_arr.len() >= 1 {
|
||||||
|
content = grid(
|
||||||
|
columns: content_arr.len(),
|
||||||
|
rows: 1,
|
||||||
|
column-gutter: 1em, row-gutter: 1em,
|
||||||
|
align: center + horizon,
|
||||||
|
|
||||||
|
..content_arr
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if theme == "bold" {
|
||||||
|
splash_chapter_page(
|
||||||
|
"Chip Renders",
|
||||||
|
page_colour: selected_theme_colour,
|
||||||
|
invert_text_colour: true,
|
||||||
|
footer_text: shuttle_id,
|
||||||
|
additional_content: content
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
splash_chapter_page(
|
||||||
|
"Chip Renders",
|
||||||
|
invert_text_colour: false,
|
||||||
|
footer_text: shuttle_id,
|
||||||
|
additional_content: content
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// display chip renders
|
||||||
|
chip_renders
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make project splash page + show all projects
|
||||||
|
if projects != none {
|
||||||
|
if theme == "bold" {
|
||||||
|
splash_chapter_page(
|
||||||
|
"Projects",
|
||||||
|
page_colour: selected_theme_colour,
|
||||||
|
invert_text_colour: true,
|
||||||
|
footer_text: shuttle_id
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
splash_chapter_page(
|
||||||
|
"Projects",
|
||||||
|
footer_text: shuttle_id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
projects
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset counters back to 0, to be used in the info section of the datasheet
|
||||||
|
counter(figure.where(kind: image)).update(0)
|
||||||
|
counter(footnote).update(0)
|
||||||
|
body
|
||||||
|
|
||||||
|
// add informational content
|
||||||
|
// chip pinout diagram
|
||||||
|
|
||||||
|
let pinout_map = (
|
||||||
|
caravel: (diagram: "chip_pinout_qfn64", table: pins.caravel),
|
||||||
|
openframe: (diagram: "chip_pinout_qfn64", table: pins.openframe),
|
||||||
|
openframe_sky130: (diagram: "chip_pinout_qfn64_sky130_openframe", table: pins.openframe_sky130),
|
||||||
|
customframe_ihp_sg13g2: (diagram: "chip_pinout_qfn64_ihp_sg13g2", table: pins.customframe_ihp_sg13g2),
|
||||||
|
customframe_gf180mcud: (diagram: "chip_pinout_qfn64_gf180mcud", table: pins.customframe_gf180mcud)
|
||||||
|
)
|
||||||
|
|
||||||
|
page[
|
||||||
|
= Pinout
|
||||||
|
|
||||||
|
The chip is packaged in a 64-pin QFN package. The pinout is shown below.
|
||||||
|
|
||||||
|
#align(center, get_image_by_id("images", pinout_map.at(show_pinouts).at("diagram"), height: 60%))
|
||||||
|
|
||||||
|
#callout("info", "Note")[
|
||||||
|
You will receive the chip mounted on a breakout board (#link("https://github.com/tinytapeout/breakout-pcb")[github.com/tinytapeout/breakout-pcb]).
|
||||||
|
|
||||||
|
The pinout is provided for advanced users, as most users will not need to solder the chip directly.
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
// multiplexer infrastructure explanation
|
||||||
|
page({
|
||||||
|
include "../chapters/tt-multiplexer.typ"
|
||||||
|
|
||||||
|
pinout_map.at(show_pinouts).at("table")
|
||||||
|
})
|
||||||
|
|
||||||
|
// add funding/sponsor page
|
||||||
|
context {
|
||||||
|
// check if funding has been provided, otherwise we risk adding an empty page instead
|
||||||
|
if _funding_content.final() != [] {
|
||||||
|
page({
|
||||||
|
let funding_content = _funding_content.final()
|
||||||
|
let alt_title = _funding_alt_title.final()
|
||||||
|
|
||||||
|
if alt_title != none {
|
||||||
|
heading(level: 1, alt_title)
|
||||||
|
} else {
|
||||||
|
heading(level: 1, "Funding")
|
||||||
|
}
|
||||||
|
|
||||||
|
funding_content
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
page(include "../chapters/team.typ")
|
||||||
|
page(include "../chapters/using-this-datasheet.typ")
|
||||||
|
|
||||||
|
// add call to action page
|
||||||
|
if theme == "classic" or theme == "monochrome" {
|
||||||
|
page(call_to_action_page())
|
||||||
|
} else if theme == "bold" {
|
||||||
|
page(
|
||||||
|
fill: selected_theme_colour,
|
||||||
|
footer: none,
|
||||||
|
{
|
||||||
|
set text(white)
|
||||||
|
call_to_action_page(qrcode_colour: black)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#let funding(doc, alt_title: none) = {
|
||||||
|
context {
|
||||||
|
_funding_content.update(doc)
|
||||||
|
_funding_alt_title.update(alt_title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#let renders(sponsor_text: none, sponsor_logo: none, doc) = {
|
||||||
|
context {
|
||||||
|
_chip_render_content.update(doc)
|
||||||
|
_chip_render_sponsor_text.update(sponsor_text)
|
||||||
|
_chip_render_sponsor_logo.update(sponsor_logo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#let art(id, rot: 90deg) = {
|
||||||
|
|
||||||
|
// all artwork is assumed to be external (hosted in a separate repo)
|
||||||
|
let art_img = get_image_by_id("artwork", id, height: 100%)
|
||||||
|
let info = yaml("../resources/external/manifest.yaml").at("artwork").at(id)
|
||||||
|
|
||||||
|
let attribution = strong[
|
||||||
|
#info.title $dash.fig$
|
||||||
|
#if info.at("designer", default: none) != none [Designed by #info.designer.]
|
||||||
|
Illustrated by #info.artist.
|
||||||
|
]
|
||||||
|
|
||||||
|
// heading shown in table of contents
|
||||||
|
let art_heading = [#box(baseline: 0.4em, badge(colours.BADGE_PURPLE, strong(`Artwork`))) #info.title]
|
||||||
|
set text(fill: white, size: 10pt)
|
||||||
|
|
||||||
|
page(
|
||||||
|
paper: "a4",
|
||||||
|
margin: 0pt,
|
||||||
|
footer: none,
|
||||||
|
// add artwork as background
|
||||||
|
background: rotate(rot, reflow: true, art_img),
|
||||||
|
{
|
||||||
|
// make fake & read heading
|
||||||
|
// first is shown in ToC, the second in the PDF list
|
||||||
|
place(top, hide(heading(level: 2, outlined: true, bookmarked: false, art_heading)))
|
||||||
|
place(top, hide(heading(level: 2, outlined: false, bookmarked: true)[(Artwork) #info.title]))
|
||||||
|
|
||||||
|
// determine position for artwork attribution
|
||||||
|
context {
|
||||||
|
let attribution_placement = bottom + left
|
||||||
|
|
||||||
|
// if even
|
||||||
|
if calc.even(counter(page).get().first()) {
|
||||||
|
// but flipped
|
||||||
|
if _flip_footer_ordering.final() {
|
||||||
|
attribution_placement = bottom + right
|
||||||
|
} else {
|
||||||
|
attribution_placement = bottom + left
|
||||||
|
}
|
||||||
|
|
||||||
|
// if odd
|
||||||
|
} else {
|
||||||
|
if _flip_footer_ordering.final() {
|
||||||
|
attribution_placement = bottom + left
|
||||||
|
} else {
|
||||||
|
attribution_placement = bottom + right
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add artwork attribution
|
||||||
|
place(
|
||||||
|
attribution_placement,
|
||||||
|
block(
|
||||||
|
fill: colours.ARTWORK_GREY_INFO,
|
||||||
|
inset: 4pt,
|
||||||
|
attribution
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#let add_render(title, subtitle: none, img) = {
|
||||||
|
page(
|
||||||
|
align(center, grid(
|
||||||
|
columns: 1,
|
||||||
|
row-gutter: 10pt,
|
||||||
|
align: center,
|
||||||
|
|
||||||
|
heading(level: 2, title),
|
||||||
|
subtitle,
|
||||||
|
img
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
#import "@preview/tiaoma:0.3.0"
|
||||||
|
#import "colours.typ" as colours
|
||||||
|
|
||||||
|
/// Create a badge with a given colour and text
|
||||||
|
///
|
||||||
|
/// NOTE: This function will replace any instances of a hyphen ("-")
|
||||||
|
/// with a coloured zero digit. This is intended, and is used for
|
||||||
|
/// the address badge.
|
||||||
|
/// - colour (color): Colour of the badge
|
||||||
|
/// - doc (content, str): Text to display within the badge
|
||||||
|
/// -> content
|
||||||
|
#let badge(colour, body) = {
|
||||||
|
set text(white)
|
||||||
|
show "-": text(colours.BADGE_MUTED_TEAL, "0")
|
||||||
|
rect(fill: colour, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a callout to display important information
|
||||||
|
///
|
||||||
|
/// - type (): Callout type (warning, danger, info, custom)
|
||||||
|
/// - title (str): Title of the callout
|
||||||
|
/// - custom_title_colour (color): Colour to use for the title of the custom callout
|
||||||
|
/// - custom_body_colour (color): Colour to use for the body of the custom callout
|
||||||
|
/// - body (content, str): Content to display in the body of the callout
|
||||||
|
/// -> content
|
||||||
|
#let callout(
|
||||||
|
type,
|
||||||
|
title,
|
||||||
|
custom_title_colour: none,
|
||||||
|
custom_body_colour: none,
|
||||||
|
body
|
||||||
|
) = {
|
||||||
|
|
||||||
|
set par(justify: false)
|
||||||
|
let title_colour
|
||||||
|
let body_colour
|
||||||
|
|
||||||
|
if type == "warning" {
|
||||||
|
title_colour = colours.WARNING_STRONG
|
||||||
|
body_colour = colours.WARNING_WEAK
|
||||||
|
|
||||||
|
} else if type == "danger" {
|
||||||
|
title_colour = colours.DANGER_STRONG
|
||||||
|
body_colour = colours.DANGER_WEAK
|
||||||
|
|
||||||
|
} else if type == "info" {
|
||||||
|
title_colour = colours.INFO_STRONG
|
||||||
|
body_colour = colours.INFO_WEAK
|
||||||
|
|
||||||
|
} else if type == "custom" {
|
||||||
|
title_colour = custom_title_colour
|
||||||
|
body_colour = custom_body_colour
|
||||||
|
|
||||||
|
} else {
|
||||||
|
panic([unknown callout type (#type)])
|
||||||
|
}
|
||||||
|
|
||||||
|
set text(white)
|
||||||
|
show link: set text(white)
|
||||||
|
|
||||||
|
// make body first
|
||||||
|
block(
|
||||||
|
fill: body_colour,
|
||||||
|
width: 100%,
|
||||||
|
inset: 8pt,
|
||||||
|
|
||||||
|
{
|
||||||
|
// make title
|
||||||
|
block(fill: title_colour, width: 100%, outset: 8pt, strong(title))
|
||||||
|
body
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an annotated QR code
|
||||||
|
///
|
||||||
|
/// An annotated QR code consists of a title, the QR code, and the link to the website.
|
||||||
|
/// They are stacked on top of each other.
|
||||||
|
///
|
||||||
|
/// - url (str): Link to a website
|
||||||
|
/// - title (str): Title of the QR code
|
||||||
|
/// - title_colour (color): Colour of the title of the QR code
|
||||||
|
/// - url_colour (color): Colour of the link
|
||||||
|
/// - tiaoma_args (dict): Arguments to pass to the tiaoma package
|
||||||
|
/// -> grid
|
||||||
|
#let annotated_qrcode(
|
||||||
|
url,
|
||||||
|
title,
|
||||||
|
title_colour: black,
|
||||||
|
url_colour: black,
|
||||||
|
tiaoma_args: (:)
|
||||||
|
) = {
|
||||||
|
let qr = tiaoma.qrcode(url, options: tiaoma_args)
|
||||||
|
|
||||||
|
grid(
|
||||||
|
columns: 1,
|
||||||
|
rows: 3,
|
||||||
|
align: center + horizon,
|
||||||
|
row-gutter: 1em,
|
||||||
|
|
||||||
|
text(fill: title_colour, title),
|
||||||
|
qr,
|
||||||
|
|
||||||
|
// make url be the width of the QR code
|
||||||
|
context {
|
||||||
|
block(
|
||||||
|
width: measure(qr).width + 1cm,
|
||||||
|
height: auto,
|
||||||
|
breakable: false,
|
||||||
|
link(url, text(url_colour, url.trim("https://", at: start))))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve an image from via an ID
|
||||||
|
///
|
||||||
|
/// The artwork repo must be cloned into resources/external, and its `manifest.yaml`
|
||||||
|
/// must be present for this function to work.
|
||||||
|
///
|
||||||
|
/// - type (str): Type of image to fetch (must be a key in one of the manifests)
|
||||||
|
/// - id (str): ID of image to fetch
|
||||||
|
/// - args (arguments): Arguments to supply to `image()`
|
||||||
|
/// -> content
|
||||||
|
#let get_image_by_id(type, id, ..args) = {
|
||||||
|
|
||||||
|
// external manifest is used for artwork repo
|
||||||
|
// since large photos would bloat the template size
|
||||||
|
let manifest = yaml("../resources/external/manifest.yaml")
|
||||||
|
|
||||||
|
if type not in manifest {
|
||||||
|
panic("type not in manifest")
|
||||||
|
}
|
||||||
|
|
||||||
|
let root_path = "../resources/external"
|
||||||
|
let img_details = manifest.at(type).at(id)
|
||||||
|
|
||||||
|
// make image
|
||||||
|
image(root_path + "/" + type + "/" + img_details.file, ..args)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a horizontal line
|
||||||
|
///
|
||||||
|
/// Used for `pandoc` compatability
|
||||||
|
///
|
||||||
|
/// -> content
|
||||||
|
#let horizontalrule() = {
|
||||||
|
line(
|
||||||
|
length: 100%,
|
||||||
|
stroke: 2pt + colours.HORIZONTAL_RULE_GREY
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a block quote
|
||||||
|
///
|
||||||
|
/// Used for `pandoc` compatability
|
||||||
|
///
|
||||||
|
/// - body (content): Body of the block quote
|
||||||
|
/// -> content
|
||||||
|
#let blockquote(body) = {
|
||||||
|
quote(
|
||||||
|
block: true,
|
||||||
|
body
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#import "lib.typ" as lib
|
||||||
|
#import "datasheet.typ" as datasheet
|
||||||
|
#import "colours.typ" as colours
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "tt-docs"
|
||||||
|
version = "1.0.0"
|
||||||
|
authors = ["Kristaps Jurkans <kris.jurkans@gmail.com>"]
|
||||||
|
license = "Apache-2.0"
|
||||||
|
description = "Tiny Tapeout Docs"
|
||||||
|
repository = "?"
|
||||||
|
keywords = ["TinyTapeout"]
|
||||||
|
entrypoint = "src/tt.typ"
|
||||||
|
exclude = ["images"]
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#import "/tt/docs/typst/src/tt.typ" as tt
|
||||||
|
#let horizontalrule = tt.lib.horizontalrule()
|
||||||
|
#let blockquote = tt.lib.blockquote
|
||||||
|
|
||||||
|
#show: tt.datasheet.project.with(
|
||||||
|
title: "{{&project_title}}",
|
||||||
|
author: {{&project_author}},
|
||||||
|
repo_link: "{{project_repo_link}}",
|
||||||
|
description: "{{&project_description}}",
|
||||||
|
address: "{{&project_address}}",
|
||||||
|
clock: "{{project_clock}}",
|
||||||
|
type: "{{project_type}}",
|
||||||
|
{{#is_wokwi}}
|
||||||
|
wokwi_id: "{{project_wokwi_id}}",
|
||||||
|
{{/is_wokwi}}
|
||||||
|
{{#is_dangerous}}
|
||||||
|
danger_level: "{{project_danger_level}}",
|
||||||
|
danger_reason: "{{project_danger_reason}}",
|
||||||
|
{{/is_dangerous}}
|
||||||
|
)
|
||||||
|
|
||||||
|
{{&project_doc_body}}
|
||||||
|
|
||||||
|
= Project Pinout
|
||||||
|
== Digital Pins
|
||||||
|
#align(center)[#table(
|
||||||
|
columns: 4,
|
||||||
|
align: left,
|
||||||
|
table.header(
|
||||||
|
[\#], [Input], [Output], [Bidirectional]
|
||||||
|
),
|
||||||
|
{{&digital_pins}}
|
||||||
|
)]
|
||||||
|
|
||||||
|
{{#is_analog}}
|
||||||
|
== Analog Pins
|
||||||
|
#align(center)[#table(
|
||||||
|
columns: 3,
|
||||||
|
align: left,
|
||||||
|
table.header(
|
||||||
|
[`ua`\#], [`analog`\#], [Description]
|
||||||
|
),
|
||||||
|
{{&analog_pins}}
|
||||||
|
)]
|
||||||
|
{{/is_analog}}
|
||||||
+195
-88
@@ -1,14 +1,17 @@
|
|||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import math
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
from typing import List
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
import chevron
|
import chevron
|
||||||
import frontmatter # type: ignore
|
import frontmatter # type: ignore
|
||||||
import git # type: ignore
|
import git # type: ignore
|
||||||
|
import yaml
|
||||||
|
|
||||||
from config import Config
|
from config import Config
|
||||||
|
from doc_utils import DocsHelper
|
||||||
from git_utils import get_first_remote
|
from git_utils import get_first_remote
|
||||||
from markdown_utils import rewrite_image_paths, rewrite_image_paths_for_website
|
from markdown_utils import rewrite_image_paths, rewrite_image_paths_for_website
|
||||||
from project import Project
|
from project import Project
|
||||||
@@ -63,104 +66,208 @@ class Docs:
|
|||||||
else:
|
else:
|
||||||
return doc.content + "\n"
|
return doc.content + "\n"
|
||||||
|
|
||||||
def write_datasheet(self, markdown_file: str, pdf_file: Optional[str] = None):
|
def build_datasheet(
|
||||||
doc_header = self.load_doc_template("doc_header.md.mustache")
|
self,
|
||||||
doc_chip_map = self.load_doc_template("../../docs/chip_map.md")
|
template_version: str,
|
||||||
doc_template = self.load_doc_template("doc_template.md.mustache")
|
tapeout_index_path: str,
|
||||||
doc_pinout = self.load_doc_template("PINOUT.md")
|
):
|
||||||
doc_info = self.load_doc_template("../../tt-multiplexer/docs/INFO.md")
|
logging.info(f"building datasheet with version {template_version}")
|
||||||
doc_credits = self.load_doc_template("CREDITS.md")
|
|
||||||
|
|
||||||
with open(markdown_file, "w") as fh:
|
tapeout_index = None
|
||||||
repo = git.Repo(".")
|
# if given a path but the file doesn't exist
|
||||||
fh.write(
|
if tapeout_index_path is not None and not os.path.isfile(tapeout_index_path):
|
||||||
chevron.render(
|
raise FileNotFoundError("unable to find tapeout index at given path")
|
||||||
doc_header,
|
# if given a path but it does exist
|
||||||
{
|
elif tapeout_index_path is not None:
|
||||||
"name": self.config["name"],
|
with open(os.path.abspath(tapeout_index_path), "r") as f:
|
||||||
"repo": get_first_remote(repo),
|
index = json.load(f)
|
||||||
},
|
|
||||||
)
|
|
||||||
)
|
|
||||||
fh.write(doc_chip_map)
|
|
||||||
fh.write("# Projects\n")
|
|
||||||
|
|
||||||
|
tapeout_index = sorted(
|
||||||
|
index["projects"], key=lambda project: project["address"]
|
||||||
|
)
|
||||||
|
elif tapeout_index_path is None:
|
||||||
|
logging.warning("tapeout index not provided, using project list")
|
||||||
self.projects.sort(key=lambda x: x.mux_address)
|
self.projects.sort(key=lambda x: x.mux_address)
|
||||||
|
tapeout_index = map(DocsHelper.normalise_project_info, self.projects)
|
||||||
|
|
||||||
for project in self.projects:
|
datasheet_content_config = None
|
||||||
yaml_data = project.get_project_docs_dict()
|
if "datasheet_config" in self.config:
|
||||||
analog_pins = project.info.analog_pins
|
logging.info("using datasheet config in config.yaml")
|
||||||
yaml_data.update(
|
datasheet_content_config = self.config["datasheet_config"]
|
||||||
{
|
|
||||||
"user_docs": rewrite_image_paths(
|
with open(os.path.join(self.script_dir, "docs/datasheet.typ.mustache")) as f:
|
||||||
yaml_data["user_docs"],
|
datasheet_template = f.read()
|
||||||
f"projects/{project.get_macro_name()}/docs",
|
|
||||||
),
|
DocsHelper.configure_datasheet(
|
||||||
"mux_address": project.mux_address,
|
self.config, datasheet_template, template_version
|
||||||
"pins": [
|
|
||||||
{
|
|
||||||
"pin_index": str(i),
|
|
||||||
"ui": project.info.pinout.ui[i],
|
|
||||||
"uo": project.info.pinout.uo[i],
|
|
||||||
"uio": project.info.pinout.uio[i],
|
|
||||||
}
|
|
||||||
for i in range(8)
|
|
||||||
],
|
|
||||||
"analog_pins": [
|
|
||||||
{
|
|
||||||
"ua_index": str(i),
|
|
||||||
"analog_index": str(project.analog_pins[i]),
|
|
||||||
"desc": desc,
|
|
||||||
}
|
|
||||||
for i, desc in enumerate(
|
|
||||||
project.info.pinout.ua[:analog_pins]
|
|
||||||
)
|
|
||||||
],
|
|
||||||
"is_analog": analog_pins > 0,
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
logging.info(f"building datasheet for {project}")
|
danger_info = {}
|
||||||
|
if not os.path.isfile("./projects/danger_level.yaml"):
|
||||||
# ensure there are no LaTeX escape sequences in various fields, and that optional fields are set
|
logging.warning("danger_level.yaml not found")
|
||||||
for key in [
|
|
||||||
"author",
|
|
||||||
"description",
|
|
||||||
"clock_hz",
|
|
||||||
"git_url",
|
|
||||||
"doc_link",
|
|
||||||
]:
|
|
||||||
if key in yaml_data:
|
|
||||||
yaml_data[key] = str(yaml_data[key]).replace(
|
|
||||||
"\\", "\\mbox{\\textbackslash}"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
yaml_data[key] = ""
|
with open(os.path.abspath("./projects/danger_level.yaml"), "r") as f:
|
||||||
|
content = yaml.safe_load(f)
|
||||||
|
# yaml.safe_load() returns None for an empty file
|
||||||
|
# check before overwriting since we rely on danger_info being a dict later
|
||||||
|
if content is not None:
|
||||||
|
danger_info = content
|
||||||
|
|
||||||
|
with open(os.path.join(self.script_dir, "docs/user_project.typ.mustache")) as f:
|
||||||
|
project_template = f.read()
|
||||||
|
|
||||||
|
datasheet_manifest = [f'#import "/tt/docs/typst/src/tt.typ" as tt\n']
|
||||||
|
|
||||||
|
# handle art
|
||||||
|
current_project = 0
|
||||||
|
art_index = 0
|
||||||
|
total_available_art = None
|
||||||
|
if (
|
||||||
|
datasheet_content_config is not None
|
||||||
|
and "artwork" in datasheet_content_config
|
||||||
|
):
|
||||||
|
total_available_art = len(datasheet_content_config["artwork"])
|
||||||
|
|
||||||
|
if total_available_art is not None:
|
||||||
|
if total_available_art > 0:
|
||||||
|
insert_art_after = math.floor(len(self.projects) / total_available_art)
|
||||||
|
|
||||||
|
temp_subtile_projects = {}
|
||||||
|
for project in tapeout_index:
|
||||||
|
logging.info(
|
||||||
|
f"processing datasheet for [{project['address']} : {project['repo']}]"
|
||||||
|
)
|
||||||
|
|
||||||
|
# fetch info.yaml
|
||||||
|
yaml_path = os.path.abspath(f"./projects/{project['macro']}/info.yaml")
|
||||||
|
if "type" in project:
|
||||||
|
if project["type"] == "subtile":
|
||||||
|
yaml_path = os.path.abspath(
|
||||||
|
f"./projects/{project['subtile_group']}/docs/{project['macro']}/info.yaml"
|
||||||
|
)
|
||||||
|
|
||||||
# now build the doc & print it
|
|
||||||
try:
|
try:
|
||||||
doc = chevron.render(doc_template, yaml_data)
|
with open(yaml_path, "r") as f:
|
||||||
fh.write(doc)
|
yaml_info = yaml.safe_load(f)
|
||||||
fh.write("\n\\clearpage\n")
|
except FileNotFoundError:
|
||||||
except IndexError:
|
logging.warning(
|
||||||
logging.warning("missing pins in info.yaml, skipping")
|
f"unable to read {yaml_path}... project exists in tapeout index but not in local directory? skipping"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
# ending
|
info = DocsHelper.format_project_info(yaml_info, project)
|
||||||
fh.write(doc_pinout)
|
|
||||||
fh.write("\n\\clearpage\n")
|
|
||||||
fh.write(doc_info)
|
|
||||||
fh.write("\n\\clearpage\n")
|
|
||||||
fh.write(doc_credits)
|
|
||||||
|
|
||||||
logging.info(f"wrote markdown to {markdown_file}")
|
# decide what to do given project type
|
||||||
|
if info["type"] == "subtile":
|
||||||
|
# defer for later (when we hit the group module)
|
||||||
|
# we want the group module to appear first in the datasheet
|
||||||
|
temp_subtile_projects[info["subtile_addr"]] = info
|
||||||
|
continue
|
||||||
|
|
||||||
if pdf_file is not None:
|
elif info["type"] == "group":
|
||||||
pdf_cmd = f"pandoc --toc --toc-depth 2 --pdf-engine=xelatex -i {markdown_file} -o {pdf_file}"
|
# write group doc
|
||||||
logging.info(pdf_cmd)
|
group_md_path = f"projects/{info['macro']}/docs/info.md"
|
||||||
p = subprocess.run(pdf_cmd, shell=True)
|
group_typ_path = f"projects/{info['macro']}/docs/doc.typ"
|
||||||
if p.returncode != 0:
|
DocsHelper.write_doc(
|
||||||
logging.error("pdf generation failed")
|
path=os.path.abspath(group_typ_path),
|
||||||
raise RuntimeError(f"pdf generation failed with code {p.returncode}")
|
template=project_template,
|
||||||
|
content=DocsHelper.populate_template_tags(
|
||||||
|
info=info,
|
||||||
|
danger_info=danger_info,
|
||||||
|
docs=DocsHelper.get_docs_as_typst(group_md_path),
|
||||||
|
template_version=template_version,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# add group doc to manifest
|
||||||
|
include_str = f'#include "{group_typ_path}"\n'
|
||||||
|
if (
|
||||||
|
datasheet_content_config is not None
|
||||||
|
and "disabled" in datasheet_content_config
|
||||||
|
):
|
||||||
|
if info["macro"] in datasheet_content_config["disabled"]:
|
||||||
|
logging.warning(
|
||||||
|
f"datasheet disabled for [{info['address']} : {info['git_url']}"
|
||||||
|
)
|
||||||
|
include_str = "// " + include_str
|
||||||
|
datasheet_manifest.append(include_str)
|
||||||
|
|
||||||
|
# write subtile doc
|
||||||
|
for _, subtile_info in temp_subtile_projects.items():
|
||||||
|
partial_doc_path = f"projects/{subtile_info['subtile_group']}/docs/{subtile_info['macro']}"
|
||||||
|
typ_path = os.path.join(partial_doc_path, "doc.typ")
|
||||||
|
md_path = os.path.join(partial_doc_path, "info.md")
|
||||||
|
|
||||||
|
DocsHelper.write_doc(
|
||||||
|
path=typ_path,
|
||||||
|
template=project_template,
|
||||||
|
content=DocsHelper.populate_template_tags(
|
||||||
|
info=subtile_info,
|
||||||
|
danger_info=danger_info,
|
||||||
|
docs=DocsHelper.get_docs_as_typst(md_path),
|
||||||
|
template_version=template_version,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# add subtile doc to manifest
|
||||||
|
include_str = f'#include "{typ_path}"\n'
|
||||||
|
if DocsHelper.project_is_disabled(
|
||||||
|
datasheet_content_config, subtile_info["macro"]
|
||||||
|
):
|
||||||
|
logging.warning(
|
||||||
|
f"datasheet disabled for [{subtile_info['address']} : {subtile_info['git_url']}]"
|
||||||
|
)
|
||||||
|
include_str = "// " + include_str
|
||||||
|
datasheet_manifest.append(include_str)
|
||||||
|
|
||||||
|
# clear subtiles for next group
|
||||||
|
temp_subtile_projects = {}
|
||||||
|
|
||||||
|
elif info["type"] == "project":
|
||||||
|
project_md_path = f"projects/{info['macro']}/docs/info.md"
|
||||||
|
project_typ_path = f"projects/{info['macro']}/docs/doc.typ"
|
||||||
|
|
||||||
|
DocsHelper.write_doc(
|
||||||
|
path=project_typ_path,
|
||||||
|
template=project_template,
|
||||||
|
content=DocsHelper.populate_template_tags(
|
||||||
|
info=info,
|
||||||
|
danger_info=danger_info,
|
||||||
|
docs=DocsHelper.get_docs_as_typst(project_md_path),
|
||||||
|
template_version=template_version,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# add project doc to manifest
|
||||||
|
include_str = f'#include "{project_typ_path}"\n'
|
||||||
|
if DocsHelper.project_is_disabled(
|
||||||
|
datasheet_content_config, info["macro"]
|
||||||
|
):
|
||||||
|
logging.warning(
|
||||||
|
f"datasheet disabled for [{info['address']} : {info['git_url']}]"
|
||||||
|
)
|
||||||
|
include_str = "// " + include_str
|
||||||
|
datasheet_manifest.append(include_str)
|
||||||
|
|
||||||
|
else:
|
||||||
|
logging.error(f"unhandled project type! what is \"{info['type']}\"?")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
# insert artwork
|
||||||
|
current_project += 1
|
||||||
|
if total_available_art is not None:
|
||||||
|
if not (art_index >= len(datasheet_content_config["artwork"])):
|
||||||
|
if current_project % insert_art_after == 0:
|
||||||
|
details = datasheet_content_config["artwork"][art_index]
|
||||||
|
datasheet_manifest.append(
|
||||||
|
f"#tt.datasheet.art(\"{details['id']}\", rot:{details['rotate']})\n"
|
||||||
|
)
|
||||||
|
art_index += 1
|
||||||
|
|
||||||
|
with open("datasheet_manifest.typ", "w") as f:
|
||||||
|
f.writelines(datasheet_manifest)
|
||||||
|
|
||||||
|
DocsHelper.compile()
|
||||||
|
|
||||||
def build_hugo_content(self, hugo_root: str) -> None:
|
def build_hugo_content(self, hugo_root: str) -> None:
|
||||||
hugo_images = os.path.join(hugo_root, "images")
|
hugo_images = os.path.join(hugo_root, "images")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
klayout==0.29.0
|
klayout<0.30.0,>=0.29.0
|
||||||
gdstk==0.9.50
|
gdstk==0.9.50
|
||||||
pytest==7.4.3
|
pytest==7.4.3
|
||||||
PyYAML==6.0.1
|
PyYAML==6.0.1
|
||||||
|
|||||||
+38
-25
@@ -17,6 +17,7 @@ from git.repo import Repo
|
|||||||
|
|
||||||
import git_utils
|
import git_utils
|
||||||
from cells import Cell, load_cells
|
from cells import Cell, load_cells
|
||||||
|
from doc_utils import DocsHelper
|
||||||
from markdown_utils import limit_markdown_headings
|
from markdown_utils import limit_markdown_headings
|
||||||
from project_info import ProjectInfo, ProjectYamlError
|
from project_info import ProjectInfo, ProjectYamlError
|
||||||
|
|
||||||
@@ -590,8 +591,7 @@ class Project:
|
|||||||
logging.error("Missing 'How to test' section in docs/info.md")
|
logging.error("Missing 'How to test' section in docs/info.md")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
# use pandoc to create a single page PDF preview
|
def create_project_datasheet(self, template_version: str):
|
||||||
def create_pdf(self):
|
|
||||||
template_args = copy.deepcopy(self.info.__dict__)
|
template_args = copy.deepcopy(self.info.__dict__)
|
||||||
template_args.update(
|
template_args.update(
|
||||||
{
|
{
|
||||||
@@ -616,32 +616,45 @@ class Project:
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
logging.info("Creating PDF")
|
logging.info("creating project datasheet")
|
||||||
with open(os.path.join(SCRIPT_DIR, "docs/project_header.md")) as fh:
|
|
||||||
doc_header = fh.read()
|
|
||||||
with open(os.path.join(SCRIPT_DIR, "docs/project_preview.md.mustache")) as fh:
|
|
||||||
doc_template = fh.read()
|
|
||||||
info_md = os.path.join(self.local_dir, "docs/info.md")
|
|
||||||
with open(info_md) as fh:
|
|
||||||
template_args["info"] = fh.read()
|
|
||||||
|
|
||||||
with open("datasheet.md", "w") as fh:
|
with open(os.path.join(SCRIPT_DIR, "docs/user_project.typ.mustache")) as f:
|
||||||
fh.write(doc_header)
|
project_template = f.read()
|
||||||
|
|
||||||
# now build the doc & print it
|
result = DocsHelper.get_docs_as_typst(
|
||||||
try:
|
os.path.join(self.local_dir, "docs/info.md")
|
||||||
doc = chevron.render(doc_template, template_args)
|
)
|
||||||
fh.write(doc)
|
|
||||||
fh.write("\n\\pagebreak\n")
|
|
||||||
except IndexError:
|
|
||||||
logging.warning("missing pins in info.yaml, skipping")
|
|
||||||
|
|
||||||
pdf_cmd = "pandoc --pdf-engine=xelatex --resource-path=docs -i datasheet.md -o datasheet.pdf"
|
content = {
|
||||||
logging.info(pdf_cmd)
|
"template_version": template_version,
|
||||||
p = subprocess.run(pdf_cmd, shell=True)
|
"project_title": template_args["title"].replace('"', '\\"'),
|
||||||
if p.returncode != 0:
|
"project_author": f"({DocsHelper.format_authors(template_args['author'])})",
|
||||||
logging.error("pdf generation failed")
|
"project_repo_link": self.get_git_remote(),
|
||||||
raise RuntimeError(f"pdf generation failed with code {p.returncode}")
|
"project_description": template_args["description"],
|
||||||
|
"project_address": "----",
|
||||||
|
"project_clock": DocsHelper.pretty_clock(self.info.clock_hz),
|
||||||
|
"project_type": DocsHelper.get_project_type(
|
||||||
|
template_args["language"], self.is_wokwi(), template_args["is_analog"]
|
||||||
|
),
|
||||||
|
"project_doc_body": result,
|
||||||
|
"digital_pins": DocsHelper.format_digital_pins(template_args["pins"]),
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.is_wokwi():
|
||||||
|
content["is_wokwi"] = True
|
||||||
|
content["project_wokwi_id"] = self.info.wokwi_id
|
||||||
|
|
||||||
|
if template_args["is_analog"]:
|
||||||
|
content["is_analog"] = True
|
||||||
|
content["analog_pins"] = DocsHelper.format_analog_pins(
|
||||||
|
template_args["analog_pins"]
|
||||||
|
)
|
||||||
|
|
||||||
|
with open(os.path.abspath(f"./docs/doc.typ"), "w") as f:
|
||||||
|
logging.info("writing datasheet to ./docs/doc.typ")
|
||||||
|
f.write(chevron.render(project_template, content))
|
||||||
|
|
||||||
|
DocsHelper.compile("./docs/doc.typ")
|
||||||
|
|
||||||
# Read and return top-level GDS data from the final GDS file, using gdstk:
|
# Read and return top-level GDS data from the final GDS file, using gdstk:
|
||||||
def get_final_gds_top_cells(self):
|
def get_final_gds_top_cells(self):
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
[project]
|
||||||
|
name = "tt-support-tools"
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
dynamic = ["version"]
|
||||||
|
dependencies = [
|
||||||
|
"appdirs",
|
||||||
|
"brotli",
|
||||||
|
"CairoSVG",
|
||||||
|
"chevron",
|
||||||
|
"cocotb==1.8.0",
|
||||||
|
"gdstk",
|
||||||
|
"GitPython",
|
||||||
|
"klayout<0.30.0,>=0.29.0",
|
||||||
|
"mistune",
|
||||||
|
"numpy<2",
|
||||||
|
"pytz",
|
||||||
|
"pre-commit",
|
||||||
|
"pytest",
|
||||||
|
"python-frontmatter",
|
||||||
|
"PyYAML==6.0.1",
|
||||||
|
"requests",
|
||||||
|
"stripe",
|
||||||
|
"yowasp-yosys",
|
||||||
|
"zipp",
|
||||||
|
"matplotlib"
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
packages = []
|
||||||
+131
-23
@@ -1,33 +1,141 @@
|
|||||||
|
#
|
||||||
|
# This file is autogenerated by pip-compile with Python 3.11
|
||||||
|
# by the following command:
|
||||||
|
#
|
||||||
|
# pip-compile
|
||||||
|
#
|
||||||
appdirs==1.4.4
|
appdirs==1.4.4
|
||||||
|
# via tt-support-tools (pyproject.toml)
|
||||||
|
brotli==1.2.0
|
||||||
|
# via tt-support-tools (pyproject.toml)
|
||||||
cairocffi==1.6.0
|
cairocffi==1.6.0
|
||||||
CairoSVG==2.7.0
|
# via cairosvg
|
||||||
|
cairosvg==2.7.0
|
||||||
|
# via tt-support-tools (pyproject.toml)
|
||||||
certifi==2023.5.7
|
certifi==2023.5.7
|
||||||
|
# via requests
|
||||||
cffi==1.15.1
|
cffi==1.15.1
|
||||||
|
# via cairocffi
|
||||||
|
cfgv==3.5.0
|
||||||
|
# via pre-commit
|
||||||
charset-normalizer==3.1.0
|
charset-normalizer==3.1.0
|
||||||
|
# via requests
|
||||||
chevron==0.14.0
|
chevron==0.14.0
|
||||||
click==8.1.3
|
# via tt-support-tools (pyproject.toml)
|
||||||
cssselect2==0.7.0
|
click==8.1.7
|
||||||
|
# via yowasp-yosys
|
||||||
|
cocotb==1.8.0
|
||||||
|
# via tt-support-tools (pyproject.toml)
|
||||||
|
contourpy==1.3.3
|
||||||
|
# via matplotlib
|
||||||
|
cssselect2==0.8.0
|
||||||
|
# via cairosvg
|
||||||
|
cycler==0.12.1
|
||||||
|
# via matplotlib
|
||||||
defusedxml==0.7.1
|
defusedxml==0.7.1
|
||||||
gdstk==0.9.49
|
# via cairosvg
|
||||||
gitdb==4.0.10
|
distlib==0.3.9
|
||||||
GitPython==3.1.40
|
# via virtualenv
|
||||||
idna==3.4
|
filelock==3.16.1
|
||||||
importlib-resources==5.12.0
|
# via virtualenv
|
||||||
mistune==3.0.1
|
find-libpython==0.4.0
|
||||||
|
# via cocotb
|
||||||
|
fonttools==4.60.1
|
||||||
|
# via matplotlib
|
||||||
|
gdstk==0.9.60
|
||||||
|
# via tt-support-tools (pyproject.toml)
|
||||||
|
gitdb==4.0.11
|
||||||
|
# via gitpython
|
||||||
|
gitpython==3.1.43
|
||||||
|
# via tt-support-tools (pyproject.toml)
|
||||||
|
identify==2.6.3
|
||||||
|
# via pre-commit
|
||||||
|
idna==3.10
|
||||||
|
# via requests
|
||||||
|
importlib-resources==6.4.5
|
||||||
|
# via wasmtime
|
||||||
|
iniconfig==2.0.0
|
||||||
|
# via pytest
|
||||||
|
kiwisolver==1.4.9
|
||||||
|
# via matplotlib
|
||||||
|
klayout==0.29.12
|
||||||
|
# via tt-support-tools (pyproject.toml)
|
||||||
|
matplotlib==3.10.6
|
||||||
|
# via tt-support-tools (pyproject.toml)
|
||||||
|
mistune==3.0.2
|
||||||
|
# via tt-support-tools (pyproject.toml)
|
||||||
|
nodeenv==1.9.1
|
||||||
|
# via pre-commit
|
||||||
numpy==1.26.4
|
numpy==1.26.4
|
||||||
Pillow==9.5.0
|
# via
|
||||||
pycparser==2.21
|
# contourpy
|
||||||
python-frontmatter==1.0.0
|
# gdstk
|
||||||
pytz==2023.3
|
# matplotlib
|
||||||
PyYAML==6.0.1
|
# tt-support-tools (pyproject.toml)
|
||||||
requests==2.31.0
|
packaging==25.0
|
||||||
smmap==5.0.0
|
# via
|
||||||
stripe==5.4.0
|
# matplotlib
|
||||||
tinycss2==1.2.1
|
# pytest
|
||||||
urllib3==2.0.3
|
pillow==11.3.0
|
||||||
wasmtime==9.0.0
|
# via
|
||||||
|
# cairosvg
|
||||||
|
# matplotlib
|
||||||
|
platformdirs==4.3.8
|
||||||
|
# via
|
||||||
|
# virtualenv
|
||||||
|
# yowasp-runtime
|
||||||
|
pluggy==1.5.0
|
||||||
|
# via pytest
|
||||||
|
pre-commit==4.0.1
|
||||||
|
# via tt-support-tools (pyproject.toml)
|
||||||
|
pycparser==2.22
|
||||||
|
# via cffi
|
||||||
|
pygments==2.19.2
|
||||||
|
# via pytest
|
||||||
|
pyparsing==3.2.5
|
||||||
|
# via matplotlib
|
||||||
|
pytest==8.4.2
|
||||||
|
# via tt-support-tools (pyproject.toml)
|
||||||
|
python-dateutil==2.9.0.post0
|
||||||
|
# via matplotlib
|
||||||
|
python-frontmatter==1.1.0
|
||||||
|
# via tt-support-tools (pyproject.toml)
|
||||||
|
pytz==2025.2
|
||||||
|
# via tt-support-tools (pyproject.toml)
|
||||||
|
pyyaml==6.0.1
|
||||||
|
# via
|
||||||
|
# pre-commit
|
||||||
|
# python-frontmatter
|
||||||
|
# tt-support-tools (pyproject.toml)
|
||||||
|
requests==2.32.3
|
||||||
|
# via
|
||||||
|
# stripe
|
||||||
|
# tt-support-tools (pyproject.toml)
|
||||||
|
six==1.17.0
|
||||||
|
# via python-dateutil
|
||||||
|
smmap==5.0.2
|
||||||
|
# via gitdb
|
||||||
|
stripe==14.0.1
|
||||||
|
# via tt-support-tools (pyproject.toml)
|
||||||
|
tinycss2==1.4.0
|
||||||
|
# via
|
||||||
|
# cairosvg
|
||||||
|
# cssselect2
|
||||||
|
typing-extensions==4.15.0
|
||||||
|
# via stripe
|
||||||
|
urllib3==2.2.3
|
||||||
|
# via requests
|
||||||
|
virtualenv==20.28.0
|
||||||
|
# via pre-commit
|
||||||
|
wasmtime==27.0.2
|
||||||
|
# via yowasp-runtime
|
||||||
webencodings==0.5.1
|
webencodings==0.5.1
|
||||||
yowasp-runtime==1.22
|
# via
|
||||||
yowasp-yosys==0.30.0.0.post538
|
# cssselect2
|
||||||
|
# tinycss2
|
||||||
|
yowasp-runtime==1.82
|
||||||
|
# via yowasp-yosys
|
||||||
|
yowasp-yosys==0.60.0.0.post1055
|
||||||
|
# via tt-support-tools (pyproject.toml)
|
||||||
zipp==3.15.0
|
zipp==3.15.0
|
||||||
pre-commit==3.4.0
|
# via tt-support-tools (pyproject.toml)
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
TechName = Literal["sky130A", "ihp-sg13g2", "gf180mcuD"]
|
||||||
+8
-2
@@ -73,10 +73,16 @@ if __name__ == "__main__":
|
|||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--create-pdf",
|
"--create-pdf",
|
||||||
help="create a single page PDF",
|
help="create a datasheet for the current project",
|
||||||
action="store_const",
|
action="store_const",
|
||||||
const=True,
|
const=True,
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--template-version",
|
||||||
|
help="set typst template version (default 1.0.0)",
|
||||||
|
default="1.0.0",
|
||||||
|
nargs="?",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--create-svg",
|
"--create-svg",
|
||||||
help="create a svg of the GDS layout",
|
help="create a svg of the GDS layout",
|
||||||
@@ -158,7 +164,7 @@ if __name__ == "__main__":
|
|||||||
project.harden()
|
project.harden()
|
||||||
|
|
||||||
if args.create_pdf:
|
if args.create_pdf:
|
||||||
project.create_pdf()
|
project.create_project_datasheet(args.template_version)
|
||||||
|
|
||||||
if args.create_png:
|
if args.create_png:
|
||||||
project.create_png()
|
project.create_png()
|
||||||
|
|||||||
Reference in New Issue
Block a user