diff --git a/config.py b/config.py index 2f13baa..8ebc58b 100644 --- a/config.py +++ b/config.py @@ -1,4 +1,33 @@ -from typing import TypedDict +from typing import List, Literal, TypedDict + +from tech import TechName + + +class ArtworkEntryType(TypedDict): + """TypedDict for artwork, used by `config.DatasheetConfig`""" + + id: str + rotate: str + + +class DatasheetConfig(TypedDict): + """TypedDict for the datasheet config within Tiny Tapeout's config.yaml file""" + + pinout: Literal[ + "caravel", + "openframe", + "openframe_sky130", + "customframe_ihp_sg13g2", + "customframe_gf180mcud", + ] + theme_override_colour: str + show_chip_viewer: bool + link_disable_colour: bool + link_override_colour: str + qrcode_follows_theme: bool + include: List[str] + disabled: List[str] + artwork: List[ArtworkEntryType] class Config(TypedDict): @@ -11,3 +40,5 @@ class Config(TypedDict): top_level_macro: str powered_netlists: bool no_power_gating: bool + pdk: TechName + datasheet_config: DatasheetConfig diff --git a/configure.py b/configure.py index 03c30d1..9742d25 100755 --- a/configure.py +++ b/configure.py @@ -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) diff --git a/doc_utils.py b/doc_utils.py new file mode 100644 index 0000000..b078dda --- /dev/null +++ b/doc_utils.py @@ -0,0 +1,512 @@ +import logging +import os +import re +import subprocess +from typing import Optional + +import chevron +import matplotlib as mpl + +from config import Config, DatasheetConfig + + +class DocsHelper: + @staticmethod + def pretty_address(address: int, subtile_address: Optional[int] = None) -> str: + """ + Format the address and subtile address (if applicable) into an nice string for the typst template + """ + content = str(address).rjust(4, "-") + if subtile_address is not None: + content += f"/{subtile_address}" + return content + + @staticmethod + def pretty_clock(clock: int) -> str: + """ + Format the clock with engineering notation + """ + if clock == 0: + return "No Clock" + else: + formatter = mpl.ticker.EngFormatter(sep=" ") + # [clock, SI suffix] + hz_as_eng = formatter(clock).split(" ") + + if len(hz_as_eng) == 2: + return f"{hz_as_eng[0]} {hz_as_eng[1]}Hz" + elif len(hz_as_eng) == 1: + return f"{hz_as_eng[0]} Hz" + else: + raise RuntimeError( + "unexpected amount of entries when formatting clock for datasheet" + ) + + @staticmethod + def format_authors(authors: str) -> str: + """ + Format the authors properly + + Split the authors with a set of delimiters, then repackage as a typst array for the template + """ + split_authors = re.split(r"[,|;|+]| and | & ", authors) + formatted_authors = [] + for author in split_authors: + stripped = author.strip() + if stripped == "": + continue + + # typst needs a trailing comma if len(array) == 1, so that it doesn't interpret it as an expression + # https://typst.app/docs/reference/foundations/array/ + formatted_authors.append(f'"{stripped}",') + return "".join(formatted_authors) + + @staticmethod + def _escape_characters_in_content(text: str) -> str: + """ + Helper function to escape certain characters in content blocks + """ + return ( + text.replace("[", "\\[") + .replace("]", "\\]") + .replace("/", "\\/") + .replace("#", "\\#") + ) + + @staticmethod + def _escape_characters_in_string(text: str) -> str: + """ + Helper function to escape characters in strings + + - Escapes double quotation mark + - Removes newlines, carrige returns + """ + + return text.replace('"', '\\"').replace("\n", "").replace("\r", "") + + @staticmethod + def format_digital_pins(pins: dict) -> str: + """ + Iterate through and create the digital pin table as a string + """ + pin_table = "" + for pin in pins: + ui_text = DocsHelper._escape_characters_in_content(pin["ui"]) + uo_text = DocsHelper._escape_characters_in_content(pin["uo"]) + uio_text = DocsHelper._escape_characters_in_content(pin["uio"]) + pin_table += ( + f"[`{pin['pin_index']}`], [{ui_text}], [{uo_text}], [{uio_text}],\n" + ) + + return pin_table + + @staticmethod + def format_analog_pins(pins: dict): + """ + Iterate through and create the analog pin table as a string + """ + pin_table = "" + for pin in pins: + desc_text = DocsHelper._escape_characters_in_content(pin["desc"]) + pin_table += ( + f"[`{pin['ua_index']}`], [`{pin['analog_index']}`], [{desc_text}],\n" + ) + return pin_table + + @staticmethod + def get_docs_as_typst(path: str) -> str: + """ + Run pandoc to convert a given file to typst + """ + # enable/disable certain extensions + # see all with `pandoc --list-extensions=markdown` + # documentation at https://pandoc.org/MANUAL.html + # in general, these extensions should offer some additional formatting options whilst still + # remaining similar to github flavoured markdown + pandoc_extensions = [ + "-auto_identifiers", + "+autolink_bare_uris", + "-citations", + "-definition_lists", + "+emoji", + "-example_lists", + "-fancy_lists", + "+lists_without_preceding_blankline", + "-fenced_divs", + ] + pandoc_command = [ + "pandoc", + path, + "--shift-heading-level-by=-1", + "-f", + f"markdown{''.join(pandoc_extensions)}", + "-t", + "typst-citations", + "--wrap=preserve", + ] + logging.info(pandoc_command) + + result = subprocess.run(pandoc_command, capture_output=True) + + if result.stderr != b"": + logging.warning(result.stderr.decode()) + + return result.stdout.decode() + + @staticmethod + def get_project_type(language: str, is_wokwi: bool, is_analog: bool) -> str: + if is_wokwi: + return "Wokwi" + elif is_analog: + return "Analog" + else: + return "HDL" + + @staticmethod + def format_project_info(yaml_info: dict, index_info: dict) -> dict: + """ + Merge two sources of project information into one dictionary + + Accepts `info.yaml` from the project root, and project information from the tapeout index. + Using these two sources, the function will pick and merge the required information for the datasheet. + """ + info = { + "title": yaml_info["project"]["title"], + "author": yaml_info["project"]["author"], + "description": yaml_info["project"]["description"], + "address": index_info["address"], + "clock_hz": index_info["clock_hz"], + "git_url": index_info["repo"], + "language": yaml_info["project"]["language"], + "macro": index_info["macro"], + "is_analog": len(index_info["analog_pins"]) > 0, + "is_wokwi": True if "wokwi_id" in yaml_info["project"] else False, + "type": "project", + } + + if info["is_wokwi"]: + info["wokwi_id"] = yaml_info["project"]["wokwi_id"] + + try: + info["pins"] = [ + { + "pin_index": str(i), + "ui": index_info["pinout"][f"ui[{i}]"], + "uo": index_info["pinout"][f"uo[{i}]"], + "uio": index_info["pinout"][f"uio[{i}]"], + } + for i in range(8) + ] + except KeyError as e: + logging.info( + f"project is missing a pin entry ({e}), falling back to info.yaml (is this a subtile?)" + ) + + info["pins"] = [ + { + "pin_index": str(i), + "ui": yaml_info["pinout"][f"ui[{i}]"], + "uo": yaml_info["pinout"][f"uo[{i}]"], + "uio": yaml_info["pinout"][f"uio[{i}]"], + } + for i in range(8) + ] + + if info["is_analog"]: + info["analog_pins"] = [ + { + "ua_index": str(i), + "analog_index": index_info["analog_pins"][i], + "desc": index_info["pinout"][f"ua[{i}]"], + } + for i in range(len(index_info["analog_pins"])) + ] + + if "type" in index_info: + if index_info["type"] == "subtile": + info["type"] = "subtile" + info["subtile_addr"] = index_info["subtile_addr"] + info["subtile_group"] = index_info["subtile_group"] + elif index_info["type"] == "group": + info["type"] = "group" + + return info + + @staticmethod + def normalise_project_info(project) -> dict: + """ + Similar to `DocHelper.format_project_info`, but this accepts a `Project` object and attempts to + normalise it to the tapeout index format that is expected by the `Docs.build_datasheet()` function. + + To help visualise it, the output of this function would be fed to the `index_info` parameter of + the `DocsHelper.format_project_info()`. + """ + info = { + "title": project.info.title, + "author": project.info.author, + "description": project.info.description, + "address": project.mux_address, + "clock_hz": project.info.clock_hz, + "repo": project.git_url, + "language": project.info.language, + "macro": project.info.top_module, + "is_analog": project.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 (see DatasheetConfig.pinout in config.py) + - `theme_override_colour`: set a custom colour for the datasheet (rgb object) + - `show_chip_viewer`*: toggle the chip viewer QR code (boolean) + - `link_disable_colour`: disable link colouring (boolean) + - `link_override_colour`: set a custom link colour (rgb object) + - `qrcode_follows_theme`: colour template-generated QR codes with the main theme colour (boolean) + - `include`*: a list of additional `.typ` files to include - they are copied into the body of `datasheet.typ` + + The keys correspond to the arguments available in the template, except those marked with an asterisk. + + Example usage (in `config.yaml`): + ```yaml + datasheet_config: + pinout: caravel + theme_override_colour: tt.colours.THEME_TT06_PINK + link_override_colour: rgb("#a5415c") + include: + - doc/chip_map.typ + - doc/funding.typ + ``` + + There are other keys which are *not* used to configure the datasheet, but instead control the content of it: + - `disabled`: a list of projects which are excluded from the datasheet, mainly due to compilation issues + - `artwork`: a list of key pairs that specify which artwork is added in the projects section (order sensitive) + + The `artwork` key pairs consist of an `ID` and a `rotate` value that specify which image to use and its rotation: + ```yaml + datasheet_config: + disabled: + - tt_um_project_1 + - tt_um_project_2 + artwork: + - {id: photo1, rotate: 90deg} + - {id: photo2, rotate: -90deg} + ``` + """ + logging.info("configuring datasheet.typ") + content = { + "template_version": template_version, + "shuttle_name": shuttle_config["name"], + "shuttle_id": shuttle_config["id"], + "shuttle_id_upper": shuttle_config["id"].upper(), + "if_chip_viewer": True, + # themeing + "qrcode_follows_theme": "false", + "link_disable_colour": "false", + } + + # get repo link + git_repo_link_cmd = ["git", "config", "--get", "remote.origin.url"] + logging.info(git_repo_link_cmd) + result = subprocess.run(git_repo_link_cmd, capture_output=True) + git_repo_link = result.stdout.decode().strip() + content["repo_link"] = git_repo_link + + if "datasheet_config" in shuttle_config: + datasheet_config = shuttle_config["datasheet_config"] + + if datasheet_config is None: + logging.warning( + "datasheet config specified in config.yaml but has no entries" + ) + else: + content["if_pinout"] = True + if "pinout" in datasheet_config: + content["pinout"] = shuttle_config["datasheet_config"]["pinout"] + logging.info( + f"using pinout config verbatim ('{content['pinout']}')" + ) + else: + # determine appropriate pinout table from pdk first, otherwise fall back to shuttle id + if "pdk" in shuttle_config: + match shuttle_config["pdk"]: + case "sky130A": + content["pinout"] = "openframe_sky130" + case "ihp-sg13g2": + content["pinout"] = "customframe_ihp_sg13g2" + case "gf180mcuD": + content["pinout"] = "customframe_gf180mcud" + case _: + logging.warning( + f"unable to determine appropriate pinout from PDK, falling back to 'openframe_sky130'" + ) + content["pinout"] = "openframe_sky130" + + logging.info(f"PDK defined, using pinout '{content['pinout']}'") + + else: + if shuttle_config["id"].startswith("ttsky"): + content["pinout"] = "openframe_sky130" + + elif shuttle_config["id"].startswith("ttihp"): + content["pinout"] = "customframe_ihp_sg13g2" + + elif shuttle_config["id"].startswith("ttgf"): + content["pinout"] = "customframe_gf180mcud" + else: + logging.warning( + f"unable to determine appropriate pinout, falling back to 'openframe_sky130'" + ) + logging.warning( + f"either define 'pinout' or 'pdk' in shuttle config" + ) + content["pinout"] = "openframe_sky130" + + logging.info( + f"PDK not defined, using '{content['pinout']}' pinout" + ) + + if "theme_override_colour" in datasheet_config: + content["if_theme_override_colour"] = True + content["theme_override_colour"] = datasheet_config[ + "theme_override_colour" + ] + + if "show_chip_viewer" in datasheet_config: + content["if_chip_viewer"] = datasheet_config["show_chip_viewer"] + + if "link_disable_colour" in datasheet_config: + # convert to string and lowercase because python "False" != typst "false" + content["link_disable_colour"] = str( + datasheet_config["link_disable_colour"] + ).lower() + + if "link_override_colour" in datasheet_config: + content["if_link_override_colour"] = True + content["link_override_colour"] = datasheet_config[ + "link_override_colour" + ] + + if "qrcode_follows_theme" in datasheet_config: + content["qrcode_follows_theme"] = str( + datasheet_config["qrcode_follows_theme"] + ).lower() + + if "include" in datasheet_config: + doc_body = [] + for path in datasheet_config["include"]: + logging.info(f"including {os.path.abspath(path)}") + + with open(os.path.abspath(path)) as f: + doc_body.append(f.read()) + + content["datasheet_body"] = "\n".join(doc_body) + + with open("datasheet.typ", "w") as f: + logging.info("writing datasheet.typ") + f.write(chevron.render(datasheet_template, content)) + + @staticmethod + def compile(path="datasheet.typ") -> None: + typst_cmd = [ + "typst", + "compile", + path, + "datasheet.pdf", + "--root", + ".", + "--font-path", + "./tt/docs/typst/resources/fonts", + ] + logging.info(typst_cmd) + result = subprocess.call(typst_cmd) + + if result != 0: + logging.error("running typst returned non-zero exit code") + exit(1) + + @staticmethod + def project_is_disabled(config: DatasheetConfig, macro: str) -> bool: + if config is not None: + if "disabled" in config and config["disabled"] is not None: + return macro in config["disabled"] + + return False diff --git a/docs/datasheet.typ.mustache b/docs/datasheet.typ.mustache new file mode 100644 index 0000000..7ab133d --- /dev/null +++ b/docs/datasheet.typ.mustache @@ -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}} \ No newline at end of file diff --git a/docs/typst/LICENSE b/docs/typst/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/docs/typst/LICENSE @@ -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. diff --git a/docs/typst/chapters/call-to-action.typ b/docs/typst/chapters/call-to-action.typ new file mode 100644 index 0000000..20cacd3 --- /dev/null +++ b/docs/typst/chapters/call-to-action.typ @@ -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) + ) + ) +] \ No newline at end of file diff --git a/docs/typst/chapters/pinout-tables.typ b/docs/typst/chapters/pinout-tables.typ new file mode 100644 index 0000000..b810761 --- /dev/null +++ b/docs/typst/chapters/pinout-tables.typ @@ -0,0 +1,405 @@ +#let caravel = { + align(center, + table( + columns: 4, + align: left, + table.header( + [mprj_io_pin], [Function], [Signal], [QFN64 pin] + ), + + `0`, [Input], [`ui_in[0]`], [`31`], + `1`, [Input], [`ui_in[1]`], [`32`], + `2`, [Input], [`ui_in[2]`], [`33`], + `3`, [Input], [`ui_in[3]`], [`34`], + `4`, [Input], [`ui_in[4]`], [`35`], + `5`, [Input], [`ui_in[5]`], [`36`], + `6`, [Input], [`ui_in[6]`], [`37`], + `7`, [Analog], [`analog[0]`], [`41`], + `8`, [Analog], [`analog[1]`], [`42`], + `9`, [Analog], [`analog[2]`], [`43`], + `10`, [Analog], [`analog[3]`], [`44`], + `11`, [Analog], [`analog[4]`], [`45`], + `12`, [Analog], [`analog[5]`], [`46`], + `13`, [Input], [`ui_in[7]`], [48], + `14`, [Input], [`clk` $dagger$], [50], + `15`, [Input], [`rst_n` $dagger$], [`51`], + `16`, [Bidirectional], [`uio[0]`], [`53`], + `17`, [Bidirectional], [`uio[1]`], [`54`], + `18`, [Bidirectional], [`uio[2]`], [`55`], + `19`, [Bidirectional], [`uio[3]`], [`57`], + `20`, [Bidirectional], [`uio[4]`], [`58`], + `21`, [Bidirectional], [`uio[5]`], [`59`], + `22`, [Bidirectional], [`uio[6]`], [`60`], + `23`, [Bidirectional], [`uio[7]`], [`61`], + `24`, [Output], [`uo_out[0]`], [`62`], + `25`, [Output], [`uo_out[1]`], [`2`], + `26`, [Output], [`uo_out[2]`], [`3`], + `27`, [Output], [`uo_out[3]`], [`4`], + `28`, [Output], [`uo_out[4]`], [`5`], + `29`, [Output], [`uo_out[5]`], [`6`], + `30`, [Output], [`uo_out[6]`], [`7`], + `31`, [Output], [`uo_out[7]`], [`8`], + `32`, [Analog], [`analog[6]`], [`11`], + `33`, [Analog], [`analog[7]`], [`12`], + `34`, [Analog], [`analog[8]`], [`13`], + `35`, [Analog], [`analog[9]`], [`14`], + `36`, [Analog], [`analog[10]`], [`15`], + `37`, [Analog], [`analog[11]`], [`16`], + `38`, [Mux Control], [`ctrl_ena`], [`22`], + `39`, [Mux Control], [`ctrl_sel_inc`], [`24`], + `40`, [Mux Control], [`ctrl_sel_rst_n`], [`25`], + `41`, [Reserved], [(none)], [`26`], + `42`, [Reserved], [(none)], [`27`], + `43`, [Reserved], [(none)], [`28`], + ) + ) + + [ + $dagger$ Internally, there's no difference between `clk`, `rst_n` and `ui_in` pins. They are all just bits in the + `pad_ui_in` bus. However, we use different names to make it easier to understand the purpose of each signal. + ] +} + +#let openframe = { + align(center, + table( + columns: 3, + align: left, + + table.header( + [QFN64 Pin], [Function], [Signal] + ), + + `1`, [Mux Control], [`ctrl_ena`], + `2`, [Mux Control], [`ctrl_sel_inc`], + `3`, [Mux Control], [`ctrl_sel_rst_n`], + `4`, [Reserved], [(none)], + `5`, [Reserved], [(none)], + `6`, [Reserved], [(none)], + `7`, [Reserved], [(none)], + `8`, [Reserved], [(none)], + `9`, [Output], [`uo_out[0]`], + `10`, [Output], [`uo_out[1]`], + `11`, [Output], [`uo_out[2]`], + `12`, [Output], [`uo_out[3]`], + `13`, [Output], [`uo_out[4]`], + `14`, [Output], [`uo_out[5]`], + `15`, [Output], [`uo_out[6]`], + `16`, [Output], [`uo_out[7]`], + `17`, [Power], [VDD IO], + `18`, [Ground], [GND IO], + `19`, [Analog], [`analog[0]`], + `20`, [Analog], [`analog[1]`], + `21`, [Analog], [`analog[2]`], + `22`, [Analog], [`analog[3]`], + `23`, [Power], [VAA Analog], + `24`, [Ground], [GND Analog], + `25`, [Analog], [`analog[4]`], + `26`, [Analog], [`analog[5]`], + `27`, [Analog], [`analog[6]`], + `28`, [Analog], [`analog[7]`], + `29`, [Ground], [VDD Core], + `30`, [Power], [VDD Core], + `31`, [Ground], [GND IO], + `32`, [Power], [VDD IO], + `33`, [Bidirectional], [`uio[0]`], + `34`, [Bidirectional], [`uio[1]`], + `35`, [Bidirectional], [`uio[2]`], + `36`, [Bidirectional], [`uio[3]`], + `37`, [Bidirectional], [`uio[4]`], + `38`, [Bidirectional], [`uio[5]`], + `39`, [Bidirectional], [`uio[6]`], + `40`, [Bidirectional], [`uio[7]`], + `41`, [Input], [`ui_in[0]`], + `42`, [Input], [`ui_in[1]`], + `43`, [Input], [`ui_in[2]`], + `44`, [Input], [`ui_in[3]`], + `45`, [Input], [`ui_in[4]`], + `46`, [Input], [`ui_in[5]`], + `47`, [Input], [`ui_in[6]`], + `48`, [Input], [`ui_in[7]`], + `49`, [Input], [`rst_n` $dagger$], + `50`, [Input], [`clk` $dagger$], + `51`, [Ground], [GND IO], + `52`, [Power], [VDD IO], + `53`, [Analog], [`analog[8]`], + `54`, [Analog], [`analog[9]`], + `55`, [Analog], [`analog[10]`], + `56`, [Analog], [`analog[11]`], + `57`, [Ground], [GND Analog], + `58`, [Power], [VDD Analog], + `59`, [Analog], [`analog[12]`], + `60`, [Analog], [`analog[13]`], + `61`, [Analog], [`analog[14]`], + `62`, [Analog], [`analog[15]`], + `63`, [Ground], [GND Core], + `64`, [Power], [VDD Core] + ) + ) + + [ + $dagger$ Internally, there's no difference between `clk`, `rst_n` and `ui_in` pins. They are all just bits in the + `pad_ui_in` bus. However, we use different names to make it easier to understand the purpose of each signal. + ] +} + +#let openframe_sky130 = { + align(center, + table( + columns: 4, + align: left, + + table.header( + [QFN64 pin], [mprj_io_pin], [Function], [Signal] + ), + + `1`, `vssa2`, [Ground], [GND], + `2`, `25`, [Output], `uo[1]`, + `3`, `26`, [Output], `uo[2]`, + `4`, `27`, [Output], `uo[3]`, + `5`, `28`, [Output], `uo[4]`, + `6`, `29`, [Output], `uo[5]`, + `7`, `30`, [Output], `uo[6]`, + `8`, `31`, [Output], `uo[7]`, + `9`, `vdda2`, [Power], [VDD Analog], + `10`, `vssd2`, [Ground], [GND], + `11`, `32`, [Analog], `analog[6]`, + `12`, `33`, [Analog], `analog[7]`, + `13`, `34`, [Analog], `analog[8]`, + `14`, `35`, [Analog], `analog[9]`, + `15`, `36`, [Analog], `analog[10]`, + `16`, `37`, [Analog], `analog[11]`, + `17`, `vddio`, [Power], [VDD IO], + `18`, `vccd`, [Power], [VDD Core], + `19`, `NC`, [], [], + `20`, `vssa`, [Ground], [GND], + `21`, `resetb`, [], [], + `22`, `38`, [Mux], `ctrl_ena`, + `23`, `vssd`, [Ground], [GND], + `24`, `39`, [Mux], `ctrl_sel_inc`, + `25`, `40`, [Mux], `ctrl_sel_rst_n`, + `26`, `41`, [Reserved], [(none)], + `27`, `42`, [Reserved], [(none)], + `28`, `43`, [Reserved], [(none)], + `29`, `vssio`, [Ground], [GND], + `30`, `vdda`, [Power], [VDD Analog], + `31`, `0`, [Input], `ui[0]`, + `32`, `1`, [Input], `ui[1]`, + `33`, `2`, [Input], `ui[2]`, + `34`, `3`, [Input], `ui[3]`, + `35`, `4`, [Input], `ui[4]`, + `36`, `5`, [Input], `ui[5]`, + `37`, `6`, [Input], `ui[6]`, + `38`, `vssa1`, [Ground], [GND], + `39`, `vssd1`, [Ground], [GND], + `40`, `vdda1`, [Power], [VDD Analog], + `41`, `7`, [Analog], `analog[0]`, + `42`, `8`, [Analog], `analog[1]`, + `43`, `9`, [Analog], `analog[2]`, + `44`, `10`, [Analog], `analog[3]`, + `45`, `11`, [Analog], `analog[4]`, + `46`, `12`, [Analog], `analog[5]`, + `47`, `vdda1`, [Power], [VDD Analog], + `48`, `13`, [Input], `ui[7]`, + `49`, `vccd1`, [Power], [VDD Core], + `50`, `14`, [Input], [`u_clk` $dagger$], + `51`, `15`, [Input], [`u_rst_n` $dagger$], + `52`, `vssa1`, [Ground], [GND], + `53`, `16`, [Bidirectional], `io[0]`, + `54`, `17`, [Bidirectional], `io[1]`, + `55`, `18`, [Bidirectional], `io[2]`, + `56`, `vssio`, [Ground], [GND], + `57`, `19`, [Bidirectional], `io[3]`, + `58`, `20`, [Bidirectional], `io[4]`, + `59`, `21`, [Bidirectional], `io[5]`, + `60`, `22`, [Bidirectional], `io[6]`, + `61`, `23`, [Bidirectional], `io[7]`, + `62`, `24`, [Output], `uo[0]`, + `63`, `vccd2`, [Power], [VDD Core], + `64`, `vddio`, [Power], [VDD IO], + `EPAD`, [], [Ground], [] + ) + ) + + [ + $dagger$ Internally, there's no difference between `u_clk`, `u_rst_n`, and `ui` pins. They are all just bits in the + `pad_ui_in` bus. However, we use different names to make it easier to understand the purpose of each signal. + ] +} + +#let customframe_ihp_sg13g2 = { + align(center, + table( + columns: 4, + align: left, + + table.header( + [Die Pad], [QFN64 pin], [Function], [Signal] + ), + + `0`, `1`, [Mux Control], `ctrl_ena`, + `1`, `2`, [Mux Control], `ctrl_sel_inc`, + `2`, `3`, [Mux Control], `ctrl_sel_rst_n`, + `3`, `4`, [Reserved], [], + `4`, `5`, [Reserved], [], + `5`, `6`, [Reserved], [], + `6`, `7`, [Reserved], [], + `7`, `8`, [Reserved], [], + `8`, `9`, [Output], `uo[0]`, + `9`, `10`, [Output], `uo[1]`, + `10`, `11`, [Output], `uo[2]`, + `11`, `12`, [Output], `uo[3]`, + `12`, `13`, [Output], `uo[4]`, + `13`, `14`, [Output], `uo[5]`, + `14`, `15`, [Output], `uo[6]`, + `15`, `16`, [Output], `uo[7]`, + `16`, `17`, [Power], [VDD IO], + `17`, `18`, [Ground], [GND IO], + `18`, `19`, [Analog], `analog[0]`, + `19`, `20`, [Analog], `analog[1]`, + `20`, `21`, [Analog], `analog[2]`, + `21`, `22`, [Analog], `analog[3]`, + `22`, `23`, [Power], [VDD Analog], + `23`, `24`, [Ground], [GND Analog], + `24`, `25`, [Analog], `analog[4]`, + `25`, `26`, [Analog], `analog[5]`, + `26`, `27`, [Analog], `analog[6]`, + `27`, `28`, [Analog], `analog[7]`, + `28`, `29`, [Ground], [GND Core], + `29`, `30`, [Power], [VDD Core], + `30`, `31`, [Ground], [GND IO], + `31`, `32`, [Power], [VDD IO], + `32`, `33`, [Bidirectional], `uio[0]`, + `33`, `34`, [Bidirectional], `uio[1]`, + `34`, `35`, [Bidirectional], `uio[2]`, + `35`, `36`, [Bidirectional], `uio[3]`, + `36`, `37`, [Bidirectional], `uio[4]`, + `37`, `38`, [Bidirectional], `uio[5]`, + `38`, `39`, [Bidirectional], `uio[6]`, + `39`, `40`, [Bidirectional], `uio[7]`, + `40`, `41`, [Input], `ui[0]`, + `41`, `42`, [Input], `ui[1]`, + `42`, `43`, [Input], `ui[2]`, + `43`, `44`, [Input], `ui[3]`, + `44`, `45`, [Input], `ui[4]`, + `45`, `46`, [Input], `ui[5]`, + `46`, `47`, [Input], `ui[6]`, + `47`, `48`, [Input], `ui[7]`, + `48`, `49`, [Input], [`u_rst_n` $dagger$], + `49`, `50`, [Input], [`u_clk` $dagger$], + `50`, `51`, [Ground], [GND IO], + `51`, `52`, [Power], [VDD IO], + `52`, `53`, [Analog], `analog[8]`, + `53`, `54`, [Analog], `analog[9]`, + `54`, `55`, [Analog], `analog[10]`, + `55`, `56`, [Analog], `analog[11]`, + `56`, `57`, [Ground], [GND Analog], + `57`, `58`, [Power], [VDD Analog], + `58`, `59`, [Analog], `analog[12]`, + `59`, `60`, [Analog], `analog[13]`, + `60`, `61`, [Analog], `analog[14]`, + `61`, `62`, [Analog], `analog[15]`, + `62`, `63`, [Ground], [GND Core], + `63`, `64`, [Power], [VDD Core], + `SUB`, `EPAD`, [Ground], [], + ) + ) + + [ + $dagger$ Internally, there's no difference between `u_clk`, `u_rst_n`, and `ui` pins. They are all just bits in the + `pad_ui_in` bus. However, we use different names to make it easier to understand the purpose of each signal. + ] +} + +#let customframe_gf180mcud = { + align(center, + table( + columns: 4, + align: left, + + table.header( + [Die Pad], [QFN64 pin], [Function], [Signal] + ), + + `0`, `1`, [Mux Control], `ctrl_ena`, + `1`, `2`, [Mux Control], `ctrl_sel_inc`, + `2`, `3`, [Mux Control], `ctrl_sel_rst_n`, + `3`, `4`, [Reserved], [], + `4`, `5`, [Reserved], [], + `5`, `6`, [Reserved], [], + `6`, `7`, [Reserved], [], + `7`, `8`, [Reserved], [], + `8`, `EPAD`, [Ground], [GND IO], + `9`, `9`, [Output], `uo[0]`, + `10`, `10`, [Output], `uo[1]`, + `11`, `11`, [Output], `uo[2]`, + `12`, `12`, [Output], `uo[3]`, + `13`, `13`, [Output], `uo[4]`, + `14`, `14`, [Output], `uo[5]`, + `15`, `15`, [Output], `uo[6]`, + `16`, `16`, [Output], `uo[7]`, + `17`, `17`, [Power], [VDD IO], + `18`, `EPAD`, [Ground], [GND IO], + `19`, `18`, [Analog], `analog[0]`, + `20`, `19`, [Analog], `analog[1]`, + `21`, `20`, [Analog], `analog[2]`, + `22`, `21`, [Analog], `analog[3]`, + `23`, `22`, [Analog], `analog[4]`, + `24`, `23`, [Analog], `analog[5]`, + `25`, `24`, [Power], [PWR Analog], + `26`, `EPAD`, [Ground], [GND Analog], + `27`, `25`, [Analog], `analog[6]`, + `28`, `26`, [Analog], `analog[7]`, + `29`, `27`, [Analog], `analog[8]`, + `30`, `28`, [Analog], `analog[9]`, + `31`, `29`, [Analog], `analog[10]`, + `32`, `30`, [Analog], `analog[11]`, + `33`, `EPAD`, [Ground], [GND Core], + `34`, `31`, [Power], [VDD Core], + `35`, `EPAD`, [Ground], [GND IO], + `36`, `32`, [Power], [VDD IO], + `37`, `33`, [Bidirectional], `uio[0]`, + `38`, `34`, [Bidirectional], `uio[1]`, + `39`, `35`, [Bidirectional], `uio[2]`, + `40`, `36`, [Bidirectional], `uio[3]`, + `41`, `37`, [Bidirectional], `uio[4]`, + `42`, `38`, [Bidirectional], `uio[5]`, + `43`, `39`, [Bidirectional], `uio[6]`, + `44`, `40`, [Bidirectional], `uio[7]`, + `45`, `EPAD`, [Ground], [GND IO], + `46`, `41`, [Input], `ui[0]`, + `47`, `42`, [Input], `ui[1]`, + `48`, `43`, [Input], `ui[2]`, + `49`, `44`, [Input], `ui[3]`, + `50`, `45`, [Input], `ui[4]`, + `51`, `46`, [Input], `ui[5]`, + `52`, `47`, [Input], `ui[6]`, + `53`, `48`, [Input], `ui[7]`, + `54`, `49`, [Input], [`u_rst_n` $dagger$], + `55`, `50`, [Input], [`u_clk` $dagger$], + `56`, `EPAD`, [Ground], [GND IO], + `57`, `51`, [Power], [VDD IO], + `58`, `52`, [Analog], `analog[12]`, + `59`, `53`, [Analog], `analog[13]`, + `60`, `54`, [Analog], `analog[14]`, + `61`, `55`, [Analog], `analog[15]`, + `62`, `EPAD`, [Ground], [GND Analog], + `63`, `56`, [Power], [PWR Analog], + `64`, `57`, [Analog], `analog[16]`, + `65`, `58`, [Analog], `analog[17]`, + `66`, `59`, [Analog], `analog[18]`, + `67`, `60`, [Analog], `analog[19]`, + `68`, `61`, [Analog], `analog[20]`, + `69`, `62`, [Analog], `analog[21]`, + `70`, `EPAD`, [Ground], [GND Core], + `71`, `63`, [Power], [VDD Core], + `72`, `EPAD`, [Ground], [GND IO], + `73`, `64`, [Power], [VDD IO] + ) + ) + + [ + $dagger$ Internally, there's no difference between `u_clk`, `u_rst_n`, and `ui` pins. They are all just bits in the + `pad_ui_in` bus. However, we use different names to make it easier to understand the purpose of each signal. + ] +} \ No newline at end of file diff --git a/docs/typst/chapters/team.typ b/docs/typst/chapters/team.typ new file mode 100644 index 0000000..7bf3077 --- /dev/null +++ b/docs/typst/chapters/team.typ @@ -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 diff --git a/docs/typst/chapters/tt-multiplexer.typ b/docs/typst/chapters/tt-multiplexer.typ new file mode 100644 index 0000000..150d18d --- /dev/null +++ b/docs/typst/chapters/tt-multiplexer.typ @@ -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 \ No newline at end of file diff --git a/docs/typst/chapters/using-this-datasheet.typ b/docs/typst/chapters/using-this-datasheet.typ new file mode 100644 index 0000000..8076519 --- /dev/null +++ b/docs/typst/chapters/using-this-datasheet.typ @@ -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. \ No newline at end of file diff --git a/docs/typst/resources/fonts/Martian_Mono/OFL.txt b/docs/typst/resources/fonts/Martian_Mono/OFL.txt new file mode 100644 index 0000000..adffdd1 --- /dev/null +++ b/docs/typst/resources/fonts/Martian_Mono/OFL.txt @@ -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. diff --git a/docs/typst/resources/fonts/Martian_Mono/README.txt b/docs/typst/resources/fonts/Martian_Mono/README.txt new file mode 100644 index 0000000..737b598 --- /dev/null +++ b/docs/typst/resources/fonts/Martian_Mono/README.txt @@ -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. diff --git a/docs/typst/resources/fonts/Martian_Mono/static/MartianMono-Bold.ttf b/docs/typst/resources/fonts/Martian_Mono/static/MartianMono-Bold.ttf new file mode 100644 index 0000000..e703937 Binary files /dev/null and b/docs/typst/resources/fonts/Martian_Mono/static/MartianMono-Bold.ttf differ diff --git a/docs/typst/resources/fonts/Martian_Mono/static/MartianMono-Light.ttf b/docs/typst/resources/fonts/Martian_Mono/static/MartianMono-Light.ttf new file mode 100644 index 0000000..ca29edb Binary files /dev/null and b/docs/typst/resources/fonts/Martian_Mono/static/MartianMono-Light.ttf differ diff --git a/docs/typst/resources/fonts/Martian_Mono/static/MartianMono-Medium.ttf b/docs/typst/resources/fonts/Martian_Mono/static/MartianMono-Medium.ttf new file mode 100644 index 0000000..896d03e Binary files /dev/null and b/docs/typst/resources/fonts/Martian_Mono/static/MartianMono-Medium.ttf differ diff --git a/docs/typst/resources/fonts/Martian_Mono/static/MartianMono-Regular.ttf b/docs/typst/resources/fonts/Martian_Mono/static/MartianMono-Regular.ttf new file mode 100644 index 0000000..d85c48a Binary files /dev/null and b/docs/typst/resources/fonts/Martian_Mono/static/MartianMono-Regular.ttf differ diff --git a/docs/typst/resources/fonts/Martian_Mono/static/MartianMono-SemiBold.ttf b/docs/typst/resources/fonts/Martian_Mono/static/MartianMono-SemiBold.ttf new file mode 100644 index 0000000..a4f634a Binary files /dev/null and b/docs/typst/resources/fonts/Martian_Mono/static/MartianMono-SemiBold.ttf differ diff --git a/docs/typst/resources/fonts/Montserrat/OFL.txt b/docs/typst/resources/fonts/Montserrat/OFL.txt new file mode 100644 index 0000000..8e618c8 --- /dev/null +++ b/docs/typst/resources/fonts/Montserrat/OFL.txt @@ -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. diff --git a/docs/typst/resources/fonts/Montserrat/README.txt b/docs/typst/resources/fonts/Montserrat/README.txt new file mode 100644 index 0000000..930137d --- /dev/null +++ b/docs/typst/resources/fonts/Montserrat/README.txt @@ -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. diff --git a/docs/typst/resources/fonts/Montserrat/static/Montserrat-Bold.ttf b/docs/typst/resources/fonts/Montserrat/static/Montserrat-Bold.ttf new file mode 100644 index 0000000..02ff6ff Binary files /dev/null and b/docs/typst/resources/fonts/Montserrat/static/Montserrat-Bold.ttf differ diff --git a/docs/typst/resources/fonts/Montserrat/static/Montserrat-BoldItalic.ttf b/docs/typst/resources/fonts/Montserrat/static/Montserrat-BoldItalic.ttf new file mode 100644 index 0000000..998ed88 Binary files /dev/null and b/docs/typst/resources/fonts/Montserrat/static/Montserrat-BoldItalic.ttf differ diff --git a/docs/typst/resources/fonts/Montserrat/static/Montserrat-Italic.ttf b/docs/typst/resources/fonts/Montserrat/static/Montserrat-Italic.ttf new file mode 100644 index 0000000..84bc0f4 Binary files /dev/null and b/docs/typst/resources/fonts/Montserrat/static/Montserrat-Italic.ttf differ diff --git a/docs/typst/resources/fonts/Montserrat/static/Montserrat-Medium.ttf b/docs/typst/resources/fonts/Montserrat/static/Montserrat-Medium.ttf new file mode 100644 index 0000000..dfbcfe4 Binary files /dev/null and b/docs/typst/resources/fonts/Montserrat/static/Montserrat-Medium.ttf differ diff --git a/docs/typst/resources/fonts/Montserrat/static/Montserrat-MediumItalic.ttf b/docs/typst/resources/fonts/Montserrat/static/Montserrat-MediumItalic.ttf new file mode 100644 index 0000000..1e67477 Binary files /dev/null and b/docs/typst/resources/fonts/Montserrat/static/Montserrat-MediumItalic.ttf differ diff --git a/docs/typst/resources/fonts/Montserrat/static/Montserrat-Regular.ttf b/docs/typst/resources/fonts/Montserrat/static/Montserrat-Regular.ttf new file mode 100644 index 0000000..48ba65e Binary files /dev/null and b/docs/typst/resources/fonts/Montserrat/static/Montserrat-Regular.ttf differ diff --git a/docs/typst/src/colours.typ b/docs/typst/src/colours.typ new file mode 100644 index 0000000..1910640 --- /dev/null +++ b/docs/typst/src/colours.typ @@ -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") \ No newline at end of file diff --git a/docs/typst/src/datasheet.typ b/docs/typst/src/datasheet.typ new file mode 100644 index 0000000..b9b1712 --- /dev/null +++ b/docs/typst/src/datasheet.typ @@ -0,0 +1,640 @@ +#import "@preview/tiaoma:0.3.0" + +#import "lib.typ": badge, callout, annotated_qrcode, get_image_by_id +#import "colours.typ" + +#import "../chapters/pinout-tables.typ" as pins +#import "../chapters/call-to-action.typ": call_to_action_page + +#let _funding_content = state("_funding_content", []) +#let _funding_alt_title = state("_funding_alt_title", []) +#let _flip_footer_ordering = state("_flip_footer_ordering", false) +#let _chip_render_content = state("_chip_render_content", none) +#let _chip_render_sponsor_text = state("_chip_render_sponsor_text", none) +#let _chip_render_sponsor_logo = state("_chip_render_sponsor_logo", none) + +#let styling(body) = { + set page(paper: "a4", margin: auto) + set text(font: "Montserrat", size: 12pt) + show raw: set text(font: "Martian Mono", weight: "light") + + // style headings + show heading.where(level: 1): this => { + text(size: 28pt, this) + v(0.1cm) + } + show heading.where(level: 2): set text(size: 22pt) + show heading.where(level: 3): set text(size: 16pt) + + // indent numbered and bullet point lists + set enum(indent: 1em) + set list(indent: 1em) + + // format table so that empty cells show dark grey em dash + show table.cell: this => { + if this.body == [] { + align(center + horizon, text(fill: colours.TABLE_GREY)[$dash.em$]) + } else { + this + } + } + + // set quote styling + // used for project description + show quote: set align(center) + show quote: set pad(x: 1em) + show quote: set block(above: 1.75em) + + body +} + +#let styling_set_par(body) = { + // justify text, except for in tables + set par(justify: true) + show table: this => { + set par(justify: false) + this + } + body +} + +#let _footer(shuttle, invert_text_colour: false, display_pg_as: "1", flip_ordering: false) = { + // setup + set text(size: 10pt) + set text(white) if invert_text_colour + + let logo = box(baseline: 0.25em, { + if invert_text_colour { + get_image_by_id("logos", "tt_roundel_white", height: 25%) + } else { + get_image_by_id("logos", "tt_roundel_black", height: 25%) + } + }) + + // get the title of the current chapter + let title = context query(selector(heading.where(level: 1)).before(here())).last().body + let pg_num = context counter(page).display(display_pg_as) + + let TITLE_SPACING = 0.5cm + let LOGO_SPACING = 0.25cm + + let set_1 = {strong(pg_num); h(TITLE_SPACING); emph(title); h(1fr); emph(shuttle); h(LOGO_SPACING); logo} + let set_2 = {logo; h(LOGO_SPACING); emph(shuttle); h(1fr); emph(title); h(TITLE_SPACING); strong(pg_num)} + + // actual styling + context { + // alternate odd-even pages + if calc.even(counter(page).get().first()) { + if flip_ordering {set_2} else {set_1} + } else { + if flip_ordering {set_1} else {set_2} + } + } +} + +#let project( + title: "Title", + author: ("Author 1", "Author 2"), + repo_link: "!!! missing repository link !!!", + description: "!!! Missing description !!!", + address: "----", + clock: "No Clock", + type: "HDL", + wokwi_id: none, + danger_level: none, + danger_reason: none, + + body +) = { + + show: styling; + show: styling_set_par; + + // make fake heading - this is the one that gets shown in the table of contents + // which has the nice address marker next to it + let fake_heading = block( + heading(level: 2, outlined: true, bookmarked: false)[ + #box(baseline: 0.4em, badge(colours.BADGE_TEAL, strong(raw(address)))) + #title + ] + ) + + // move fake heading so it doesn't affect layout + place(top + left, hide(fake_heading)) + + // make heading thats seen on the project page + heading(level: 2, outlined: false, bookmarked: true, title) + + // display author names + // if 1, it is displayed as is + // if 2, it is displayed as "a & b" + // if >2, it is displayed as "a, b & c" + let author_text = [] + author_text += [by *#author.at(0)*] + + if author.len() == 2 { + author_text += [ & *#author.at(1)*] + + } else if author.len() > 2 { + let names = author.slice(1, author.len()-1) + + for name in names { + author_text += [, *#name*] + } + + author_text += [ & *#author.at(author.len()-1)*] + } + author_text + + // makes badges array, add address first + let badges = (badge(colours.BADGE_TEAL, strong(raw(address))),) + if clock != "No Clock" {badges.push(badge(colours.BADGE_BLUE, strong(raw(clock))))} + badges.push(badge(colours.BADGE_PINK, strong(raw(type + " Project")))) + + // add asic danger callout if necessary + let danger_callout + if danger_level != none { + if danger_level == "medium" { + badges.push(badge(colours.WARNING_STRONG, strong(raw("Medium Danger")))) + danger_callout = callout( + "warning", + raw("This project can damage the ASIC under certain conditions."), + raw(danger_reason) + ) + + } else if danger_level == "high" { + badges.push(badge(colours.DANGER_STRONG, strong(raw("High Danger")))) + danger_callout = callout( + "danger", + raw("This project will damage the ASIC."), + raw(danger_reason) + ) + } else { + panic([unexpected danger_level value (#danger_level)]) + } + } + + // add badges to page, unpack badge array + grid(columns: badges.len(), column-gutter: 1em, ..badges) + + // hardcode these repo/wokwi links to be black and to not follow link-override-colour + link(repo_link, text(black, raw(repo_link.trim("https://", at: start)))) + + if wokwi_id != none { + let wokwi_link = "https://wokwi.com/projects/" + wokwi_id + parbreak() + link(wokwi_link, text(black, raw(wokwi_link.trim("https://", at: start)))) + } + + // add description and danger callout to page + quote(block: true, quotes: true, emph(description)) + danger_callout + + set heading(offset: 2) + + // ensure each figure and footnote is only counted within its project + // format numbering as "." + let project_numbering(count) = {address.trim("-", at: start) + "." + str(count)} + + counter(figure.where(kind: image)).update(0) + counter(footnote).update(0) + set figure(numbering: project_numbering) + set footnote(numbering: project_numbering) + + body + pagebreak(weak: true) +} + +#let splash_chapter_page( + title, + page_colour: white, + invert_text_colour: false, + footer_text: none, + additional_content: none +) = { + + page( + fill: page_colour, + footer: context { + _footer( + footer_text, + invert_text_colour: invert_text_colour, + flip_ordering: _flip_footer_ordering.final() + ) + }, + + // page body - center title and add additional content if necessary + { + set text(white) if invert_text_colour + + // make headings + align(center + horizon, block(text(size: 100pt, strong(title)))) + place(top + left, hide(heading(level: 1, title))) + + // make info box if needed + if additional_content != none { + set text(black) + place(center + bottom, block(fill: white, inset: 10pt, additional_content)) + } + } + ) +} + +// make a tiling logo page +#let tiling_logo_page() = { + + let pattern = tiling( + size: (3cm, 3cm), + spacing: (1cm, 1cm), + get_image_by_id("logos", "tt_roundel_light_grey") + ) + + page( + background: rect(width: 110%, height: 105%, inset: 0pt, outset: 0pt, stroke: none, fill: pattern), + [] // empty page + ) +} + +#let datasheet( + shuttle_id: none, + shuttle_name: none, + repo_link: none, + theme: "classic", + projects: none, + show_pinouts: "openframe", + theme_override_colour: none, + date: datetime.today(), + link_disable_colour: false, + link_override_colour: none, + chip_viewer_link: none, + qrcode_follows_theme: false, + + body +) = { + + set document( + title: [#shuttle_name Datasheet], + author: "Tiny Tapeout & Contributors", + description: [The official Tiny Tapeout datasheet for #shuttle_id. Template by Kristaps Jurkans (bluesky/\@krisj.dev)] + ) + + show: styling; + + let date_str = date.display("[month repr:long] [day padding:none], [year]") + + // contains title, repo link and compilation date + let cover_text = align(center)[ + #text(size: 32pt, strong[#shuttle_name Datasheet]) + + #link( + repo_link, + text( + size: 14pt, + font: "Martian Mono", + repo_link.trim("https://", at: start) + ) + ) + + #v(2.5cm) + #text(size: 16pt, weight: "medium", date_str) + ] + + + // configure theme variables + let selected_theme_colour = colours.THEME_PLUM // default fill + if theme_override_colour != none {selected_theme_colour = theme_override_colour} + + let qrcode_colour = black + if qrcode_follows_theme and theme == "bold" {qrcode_colour = selected_theme_colour} + + // make titlepage + if theme == "classic" { + get_image_by_id("logos", "tt_square_colourful") + cover_text + + } else if theme == "bold" { + set page( + background: get_image_by_id("backgrounds", "gds_bw_transparent", height: 100%), + fill: selected_theme_colour + ) + + align( + center + horizon, + get_image_by_id("logos", "tt_roundel_white", height: 60%) + ) + + set text(white) + cover_text + + } else if theme == "monochrome" { + align( + center + horizon, + get_image_by_id("logos", "tt_roundel_black", height: 60%) + ) + cover_text + } + + // style link colour + // moved after title page since theme-override-colour would cause it to blend in with the background + let link_colour = luma(0%) + if not link_disable_colour { + // if override colour specified, set it to that + // else fall back to default link colour + if link_override_colour != none { + link_colour = link_override_colour + } else { + link_colour = colours.LINK_DEFAULT + } + } + show link: set text(link_colour) + + // add tiling logo page (this is pg2 of the datasheet) + tiling_logo_page() + + show: styling_set_par; + + // setup for table of contents + set page(footer: _footer(shuttle_id, invert_text_colour: false, display_pg_as: "i")) + counter(page).update(1) + // add spacing for each H1, and make H1 bold + show outline.entry.where(level: 1): set block(above: 1em) + show outline.entry.where(level: 1): strong + + // make actual table of contents + outline( + depth: 2, + title: heading(level: 1, outlined: false, bookmarked: true, "Table of Contents") + ) + pagebreak(weak: true) + + // determine whether we need to flip the footer ordering depending on if the table fo contents + // ended on an odd or even page + context { + _flip_footer_ordering.update(calc.even(counter(page).get().last())) + } + + set page(footer: context {_footer( + shuttle_id, display_pg_as: "1", flip_ordering: _flip_footer_ordering.final() + )}) + + // reset page counter so that it starts counting actual content now + counter(page).update(1) + + // make chip render pages + context { + let chip_renders = _chip_render_content.final() + + if chip_renders != none { + let qr + let qr_grid + + if chip_viewer_link != none { + qr_grid = annotated_qrcode( + chip_viewer_link, "Online chip viewer", + tiaoma_args: ("scale": 2.0, "fg-color": qrcode_colour) + ) + } + + + let sponsor_grid + if _chip_render_sponsor_text.final() != none and _chip_render_sponsor_logo.final() != none { + sponsor_grid = grid( + columns: 1, + rows: 3, + align: center + horizon, + row-gutter: 1em, + + _chip_render_sponsor_text.final(), + _chip_render_sponsor_logo.final(), + [] // added for padding + ) + } + + let content + let content_arr = () + + if sponsor_grid != none { + content_arr.push(sponsor_grid) + } + + if qr_grid != none { + content_arr.push(qr_grid) + } + + if content_arr.len() >= 1 { + content = grid( + columns: content_arr.len(), + rows: 1, + column-gutter: 1em, row-gutter: 1em, + align: center + horizon, + + ..content_arr + ) + } + + if theme == "bold" { + splash_chapter_page( + "Chip Renders", + page_colour: selected_theme_colour, + invert_text_colour: true, + footer_text: shuttle_id, + additional_content: content + ) + } else { + splash_chapter_page( + "Chip Renders", + invert_text_colour: false, + footer_text: shuttle_id, + additional_content: content + ) + } + // display chip renders + chip_renders + } + } + + // make project splash page + show all projects + if projects != none { + if theme == "bold" { + splash_chapter_page( + "Projects", + page_colour: selected_theme_colour, + invert_text_colour: true, + footer_text: shuttle_id + ) + } else { + splash_chapter_page( + "Projects", + footer_text: shuttle_id + ) + } + projects + } + + // reset counters back to 0, to be used in the info section of the datasheet + counter(figure.where(kind: image)).update(0) + counter(footnote).update(0) + body + + // add informational content + // chip pinout diagram + + let pinout_map = ( + caravel: (diagram: "chip_pinout_qfn64", table: pins.caravel), + openframe: (diagram: "chip_pinout_qfn64", table: pins.openframe), + openframe_sky130: (diagram: "chip_pinout_qfn64_sky130_openframe", table: pins.openframe_sky130), + customframe_ihp_sg13g2: (diagram: "chip_pinout_qfn64_ihp_sg13g2", table: pins.customframe_ihp_sg13g2), + customframe_gf180mcud: (diagram: "chip_pinout_qfn64_gf180mcud", table: pins.customframe_gf180mcud) + ) + + page[ + = Pinout + + The chip is packaged in a 64-pin QFN package. The pinout is shown below. + + #align(center, get_image_by_id("images", pinout_map.at(show_pinouts).at("diagram"), height: 60%)) + + #callout("info", "Note")[ + You will receive the chip mounted on a breakout board (#link("https://github.com/tinytapeout/breakout-pcb")[github.com/tinytapeout/breakout-pcb]). + + The pinout is provided for advanced users, as most users will not need to solder the chip directly. + ] + ] + + // multiplexer infrastructure explanation + page({ + include "../chapters/tt-multiplexer.typ" + + pinout_map.at(show_pinouts).at("table") + }) + + // add funding/sponsor page + context { + // check if funding has been provided, otherwise we risk adding an empty page instead + if _funding_content.final() != [] { + page({ + let funding_content = _funding_content.final() + let alt_title = _funding_alt_title.final() + + if alt_title != none { + heading(level: 1, alt_title) + } else { + heading(level: 1, "Funding") + } + + funding_content + }) + } + } + + page(include "../chapters/team.typ") + page(include "../chapters/using-this-datasheet.typ") + + // add call to action page + if theme == "classic" or theme == "monochrome" { + page(call_to_action_page()) + } else if theme == "bold" { + page( + fill: selected_theme_colour, + footer: none, + { + set text(white) + call_to_action_page(qrcode_colour: black) + } + ) + } +} + +#let funding(doc, alt_title: none) = { + context { + _funding_content.update(doc) + _funding_alt_title.update(alt_title) + } +} + +#let renders(sponsor_text: none, sponsor_logo: none, doc) = { + context { + _chip_render_content.update(doc) + _chip_render_sponsor_text.update(sponsor_text) + _chip_render_sponsor_logo.update(sponsor_logo) + } +} + +#let art(id, rot: 90deg) = { + + // all artwork is assumed to be external (hosted in a separate repo) + let art_img = get_image_by_id("artwork", id, height: 100%) + let info = yaml("../resources/external/manifest.yaml").at("artwork").at(id) + + let attribution = strong[ + #info.title $dash.fig$ + #if info.at("designer", default: none) != none [Designed by #info.designer.] + Illustrated by #info.artist. + ] + + // heading shown in table of contents + let art_heading = [#box(baseline: 0.4em, badge(colours.BADGE_PURPLE, strong(`Artwork`))) #info.title] + set text(fill: white, size: 10pt) + + page( + paper: "a4", + margin: 0pt, + footer: none, + // add artwork as background + background: rotate(rot, reflow: true, art_img), + { + // make fake & read heading + // first is shown in ToC, the second in the PDF list + place(top, hide(heading(level: 2, outlined: true, bookmarked: false, art_heading))) + place(top, hide(heading(level: 2, outlined: false, bookmarked: true)[(Artwork) #info.title])) + + // determine position for artwork attribution + context { + let attribution_placement = bottom + left + + // if even + if calc.even(counter(page).get().first()) { + // but flipped + if _flip_footer_ordering.final() { + attribution_placement = bottom + right + } else { + attribution_placement = bottom + left + } + + // if odd + } else { + if _flip_footer_ordering.final() { + attribution_placement = bottom + left + } else { + attribution_placement = bottom + right + } + } + + // add artwork attribution + place( + attribution_placement, + block( + fill: colours.ARTWORK_GREY_INFO, + inset: 4pt, + attribution + ) + ) + } + } + ) +} + +#let add_render(title, subtitle: none, img) = { + page( + align(center, grid( + columns: 1, + row-gutter: 10pt, + align: center, + + heading(level: 2, title), + subtitle, + img + ) + ) + ) +} \ No newline at end of file diff --git a/docs/typst/src/lib.typ b/docs/typst/src/lib.typ new file mode 100644 index 0000000..83a32e2 --- /dev/null +++ b/docs/typst/src/lib.typ @@ -0,0 +1,164 @@ +#import "@preview/tiaoma:0.3.0" +#import "colours.typ" as colours + +/// Create a badge with a given colour and text +/// +/// NOTE: This function will replace any instances of a hyphen ("-") +/// with a coloured zero digit. This is intended, and is used for +/// the address badge. +/// - colour (color): Colour of the badge +/// - doc (content, str): Text to display within the badge +/// -> content +#let badge(colour, body) = { + set text(white) + show "-": text(colours.BADGE_MUTED_TEAL, "0") + rect(fill: colour, body) +} + +/// Create a callout to display important information +/// +/// - type (): Callout type (warning, danger, info, custom) +/// - title (str): Title of the callout +/// - custom_title_colour (color): Colour to use for the title of the custom callout +/// - custom_body_colour (color): Colour to use for the body of the custom callout +/// - body (content, str): Content to display in the body of the callout +/// -> content +#let callout( + type, + title, + custom_title_colour: none, + custom_body_colour: none, + body +) = { + + set par(justify: false) + let title_colour + let body_colour + + if type == "warning" { + title_colour = colours.WARNING_STRONG + body_colour = colours.WARNING_WEAK + + } else if type == "danger" { + title_colour = colours.DANGER_STRONG + body_colour = colours.DANGER_WEAK + + } else if type == "info" { + title_colour = colours.INFO_STRONG + body_colour = colours.INFO_WEAK + + } else if type == "custom" { + title_colour = custom_title_colour + body_colour = custom_body_colour + + } else { + panic([unknown callout type (#type)]) + } + + set text(white) + show link: set text(white) + + // make body first + block( + fill: body_colour, + width: 100%, + inset: 8pt, + + { + // make title + block(fill: title_colour, width: 100%, outset: 8pt, strong(title)) + body + } + ) +} + +/// Create an annotated QR code +/// +/// An annotated QR code consists of a title, the QR code, and the link to the website. +/// They are stacked on top of each other. +/// +/// - url (str): Link to a website +/// - title (str): Title of the QR code +/// - title_colour (color): Colour of the title of the QR code +/// - url_colour (color): Colour of the link +/// - tiaoma_args (dict): Arguments to pass to the tiaoma package +/// -> grid +#let annotated_qrcode( + url, + title, + title_colour: black, + url_colour: black, + tiaoma_args: (:) +) = { + let qr = tiaoma.qrcode(url, options: tiaoma_args) + + grid( + columns: 1, + rows: 3, + align: center + horizon, + row-gutter: 1em, + + text(fill: title_colour, title), + qr, + + // make url be the width of the QR code + context { + block( + width: measure(qr).width + 1cm, + height: auto, + breakable: false, + link(url, text(url_colour, url.trim("https://", at: start)))) + } + ) +} + +/// Retrieve an image from via an ID +/// +/// The artwork repo must be cloned into resources/external, and its `manifest.yaml` +/// must be present for this function to work. +/// +/// - type (str): Type of image to fetch (must be a key in one of the manifests) +/// - id (str): ID of image to fetch +/// - args (arguments): Arguments to supply to `image()` +/// -> content +#let get_image_by_id(type, id, ..args) = { + + // external manifest is used for artwork repo + // since large photos would bloat the template size + let manifest = yaml("../resources/external/manifest.yaml") + + if type not in manifest { + panic("type not in manifest") + } + + let root_path = "../resources/external" + let img_details = manifest.at(type).at(id) + + // make image + image(root_path + "/" + type + "/" + img_details.file, ..args) +} + +/// Create a horizontal line +/// +/// Used for `pandoc` compatability +/// +/// -> content +#let horizontalrule() = { + line( + length: 100%, + stroke: 2pt + colours.HORIZONTAL_RULE_GREY + ) +} + +/// Create a block quote +/// +/// Used for `pandoc` compatability +/// +/// - body (content): Body of the block quote +/// -> content +#let blockquote(body) = { + quote( + block: true, + body + ) +} \ No newline at end of file diff --git a/docs/typst/src/tt.typ b/docs/typst/src/tt.typ new file mode 100644 index 0000000..5c3acc4 --- /dev/null +++ b/docs/typst/src/tt.typ @@ -0,0 +1,3 @@ +#import "lib.typ" as lib +#import "datasheet.typ" as datasheet +#import "colours.typ" as colours \ No newline at end of file diff --git a/docs/typst/typst.toml b/docs/typst/typst.toml new file mode 100644 index 0000000..8116122 --- /dev/null +++ b/docs/typst/typst.toml @@ -0,0 +1,10 @@ +[package] +name = "tt-docs" +version = "1.0.0" +authors = ["Kristaps Jurkans "] +license = "Apache-2.0" +description = "Tiny Tapeout Docs" +repository = "?" +keywords = ["TinyTapeout"] +entrypoint = "src/tt.typ" +exclude = ["images"] \ No newline at end of file diff --git a/docs/user_project.typ.mustache b/docs/user_project.typ.mustache new file mode 100644 index 0000000..d239be5 --- /dev/null +++ b/docs/user_project.typ.mustache @@ -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}} \ No newline at end of file diff --git a/documentation.py b/documentation.py index 546a0c7..147d649 100644 --- a/documentation.py +++ b/documentation.py @@ -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,205 @@ 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), - }, - ) + 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"] ) - fh.write(doc_chip_map) - fh.write("# Projects\n") - + 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 + ) + + danger_info = {} + if not os.path.isfile("./projects/danger_level.yaml"): + logging.warning("danger_level.yaml not found") + else: + 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" + ) + + try: + 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 + + info = DocsHelper.format_project_info(yaml_info, project) + + # 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 + + 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, + ), ) - 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}" + # 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']}" ) - else: - yaml_data[key] = "" + include_str = "// " + include_str + datasheet_manifest.append(include_str) - # 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") + # 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") - # ending - fh.write(doc_pinout) - fh.write("\n\\clearpage\n") - fh.write(doc_info) - fh.write("\n\\clearpage\n") - fh.write(doc_credits) + 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, + ), + ) - logging.info(f"wrote markdown to {markdown_file}") + # 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) - 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}") + # 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() diff --git a/project.py b/project.py index 7273877..fef4697 100644 --- a/project.py +++ b/project.py @@ -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): diff --git a/pyproject.toml b/pyproject.toml index 894a23d..1e34984 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ dependencies = [ "requests", "volare", "yowasp-yosys", + "matplotlib" ] [tool.setuptools] diff --git a/requirements.txt b/requirements.txt index a2d9977..ccf34b6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 diff --git a/tech.py b/tech.py new file mode 100644 index 0000000..891c1b3 --- /dev/null +++ b/tech.py @@ -0,0 +1,3 @@ +from typing import Literal + +TechName = Literal["sky130A", "ihp-sg13g2", "gf180mcuD"] diff --git a/tt_tool.py b/tt_tool.py index 4664f4e..bf2bacb 100755 --- a/tt_tool.py +++ b/tt_tool.py @@ -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()