mirror of
https://github.com/TinyTapeout/tt-support-tools.git
synced 2026-11-03 04:36:09 +01:00
feat(datasheet): add typst datasheet flow
This is a squash of 5 commits in order to add the new datasheet flow. fix(datasheet): ensure datasheet can use images properly fix(datasheet): white page when funding was not specified This was caused by creating a page before first checking whether there was any content for that page beforehand, so it would result in a rogue white page with no content. fix(datasheet): missing analog pin tables fix(datasheet): tweak pandoc settings Switching from `gfm` to `markdown` should enable more formatting options. The extensions have been configured to be similar to `gfm` but provide some additional things, like `line_blocks` and `lists_without_preceding_blankline`s.
This commit is contained in:
@@ -1,4 +1,27 @@
|
||||
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"]
|
||||
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):
|
||||
@@ -11,3 +34,5 @@ class Config(TypedDict):
|
||||
top_level_macro: str
|
||||
powered_netlists: bool
|
||||
no_power_gating: bool
|
||||
pdk: TechName
|
||||
datasheet_config: DatasheetConfig
|
||||
|
||||
+15
-6
@@ -270,13 +270,22 @@ if __name__ == "__main__":
|
||||
parser.add_argument(
|
||||
"--dump-json", help="dump json of all project data to given file"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dump-markdown", help="dump markdown of all project data to given file"
|
||||
)
|
||||
parser.add_argument("--dump-pdf", help="create pdf from the markdown")
|
||||
parser.add_argument(
|
||||
"--metrics", help="print some project metrics", action="store_const", const=True
|
||||
)
|
||||
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(
|
||||
"--template-version",
|
||||
help="set typst template version (default 1.0.0)",
|
||||
default="1.0.0",
|
||||
nargs="?",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -333,6 +342,6 @@ if __name__ == "__main__":
|
||||
if args.update_image:
|
||||
docs.update_image()
|
||||
|
||||
if args.dump_markdown:
|
||||
if args.build_datasheet:
|
||||
shuttle.configure_mux()
|
||||
docs.write_datasheet(args.dump_markdown, args.dump_pdf)
|
||||
docs.build_datasheet(args.template_version, args.doc_tapeout_index)
|
||||
|
||||
+472
@@ -0,0 +1,472 @@
|
||||
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.info.is_analog,
|
||||
"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.info.is_analog:
|
||||
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 (caravel or openframe)
|
||||
- `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:
|
||||
|
||||
if "pinout" in datasheet_config:
|
||||
content["if_pinout"] = True
|
||||
content["pinout"] = shuttle_config["datasheet_config"]["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,144 @@
|
||||
#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.
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#import "../src/lib.typ": get_image_by_id
|
||||
|
||||
= Pinout
|
||||
|
||||
The chip is packaged in a 64-pin QFN package. The pinout is shown below.
|
||||
|
||||
#align(center, get_image_by_id("images", "chip_pinout_qfn64"))
|
||||
|
||||
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.
|
||||
@@ -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,625 @@
|
||||
#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
|
||||
page(include "../chapters/pinout.typ")
|
||||
|
||||
// multiplexer infrastructure explanation
|
||||
page({
|
||||
include "../chapters/tt-multiplexer.typ"
|
||||
|
||||
if show_pinouts == "caravel" {
|
||||
pins.caravel
|
||||
} else if show_pinouts == "openframe" {
|
||||
pins.openframe
|
||||
} else {
|
||||
panic([unknown pinout table referenced (#show_pinouts)])
|
||||
}
|
||||
})
|
||||
|
||||
// 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,163 @@
|
||||
#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)
|
||||
|
||||
// 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}}
|
||||
+197
-88
@@ -1,13 +1,16 @@
|
||||
import json
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
import subprocess
|
||||
from typing import List, Optional
|
||||
from typing import List
|
||||
|
||||
import chevron
|
||||
import frontmatter # type: ignore
|
||||
import git # type: ignore
|
||||
import yaml
|
||||
|
||||
from config import Config
|
||||
from doc_utils import DocsHelper
|
||||
from git_utils import get_first_remote
|
||||
from markdown_utils import rewrite_image_paths
|
||||
from project import Project
|
||||
@@ -62,101 +65,207 @@ class Docs:
|
||||
else:
|
||||
return doc.content + "\n"
|
||||
|
||||
def write_datasheet(self, markdown_file: str, pdf_file: Optional[str] = None):
|
||||
doc_header = self.load_doc_template("doc_header.md.mustache")
|
||||
doc_chip_map = self.load_doc_template("../../docs/chip_map.md")
|
||||
doc_template = self.load_doc_template("doc_template.md.mustache")
|
||||
doc_pinout = self.load_doc_template("PINOUT.md")
|
||||
doc_info = self.load_doc_template("../../tt-multiplexer/docs/INFO.md")
|
||||
doc_credits = self.load_doc_template("CREDITS.md")
|
||||
def build_datasheet(
|
||||
self,
|
||||
template_version: str,
|
||||
tapeout_index_path: str,
|
||||
):
|
||||
logging.info(f"building datasheet with version {template_version}")
|
||||
|
||||
with open(markdown_file, "w") as fh:
|
||||
repo = git.Repo(".")
|
||||
fh.write(
|
||||
chevron.render(
|
||||
doc_header,
|
||||
{
|
||||
"name": self.config["name"],
|
||||
"repo": get_first_remote(repo),
|
||||
},
|
||||
)
|
||||
)
|
||||
fh.write(doc_chip_map)
|
||||
fh.write("# Projects\n")
|
||||
tapeout_index = None
|
||||
# if given a path but the file doesn't exist
|
||||
if tapeout_index_path is not None and not os.path.isfile(tapeout_index_path):
|
||||
raise FileNotFoundError("unable to find tapeout index at given path")
|
||||
# if given a path but it does exist
|
||||
elif tapeout_index_path is not None:
|
||||
with open(os.path.abspath(tapeout_index_path), "r") as f:
|
||||
index = json.load(f)
|
||||
|
||||
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)
|
||||
tapeout_index = map(DocsHelper.normalise_project_info, self.projects)
|
||||
|
||||
for project in self.projects:
|
||||
yaml_data = project.get_project_docs_dict()
|
||||
analog_pins = project.info.analog_pins
|
||||
yaml_data.update(
|
||||
{
|
||||
"user_docs": rewrite_image_paths(
|
||||
yaml_data["user_docs"],
|
||||
f"projects/{project.get_macro_name()}/docs",
|
||||
),
|
||||
"mux_address": project.mux_address,
|
||||
"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,
|
||||
}
|
||||
datasheet_content_config = None
|
||||
if "datasheet_config" in self.config:
|
||||
logging.info("using datasheet config in config.yaml")
|
||||
datasheet_content_config = self.config["datasheet_config"]
|
||||
|
||||
with open(os.path.join(self.script_dir, "docs/datasheet.typ.mustache")) as f:
|
||||
datasheet_template = f.read()
|
||||
|
||||
DocsHelper.configure_datasheet(
|
||||
self.config, datasheet_template, template_version
|
||||
)
|
||||
|
||||
logging.info(f"building datasheet for {project}")
|
||||
|
||||
# ensure there are no LaTeX escape sequences in various fields, and that optional fields are set
|
||||
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}"
|
||||
)
|
||||
danger_info = {}
|
||||
if not os.path.isfile("./projects/danger_level.yaml"):
|
||||
logging.warning("danger_level.yaml not found")
|
||||
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:
|
||||
doc = chevron.render(doc_template, yaml_data)
|
||||
fh.write(doc)
|
||||
fh.write("\n\\clearpage\n")
|
||||
except IndexError:
|
||||
logging.warning("missing pins in info.yaml, skipping")
|
||||
with open(yaml_path, "r") as f:
|
||||
yaml_info = yaml.safe_load(f)
|
||||
except FileNotFoundError:
|
||||
logging.warning(
|
||||
f"unable to read {yaml_path}... project exists in tapeout index but not in local directory? skipping"
|
||||
)
|
||||
continue
|
||||
|
||||
# ending
|
||||
fh.write(doc_pinout)
|
||||
fh.write("\n\\clearpage\n")
|
||||
fh.write(doc_info)
|
||||
fh.write("\n\\clearpage\n")
|
||||
fh.write(doc_credits)
|
||||
info = DocsHelper.format_project_info(yaml_info, project)
|
||||
|
||||
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:
|
||||
pdf_cmd = f"pandoc --toc --toc-depth 2 --pdf-engine=xelatex -i {markdown_file} -o {pdf_file}"
|
||||
logging.info(pdf_cmd)
|
||||
p = subprocess.run(pdf_cmd, shell=True)
|
||||
if p.returncode != 0:
|
||||
logging.error("pdf generation failed")
|
||||
raise RuntimeError(f"pdf generation failed with code {p.returncode}")
|
||||
elif info["type"] == "group":
|
||||
|
||||
# write group doc
|
||||
group_md_path = f"projects/{info['macro']}/docs/info.md"
|
||||
group_typ_path = f"projects/{info['macro']}/docs/doc.typ"
|
||||
DocsHelper.write_doc(
|
||||
path=os.path.abspath(group_typ_path),
|
||||
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()
|
||||
|
||||
+38
-25
@@ -19,6 +19,7 @@ from git.repo import Repo
|
||||
import git_utils
|
||||
from cells import Sky130Cell, load_ihp_cells, load_sky130_cells
|
||||
from config_utils import read_config, write_config
|
||||
from doc_utils import DocsHelper
|
||||
from markdown_utils import limit_markdown_headings
|
||||
from project_info import ProjectInfo, ProjectYamlError
|
||||
|
||||
@@ -843,8 +844,7 @@ class Project:
|
||||
logging.error("Missing 'How to test' section in docs/info.md")
|
||||
exit(1)
|
||||
|
||||
# use pandoc to create a single page PDF preview
|
||||
def create_pdf(self):
|
||||
def create_project_datasheet(self, template_version: str):
|
||||
template_args = copy.deepcopy(self.info.__dict__)
|
||||
template_args.update(
|
||||
{
|
||||
@@ -870,32 +870,45 @@ class Project:
|
||||
}
|
||||
)
|
||||
|
||||
logging.info("Creating PDF")
|
||||
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()
|
||||
logging.info("creating project datasheet")
|
||||
|
||||
with open("datasheet.md", "w") as fh:
|
||||
fh.write(doc_header)
|
||||
with open(os.path.join(SCRIPT_DIR, "docs/user_project.typ.mustache")) as f:
|
||||
project_template = f.read()
|
||||
|
||||
# now build the doc & print it
|
||||
try:
|
||||
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")
|
||||
result = DocsHelper.get_docs_as_typst(
|
||||
os.path.join(self.local_dir, "docs/info.md")
|
||||
)
|
||||
|
||||
pdf_cmd = "pandoc --pdf-engine=xelatex --resource-path=docs -i datasheet.md -o datasheet.pdf"
|
||||
logging.info(pdf_cmd)
|
||||
p = subprocess.run(pdf_cmd, shell=True)
|
||||
if p.returncode != 0:
|
||||
logging.error("pdf generation failed")
|
||||
raise RuntimeError(f"pdf generation failed with code {p.returncode}")
|
||||
content = {
|
||||
"template_version": template_version,
|
||||
"project_title": template_args["title"].replace('"', '\\"'),
|
||||
"project_author": f"({DocsHelper.format_authors(template_args['author'])})",
|
||||
"project_repo_link": self.get_git_remote(),
|
||||
"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:
|
||||
def get_final_gds_top_cells(self):
|
||||
|
||||
@@ -19,6 +19,7 @@ dependencies = [
|
||||
"requests",
|
||||
"volare",
|
||||
"yowasp-yosys",
|
||||
"matplotlib"
|
||||
]
|
||||
|
||||
[tool.setuptools]
|
||||
|
||||
+34
-12
@@ -31,8 +31,12 @@ click==8.1.7
|
||||
# yowasp-yosys
|
||||
cocotb==1.9.1
|
||||
# via tt-support-tools (pyproject.toml)
|
||||
cssselect2==0.7.0
|
||||
contourpy==1.3.3
|
||||
# via matplotlib
|
||||
cssselect2==0.8.0
|
||||
# via cairosvg
|
||||
cycler==0.12.1
|
||||
# via matplotlib
|
||||
defusedxml==0.7.1
|
||||
# via cairosvg
|
||||
distlib==0.3.8
|
||||
@@ -41,7 +45,9 @@ filelock==3.14.0
|
||||
# via virtualenv
|
||||
find-libpython==0.4.0
|
||||
# via cocotb
|
||||
gdstk==0.9.52
|
||||
fonttools==4.60.1
|
||||
# via matplotlib
|
||||
gdstk==0.9.60
|
||||
# via tt-support-tools (pyproject.toml)
|
||||
gitdb==4.0.11
|
||||
# via gitpython
|
||||
@@ -64,8 +70,12 @@ importlib-resources==6.4.0
|
||||
# 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)
|
||||
markdown-it-py==3.0.0
|
||||
# via rich
|
||||
mdurl==0.1.2
|
||||
@@ -76,15 +86,21 @@ nodeenv==1.9.1
|
||||
# via pre-commit
|
||||
numpy==1.26.4
|
||||
# via
|
||||
# contourpy
|
||||
# gdstk
|
||||
# matplotlib
|
||||
# tt-support-tools (pyproject.toml)
|
||||
packaging==24.1
|
||||
# via pytest
|
||||
packaging==25.0
|
||||
# via
|
||||
# matplotlib
|
||||
# pytest
|
||||
pcpp==1.30
|
||||
# via volare
|
||||
pillow==10.3.0
|
||||
# via cairosvg
|
||||
platformdirs==4.2.2
|
||||
pillow==11.3.0
|
||||
# via
|
||||
# cairosvg
|
||||
# matplotlib
|
||||
platformdirs==4.3.8
|
||||
# via
|
||||
# virtualenv
|
||||
# yowasp-runtime
|
||||
@@ -94,10 +110,14 @@ pre-commit==3.7.1
|
||||
# via tt-support-tools (pyproject.toml)
|
||||
pycparser==2.22
|
||||
# via cffi
|
||||
pygments==2.18.0
|
||||
# via rich
|
||||
pytest==8.2.2
|
||||
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)
|
||||
pyyaml==6.0.1
|
||||
@@ -108,9 +128,11 @@ pyyaml==6.0.1
|
||||
# volare
|
||||
requests==2.32.3
|
||||
# via tt-support-tools (pyproject.toml)
|
||||
rich==13.8.1
|
||||
six==1.17.0
|
||||
# via python-dateutil
|
||||
rich==13.9.4
|
||||
# via volare
|
||||
smmap==5.0.1
|
||||
smmap==5.0.2
|
||||
# via gitdb
|
||||
sniffio==1.3.1
|
||||
# via
|
||||
|
||||
+8
-2
@@ -80,10 +80,16 @@ if __name__ == "__main__":
|
||||
)
|
||||
parser.add_argument(
|
||||
"--create-pdf",
|
||||
help="create a single page PDF",
|
||||
help="create a datasheet for the current project",
|
||||
action="store_const",
|
||||
const=True,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--template-version",
|
||||
help="set typst template version (default 1.0.0)",
|
||||
default="1.0.0",
|
||||
nargs="?",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--create-svg",
|
||||
help="create a svg of the GDS layout",
|
||||
@@ -165,7 +171,7 @@ if __name__ == "__main__":
|
||||
project.harden()
|
||||
|
||||
if args.create_pdf:
|
||||
project.create_pdf()
|
||||
project.create_project_datasheet(args.template_version)
|
||||
|
||||
if args.create_png:
|
||||
project.create_png()
|
||||
|
||||
Reference in New Issue
Block a user