From 28a43402408c7ac965de7640737093dd6d59d8ae Mon Sep 17 00:00:00 2001 From: matt venn Date: Mon, 13 Mar 2023 18:05:44 +0100 Subject: [PATCH 01/45] wip --- caravel.py | 271 ++++++++++ config.yaml | 5 + configure.py | 263 +++++++++ docs/CREDITS.md | 20 + docs/INFO.md | 141 +++++ docs/README_init.md | 22 + docs/VERIFICATION.md | 149 ++++++ docs/datasheet.md | 709 +++++++++++++++++++++++++ docs/doc_header.md | 25 + docs/doc_template.md | 32 ++ docs/hardening.md | 28 + docs/hugo_index_template.md | 73 +++ docs/hugo_template.md | 38 ++ documentation.py | 198 ++++--- git_utils.py | 199 +++++++ orders.py | 63 +++ project.py | 570 +++++++++++++++++--- projects.yaml | 1000 +++++++++++++++++++++++++++++++++++ reports.py | 116 ---- tt_tool.py | 29 +- 20 files changed, 3686 insertions(+), 265 deletions(-) create mode 100644 caravel.py create mode 100644 config.yaml create mode 100755 configure.py create mode 100644 docs/CREDITS.md create mode 100644 docs/INFO.md create mode 100644 docs/README_init.md create mode 100644 docs/VERIFICATION.md create mode 100644 docs/datasheet.md create mode 100644 docs/doc_header.md create mode 100644 docs/doc_template.md create mode 100644 docs/hardening.md create mode 100644 docs/hugo_index_template.md create mode 100644 docs/hugo_template.md create mode 100644 git_utils.py create mode 100644 orders.py create mode 100644 projects.yaml delete mode 100755 reports.py diff --git a/caravel.py b/caravel.py new file mode 100644 index 0000000..3660272 --- /dev/null +++ b/caravel.py @@ -0,0 +1,271 @@ +class CaravelConfig(): + + def __init__(self, projects, num_projects): + self.projects = projects + self.num_projects = num_projects + + # create macro file & positions, power hooks + def create_macro_config(self): + # array size + rows = 18 + cols = 14 + + # start point (lower left) + start_x = 50 + start_y = 95 + + # module block sizes + scanchain_w = 30 + scanchain_spc = 6 + module_w = 150 + module_h = 170 + + # how much x & y space to leave between blocks + space_x = 15 + space_y = 15 + + # step sizes + step_x = scanchain_w + module_w + scanchain_spc + space_x + step_y = module_h + space_y + + logging.info(f"start_x {start_x} start_y {start_y} step_x {step_x} step_y {step_y }") + + num_macros_placed = 0 + + # macro.cfg: where macros are placed + logging.info("creating macro.cfg") + with open("openlane/user_project_wrapper/macro.cfg", 'w') as fh: + fh.write("scan_controller 100 100 N\n") + for row in range(rows): + if row % 2 == 0: + col_order = range(cols) + orientation = 'N' + else: + # reverse odd rows to place instances in a zig zag pattern, shortening the scan chain wires + col_order = range(cols - 1, -1, -1) + orientation = 'S' + for col in col_order: + # skip the space where the scan controller goes on the first row + if row == 0 and col <= 1: + continue + + if num_macros_placed < self.num_projects: + if orientation == 'N': + # scanchain first + # co-ords are bottom left corner + macro_instance = self.projects[num_macros_placed].get_scanchain_instance() + instance = "{} {:<4} {:<4} {}\n".format( + macro_instance, start_x + col * step_x, start_y + row * step_y, orientation + ) + fh.write(instance) + + macro_instance = self.projects[num_macros_placed].get_macro_instance() + instance = "{} {:<4} {:<4} {}\n".format( + macro_instance, start_x + scanchain_spc + scanchain_w + col * step_x, start_y + row * step_y, orientation + ) + fh.write(instance) + else: + # macro first + macro_instance = self.projects[num_macros_placed].get_macro_instance() + instance = "{} {:<4} {:<4} {}\n".format( + macro_instance, start_x + col * step_x, start_y + row * step_y, orientation + ) + fh.write(instance) + + macro_instance = self.projects[num_macros_placed].get_scanchain_instance() + instance = "{} {:<4} {:<4} {}\n".format( + macro_instance, start_x + module_w + scanchain_spc + col * step_x, start_y + row * step_y, orientation + ) + fh.write(instance) + + num_macros_placed += 1 + + logging.info(f"total user macros placed: {num_macros_placed}") + + # macro_power.tcl: extra file for macro power hooks + logging.info("creating macro_power.tcl") + with open("openlane/user_project_wrapper/macro_power.tcl", 'w') as fh: + fh.write('set ::env(FP_PDN_MACRO_HOOKS) "\\\n') + fh.write(" ") + fh.write("scan_controller") + fh.write(" vccd1 vssd1 vccd1 vssd1") + fh.write(", \\\n") + for i in range(self.num_projects): + fh.write(" ") + fh.write(self.projects[i].get_scanchain_instance()) + fh.write(" vccd1 vssd1 vccd1 vssd1, \\\n") + fh.write(" ") + fh.write(self.projects[i].get_macro_instance()) + fh.write(" vccd1 vssd1 vccd1 vssd1") + if i != self.num_projects - 1: + fh.write(", \\\n") + fh.write('"\n') + + # extra_lef_gds.tcl + lefs = [] + gdss = [] + logging.info("creating extra_lef_gds.tcl") + for project in self.projects: + if not project.is_fill(): + lefs.append(project.get_macro_lef_filename()) + gdss.append(project.get_macro_gds_filename()) + + with open("openlane/user_project_wrapper/extra_lef_gds.tcl", 'w') as fh: + fh.write('set ::env(EXTRA_LEFS) "\\\n') + fh.write("$script_dir/../../lef/scan_controller.lef \\\n") + fh.write("$script_dir/../../lef/scanchain.lef \\\n") + for i, lef in enumerate(lefs): + fh.write("$script_dir/../../lef/{}".format(lef)) + if i != len(lefs) - 1: + fh.write(" \\\n") + else: + fh.write('"\n') + fh.write('set ::env(EXTRA_GDS_FILES) "\\\n') + fh.write("$script_dir/../../gds/scan_controller.gds \\\n") + fh.write("$script_dir/../../gds/scanchain.gds \\\n") + for i, gds in enumerate(gdss): + fh.write("$script_dir/../../gds/{}".format(gds)) + if i != len(gdss) - 1: + fh.write(" \\\n") + else: + fh.write('"\n') + + # instantiate inside user_project_wrapper + def instantiate(self): + logging.info("instantiating designs in user_project_wrapper.v") + + # NOTE: The user project wrapper initially used vectored signals for clk, + # scan, and latch signals. However, this leads to atrocious sim + # performance, as any change within the vectored signal is + # interpreted as a trigger condition for re-evaluating logic (at + # least this is the case under Icarus and Verilator). Therefore + # single bit signals are used between stages to limit the impact + # of any wire changing. + + # Instance the scan controller + body = [ + "", + "wire sc_clk_out, sc_data_out, sc_latch_out, sc_scan_out;", + "wire sc_clk_in, sc_data_in;", + "", + f"scan_controller #(.NUM_DESIGNS({self.num_projects})) scan_controller (", + " .clk (wb_clk_i),", + " .reset (wb_rst_i),", + " .active_select (io_in[20:12]),", + " .inputs (io_in[28:21]),", + " .outputs (io_out[36:29]),", + " .ready (io_out[37]),", + " .slow_clk (io_out[10]),", + " .set_clk_div (io_in[11]),", + "", + " .scan_clk_out (sc_clk_out),", + " .scan_clk_in (sc_clk_in),", + " .scan_data_out (sc_data_out),", + " .scan_data_in (sc_data_in),", + " .scan_select (sc_scan_out),", + " .scan_latch_en (sc_latch_out),", + "", + " .la_scan_clk_in (la_data_in[0]),", + " .la_scan_data_in (la_data_in[1]),", + " .la_scan_data_out (la_data_out[0]),", + " .la_scan_select (la_data_in[2]),", + " .la_scan_latch_en (la_data_in[3]),", + "", + " .driver_sel (io_in[9:8]),", + "", + " .oeb (io_oeb)", + ");", + ] + + # Instance every design on the scan chain + for idx in range(self.num_projects): + # First design driven by scan controller, all others are chained + pfx = f"sw_{idx:03d}" + prev_pfx = f"sw_{idx-1:03d}" if idx > 0 else "sc" + # Pickup the Wokwi design ID and github URL for the project + giturl = self.projects[idx].get_git_url() + + # Append the instance to the body + body += [ + "", + f"// [{idx:03d}] {giturl}", + f"wire {pfx}_clk_out, {pfx}_data_out, {pfx}_scan_out, {pfx}_latch_out;", + f"wire [7:0] {pfx}_module_data_in;", + f"wire [7:0] {pfx}_module_data_out;", + f"scanchain #(.NUM_IOS(8)) {self.projects[idx].get_scanchain_instance()} (", + f" .clk_in ({prev_pfx}_clk_out),", + f" .data_in ({prev_pfx}_data_out),", + f" .scan_select_in ({prev_pfx}_scan_out),", + f" .latch_enable_in ({prev_pfx}_latch_out),", + f" .clk_out ({pfx}_clk_out),", + f" .data_out ({pfx}_data_out),", + f" .scan_select_out ({pfx}_scan_out),", + f" .latch_enable_out({pfx}_latch_out),", + f" .module_data_in ({pfx}_module_data_in),", + f" .module_data_out ({pfx}_module_data_out)", + ");" + ] + + # Append the user module to the body + body += [ + "", + f"{self.projects[idx].get_macro_name()} {self.projects[idx].get_macro_instance()} (", + f" .io_in ({pfx}_module_data_in),", + f" .io_out ({pfx}_module_data_out)", + ");" + + ] + + # Link the final design back to the scan controller + body += [ + "", + "// Connect final signals back to the scan controller", + f"assign sc_clk_in = sw_{idx:03d}_clk_out;", + f"assign sc_data_in = sw_{idx:03d}_data_out;", + "", + "" + ] + + # Write to file + with open('verilog/rtl/user_project_wrapper.v', 'w') as fh: + # Insert the Caravel preamble + with open("upw_pre.v", "r") as fh_pre: + fh.write(fh_pre.read()) + # Indent, join, and insert the module instances + fh.write("\n".join([(" " + x).rstrip() for x in body])) + # Insert the Caravel postamble + with open("upw_post.v", "r") as fh_post: + fh.write(fh_post.read()) + + # build the blackbox_project_includes.v file - used for blackboxing when building the GDS + with open('verilog/blackbox_project_includes.v', 'w') as fh: + fh.write('`include "rtl/scan_controller/scan_controller.v"\n') + fh.write('`include "rtl/scanchain/scanchain.v"\n') + for project in self.projects: + if not project.is_fill(): + fh.write(f'`include "gl/{project.get_gl_verilog_filename()}"\n') + + # build complete list of filenames for sim + with open('verilog/includes/includes.rtl.caravel_user_project', 'w') as fh: + fh.write('-v $(USER_PROJECT_VERILOG)/rtl/user_project_wrapper.v\n') + fh.write('-v $(USER_PROJECT_VERILOG)/rtl/scan_controller/scan_controller.v\n') + fh.write('-v $(USER_PROJECT_VERILOG)/rtl/scanchain/scanchain.v\n') + fh.write('-v $(USER_PROJECT_VERILOG)/rtl/cells.v\n') + for project in self.projects: + if not project.is_fill(): + fh.write(f'-v $(USER_PROJECT_VERILOG)/rtl/{project.get_top_verilog_filename()}\n') + + # build GL includes + with open('verilog/includes/includes.gl.caravel_user_project', 'w') as fh: + fh.write('-v $(USER_PROJECT_VERILOG)/gl/user_project_wrapper.v\n') + fh.write('-v $(USER_PROJECT_VERILOG)/gl/scan_controller.v\n') + fh.write('-v $(USER_PROJECT_VERILOG)/gl/scanchain.v\n') + for project in self.projects: + if not project.is_fill(): + fh.write(f'-v $(USER_PROJECT_VERILOG)/gl/{project.get_gl_verilog_filename()}\n') + + def list(self): + for project in self.projects: + logging.info(project) + + diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..3aa256d --- /dev/null +++ b/config.yaml @@ -0,0 +1,5 @@ +start_date: '2023-03-01' +filler_project: https://github.com/TinyTapeout/tt03-test-invert +project_dir: 'projects' +tmp_dir: '/tmp/tt' +num_projects: 250 diff --git a/configure.py b/configure.py new file mode 100755 index 0000000..808698c --- /dev/null +++ b/configure.py @@ -0,0 +1,263 @@ +#!/usr/bin/env python3 +import re, glob, json, csv +from orders import Orders +import datetime +import yaml +import subprocess +import argparse, logging, shutil, sys, os, collections +from git_utils import install_artifacts, get_latest_action_url, unique +import git +from project import Project + + +# pipe handling +from signal import signal, SIGPIPE, SIG_DFL +signal(SIGPIPE, SIG_DFL) + + +""" +# if the project is a filler, then use the config from the first fill project +def gen_local_dir(self, fill_id): + if self.fill: + return os.path.join(os.path.join(self.project_dir, f'{fill_id :03}')) + else: + return os.path.join(os.path.join(self.project_dir, f'{self.index :03}')) +""" + + +class Projects(): + + def __init__(self, config, args): + self.args = args + self.project_dir = config['project_dir'] + self.config = config + if not os.path.exists(self.project_dir): + os.makedirs(self.project_dir) + + try: + with open('projects.yaml') as fh: + self.project_config = yaml.safe_load(fh) + except FileNotFoundError: + logging.error("projects.yaml not found, create it with --update-orders") + exit(1) + + """ + # useful for contacting people when I find problems in the repo + try: + with open('git_url_to_email.json') as fh: + self.git_url_to_email_map = json.load(fh) + except FileNotFoundError: + self.git_url_to_email_map = {} + """ + + self.projects = [] + for index in range(args.limit_num_projects): + if args.single >= 0 and args.single != index: + continue + if index < args.start_from: + continue + if args.end_at != 0 and index > args.end_at: + continue + if self.project_config[index]['status'] == 'disable': + continue + """ + try: + email = self.git_url_to_email_map[git_url] + except KeyError: + email = 'none' + """ + + git_url = self.project_config[index]['url'] + filler = self.project_config[index]['fill'] + + # fill projects don't have their own directory, reuse the first project which is always fill + if filler: + project_dir = os.path.join(os.path.join(self.project_dir, f'{0 :03}')) + else: + project_dir = os.path.join(os.path.join(self.project_dir, f'{index :03}')) + + project = Project(index, git_url, project_dir, args, fill=filler) + + # clone git repos locally & gds artifacts from action build + if args.clone_all: + if filler is False: + logging.info(f"cloning {project}") + project.clone() + + if args.fetch_gds: + if filler is False: + logging.info(f"fetching gds for {project}") + project.fetch_gds() + + if args.update_all: + if filler is False: + # only updates code, not gds artifacts + logging.info(f"git pull for {project}") + project.pull() + + # projects should now be installed, so load all the data from the yaml files + logging.debug("post clone setup") + # fill projects will load from the fill project's directory + project.post_clone_setup() + logging.debug(project) + + if args.harden: + if filler is False: + project.harden() + + if args.update_caravel: + logging.debug("copying files to caravel") + if filler is False: + project.copy_files_to_caravel() + + # check all top level module ports are correct + project.check_ports() + project.check_num_cells() + + self.projects.append(project) + + # now do some sanity checks + all_macro_instances = [project.get_macro_instance() for project in self.projects] + self.assert_unique(all_macro_instances) + + all_top_files = [project.get_top_verilog_filename() for project in self.projects if not project.is_fill()] + self.assert_unique(all_top_files) + + all_gds_files = [project.get_macro_gds_filename() for project in self.projects if not project.is_fill()] + self.assert_unique(all_gds_files) + + + def assert_unique(self, check): + duplicates = [item for item, count in collections.Counter(check).items() if count > 1] + if duplicates: + logging.error("duplicate projects: {}".format(duplicates)) + exit(1) + + def build_metrics(self): + total_seconds = 0 + total_wire_length = 0 + total_wires_count = 0 + total_physical_cells = 0 + max_cells = 0 + min_cells = 1000 + max_util = 0 + min_util = 100 + languages = {} + + for project in self.projects: + if not project.is_fill(): + dt = datetime.datetime.strptime(project.metrics['total_runtime'][:-3], '%Hh%Mm%Ss') + delt = datetime.timedelta(hours=dt.hour, minutes=dt.minute, seconds=dt.second) + total_seconds += delt.total_seconds() + + total_wire_length += int(project.metrics['wire_length']) + total_wires_count += int(project.metrics['wires_count']) + util = float(project.metrics['OpenDP_Util']) + num_cells = project.check_num_cells() + total_physical_cells += num_cells + + yaml_data = project.get_project_doc_yaml() + lang = yaml_data['language'].lower() + if lang in languages: + languages[lang] += 1 + else: + languages[lang] = 1 + + if num_cells > max_cells: + max_cells = num_cells + max_cell_project = project + if num_cells < min_cells: + min_cells = num_cells + + if util > max_util: + max_util = util + max_util_project = project + if util < min_util: + min_util = util + + + logging.info(f"build time for all projects {total_seconds / 3600} hrs") + logging.info(f"total wire length {total_wire_length} um") + logging.info(f"total cells {total_physical_cells}") + logging.info(f"max cells {max_cells} for project {max_cell_project}") + logging.info(f"min cells {min_cells}") + logging.info(f"max util {max_util} for project {max_util_project}") + logging.info(f"min util {min_util}") + logging.info(f"languages {languages}") + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="TinyTapeout configuration and docs") + + with open('config.yaml') as fh: + config = yaml.safe_load(fh) + + parser.add_argument('--update-orders', help="update the order config file", action="store_const", const=True) + parser.add_argument('--list', help="list projects", action='store_const', const=True) + parser.add_argument('--clone-all', help="clone all projects", action="store_const", const=True) + parser.add_argument('--update-all', help="git pull all projects", action="store_const", const=True) + parser.add_argument('--fetch-gds', help="git fetch latest gds", action="store_const", const=True) + parser.add_argument('--single', help="do action on single project", type=int, default=-1) + parser.add_argument('--start-from', help="do action on projects after this index", type=int, default=0) + parser.add_argument('--end-at', help="do action on projects before this index", type=int, default=0) + parser.add_argument('--update-caravel', help='configure caravel for build', action='store_const', const=True) + parser.add_argument('--harden', help="harden project", action="store_const", const=True) + parser.add_argument('--limit-num-projects', help='only configure for the first n projects', type=int, default=int(config['num_projects'])) + parser.add_argument('--test', help='use test projects', action='store_const', const=True) + parser.add_argument('--debug', help="debug logging", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) + parser.add_argument('--log-email', help="print persons email in messages", action="store_const", const=True) + parser.add_argument('--update-image', help="update the image", action="store_const", const=True) + 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('--build-hugo-content', help="directory to where to build hugo content") + parser.add_argument('--metrics', help="print some project metrics", action="store_const", const=True) + + args = parser.parse_args() + + # setup log + log_format = logging.Formatter('%(asctime)s - %(levelname)-8s - %(message)s') + # configure the client logging + log = logging.getLogger('') + # has to be set to debug as is the root logger + log.setLevel(args.loglevel) + + # create console handler and set level to info + ch = logging.StreamHandler(sys.stdout) + # create formatter for console + ch.setFormatter(log_format) + log.addHandler(ch) + + projects = Projects(config, args) + +# docs = Docs(projects.projects, args=args) +# caravel = CaravelConfig(projects.projects, num_projects=args.limit_num_projects) + + if args.list: + caravel.list() + + if args.metrics: + projects.build_metrics() + + if args.update_orders: + orders = Orders(config) + orders.fetch_orders() + orders.update_project_list() + + if args.update_caravel: + caravel.create_macro_config() + caravel.instantiate() + if not args.test: + docs.build_index() + + if args.update_image: + docs.update_image() + + if args.dump_json: + docs.dump_json() + + if args.dump_markdown: + docs.dump_markdown() + + if args.build_hugo_content: + docs.build_hugo_content() diff --git a/docs/CREDITS.md b/docs/CREDITS.md new file mode 100644 index 0000000..b5eefdf --- /dev/null +++ b/docs/CREDITS.md @@ -0,0 +1,20 @@ +# Sponsored by + +[![efabless](efabless.png)](https://efabless.com/) + +# Team + +Tiny Tapeout would not be possible without a lot of people helping. We would especially like to thank: + +* Uri Shaked for [wokwi](https://wokwi.com/) development and lots more +* [Sylvain Munaut](https://twitter.com/tnt) for help with scan chain improvements +* [Mike Thompson](https://www.linkedin.com/in/michael-thompson-0a581a/) for verification expertise +* [Jix](https://twitter.com/jix_) for formal verification support +* [Proppy](https://twitter.com/proppy) for help with GitHub actions +* [Maximo Balestrini](https://twitter.com/maxiborga) 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](/runs/tt01) and volunteered time to improve docs and test the flow +* The team at [YosysHQ](https://www.yosyshq.com/) and all the other open source EDA tool makers +* [Efabless](https://efabless.com/) for running the shuttles and providing OpenLane and sponsorship +* [Tim Ansell and Google](https://www.youtube.com/watch?v=EczW2IWdnOM) for supporting the open source silicon movement +* [Zero to ASIC course](https://zerotoasiccourse.com/) community for all your support diff --git a/docs/INFO.md b/docs/INFO.md new file mode 100644 index 0000000..a36d0c5 --- /dev/null +++ b/docs/INFO.md @@ -0,0 +1,141 @@ +# Technical info + +## Scan chain + +All 250 designs are joined together in a long chain similiar to JTAG. We provide the inputs and outputs of that chain (see pinout below) externally, to the Caravel logic analyser, and to an internal scan chain driver. + +The default is to use an external driver, this is in case anything goes wrong with the Caravel logic analyser or the internal driver. + +The scan chain is identical for each little project, and you can [read it here](https://github.com/mattvenn/wokwi-verilog-gds-test/blob/main/template/scan_wrapper.v). + +![block diagram](pics/block_diagram.png) + +### Updating inputs and outputs of a specified design + +A good way to see how this works is to read the FSM in the [scan controller](verilog/rtl/scan_controller/scan_controller.v). +You can also run one of the simple tests and check the waveforms. See how in the [scan chain verification](verification.md) doc. + +* Signal names are from the perspective of the scan chain driver. +* The desired project shall be called DUT (design under test) + +Assuming you want to update DUT at position 2 (0 indexed) with inputs = 0x02 and then fetch the output. +This design connects an inverter between each input and output. + +* Set scan_select low so that the data is clocked into the scan flops (rather than from the design) +* For the next 8 clocks, set scan_data_out to 0, 0, 0, 0, 0, 0, 1, 0 +* Toggle scan_clk_out 16 times to deliver the data to the DUT +* Toggle scan_latch_en to deliver the data from the scan chain to the DUT +* Set scan_select high to set the scan flop's input to be from the DUT +* Toggle the scan_clk_out to capture the DUT's data into the scan chain +* Toggle the scan_clk_out another 8 x number of remaining designs to receive the data at scan_data_in + +![update cycle](pics/update_cycle.png) + +*Notes on understanding the trace* + +* There are large wait times between the latch and scan signals to ensure no hold violations across the whole chain. For the internal scan controller, these can be configured (see section on wait states below). +* The input looks wrong (0x03) because the input is incremented by the test bench as soon as the scan controller captures the data. The input is actually 0x02. +* The output in the trace looks wrong (0xFE) because it's updated after a full refresh, the output is 0xFD. + +## Clocking + +Assuming: + +* 100MHz input clock +* 8 ins & 8 outs +* 2 clock cycles to push one bit through the scan chain (scan clock is half input clock rate) +* 250 designs +* scan controller can do a read/write cycle in one refresh + +So the max refresh rate is 100MHz / (8 * 2 * 250) = 25000Hz. + +## Clock divider + +A rising edge on the set_clk_div input will capture what is set on the input pins and use this as a divider for an internal slow clock that can be provided to the first input bit. + +The slow clock is only enabled if the set_clk_div is set, and the resulting clock is connected to input0 and also output on the slow_clk pin. + +The slow clock is synced with the scan rate. A divider of 0 mean it toggles the input0 every scan. Divider of 1 toggles it every 2 cycles. +So the resultant slow clock frequency is scan_rate / (2 * (N+1)). + +See the test_clock_div test in the [scan chain verification](verification.md). + +## Wait states + +This dictates how many wait cycle we insert in various state +of the load process. We have a sane default, but also allow +override externally. + +To override, set the wait amount on the inputs, set the driver_sel inputs both high, and then reset the chip. + +See the test_wait_state test in the [scan chain verification](verification.md). + +## Pinout + + PIN NAME DESCRIPTION + 20:12 active_select 9 bit input to set which design is active + 28:21 inputs 8 inputs + 36:29 outputs 8 outputs + 37 ready goes high for one cycle everytime the scanchain is refreshed + 10 slow_clk slow clock from internal clock divider + 11 set_clk_div enable clock divider + 9:8 driver_sel which scan chain driver: 00 = external, 01 = logic analyzer, 1x = internal + + 21 ext_scan_clk_out for external driver, clk input + 22 ext_scan_data_out data input + 23 ext_scan_select scan select + 24 ext_scan_latch_en latch + 29 ext_scan_clk_in clk output from end of chain + 30 ext_scan_data_in data output from end of chain + +## Instructions to build GDS + +To run the tool locally or have a fork's GitHub action work, you need the GH_USERNAME and GH_TOKEN set in your environment. + +GH_USERNAME should be set to your GitHub username. + +To generate your GH_TOKEN go to https://github.com/settings/tokens/new . Set the checkboxes for repo and workflow. + +To run locally, make a file like this: + + export GH_USERNAME= + export GH_TOKEN= + +And then source it before running the tool. + +### Fetch all the projects + +This goes through all the projects in project_urls.py, and fetches the latest artifact zip from GitHub. It takes the verilog, the GL verilog, and the GDS and copies +them to the correct place. + + ./configure.py --clone-all --fetch-gds + +### Configure Caravel + +Caravel needs the list of macros, how power is connected, instantiation of all the projects etc. This command builds these configs and also makes the README.md index. + + ./configure.py --update-caravel + +### Build the GDS + +To build the GDS and run the simulations, you will need to install the Sky130 PDK and OpenLane tool. +It takes about 5 minutes and needs about 3GB of disk space. + + export PDK_ROOT=/pdk + export OPENLANE_ROOT=/openlane + cd + make setup + +Then to create the GDS: + + make user_project_wrapper + +## Changing macro block size + +After working out what size you want: + +* adjust configure.py in `CaravelConfig.create_macro_config()`. +* adjust the PDN spacing to match in openlane/user_project_wrapper/config.tcl: + * ```set ::env(FP_PDN_HPITCH)``` + * ```set ::env(FP_PDN_HOFFSET)``` + diff --git a/docs/README_init.md b/docs/README_init.md new file mode 100644 index 0000000..fad6438 --- /dev/null +++ b/docs/README_init.md @@ -0,0 +1,22 @@ +[![tinytapeout](https://github.com/tinytapeout/tinytapeout-02/actions/workflows/gds.yaml/badge.svg)](https://github.com/tinytapeout/tinytapeout-02/actions/workflows/gds.yaml) +[![verification](https://github.com/tinytapeout/tinytapeout-02/actions/workflows/verification.yaml/badge.svg)](https://github.com/tinytapeout/tinytapeout-02/actions/workflows/verification.yaml) +[![tt_datasheet](https://github.com/tinytapeout/tinytapeout-02/actions/workflows/tt_datasheet.yaml/badge.svg)](https://github.com/tinytapeout/tinytapeout-02/actions/workflows/tt_datasheet.yaml) + +# TinyTapeout + +* Visit https://tinytapeout.com for more information on the project and how to get involved. +* Read [INFO](INFO.md) for how the project is built and technical project notes. +* Read [Verification](VERIFICATION.md) for how we verify the project. + +# Datasheet + +[PDF Download](datasheet.pdf). + +For the most recent automatic build, click the tt_datasheet badge above, then download the PDF artifact. + +# GDS layout of all projects + +![tiny tapeout](tinytapeout.png) + +# Project Index + diff --git a/docs/VERIFICATION.md b/docs/VERIFICATION.md new file mode 100644 index 0000000..71900ff --- /dev/null +++ b/docs/VERIFICATION.md @@ -0,0 +1,149 @@ +# Verification + +We are not trying to verify every single design. That is up to the person who makes it. What we want is to ensure that every design is accessible, even if some designs are broken. + +We can split the verification effort into functional testing (simulation), static tests (formal verification), timing tests (STA) and physical tests (LVS & DRC). + +See the sections below for details on each type of verification. + +## Setup + +You will need the GitHub tokens setup as described in [INFO](INFO.md#instructions-to-build-gds). + +The default of 250 projects takes a very long time to simulate, so I advise overriding the configuration: + + # fetch the test projects + ./configure.py --test --clone-all + # rebuild config with only 20 projects + ./configure.py --test --update-caravel --limit 20 + +You will also need iVerilog & cocotb. The easist way to install these are to download and install the [oss-cad-suite](https://github.com/YosysHQ/oss-cad-suite-build). + +## Simulations + +* Simulation of some test projects at RTL and GL level. +* Simulation of the whole chip with scan controller, external controller, logic analyser. +* Check wait state setting. +* Check clock divider setting. + +### Scan controller + +This test only instantiates user_project_wrapper (which contains all the small projects). It doesn't simulate the rest of the ASIC. + + cd verilog/dv/scan_controller + make test_scan_controller + +The Gate Level simulation requires scan_controller and user_project_wrapper to be re-hardened to get the correct gate level netlists: + +* Edit openlane/scan_controller/config.tcl and change NUM_DESIGNS=250 to NUM_DESIGNS=20. +* Then from the top level directory: + + + make scan_controller + make user_project_wrapper + + +* Then run the GL test + + + cd verilog/dv/scan_controller + make test_scan_controller_gl + + +#### single + +Just check one inverter module. Mainly for easy understanding of the traces. + + make test_single + +#### custom wait state + +Just check one inverter module. Set a custom wait state value. + + make test_wait_state + +#### clock divider + +Test one inverter module with an automatically generated clock on input 0. Sets the clock rate to 1/2 of the scan refresh rate. + + make test_clock_div + +## Top level tests setup + +For all the top level tests, you will also need a [RISCV compiler to build the firmware](https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.3.0-2020.04.1-x86_64-linux-ubuntu14.tar.gz). + +You will also need to install the 'management core' for the Caravel ASIC submission wrapper. This is done automatically by following the [PDK install instructions](INFO.md#build-the-gds). + +### Top level test: internal control + +Uses the scan controller, instantiated inside the whole chip. + + cd verilog/dv/scan_controller_int + make coco_test + +### Top level test: external control + +Uses external signals to control the scan chain. Simulates the whole chip. + + cd verilog/dv/scan_controller_ext + make coco_test + +### Top level test: logic analyser control + +Uses the RISCV co-processor to drive the scanchain with firmware. Simulates the whole chip. + + cd verilog/dv/scan_controller_la + make coco_test + +## Formal Verification + +* Formal verification that each small project's scan chain is correct. +* Formal verification that the correct signals are passed through for the 3 different scan chain control modes. + +### Scan chain + +Each GL netlist for each small project is proven to be equivalent to the reference scan chain implementation. +The verification is done on the GL netlist, so an RTL version of the cells used needed to be created. +See [here for more info](tinytapeout_scan/README.md). + +### Scan controller MUX + +In case the internal scan controller doesn't work, we also have ability to control the chain from external pins or the Caravel Logic Analyser. +We implement a simple MUX to achieve this and [formally prove it is correct](verilog/rtl/scan_controller/properties.v). + +## Timing constraints + +Due to limitations in OpenLane - a top level timing analyis is not possible. This would allow us to detect setup and hold violations in the scan chain. + +Instead, we design the chain and the timing constraints for each project and the scan controller with this in mind. + +* [Each small project has a negedge flop flop at the end of the shift register to reclock the data](https://github.com/mattvenn/wokwi-verilog-gds-test/blob/17f106db36f022536d013b960316bcc7f02c572c/template/scan_wrapper.v#L67). This gives more hold margin. +* [Each small project has SDC timing constraints](https://github.com/mattvenn/wokwi-verilog-gds-test/blob/main/src/base.sdc) +* [Scan controller](https://github.com/mattvenn/tinytapeout-mpw7/blob/aacae16304f4a4878943a49fd479d8a284736e32/verilog/rtl/scan_controller/scan_controller.v#L334) uses a shift register clocked with the end of the chain to ensure correct data is captured. +* [Scan controller has its own SDC timing constraints](openlane/scan_controller/base.sdc) +* Scan controller can be configured to wait for a programmable time at latching data into the design and capturing it from the design. +* External pins (by default) control the scan chain. + +## Physical tests + +* LVS +* DRC +* CVC + +### LVS + +Each project is built with OpenLane, which will check LVS for each small project. +Then when we combine all the projects together we run a top level LVS & DRC for routing, power supply and macro placement. + +The extracted netlist from the GDS is what is used in the formal scan chain proof. + +### DRC + +DRC is checked by OpenLane for each small project, and then again at the top level when we combine all the projects. + +### CVC + +Mitch Bailey' CVC checker is a device level static verification system for quickly and easily detecting common circuit errors in CDL (Circuit Definition Language) netlists. We ran the test on the final design and found no errors. + +* See [the paper here](https://woset-workshop.github.io/PDFs/2020/a05-slides.pdf). +* Github repo for the tool: https://github.com/d-m-bailey/cvc diff --git a/docs/datasheet.md b/docs/datasheet.md new file mode 100644 index 0000000..295826e --- /dev/null +++ b/docs/datasheet.md @@ -0,0 +1,709 @@ +--- +title: | + ![](ttlogo.png){width=15cm} + Tiny Tapeout 02 Datasheet +subtitle: Project Repository [https://github.com/TinyTapeout/tinytapeout-02](https://github.com/TinyTapeout/tinytapeout-02) +documentclass: scrartcl +date: \today{} +geometry: margin=2cm +fontsize: 14pt +mainfont: Latin Modern Sans +header-includes: +- \hypersetup{colorlinks=false, + allbordercolors={0 0 0}, + pdfborderstyle={/S/U/W 1}} +--- + +\pagebreak + +# Render of whole chip + +![Full GDS](tinytapeout.png) + +\pagebreak + +# Projects +## 0 : Test Inverter Project + + + +* Author: Matt Venn +* Description: Inverts every line. This project is also used to fill any empty design spaces. +* [GitHub repository](https://github.com/TinyTapeout/tt03-test-invert) +* [Wokwi](https://wokwi.com/projects/357464855584307201) project +* [Extra docs]() +* Clock: 0 Hz +* External hardware: + +### How it works + +Uses 8 inverters to invert every line. + + +### How to test + +Setting the input switch to on should turn the corresponding LED off. + + +### IO + +| # | Input | Output | +|---|--------------|--------------| +| 0 | a | segment a | +| 1 | b | segment b | +| 2 | c | segment c | +| 3 | d | segment d | +| 4 | e | segment e | +| 5 | f | segment f | +| 6 | g | segment g | +| 7 | dot | dot | + +\pagebreak +## 2 : 7 Segment Life + + + +* Author: icegoat9 +* Description: Simple 7-segment cellular automaton +* [GitHub repository](https://github.com/icegoat9/tinytapeout03-7seglife) +* [Wokwi](https://wokwi.com/projects/357752736742764545) project +* [Extra docs](https://github.com/icegoat9/tinytapeout03-7seglife/blob/main/README.md) +* Clock: 0 Hz +* External hardware: None. Could add debounced momentary pushbuttons in parallel with dip switches 1,2,3 to make loading in new patterns and stepping through a run easier. + +### How it works + +See the [Wokwi gate layout and simulation](https://wokwi.com/projects/357752736742764545). At a high level... + +* Seven flip-flops hold the cellular automaton's internal state, which is also displayed in the seven-segment display. +* Combinatorial logic generates the next state for each segment based on its neighbors, according to the ruleset... + * Living segments with exactly one living neighbor (another segment that touches it end to end) survive, and all others die. + * Dead segments with exactly two living neighbors come to life. +* When either the system clock or a user toggling the clock input go high, this new state is latched into the automaton's state. +* There's minor additional support logic to let the user manually shift in an initial condition and handle clock dividing. + + +### How to test + +For full details and a few 'exercises for the reader', see the [github README](https://github.com/icegoat9/tinytapeout03-7seglife) doc link. But at a high level, assuming the IC is mounted on the standard tinytapeout PCB which provides dip switches, clock, and a seven-segment display for output... + +* Set all dip switches off and the clock slide switch to the 'manual' clock side. +* Power on the system. An arbitrary state may appear on the 7-segment display. +* Set dip switch 4 on ('run mode'). +* Toggle dip switch 1 on and off to advance the automaton to the next state, you should see the 7-segment display update. + +If you want to watch it run automatically (which may quickly settle on an empty state or a static pattern, depending on start state)... + +* Set the PCB clock divider to the maximum clock division (255). With a system clock of 6.25kHz, the clock input should now be ~24.5Hz. +* Set dip switches 5 and 7 on to add a reasonable additional clock divider (see docs for more details on a higher or lower divider). +* Set dip switch 4 on. +* Switch the clock slide switch to the 'system clock' side. The display should advance at roughly 1.5Hz if I've done math correctly. + +If you want to load an initial state... + +* Set dip switch 4 off ('load mode'). +* Toggle dip switches 2 and/or 3 on and off seven times total, to shift in 0 and 1 values to the automaton internal state. +* Set dip switch 4 on and run manually or automatically as above. + + +### IO + +| # | Input | Output | +|---|--------------|--------------| +| 0 | clock | 7segmentA | +| 1 | load0 | 7segmentB | +| 2 | load1 | 7segmentC | +| 3 | runmode | 7segmentD | +| 4 | clockdiv8 | 7segmentE | +| 5 | clockdiv4 | 7segmentF | +| 6 | clockdiv2 | 7segmentG | +| 7 | unused | 7segmentDP | + +\pagebreak +## 3 : Another Piece of Pi + + + +* Author: Meinhard Kissich, EAS Group, Graz University of Technology +* Description: This design takes up the idea of James Ross [1], who submitted a circuit to Tiny Tapeout 02 that stores and outputs the first 1024 decimal digits of the number Pi (including the decimal point) to a 7-segment display. In contrast to his approach, a densely packed decimal encoding is used to store the data. With this approach, 1400 digits can be stored and output within the design area of 150um x 170um. However, at 1400 decimals and utilization of 38.99%, the limitation seems to be routing. Like James, I'm also interested to hear about better strategies to fit more information into the design with synthesizable Verilog code. [1] https://github.com/jar/tt02_freespeech +* [GitHub repository](https://github.com/meiniKi/tt03-another-piece-of-pi) +* HDL project +* [Extra docs]() +* Clock: 0 Hz +* External hardware: 7-segment display + +### How it works + +The circuit stores each triplet of decimals in a 10-bit vector encoded as densely packed decimals. An index vector selects the current digits to be output to the 7-segment display. It consists of an upper part `index[11:2]` that selects the triplet and a lower part `index[1:0]` that specifies the digit within the triplet. First, the upper part decides on the triplet, which is then decoded into three decimals. Afterwards, the lower part selects one of the three decimals to be decoded into 7-segment display logic and applied to the outputs. The index is incremented at each primary clock edge. However, when the lower part equals three, i.e., `index[1:0]==1'b10`, two is added, as the triplet consists of three (not four) digits. + +- `index == 'b0000000000|00`: triplet[0], digit 0 within triplet +- `index == 'b0000000000|01`: triplet[0], digit 1 within triplet +- `index == 'b0000000000|10`: triplet[0], digit 2 within triplet +- `index == 'b0000000001|00`: triplet[1], digit 0 within triplet +- `index == 'b0000000001|01`: triplet[1], digit 1 within triplet +- `index == 'b0000000001|10`: triplet[1], digit 2 within triplet + +There is one exception to the rule above: the decimal point. Another multiplexer at the input of the 7-segment decoder can either forward a digit from the decoded tripled or a constant -- the decimal point. Once the lower part of the index counter, i.e., `index[1:0]` reaches `2'b10` for the first time, the multiplexer selects the decimal point and pauses incrementing the index for one clock cycle. + +- `index == 'b0000000000|00`: triplet[0], digit 0 within triplet +- `index == 'b0000000000|01`: triplet[0], decimal point +- `index == 'b0000000000|01`: triplet[0], digit 1 within triplet +- `index == 'b0000000000|10`: triplet[0], digit 2 within triplet +- `index == 'b0000000001|00`: triplet[1], digit 0 within triplet + + +### How to test + +For simulation, please use the provided testbench and Makefile. It is important to run the `genmux.py` Python script first, as it generates the test vectors required by the Verilog testbench. For testing the physical chip, release the reset and compare the digits of Pi against a reference. + + +### IO + +| # | Input | Output | +|---|--------------|--------------| +| 0 | clk | segment a | +| 1 | reset | segment b | +| 2 | none | segment c | +| 3 | none | segment d | +| 4 | none | segment e | +| 5 | none | segment f | +| 6 | none | segment g | +| 7 | none | decimal LED | + +\pagebreak +## 4 : Wormy + + + +* Author: nqbit +* Description: MC Wormy Pants squirms like a worm and grows just as fast. +* [GitHub repository](https://github.com/nqbit/wormy) +* HDL project +* [Extra docs](https://github.com/nqbit/wormy) +* Clock: 300 Hz +* External hardware: + +### How it works + +Wormy is a very simple, addictive last person video game. This last person, +open-world game takes you down the path of an earthworm. Wormy's world is +made up of a 4x4 grid represented by 3x16-bit arrays: Direction[0], +Direction[1], and Occupied. The Direction[x] maps keep track of which way a +segment of worm moves, if it is on, and Occupied keeps track of if the grid +location is occupied. + +Example Occupied Grid: + +``` + _________ + | X X X X | + | X | + | X | + |_________| +``` + +In addition to direction and occupied there are also pointers to the head +and the tail. The same grid would look something like the following: + +Example Occupied Grid with head(H) and tail(T) highlighted: + +``` + _________ + | T X X X | + | X | + | H | + |_________| +``` + +BOOM! Wormy shouldn't run into itself. If its head hits any part of its +body, that causes a collision. There is a collision if the location of the +Wormy's head is occupied by another segment of the Wormy. To determine +this, we keep track of the worm location, and specifically the current and +future locations of the worm head (H) and tail (T). If the future location +of H will occupy a location that will already be occupied, this causes a +collision. + +This is made a bit trickier with growth (see below), because if the future +state of H is set to occupy the current state of T - there is only a +collision if growth is set to occur in the next cycle, so be careful if you +plan to have Wormy chase its tail! SPLAT! + +NOM NOM! Wormy eats the tasty earth around it and grows. Every so often +Wormy, after having eaten all of its lunch, grows a whole segment. During a +growth cycle, the state of T simply persists (remains occupied and heading +in the same direction as it is). + +Last Person Input - see user_input.v +To move Wormy along, the last player needs to push buttons to help Wormy +find more tasty earth: up, right, down or left. + +Buttons are nasty little bugs in general. When pushed the button generates +an analog signal that might not look exactly like a single rising edge. + +It might look something like this: + +``` + xxxxxxxxxxxxxxxxx + xx + x + x + x x + x x + x x + x x + x xx x x + x xxxx x + x x xx x + x x x x + x x x x + xx x x x + xxx x x x + x xx x x x + x x x x x + x x x xx x + x xx x x + xxxxxxxxxxxxxxxxxxx x +``` + +In order to help protect our logic from this scary looking signal that +might introduce metastability (<- WHAT?), we can filter it with a couple +flippy-floppies and keep the metastability at bay. ARGH. + +Once we have a clear pushed or not-pushed, we can suggest that Wormy move +in a specific direction. If the last player tries button mashing, Wormy +won't listen. Once a second Wormy checks what the last button press was and +tries really hard to go that way (see BOOM!). + +Earthworms don't have eyes - see multiplexer.v +The game's display is made up of a 4x4 grid of LEDs controlled by a +multiplexer. Why multiplexing? With a multiplexed LED setup, we can control +more display units (LEDs), with a limited number of outputs (8 on this +TinyTapeout project). + +To get multiplexing working the network of outputs is mapped to each +display unit. This allows us to manipulate assigned outputs to control the +state of each display unit, one at a time. We then cycle through each +display unit quickly enough to display a persistent image to the last +player. + +Wires (A1-4, B1-4) map to each location on the game arena (4x4 grid): + +``` + A1| | | | + __________________ + A2| | | | + __________________ + A3| | | | + __________________ + A4| | | | + __________________ + |B1 |B2 |B3 |B4 +``` + +When B's voltage is OFF the LED's state changes to ON if A is also ON. + - ON LED state: A(ON) — >| — B(OFF) + - OFF LED state: A(ON) — >| — B(ON) + +Example: The 3 filled squares below each represent a Wormy segment in the +ON state as controlled by the multiplexer. Notice how each is lighter than +the last. This is because the multiplexer cycles through each LED to update +the state, creating one persistent image even though the LEDS are not on +over the entire period of time. + +``` + A1| | O | | + __________________ + A2| | o | | + __________________ + A3| | . | | + __________________ + A4| | | | + __________________ + |B1 |B2 |B3 |B4 +``` + +Another Example: If you enter a dark cave and point a flashlight straight +ahead at one point on the wall you have a very small visual field that is +contained within the beam of light. However, you can expand your visual +field in the cave by waving the flashlight back and forth across the wall. +Despite the fact that the beam is moving over individual points on the +wall, the entire wall can be seen at once. This is similar to the concept +used in the Wormy display, since the multiplexer changes the state of the +worm occupied locations to ON one at a time, but in a cycle. The result is +a solid image, made up of LEDs cycling through ON states to produce a +persistent image of Wormy (that beautiful Lumbricina). + + +### How to test + +After reset, you should see a single pixel moving along the display and +it should grow every now and then. + + +### IO + +| # | Input | Output | +|---|--------------|--------------| +| 0 | clock | A0 - Multiplexer channel A to be tied to a an array of 16 multiplexed LEDs | +| 1 | reset | A1 | +| 2 | button0 | A2 | +| 3 | button1 | A3 | +| 4 | button2 | B0 - Multiplexer channel B to be tied to a an array of 16 multiplexed LEDs | +| 5 | button3 | B1 | +| 6 | none | B2 | +| 7 | none | B3 | + +\pagebreak +## 5 : Knight Rider Sensor Lights + +![picture](projects/005/KITT.jpg) + +* Author: Kolos Koblasz +* Description: The logic assertes output bits one by one, like KITT's sensor lights in Knight Rider. +* [GitHub repository](https://github.com/KolosKoblasz/tt03-knight_rider) +* HDL project +* [Extra docs]() +* Clock: 6000 Hz +* External hardware: Conect LEDs with ~1K-10K Ohm serial resistors to output pins and connect push button switches to Input[2] and Input[3] which drive the inputs with logic zeros when idle and with logic 1 when pressed. Rising edge on these inputs selects the next settings. + +### How it works + +Uses several counters, shiftregisters to create a moving light. +Input[2] and Input[3] can control speed and brightness respectively. + + +### How to test + +After reset it starts moving the switched on LED. +By creating rising edges on Input[2] and Input[3] the two config spaces can be discovered. Input[0] is clk Input[1] is reset (1=reset on, 0=Reset off). +Simulated with 6KHz clock signal. + + +### IO + +| # | Input | Output | +|---|--------------|--------------| +| 0 | clock | LED 0 | +| 1 | reset | LED 1 | +| 2 | speed control | LED 2 | +| 3 | brightness control | LED 3 | +| 4 | none | LED 4 | +| 5 | none | LED 5 | +| 6 | none | LED 6 | +| 7 | none | LED 7 | + +\pagebreak +# Technical info + +## Scan chain + +All 250 designs are joined together in a long chain similiar to JTAG. We provide the inputs and outputs of that chain (see pinout below) externally, to the Caravel logic analyser, and to an internal scan chain driver. + +The default is to use an external driver, this is in case anything goes wrong with the Caravel logic analyser or the internal driver. + +The scan chain is identical for each little project, and you can [read it here](https://github.com/mattvenn/wokwi-verilog-gds-test/blob/main/template/scan_wrapper.v). + +![block diagram](pics/block_diagram.png) + +### Updating inputs and outputs of a specified design + +A good way to see how this works is to read the FSM in the [scan controller](verilog/rtl/scan_controller/scan_controller.v). +You can also run one of the simple tests and check the waveforms. See how in the [scan chain verification](verification.md) doc. + +* Signal names are from the perspective of the scan chain driver. +* The desired project shall be called DUT (design under test) + +Assuming you want to update DUT at position 2 (0 indexed) with inputs = 0x02 and then fetch the output. +This design connects an inverter between each input and output. + +* Set scan_select low so that the data is clocked into the scan flops (rather than from the design) +* For the next 8 clocks, set scan_data_out to 0, 0, 0, 0, 0, 0, 1, 0 +* Toggle scan_clk_out 16 times to deliver the data to the DUT +* Toggle scan_latch_en to deliver the data from the scan chain to the DUT +* Set scan_select high to set the scan flop's input to be from the DUT +* Toggle the scan_clk_out to capture the DUT's data into the scan chain +* Toggle the scan_clk_out another 8 x number of remaining designs to receive the data at scan_data_in + +![update cycle](pics/update_cycle.png) + +*Notes on understanding the trace* + +* There are large wait times between the latch and scan signals to ensure no hold violations across the whole chain. For the internal scan controller, these can be configured (see section on wait states below). +* The input looks wrong (0x03) because the input is incremented by the test bench as soon as the scan controller captures the data. The input is actually 0x02. +* The output in the trace looks wrong (0xFE) because it's updated after a full refresh, the output is 0xFD. + +## Clocking + +Assuming: + +* 100MHz input clock +* 8 ins & 8 outs +* 2 clock cycles to push one bit through the scan chain (scan clock is half input clock rate) +* 250 designs +* scan controller can do a read/write cycle in one refresh + +So the max refresh rate is 100MHz / (8 * 2 * 250) = 25000Hz. + +## Clock divider + +A rising edge on the set_clk_div input will capture what is set on the input pins and use this as a divider for an internal slow clock that can be provided to the first input bit. + +The slow clock is only enabled if the set_clk_div is set, and the resulting clock is connected to input0 and also output on the slow_clk pin. + +The slow clock is synced with the scan rate. A divider of 0 mean it toggles the input0 every scan. Divider of 1 toggles it every 2 cycles. +So the resultant slow clock frequency is scan_rate / (2 * (N+1)). + +See the test_clock_div test in the [scan chain verification](verification.md). + +## Wait states + +This dictates how many wait cycle we insert in various state +of the load process. We have a sane default, but also allow +override externally. + +To override, set the wait amount on the inputs, set the driver_sel inputs both high, and then reset the chip. + +See the test_wait_state test in the [scan chain verification](verification.md). + +## Pinout + + PIN NAME DESCRIPTION + 20:12 active_select 9 bit input to set which design is active + 28:21 inputs 8 inputs + 36:29 outputs 8 outputs + 37 ready goes high for one cycle everytime the scanchain is refreshed + 10 slow_clk slow clock from internal clock divider + 11 set_clk_div enable clock divider + 9:8 driver_sel which scan chain driver: 00 = external, 01 = logic analyzer, 1x = internal + + 21 ext_scan_clk_out for external driver, clk input + 22 ext_scan_data_out data input + 23 ext_scan_select scan select + 24 ext_scan_latch_en latch + 29 ext_scan_clk_in clk output from end of chain + 30 ext_scan_data_in data output from end of chain + +## Instructions to build GDS + +To run the tool locally or have a fork's GitHub action work, you need the GH_USERNAME and GH_TOKEN set in your environment. + +GH_USERNAME should be set to your GitHub username. + +To generate your GH_TOKEN go to https://github.com/settings/tokens/new . Set the checkboxes for repo and workflow. + +To run locally, make a file like this: + + export GH_USERNAME= + export GH_TOKEN= + +And then source it before running the tool. + +### Fetch all the projects + +This goes through all the projects in project_urls.py, and fetches the latest artifact zip from GitHub. It takes the verilog, the GL verilog, and the GDS and copies +them to the correct place. + + ./configure.py --clone-all --fetch-gds + +### Configure Caravel + +Caravel needs the list of macros, how power is connected, instantiation of all the projects etc. This command builds these configs and also makes the README.md index. + + ./configure.py --update-caravel + +### Build the GDS + +To build the GDS and run the simulations, you will need to install the Sky130 PDK and OpenLane tool. +It takes about 5 minutes and needs about 3GB of disk space. + + export PDK_ROOT=/pdk + export OPENLANE_ROOT=/openlane + cd + make setup + +Then to create the GDS: + + make user_project_wrapper + +## Changing macro block size + +After working out what size you want: + +* adjust configure.py in `CaravelConfig.create_macro_config()`. +* adjust the PDN spacing to match in openlane/user_project_wrapper/config.tcl: + * ```set ::env(FP_PDN_HPITCH)``` + * ```set ::env(FP_PDN_HOFFSET)``` + + +\pagebreak +# Verification + +We are not trying to verify every single design. That is up to the person who makes it. What we want is to ensure that every design is accessible, even if some designs are broken. + +We can split the verification effort into functional testing (simulation), static tests (formal verification), timing tests (STA) and physical tests (LVS & DRC). + +See the sections below for details on each type of verification. + +## Setup + +You will need the GitHub tokens setup as described in [INFO](INFO.md#instructions-to-build-gds). + +The default of 250 projects takes a very long time to simulate, so I advise overriding the configuration: + + # fetch the test projects + ./configure.py --test --clone-all + # rebuild config with only 20 projects + ./configure.py --test --update-caravel --limit 20 + +You will also need iVerilog & cocotb. The easist way to install these are to download and install the [oss-cad-suite](https://github.com/YosysHQ/oss-cad-suite-build). + +## Simulations + +* Simulation of some test projects at RTL and GL level. +* Simulation of the whole chip with scan controller, external controller, logic analyser. +* Check wait state setting. +* Check clock divider setting. + +### Scan controller + +This test only instantiates user_project_wrapper (which contains all the small projects). It doesn't simulate the rest of the ASIC. + + cd verilog/dv/scan_controller + make test_scan_controller + +The Gate Level simulation requires scan_controller and user_project_wrapper to be re-hardened to get the correct gate level netlists: + +* Edit openlane/scan_controller/config.tcl and change NUM_DESIGNS=250 to NUM_DESIGNS=20. +* Then from the top level directory: + + + make scan_controller + make user_project_wrapper + + +* Then run the GL test + + + cd verilog/dv/scan_controller + make test_scan_controller_gl + + +#### single + +Just check one inverter module. Mainly for easy understanding of the traces. + + make test_single + +#### custom wait state + +Just check one inverter module. Set a custom wait state value. + + make test_wait_state + +#### clock divider + +Test one inverter module with an automatically generated clock on input 0. Sets the clock rate to 1/2 of the scan refresh rate. + + make test_clock_div + +## Top level tests setup + +For all the top level tests, you will also need a [RISCV compiler to build the firmware](https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.3.0-2020.04.1-x86_64-linux-ubuntu14.tar.gz). + +You will also need to install the 'management core' for the Caravel ASIC submission wrapper. This is done automatically by following the [PDK install instructions](INFO.md#build-the-gds). + +### Top level test: internal control + +Uses the scan controller, instantiated inside the whole chip. + + cd verilog/dv/scan_controller_int + make coco_test + +### Top level test: external control + +Uses external signals to control the scan chain. Simulates the whole chip. + + cd verilog/dv/scan_controller_ext + make coco_test + +### Top level test: logic analyser control + +Uses the RISCV co-processor to drive the scanchain with firmware. Simulates the whole chip. + + cd verilog/dv/scan_controller_la + make coco_test + +## Formal Verification + +* Formal verification that each small project's scan chain is correct. +* Formal verification that the correct signals are passed through for the 3 different scan chain control modes. + +### Scan chain + +Each GL netlist for each small project is proven to be equivalent to the reference scan chain implementation. +The verification is done on the GL netlist, so an RTL version of the cells used needed to be created. +See [here for more info](tinytapeout_scan/README.md). + +### Scan controller MUX + +In case the internal scan controller doesn't work, we also have ability to control the chain from external pins or the Caravel Logic Analyser. +We implement a simple MUX to achieve this and [formally prove it is correct](verilog/rtl/scan_controller/properties.v). + +## Timing constraints + +Due to limitations in OpenLane - a top level timing analyis is not possible. This would allow us to detect setup and hold violations in the scan chain. + +Instead, we design the chain and the timing constraints for each project and the scan controller with this in mind. + +* [Each small project has a negedge flop flop at the end of the shift register to reclock the data](https://github.com/mattvenn/wokwi-verilog-gds-test/blob/17f106db36f022536d013b960316bcc7f02c572c/template/scan_wrapper.v#L67). This gives more hold margin. +* [Each small project has SDC timing constraints](https://github.com/mattvenn/wokwi-verilog-gds-test/blob/main/src/base.sdc) +* [Scan controller](https://github.com/mattvenn/tinytapeout-mpw7/blob/aacae16304f4a4878943a49fd479d8a284736e32/verilog/rtl/scan_controller/scan_controller.v#L334) uses a shift register clocked with the end of the chain to ensure correct data is captured. +* [Scan controller has its own SDC timing constraints](openlane/scan_controller/base.sdc) +* Scan controller can be configured to wait for a programmable time at latching data into the design and capturing it from the design. +* External pins (by default) control the scan chain. + +## Physical tests + +* LVS +* DRC +* CVC + +### LVS + +Each project is built with OpenLane, which will check LVS for each small project. +Then when we combine all the projects together we run a top level LVS & DRC for routing, power supply and macro placement. + +The extracted netlist from the GDS is what is used in the formal scan chain proof. + +### DRC + +DRC is checked by OpenLane for each small project, and then again at the top level when we combine all the projects. + +### CVC + +Mitch Bailey' CVC checker is a device level static verification system for quickly and easily detecting common circuit errors in CDL (Circuit Definition Language) netlists. We ran the test on the final design and found no errors. + +* See [the paper here](https://woset-workshop.github.io/PDFs/2020/a05-slides.pdf). +* Github repo for the tool: https://github.com/d-m-bailey/cvc + +\pagebreak +# Sponsored by + +[![efabless](efabless.png)](https://efabless.com/) + +# Team + +Tiny Tapeout would not be possible without a lot of people helping. We would especially like to thank: + +* Uri Shaked for [wokwi](https://wokwi.com/) development and lots more +* [Sylvain Munaut](https://twitter.com/tnt) for help with scan chain improvements +* [Mike Thompson](https://www.linkedin.com/in/michael-thompson-0a581a/) for verification expertise +* [Jix](https://twitter.com/jix_) for formal verification support +* [Proppy](https://twitter.com/proppy) for help with GitHub actions +* [Maximo Balestrini](https://twitter.com/maxiborga) 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](/runs/tt01) and volunteered time to improve docs and test the flow +* The team at [YosysHQ](https://www.yosyshq.com/) and all the other open source EDA tool makers +* [Efabless](https://efabless.com/) for running the shuttles and providing OpenLane and sponsorship +* [Tim Ansell and Google](https://www.youtube.com/watch?v=EczW2IWdnOM) for supporting the open source silicon movement +* [Zero to ASIC course](https://zerotoasiccourse.com/) community for all your support diff --git a/docs/doc_header.md b/docs/doc_header.md new file mode 100644 index 0000000..e1d375b --- /dev/null +++ b/docs/doc_header.md @@ -0,0 +1,25 @@ +--- +title: | + ![](ttlogo.png){width=15cm} + Tiny Tapeout 02 Datasheet +subtitle: Project Repository [https://github.com/TinyTapeout/tinytapeout-02](https://github.com/TinyTapeout/tinytapeout-02) +documentclass: scrartcl +date: \today{} +geometry: margin=2cm +fontsize: 14pt +mainfont: Latin Modern Sans +header-includes: +- \hypersetup{colorlinks=false, + allbordercolors={0 0 0}, + pdfborderstyle={/S/U/W 1}} +--- + +\pagebreak + +# Render of whole chip + +![Full GDS](tinytapeout.png) + +\pagebreak + +# Projects diff --git a/docs/doc_template.md b/docs/doc_template.md new file mode 100644 index 0000000..72543f5 --- /dev/null +++ b/docs/doc_template.md @@ -0,0 +1,32 @@ +## {index} : {title} + +{picture_link} + +* Author: {author} +* Description: {description} +* [GitHub repository]({git_url}) +* {project_type} project +* [Extra docs]({doc_link}) +* Clock: {clock_hz} Hz +* External hardware: {external_hw} + +### How it works + +{how_it_works} + +### How to test + +{how_to_test} + +### IO + +| # | Input | Output | +|---|--------------|--------------| +| 0 | {inputs[0]} | {outputs[0]} | +| 1 | {inputs[1]} | {outputs[1]} | +| 2 | {inputs[2]} | {outputs[2]} | +| 3 | {inputs[3]} | {outputs[3]} | +| 4 | {inputs[4]} | {outputs[4]} | +| 5 | {inputs[5]} | {outputs[5]} | +| 6 | {inputs[6]} | {outputs[6]} | +| 7 | {inputs[7]} | {outputs[7]} | diff --git a/docs/hardening.md b/docs/hardening.md new file mode 100644 index 0000000..6d71a04 --- /dev/null +++ b/docs/hardening.md @@ -0,0 +1,28 @@ +# To rebuild all gds + +Build notes are also in the tt-02-build of discord + +On git tag tapeout + +run + + . setuptokens.sh + ./configure.py --harden + +Some projects will fail with golden config. Change this line in golden config + + set ::env(PL_BASIC_PLACEMENT) {1} + +Then reharden the fails: + + for i in 000 027 047 062 064 081 087 092 094 117 118 126 129 140 141 146 153 156 163 165 ; do + echo $i + ./configure.py --single $i --harden + done + +Project 106 only hardens with the config.tcl as provided + +After rehardening, this should return empty: + + grep 'Flow failed' projects/*/runs/wokwi/errors.log + diff --git a/docs/hugo_index_template.md b/docs/hugo_index_template.md new file mode 100644 index 0000000..d653a60 --- /dev/null +++ b/docs/hugo_index_template.md @@ -0,0 +1,73 @@ +--- +title: "Tiny Tapeout 02" +weight: 10 +description: Overview + +--- + +{{% toc %}} + +# Design details + +* [Github repository](https://github.com/TinyTapeout/tinytapeout-02) +* [PDF datasheet](https://github.com/TinyTapeout/tinytapeout-02/raw/tt02/datasheet.pdf) +* [Verification](https://github.com/TinyTapeout/tinytapeout-02/blob/tt02/VERIFICATION.md) +* [Efabless MPW submission](https://platform.efabless.com/projects/1410) + +# Launch stats + +* Launched: 9 November 2023 +* Submission closed: 2 December 2023 +* Submitted to Efabless 2211Q chipIgnite shuttle using Skywater 130nm open source PDK + +# Project statistics + +* 165 projects submitted. Each project is 150 x 170um +* Youngest submitter: [4 years old](https://tinytapeout.com/runs/tt02/031) +* 64 used Wokwi graphical editor, 82 Verilog, 5 Amaranth, 1 Chisel +* 39935 standard cells used across all projects. +* Most cells used in a design was 1105, the least was 8. +* Max utilisation was 52% +* Total wire length 2024 mm. + +## Whole die render + +![whole die](images/tinytapeout.png) + +# Project Showcase + +## Wokwi projects +* [UART-CC](https://tinytapeout.com/runs/tt02/057) +* [Nano-neuron](https://tinytapeout.com/runs/tt02/066) +* [Worm in a Maze](https://tinytapeout.com/runs/tt02/084) +* [Wolf sheep cabbage river crossing puzzle ASIC design](https://tinytapeout.com/runs/tt02/111) + +## HDL projects +* [Avalon Semiconductors 'TBB1143' Programmable Sound Generator](https://tinytapeout.com/runs/tt02/024/) +* [Pi (π) to 1000+ decimal places](https://tinytapeout.com/runs/tt02/036) +* [FPGA test](https://tinytapeout.com/runs/tt02/090) +* [RV8U - 8-bit RISC-V Microcore Processor](https://tinytapeout.com/runs/tt02/107) +* [8-bit CPU](https://tinytapeout.com/runs/tt02/085/) + +# Testimonials / Endorsements + +An educational chip development workflow entirely in-browser, +from graphic entry to GDSII output for #sky130! Stunning work by +@matthewvenn to make an opaque area of technology accessible. + +--- +Last week I designed an integrated circuit for #tinytapeout, my first digital circuit design not to include any kind of cpu. + +--- +I still can't get over how smooth the ASIC design workflow of the #tinytapeout is + +--- +Thank you @matthewvenn and everyone who made #tinytapeout happen! I made a simple counter FSM based on a UTexas presentation (linked below), but I made it all on my new Steamdeck! + +--- +Created a 128-Bit Memory (8 x 16-Bit Blocks) custom ASIC. I hope it will be manufactured in next shuttle run. +Aside from that, It was amazing how seamless and easy was the whole process. + +--- +More on [Twitter](https://twitter.com/search?q=tinytapeout). + diff --git a/docs/hugo_template.md b/docs/hugo_template.md new file mode 100644 index 0000000..6746583 --- /dev/null +++ b/docs/hugo_template.md @@ -0,0 +1,38 @@ +--- +title: "{index} {title}" +weight: {weight} +--- + +## {index} : {title} + +* Author: {author} +* Description: {description} +* [GitHub repository]({git_url}) +* [Most recent GDS build]({git_action}) +* {project_type} project +* [Extra docs]({doc_link}) +* Clock: {clock_hz} Hz +* External hardware: {external_hw} + +{picture_link} + +### How it works + +{how_it_works} + +### How to test + +{how_to_test} + +### IO + +| # | Input | Output | +|---|--------------|--------------| +| 0 | {inputs[0]} | {outputs[0]} | +| 1 | {inputs[1]} | {outputs[1]} | +| 2 | {inputs[2]} | {outputs[2]} | +| 3 | {inputs[3]} | {outputs[3]} | +| 4 | {inputs[4]} | {outputs[4]} | +| 5 | {inputs[5]} | {outputs[5]} | +| 6 | {inputs[6]} | {outputs[6]} | +| 7 | {inputs[7]} | {outputs[7]} | diff --git a/documentation.py b/documentation.py index 423ee40..ec16fad 100644 --- a/documentation.py +++ b/documentation.py @@ -1,75 +1,147 @@ -import logging -import re -import subprocess -import os -import glob -import gdstk -import cairosvg +class Docs(): -# doc check -# makes sure that the basic info is present -def check_yaml_docs(yaml): - logging.info("checking docs") - for key in ['author', 'title', 'description', 'how_it_works', 'how_to_test', 'language', 'inputs', 'outputs']: - if key not in yaml['documentation']: - logging.error("missing key {} in documentation".format(key)) - exit(1) - if yaml['documentation'][key] == "": - logging.error("missing value for {} in documentation".format(key)) - exit(1) + def __init__(self, projects, args): + self.projects = projects + self.args = args - # if provided, check discord handle is valid - if len(yaml['documentation']['discord']): - parts = yaml['documentation']['discord'].split('#') - if len(parts) != 2 or len(parts[0]) == 0 or not re.match('^[0-9]{4}$', parts[1]): - logging.error('Invalid format for discord username') - exit(1) + # stuff related to docs + def build_index(self): + logging.info("building doc index") + with open("README_init.md") as fh: + readme = fh.read() + with open("README.md", 'w') as fh: + fh.write(readme) + fh.write("| Index | Author | Title | Type | Git Repo |\n") + fh.write("| ----- | ------ | ------| -----| ---------|\n") + for project in self.projects: + if not project.is_fill(): + fh.write(project.get_index_row()) + def update_image(self): + cmd = "klayout -l caravel.lyp gds/user_project_wrapper.gds -r dump_pic.rb -c klayoutrc" + logging.info(cmd) + os.system(cmd) -# use pandoc to create a single page PDF preview -def create_pdf(yaml): - yaml = yaml['documentation'] - logging.info("creating pdf") - script_dir = os.path.dirname(os.path.realpath(__file__)) - 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")) as fh: - doc_template = fh.read() + # create a json file of all the project info, this is then used by tinytapeout.com to show projects + def dump_json(self): + designs = [] + for project in self.projects: + design = project.get_yaml() + designs.append(design) - with open('datasheet.md', 'w') as fh: - fh.write(doc_header) - # handle pictures - yaml['picture_link'] = '' - if yaml['picture']: - # skip SVG for now, not supported by pandoc - picture_name = yaml['picture'] - if 'svg' not in picture_name: - yaml['picture_link'] = '![picture]({})'.format(picture_name) - else: - logging.warning("svg not supported") + with open(args.dump_json, "w") as fh: + fh.write(json.dumps(designs, indent=4)) + logging.info(f'wrote json to {args.dump_json}') - # now build the doc & print it - try: - doc = doc_template.format(**yaml) - fh.write(doc) + def dump_markdown(self): + + with open("doc_header.md") as fh: + doc_header = fh.read() + + with open("doc_template.md") as fh: + doc_template = fh.read() + + with open("INFO.md") as fh: + doc_info = fh.read() + + with open("VERIFICATION.md") as fh: + doc_verification = fh.read() + + with open("CREDITS.md") as fh: + doc_credits = fh.read() + + with open(args.dump_markdown, 'w') as fh: + fh.write(doc_header) + + for project in self.projects: + if project.is_fill(): + continue + yaml_data = project.get_project_doc_yaml() + + yaml_data['index'] = project.index + + logging.info(f"building datasheet for {project}") + # handle pictures + yaml_data['picture_link'] = '' + if yaml_data['picture']: + # skip SVG for now, not supported by pandoc + picture_name = yaml_data['picture'] + if 'svg' not in picture_name: + picture_filename = os.path.join(project.local_dir, picture_name) + yaml_data['picture_link'] = '![picture]({})'.format(picture_filename) + + # now build the doc & print it + try: + doc = doc_template.format(**yaml_data) + fh.write(doc) + fh.write("\n\pagebreak\n") + except IndexError: + logging.warning("missing pins in info.yaml, skipping") + + # ending + fh.write(doc_info) fh.write("\n\pagebreak\n") - except IndexError: - logging.warning("missing pins in info.yaml, skipping") + fh.write(doc_verification) + fh.write("\n\pagebreak\n") + fh.write(doc_credits) - pdf_cmd = 'pandoc --pdf-engine=xelatex -i datasheet.md -o datasheet.pdf' - logging.info(pdf_cmd) - p = subprocess.run(pdf_cmd, shell=True) - if p.returncode != 0: - logging.error("pdf command failed") + logging.info(f'wrote markdown to {args.dump_markdown}') + if args.dump_pdf: + pdf_cmd = f'pandoc --toc --toc-depth 2 --pdf-engine=xelatex -i {args.dump_markdown} -o {args.dump_pdf}' + logging.info(pdf_cmd) + p = subprocess.run(pdf_cmd, shell=True) + if p.returncode != 0: + logging.error("pdf command failed") -# SVG and PNG renders of the GDS -def create_svg(args): - gds = glob.glob(os.path.join(args.run_dir, 'results/final/gds/*gds')) - library = gdstk.read_gds(gds[0]) - top_cells = library.top_level() - top_cells[0].write_svg('gds_render.svg') + def build_hugo_content(self): + hugo_root = args.build_hugo_content + hugo_images = os.path.join(hugo_root, 'images') + shutil.rmtree(hugo_root) + os.makedirs(hugo_root) + os.makedirs(hugo_images) + + with open("hugo_template.md") as fh: + doc_template = fh.read() + + with open("hugo_index_template.md") as fh: + index_template = fh.read() + + # copy image + shutil.copyfile('tinytapeout.png', os.path.join(hugo_images, 'tinytapeout.png')) + + # index page + logging.info("building pages - can take a minute as fetching latest GDS action URLs for all projects") + with open(os.path.join(hugo_root, '_index.md'), 'w') as fh: + fh.write(index_template) + fh.write('# All projects\n') + fh.write("| Index | Title | Author |\n") + fh.write("| ----- | ----- | -------|\n") + for project in self.projects: + logging.info(project) + if not project.is_fill(): + fh.write(project.get_hugo_row()) + + project_dir = os.path.join(hugo_root, f'{project.get_index() :03}') + project_image_dir = os.path.join(project_dir, 'images') + os.makedirs(project_dir) + os.makedirs(project_image_dir) + yaml_data = project.get_project_doc_yaml() + yaml_data['index'] = project.index + yaml_data['weight'] = project.index + 1 + yaml_data['git_action'] = project.get_latest_action_url() + yaml_data['picture_link'] = '' + if yaml_data['picture']: + picture_name = yaml_data['picture'] + picture_filename = os.path.join(project.local_dir, picture_name) + picture_basename = os.path.basename(picture_filename) + try: + shutil.copyfile(picture_filename, os.path.join(project_image_dir, picture_basename)) + yaml_data['picture_link'] = f'![picture](images/{picture_basename})' + except FileNotFoundError: + yaml_data['picture_link'] = 'Image path is broken' + doc = doc_template.format(**yaml_data) + with open(os.path.join(project_dir, '_index.md'), 'w') as pfh: + pfh.write(doc) - if args.create_png: - cairosvg.svg2png(url='gds_render.svg', write_to='gds_render.png') diff --git a/git_utils.py b/git_utils.py new file mode 100644 index 0000000..aa75837 --- /dev/null +++ b/git_utils.py @@ -0,0 +1,199 @@ +import base64 +from urllib.parse import urlparse +import logging +import requests +import os +import zipfile, io + + +def unique(duplist): + unique_list = [] + # traverse for all elements + for x in duplist: + # check if exists in unique_list or not + if x not in unique_list: + unique_list.append(x) + return unique_list + + +def fetch_file(url, filename): + logging.info("trying to download {}".format(url)) + r = requests.get(url) + if r.status_code != 200: + logging.warning("couldn't download {}".format(url)) + exit(1) + + with open(filename, 'wb') as fh: + logging.info("written to {}".format(filename)) + fh.write(r.content) + + +def check_status(r): + if r.status_code == 401: + logging.error("unauthorised, check INFO.md for information about GitHub API keys") + exit(1) + + +def fetch_file_from_git(git_url, path): + # get the basics + user_name, repo = split_git_url(git_url) + + # authenticate for rate limiting + auth_string = os.environ['GH_USERNAME'] + ':' + os.environ['GH_TOKEN'] + encoded = base64.b64encode(auth_string.encode('ascii')) + headers = { + "authorization" : 'Basic ' + encoded.decode('ascii'), + "Accept" : "application/vnd.github+json", + } + encoded = base64.b64encode(auth_string.encode('ascii')) + + api_url = 'https://api.github.com/repos/%s/%s/contents/%s' % (user_name, repo, path) + + logging.debug(api_url) + r = requests.get(api_url, headers=headers) + check_status(r) + requests_remaining = int(r.headers['X-RateLimit-Remaining']) + if requests_remaining == 0: + logging.error("no API requests remaining") + exit(1) + + logging.debug("API requests remaining %d" % requests_remaining) + + data = r.json() + if 'content' not in data: + return None + + file_content = data['content'] + + file_content_encoding = data.get('encoding') + if file_content_encoding == 'base64': + file_content = base64.b64decode(file_content) + + return file_content + + +# the latest artifact isn't necessarily the one related to the latest commit, as github +# could have taken longer to process an older commit than a newer one. +# so iterate through commits and return the artifact that matches +def get_most_recent_action_url(commits, artifacts): + release_sha_to_download_url = {artifact['workflow_run']['head_sha']: artifact['archive_download_url'] for artifact in artifacts} + for commit in commits: + if commit['sha'] in release_sha_to_download_url: + return release_sha_to_download_url[commit['sha']] + + +def get_most_recent_action_page(commits, runs): + release_sha_to_page_url = {run['head_sha']: run['html_url'] for run in runs if run['name'] == 'gds'} + for commit in commits: + if commit['sha'] in release_sha_to_page_url: + return release_sha_to_page_url[commit['sha']] + + +def split_git_url(url): + res = urlparse(url) + try: + _, user_name, repo = res.path.split('/') + except ValueError: + logging.error(f"couldn't split repo from {url}") + exit(1) + repo = repo.replace('.git', '') + return user_name, repo + + +# download the artifact for each project to get the gds & lef +def install_artifacts(url, directory): + logging.debug(url) + user_name, repo = split_git_url(url) + + # authenticate for rate limiting + auth_string = os.environ['GH_USERNAME'] + ':' + os.environ['GH_TOKEN'] + encoded = base64.b64encode(auth_string.encode('ascii')) + headers = { + "authorization" : 'Basic ' + encoded.decode('ascii'), + "Accept" : "application/vnd.github+json", + } + + # first fetch the git commit history + api_url = f'https://api.github.com/repos/{user_name}/{repo}/commits' + r = requests.get(api_url, headers=headers) + check_status(r) + requests_remaining = int(r.headers['X-RateLimit-Remaining']) + if requests_remaining == 0: + logging.error("no API requests remaining") + exit(1) + + commits = r.json() + + # now get the artifacts + api_url = f'https://api.github.com/repos/{user_name}/{repo}/actions/artifacts' + r = requests.get(api_url, headers=headers, params={'per_page': 100}) + check_status(r) + data = r.json() + + # check there are some artifacts + if 'artifacts' not in data: + logging.error(f"no artifact found for {url}") + exit(1) + else: + # only get artifacts called GDS + artifacts = [a for a in data['artifacts'] if a['name'] == 'GDS'] + logging.debug(f"found {len(artifacts)} artifacts") + + if len(artifacts) == 0: + logging.error("no artifacts for this project") + exit(1) + + download_url = get_most_recent_action_url(commits, artifacts) + + # need actions access on the token to get the artifact + # won't work on a pull request because they won't have the token + attempts = 1 + max_attempts = 3 + while attempts < max_attempts: + try: + logging.debug(f"download url {download_url} attempt {attempts}") + r = requests.get(download_url, headers=headers) + check_status(r) + z = zipfile.ZipFile(io.BytesIO(r.content)) + z.extractall(directory) + break + except zipfile.BadZipFile: + attempts += 1 + logging.warning(f"problem with zipfile, retry {attempts}") + + if attempts == max_attempts: + logging.error("gave up downloading zipfile") + exit(1) + + +def get_latest_action_url(url, directory): + logging.debug(url) + user_name, repo = split_git_url(url) + + # authenticate for rate limiting + auth_string = os.environ['GH_USERNAME'] + ':' + os.environ['GH_TOKEN'] + encoded = base64.b64encode(auth_string.encode('ascii')) + headers = { + "authorization" : 'Basic ' + encoded.decode('ascii'), + "Accept" : "application/vnd.github+json", + } + + # first fetch the git commit history + api_url = f'https://api.github.com/repos/{user_name}/{repo}/commits' + r = requests.get(api_url, headers=headers) + check_status(r) + requests_remaining = int(r.headers['X-RateLimit-Remaining']) + if requests_remaining == 0: + logging.error("no API requests remaining") + exit(1) + + commits = r.json() + + # get runs + api_url = f'https://api.github.com/repos/{user_name}/{repo}/actions/runs' + r = requests.get(api_url, headers=headers, params={'per_page': 100}) + check_status(r) + runs = r.json() + page_url = get_most_recent_action_page(commits, runs['workflow_runs']) + + return page_url diff --git a/orders.py b/orders.py new file mode 100644 index 0000000..476e143 --- /dev/null +++ b/orders.py @@ -0,0 +1,63 @@ +import stripe, os, json +from datetime import datetime +from collections import OrderedDict +import yaml + +class Orders(): + + def __init__(self, config): + self.config = config + self.open_date = datetime.strptime(self.config['start_date'], '%Y-%m-%d') + stripe.api_key = os.environ['STRIPE_TOKEN'] + self.orders = [] + + + def fetch_orders(self): + start_id = None + after_open_date = True + + while after_open_date: + checkouts = stripe.checkout.Session.list(limit=10, starting_after=start_id) + + for checkout in checkouts: + created = datetime.fromtimestamp(checkout['created']) + if created < self.open_date: + after_open_date = False + + if checkout['payment_status'] == 'paid': + if 'github' in checkout['metadata']: + git_url = checkout['metadata']['github'] + order = { + 'git_url' : git_url, + 'email' : checkout['customer_details']['email'], + } + self.orders.append(order) + + # pagination + start_id = checkout['id'] + + # put in date order + self.orders.reverse() + return self.orders + + + def update_project_list(self): + project_list = {} + # first project is the filler test project + index = 0 + project = { 'url': self.config['filler_project'], 'status': 'active', 'fill': False } + project_list[index] = project + + # add all the real orders + for index, order in enumerate(self.orders): + project = { 'url': order['git_url'], 'status': 'active', 'fill': False } + project_list[index + 1] = project + + # fill with the filler + for index in range(len(project_list), self.config['num_projects']): + project = { 'url': self.config['filler_project'], 'status': 'active', 'fill': True } + project_list[index] = project + + with open('projects.yaml', 'w') as fh: + fh.write(yaml.dump(project_list, sort_keys=False)) + diff --git a/project.py b/project.py index d252111..87edbb0 100644 --- a/project.py +++ b/project.py @@ -3,77 +3,141 @@ import logging import requests import os import subprocess +import logging +import re +import subprocess +import os +import glob +import gdstk +import cairosvg +import os +import csv +import json +import re +import glob +import git_utils +import git + +# can't find a google version +CELL_URL = 'https://antmicro-skywater-pdk-docs.readthedocs.io/en/test-submodules-in-rtd/contents/libraries/sky130_fd_sc_ls/cells/' -def load_yaml(args): - with open(args.yaml, "r") as stream: - return (yaml.safe_load(stream)) +class Project(): + + def __init__(self, index, git_url, local_dir, args, fill=False): + self.git_url = git_url + self.args = args + self.index = index + self.fill = fill + self.local_dir = local_dir + + def post_clone_setup(self): + self.load_yaml() + self.setup_source_files() + self.load_metrics() + + def load_metrics(self): + try: + with open(self.get_metrics_path()) as fh: + self.metrics = next(csv.DictReader(fh)) + except FileNotFoundError: + self.metrics = {} -def print_wokwi_id(yaml): - print(yaml['project']['wokwi_id']) + def check_ports(self): + top = self.get_macro_name() + sources = [os.path.join(self.local_dir, 'src', src) for src in self.src_files] + source_list = " ".join(sources) + + json_file = 'ports.json' + yosys_cmd = f"yosys -qp 'read_verilog -lib -sv {source_list}; hierarchy -top {top} ; proc; write_json {json_file}'" + p = subprocess.run(yosys_cmd, shell=True) + if p.returncode != 0: + logging.error(f"yosys port read failed for {self}") + exit(1) + + with open(json_file) as fh: + ports = json.load(fh) + os.unlink(json_file) + + module_ports = ports['modules'][top]['ports'] + for port in ['io_in', 'io_out']: + if port not in module_ports: + logging.error(f"{self} {port} not found in top") + exit(1) + if len(module_ports[port]['bits']) != 8: + logging.error(f"{self} {port} doesn't have 8 bits") + exit(1) -def create_user_config(yaml): - logging.info("creating include file") - sources = get_project_source(yaml) - top_module = get_top_module(yaml) - - if top_module == 'top': - logging.error("top module cannot be called top - prepend your repo name to make it unique") - exit(1) - - filename = 'user_config.tcl' - with open(os.path.join('src', filename), 'w') as fh: - fh.write("set ::env(DESIGN_NAME) {}\n".format(top_module)) - fh.write('set ::env(VERILOG_FILES) "\\\n') - for line, source in enumerate(sources): - fh.write(" $::env(DESIGN_DIR)/" + source) - if line != len(sources) - 1: - fh.write(' \\\n') - fh.write('"\n') + def check_num_cells(self): + num_cells = self.get_cell_count_from_gl() + if self.is_hdl(): + if num_cells < 20: + logging.warning(f"{self} only has {num_cells} cells") + else: + if num_cells < 11: + logging.warning(f"{self} only has {num_cells} cells") -def fetch_file(url, filename): - logging.info("trying to download {}".format(url)) - r = requests.get(url) - if r.status_code != 200: - logging.warning("couldn't download {}".format(url)) - exit(1) - - with open(filename, 'wb') as fh: - logging.info("written to {}".format(filename)) - fh.write(r.content) + def is_fill(self): + return self.fill -def get_project_source(yaml): - # wokwi_id must be an int or 0 - try: - wokwi_id = int(yaml['project']['wokwi_id']) - except ValueError: - logging.error("wokwi id must be an integer") - exit(1) + def is_wokwi(self): + if self.wokwi_id != 0: + return True - # it's a wokwi project - if wokwi_id != 0: - url = "https://wokwi.com/api/projects/{}/verilog".format(wokwi_id) - src_file = "user_module_{}.v".format(wokwi_id) - fetch_file(url, os.path.join("src", src_file)) - # also fetch the wokwi diagram - url = "https://wokwi.com/api/projects/{}/diagram.json".format(wokwi_id) - diagram_file = "wokwi_diagram.json" - fetch_file(url, os.path.join("src", diagram_file)) + def is_hdl(self): + return not self.is_wokwi() + def load_yaml(self): + try: + with open(os.path.join(self.local_dir, 'info.yaml')) as fh: + self.yaml = yaml.safe_load(fh) + except FileNotFoundError: + logging.error(f"yaml file not found for {self} - do you need to --clone the project repos?") + exit(1) + + + # find and save the location of the source files + # get name of top module and the verilog module that contains the top + def setup_source_files(self): + # wokwi_id must be an int or 0 + try: + self.wokwi_id = int(self.yaml['project']['wokwi_id']) + except ValueError: + logging.error("wokwi id must be an integer") + exit(1) + + self.yaml['project']['git_url'] = self.git_url + + if self.is_hdl(): + self.top_module = self.yaml['project']['top_module'] + self.src_files = self.get_hdl_source() + self.top_verilog_filename = self.find_top_verilog() + else: + self.top_module = f"user_module_{self.wokwi_id}" + self.src_files = self.get_wokwi_source() + self.top_verilog_filename = self.src_files[0] + + self.macro_instance = f"{self.top_module}_{self.index :03}" + self.scanchain_instance = f"scanchain_{self.index :03}" + + + def get_wokwi_source(self): + src_file = "user_module_{}.v".format(self.wokwi_id) return [src_file, 'cells.v'] - # else it's HDL, so check source files - else: - if 'source_files' not in yaml['project']: + + + def get_hdl_source(self): + if 'source_files' not in self.yaml['project']: logging.error("source files must be provided if wokwi_id is set to 0") exit(1) - source_files = yaml['project']['source_files'] + source_files = self.yaml['project']['source_files'] if source_files is None: logging.error("must be more than 1 source file") exit(1) @@ -82,34 +146,402 @@ def get_project_source(yaml): logging.error("must be more than 1 source file") exit(1) - if 'top_module' not in yaml['project']: + if 'top_module' not in self.yaml['project']: logging.error("must provide a top module name") exit(1) + if self.yaml['project']['top_module'] == 'top': + logging.error("top module cannot be called top - prepend your repo name to make it unique") + exit(1) + for filename in source_files: if '*' in filename: logging.error("* not allowed, please specify each file") exit(1) - if not os.path.exists(os.path.join('src', filename)): + if not os.path.exists(os.path.join(self.local_dir, 'src', filename)): logging.error(f"{filename} doesn't exist in the repo") exit(1) return source_files -def get_top_module(yaml): - wokwi_id = int(yaml['project']['wokwi_id']) - if wokwi_id != 0: - return "user_module_{}".format(wokwi_id) - else: - return yaml['project']['top_module'] + def get_yaml(self): + return self.yaml + + def get_hugo_row(self): + return f'| {self.index} | [{self.yaml["documentation"]["title"]}]({self.index :03}) | {self.yaml["documentation"]["author"]}|\n' + + # docs stuff for index on README.md + def get_index_row(self): + return f'| {self.index} | {self.yaml["documentation"]["author"]} | {self.yaml["documentation"]["title"]} | {self.get_project_type_string()} | {self.git_url} |\n' + + def get_project_type_string(self): + if self.is_wokwi(): + return f"[Wokwi]({self.get_wokwi_url()})" + else: + return "HDL" + + def get_project_doc_yaml(self): + # fstring dict support is limited to one level deep, so put the git url and wokwi url in the docs key + docs = self.yaml['documentation'] + docs['project_type'] = self.get_project_type_string() + docs['git_url'] = self.git_url + return docs + + def get_wokwi_url(self): + return f'https://wokwi.com/projects/{self.wokwi_id}' + + # top module name is defined in one of the source files, which one? + def find_top_verilog(self): + rgx_mod = re.compile(r"(?:^|[\W])module[\s]{1,}([\w]+)") + top_verilog = [] + for src in self.src_files: + with open(os.path.join(self.local_dir, 'src', src)) as fh: + for line in fh.readlines(): + for match in rgx_mod.finditer(line): + if match.group(1) == self.top_module: + top_verilog.append(src) + assert len(top_verilog) == 1 + return top_verilog[0] + + def clone(self): + if os.path.exists(self.local_dir): + git_remote = list(git.Repo(self.local_dir).remotes[0].urls)[0] + if self.git_url == git_remote: + logging.info("git repo already exists and is correct - skipping") + else: + logging.error("git repo exists and remote doesn't match - abort") + logging.error(f"{self.git_url} != {git_remote}") + exit(1) + else: + logging.info("clone") + git.Repo.clone_from(self.git_url, self.local_dir, recursive=True) + + def pull(self): + repo = git.Repo(self.local_dir) + # reset + repo.git.reset('--hard') + o = repo.remotes.origin + o.pull() -def harden(): - # requires PDK, PDK_ROOT, OPENLANE_ROOT & OPENLANE_IMAGE_NAME to be set in local environment - harden_cmd = 'docker run --rm -v $OPENLANE_ROOT:/openlane -v $PDK_ROOT:$PDK_ROOT -v $(pwd):/work -e PDK=$PDK -e PDK_ROOT=$PDK_ROOT -u $(id -u $USER):$(id -g $USER) $OPENLANE_IMAGE_NAME /bin/bash -c "./flow.tcl -overwrite -design /work/src -run_path /work/runs -tag wokwi"' - env = os.environ.copy() - p = subprocess.run(harden_cmd, shell=True, env=env) - if p.returncode != 0: - logging.error("harden failed") - exit(1) + def __str__(self): +# if self.args.log_email: +# return f"[{self.index:03} : {self.email} : {self.git_url}]" +# else: + return f"[{self.index:03} : {self.git_url}]" + + def fetch_gds(self): + install_artifacts(self.git_url, self.local_dir) + + def get_latest_action_url(self): + return get_latest_action_url(self.git_url, self.local_dir) + + def get_macro_name(self): + return self.top_module + + # instance name of the project, different for each id + def get_macro_instance(self): + return self.macro_instance + + # instance name of the scanchain module, different for each id + def get_scanchain_instance(self): + return self.scanchain_instance + + # unique id + def get_index(self): + return self.index + + # metrics + def get_metrics_path(self): + return os.path.join(self.local_dir, 'runs/wokwi/reports/metrics.csv') + + # name of the gds file + def get_macro_gds_filename(self): + return f"{self.top_module}.gds" + + def get_macro_lef_filename(self): + return f"{self.top_module}.lef" + + # for GL sims & blackboxing + def get_gl_verilog_filename(self): + return f"{self.top_module}.v" + + # for simulations + def get_top_verilog_filename(self): + if self.is_hdl(): + # make sure it's unique & without leading directories + # a few people use 'top.v', which is OK as long as the top module is called something more unique + # but then it needs to be made unique so the source can be found + filename = os.path.basename(self.top_verilog_filename) + return f'{self.index :03}_{filename}' + else: + return self.top_verilog_filename + + def get_git_url(self): + return self.git_url + + # todo add spef + def copy_files_to_caravel(self): + files = [ + (f"runs/wokwi/results/final/gds/{self.get_macro_gds_filename()}", f"gds/{self.get_macro_gds_filename()}"), + (f"runs/wokwi/results/final/lef/{self.get_macro_lef_filename()}", f"lef/{self.get_macro_lef_filename()}"), + (f"runs/wokwi/results/final/verilog/gl/{self.get_gl_verilog_filename()}", f"verilog/gl/{self.get_gl_verilog_filename()}"), + (f"src/{self.top_verilog_filename}", f"verilog/rtl/{self.get_top_verilog_filename()}"), + ] + + logging.debug("copying files into position") + for from_path, to_path in files: + from_path = os.path.join(self.local_dir, from_path) + logging.debug(f"copy {from_path} to {to_path}") + shutil.copyfile(from_path, to_path) + + + def print_wokwi_id(self): + print(self.wokwi_id) + + + def fetch_wokwi_files(self): + logging.info("fetching wokwi files") + url = "https://wokwi.com/api/projects/{}/verilog".format(self.wokwi_id) + git_utils.fetch_file(url, os.path.join(self.local_dir, "src", src_file)) + + # also fetch the wokwi diagram + url = "https://wokwi.com/api/projects/{}/diagram.json".format(self.wokwi_id) + diagram_file = "wokwi_diagram.json" + git_utils.fetch_file(url, os.path.join(self.local_dir, "src", diagram_file)) + + + def create_user_config(self): + self.fetch_wokwi_files() + + logging.info("creating include file") + filename = 'user_config.tcl' + with open(os.path.join(self.local_dir, 'src', filename), 'w') as fh: + fh.write("set ::env(DESIGN_NAME) {}\n".format(self.top_module)) + fh.write('set ::env(VERILOG_FILES) "\\\n') + for line, source in enumerate(self.src_files): + fh.write(" $::env(DESIGN_DIR)/" + source) + if line != len(self.src_files) - 1: + fh.write(' \\\n') + fh.write('"\n') + + + """ + def harden(self): + logging.info(f"hardening {self}") + + # copy golden config + shutil.copyfile('golden_config.tcl', os.path.join(self.local_dir, 'src', 'config.tcl')) + + cwd = os.getcwd() + os.chdir(self.local_dir) + + # setup user config, not including python fails on github action + if 'LOCAL' in os.environ: + configure_cmd = './configure.py --create-user-config' + else: + configure_cmd = 'python ./configure.py --create-user-config' + p = subprocess.run(configure_cmd, shell=True) + if p.returncode != 0: + logging.error(f"configure failed for {self}") + exit(1) + + # requires PDK_ROOT, OPENLANE_ROOT & OPENLANE_IMAGE_NAME to be set in local environment + harden_cmd = 'docker run --rm -v $OPENLANE_ROOT:/openlane -v $PDK_ROOT:$PDK_ROOT -v $(pwd):/work -e PDK=$PDK -e PDK_ROOT=$PDK_ROOT -u $(id -u $USER):$(id -g $USER) $OPENLANE_IMAGE_NAME /bin/bash -c "./flow.tcl -overwrite -design /work/src -run_path /work/runs -tag wokwi"' + env = os.environ.copy() + p = subprocess.run(harden_cmd, shell=True, env=env) + if p.returncode != 0: + logging.error(f"harden failed for {self}") + exit(1) + + os.chdir(cwd) + """ + + def harden(self): + cwd = os.getcwd() + os.chdir(self.local_dir) + # requires PDK, PDK_ROOT, OPENLANE_ROOT & OPENLANE_IMAGE_NAME to be set in local environment + harden_cmd = 'docker run --rm -v $OPENLANE_ROOT:/openlane -v $PDK_ROOT:$PDK_ROOT -v $(pwd):/work -e PDK=$PDK -e PDK_ROOT=$PDK_ROOT -u $(id -u $USER):$(id -g $USER) $OPENLANE_IMAGE_NAME /bin/bash -c "./flow.tcl -overwrite -design /work/src -run_path /work/runs -tag wokwi"' + logging.debug(harden_cmd) + env = os.environ.copy() + p = subprocess.run(harden_cmd, shell=True, env=env) + if p.returncode != 0: + logging.error("harden failed") + exit(1) + + os.chdir(cwd) + + # doc check + # makes sure that the basic info is present + def check_yaml_docs(self): + yaml = self.yaml + logging.info("checking docs") + for key in ['author', 'title', 'description', 'how_it_works', 'how_to_test', 'language', 'inputs', 'outputs']: + if key not in yaml['documentation']: + logging.error("missing key {} in documentation".format(key)) + exit(1) + if yaml['documentation'][key] == "": + logging.error("missing value for {} in documentation".format(key)) + exit(1) + + # if provided, check discord handle is valid + if len(yaml['documentation']['discord']): + parts = yaml['documentation']['discord'].split('#') + if len(parts) != 2 or len(parts[0]) == 0 or not re.match('^[0-9]{4}$', parts[1]): + logging.error('Invalid format for discord username') + exit(1) + + + # use pandoc to create a single page PDF preview + def create_pdf(self): + yaml = self.yaml + yaml = yaml['documentation'] + logging.info("creating pdf") + script_dir = os.path.dirname(os.path.realpath(__file__)) + 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")) as fh: + doc_template = fh.read() + + with open('datasheet.md', 'w') as fh: + fh.write(doc_header) + # handle pictures + yaml['picture_link'] = '' + if yaml['picture']: + # skip SVG for now, not supported by pandoc + picture_name = yaml['picture'] + if 'svg' not in picture_name: + yaml['picture_link'] = '![picture]({})'.format(picture_name) + else: + logging.warning("svg not supported") + + # now build the doc & print it + try: + doc = doc_template.format(**yaml) + fh.write(doc) + fh.write("\n\pagebreak\n") + except IndexError: + logging.warning("missing pins in info.yaml, skipping") + + pdf_cmd = 'pandoc --pdf-engine=xelatex -i datasheet.md -o datasheet.pdf' + logging.info(pdf_cmd) + p = subprocess.run(pdf_cmd, shell=True) + if p.returncode != 0: + logging.error("pdf command failed") + + + # SVG and PNG renders of the GDS + def create_svg(self): + gds = glob.glob(os.path.join(self.local_dir, 'runs/wokwi/results/final/gds/*gds')) + library = gdstk.read_gds(gds[0]) + top_cells = library.top_level() + top_cells[0].write_svg('gds_render.svg') + + if self.args.create_png: + cairosvg.svg2png(url='gds_render.svg', write_to='gds_render.png') + + + def print_warnings(self): + warnings = [] + with open(os.path.join(self.local_dir, 'runs/wokwi/logs/synthesis/1-synthesis.log')) as f: + for line in f.readlines(): + if line.startswith('Warning:'): + # bogus warning https://github.com/YosysHQ/yosys/commit/bfacaddca8a2e113e4bc3d6177612ccdba1555c8 + if 'WIDTHLABEL' not in line: + warnings.append(line.strip()) + if len(warnings): + print('# Synthesis warnings') + print() + for warning in warnings: + print(f'* {warning}') + + + def print_stats(self): + print('# Routing stats') + print() + print('| Utilisation (%) | Wire length (um) |') + print('|-------------|------------------|') + print('| {} | {} |'.format(self.metrics['OpenDP_Util'], self.metrics['wire_length'])) + + + # Print the summaries + def summarize(self): + cell_count = self.get_cell_count_from_gl() + script_dir = os.path.dirname(os.path.realpath(__file__)) + + with open(os.path.join(script_dir, 'categories.json')) as fh: + categories = json.load(fh) + with open(os.path.join(script_dir, 'defs.json')) as fh: + defs = json.load(fh) + + # print all used cells, sorted by frequency + total = 0 + if self.args.print_cell_summary: + print('# Cell usage') + print() + print('| Cell Name | Description | Count |') + print('|-----------|-------------|-------|') + for name, count in sorted(cell_count.items(), key=lambda item: item[1], reverse=True): + if count > 0: + total += count + cell_link = f'{CELL_URL}{name}' + print(f'| [{name}]({cell_link}) | {defs[name]["description"]} |{count} |') + + print(f'| | Total | {total} |') + + if self.args.print_cell_category: + by_category = {} + for cell_name in cell_count: + cat_index = categories['map'][cell_name] + cat_name = categories['categories'][cat_index] + if cat_name in by_category: + by_category[cat_name]['count'] += cell_count[cell_name] + by_category[cat_name]['examples'].append(cell_name) + else: + by_category[cat_name] = {'count' : cell_count[cell_name], 'examples' : [cell_name]} + + print('# Cell usage by Category') + print() + print('| Category | Cells | Count |') + print('|---------------|----------|-------|') + for cat_name, cat_dict in sorted(by_category.items(), key=lambda x: x[1]['count'], reverse=True): + cell_links = [f'[{name}]({CELL_URL}{name})' for name in cat_dict['examples']] + print(f'|{cat_name} | {" ".join(cell_links)} | {cat_dict["count"]}|') + + + # Parse the lib, cell and drive strength an OpenLane gate-level Verilog file + def get_cell_count_from_gl(self): + cell_count = {} + total = 0 + gl_files = glob.glob(os.path.join(self.local_dir, 'runs/wokwi/results/final/verilog/gl/*.nl.v')) + with open(gl_files[0]) as fh: + for line in fh.readlines(): + m = re.search(r'sky130_(\S+)__(\S+)_(\d+)', line) + if m is not None: + total += 1 + cell_lib = m.group(1) + cell_name = m.group(2) + cell_drive = m.group(3) + assert cell_lib in ['fd_sc_hd', 'ef_sc_hd'] + assert int(cell_drive) > 0 + try: + cell_count[cell_name] += 1 + except KeyError: + cell_count[cell_name] = 1 + return (cell_count) + + + # Load all the json defs and combine into one big dict, keyed by cellname + # Needs access to the PDK + def create_defs(self): + # replace this with a find + json_files = glob.glob('sky130_fd_sc_hd/latest/cells/*/definition.json') + definitions = {} + for json_file in json_files: + with open(json_file) as fh: + definition = json.load(fh) + definitions[definition['name']] = definition + + with open('defs.json', 'w') as fh: + json.dump(definitions, fh) diff --git a/projects.yaml b/projects.yaml new file mode 100644 index 0000000..f024b89 --- /dev/null +++ b/projects.yaml @@ -0,0 +1,1000 @@ +0: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: false +1: + url: https://github.com/WallieEverest/tt03 + status: active + fill: false +2: + url: https://github.com/icegoat9/tinytapeout03-7seglife + status: active + fill: false +3: + url: https://github.com/meiniKi/tt03-another-piece-of-pi + status: active + fill: false +4: + url: https://github.com/nqbit/wormy + status: active + fill: false +5: + url: https://github.com/KolosKoblasz/tt03-knight_rider + status: active + fill: false +6: + url: https://github.com/dgarrett/tt03-num-latch + status: disable + fill: false +7: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +8: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +9: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +10: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +11: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +12: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +13: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +14: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +15: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +16: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +17: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +18: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +19: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +20: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +21: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +22: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +23: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +24: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +25: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +26: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +27: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +28: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +29: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +30: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +31: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +32: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +33: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +34: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +35: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +36: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +37: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +38: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +39: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +40: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +41: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +42: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +43: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +44: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +45: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +46: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +47: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +48: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +49: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +50: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +51: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +52: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +53: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +54: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +55: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +56: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +57: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +58: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +59: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +60: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +61: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +62: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +63: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +64: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +65: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +66: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +67: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +68: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +69: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +70: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +71: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +72: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +73: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +74: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +75: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +76: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +77: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +78: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +79: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +80: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +81: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +82: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +83: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +84: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +85: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +86: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +87: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +88: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +89: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +90: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +91: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +92: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +93: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +94: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +95: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +96: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +97: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +98: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +99: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +100: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +101: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +102: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +103: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +104: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +105: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +106: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +107: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +108: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +109: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +110: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +111: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +112: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +113: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +114: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +115: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +116: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +117: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +118: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +119: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +120: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +121: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +122: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +123: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +124: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +125: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +126: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +127: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +128: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +129: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +130: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +131: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +132: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +133: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +134: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +135: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +136: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +137: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +138: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +139: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +140: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +141: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +142: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +143: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +144: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +145: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +146: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +147: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +148: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +149: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +150: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +151: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +152: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +153: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +154: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +155: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +156: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +157: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +158: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +159: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +160: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +161: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +162: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +163: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +164: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +165: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +166: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +167: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +168: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +169: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +170: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +171: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +172: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +173: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +174: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +175: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +176: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +177: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +178: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +179: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +180: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +181: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +182: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +183: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +184: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +185: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +186: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +187: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +188: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +189: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +190: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +191: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +192: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +193: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +194: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +195: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +196: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +197: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +198: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +199: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +200: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +201: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +202: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +203: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +204: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +205: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +206: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +207: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +208: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +209: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +210: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +211: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +212: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +213: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +214: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +215: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +216: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +217: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +218: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +219: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +220: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +221: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +222: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +223: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +224: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +225: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +226: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +227: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +228: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +229: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +230: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +231: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +232: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +233: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +234: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +235: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +236: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +237: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +238: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +239: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +240: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +241: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +242: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +243: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +244: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +245: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +246: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +247: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +248: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true +249: + url: https://github.com/TinyTapeout/tt03-test-invert + status: active + fill: true diff --git a/reports.py b/reports.py deleted file mode 100755 index 9de69ba..0000000 --- a/reports.py +++ /dev/null @@ -1,116 +0,0 @@ -import os -import csv -import json -import re -import glob - -# can't find a google version -CELL_URL = 'https://antmicro-skywater-pdk-docs.readthedocs.io/en/test-submodules-in-rtd/contents/libraries/sky130_fd_sc_ls/cells/' - - -def print_warnings(args): - warnings = [] - with open(os.path.join(args.run_dir, 'logs/synthesis/1-synthesis.log')) as f: - for line in f.readlines(): - if line.startswith('Warning:'): - # bogus warning https://github.com/YosysHQ/yosys/commit/bfacaddca8a2e113e4bc3d6177612ccdba1555c8 - if 'WIDTHLABEL' not in line: - warnings.append(line.strip()) - if len(warnings): - print('# Synthesis warnings') - print() - for warning in warnings: - print(f'* {warning}') - - -def print_stats(args): - with open(os.path.join(args.run_dir, 'reports/metrics.csv')) as f: - report = list(csv.DictReader(f))[0] - - print('# Routing stats') - print() - print('| Utilisation (%) | Wire length (um) |') - print('|-------------|------------------|') - print('| {} | {} |'.format(report['OpenDP_Util'], report['wire_length'])) - - -# Print the summaries -def summarize(args): - cell_count = get_cell_count_from_gl(args) - script_dir = os.path.dirname(os.path.realpath(__file__)) - - with open(os.path.join(script_dir, 'categories.json')) as fh: - categories = json.load(fh) - with open(os.path.join(script_dir, 'defs.json')) as fh: - defs = json.load(fh) - - # print all used cells, sorted by frequency - total = 0 - if args.print_cell_summary: - print('# Cell usage') - print() - print('| Cell Name | Description | Count |') - print('|-----------|-------------|-------|') - for name, count in sorted(cell_count.items(), key=lambda item: item[1], reverse=True): - if count > 0: - total += count - cell_link = f'{CELL_URL}{name}' - print(f'| [{name}]({cell_link}) | {defs[name]["description"]} |{count} |') - - print(f'| | Total | {total} |') - - if args.print_cell_category: - by_category = {} - for cell_name in cell_count: - cat_index = categories['map'][cell_name] - cat_name = categories['categories'][cat_index] - if cat_name in by_category: - by_category[cat_name]['count'] += cell_count[cell_name] - by_category[cat_name]['examples'].append(cell_name) - else: - by_category[cat_name] = {'count' : cell_count[cell_name], 'examples' : [cell_name]} - - print('# Cell usage by Category') - print() - print('| Category | Cells | Count |') - print('|---------------|----------|-------|') - for cat_name, cat_dict in sorted(by_category.items(), key=lambda x: x[1]['count'], reverse=True): - cell_links = [f'[{name}]({CELL_URL}{name})' for name in cat_dict['examples']] - print(f'|{cat_name} | {" ".join(cell_links)} | {cat_dict["count"]}|') - - -# Parse the lib, cell and drive strength an OpenLane gate-level Verilog file -def get_cell_count_from_gl(args): - cell_count = {} - total = 0 - gl_files = glob.glob(os.path.join(args.run_dir, 'results/final/verilog/gl/*.nl.v')) - with open(gl_files[0]) as fh: - for line in fh.readlines(): - m = re.search(r'sky130_(\S+)__(\S+)_(\d+)', line) - if m is not None: - total += 1 - cell_lib = m.group(1) - cell_name = m.group(2) - cell_drive = m.group(3) - assert cell_lib in ['fd_sc_hd', 'ef_sc_hd'] - assert int(cell_drive) > 0 - try: - cell_count[cell_name] += 1 - except KeyError: - cell_count[cell_name] = 1 - return (cell_count) - - -# Load all the json defs and combine into one big dict, keyed by cellname -# Needs access to the PDK -def create_defs(args): - # replace this with a find - json_files = glob.glob('sky130_fd_sc_hd/latest/cells/*/definition.json') - definitions = {} - for json_file in json_files: - with open(json_file) as fh: - definition = json.load(fh) - definitions[definition['name']] = definition - - with open('defs.json', 'w') as fh: - json.dump(definitions, fh) diff --git a/tt_tool.py b/tt_tool.py index 85a4fb6..b5a66d7 100755 --- a/tt_tool.py +++ b/tt_tool.py @@ -2,18 +2,15 @@ import sys import logging import argparse -import documentation -import project -import reports +from project import Project if __name__ == '__main__': parser = argparse.ArgumentParser(description="TT setup") + parser.add_argument('--project-dir', help="location of the project", default='.') parser.add_argument('--yaml', help="the project's yaml configuration file", default='info.yaml') - parser.add_argument('--run-dir', help="OpenLane run directory", default='runs/wokwi') # set in the github action yaml parser.add_argument('--debug', help="debug logging", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO) # reports & summaries - parser.add_argument('--create-cell-defs', help="create def file", action="store_const", const=True, default=False) parser.add_argument('--print-cell-summary', help="print summary", action="store_const", const=True, default=False) parser.add_argument('--print-cell-category', help="print category", action="store_const", const=True, default=False) parser.add_argument('--print-stats', help="print some stats from the run", action="store_const", const=True) @@ -45,35 +42,33 @@ if __name__ == '__main__': ch.setFormatter(log_format) log.addHandler(ch) - project_yaml = project.load_yaml(args) + project = Project(0, 'unknown', args.project_dir, args) + project.post_clone_setup() # handle the options if args.check_docs: - documentation.check_yaml_docs(project_yaml) + project.check_yaml_docs() if args.print_cell_summary or args.print_cell_category: - reports.summarize(args) - - if args.create_cell_defs: - reports.create_defs(args) + project.summarize() if args.print_stats: - reports.print_stats(args) + project.print_stats() if args.print_warnings: - reports.print_warnings(args) + project.print_warnings() elif args.print_wokwi_id: - project.print_wokwi_id(project_yaml) + project.print_wokwi_id() if args.create_user_config: - project.create_user_config(project_yaml) + project.create_user_config() if args.harden: project.harden() if args.create_pdf: - documentation.create_pdf(project_yaml) + project.create_pdf() if args.create_svg or args.create_png: - documentation.create_svg(args) + project.create_svg() From 97cfa3b15e546aa42377a4b8130be6704670de37 Mon Sep 17 00:00:00 2001 From: matt venn Date: Mon, 13 Mar 2023 18:05:56 +0100 Subject: [PATCH 02/45] lint and partially working --- caravel.py | 5 +++-- configure.py | 17 ++++++----------- documentation.py | 17 +++++++++-------- lint.sh | 2 +- orders.py | 15 ++++++--------- project.py | 45 ++++++++++----------------------------------- tt_tool.py | 2 +- 7 files changed, 36 insertions(+), 67 deletions(-) diff --git a/caravel.py b/caravel.py index 3660272..2692b4f 100644 --- a/caravel.py +++ b/caravel.py @@ -1,3 +1,6 @@ +import logging + + class CaravelConfig(): def __init__(self, projects, num_projects): @@ -267,5 +270,3 @@ class CaravelConfig(): def list(self): for project in self.projects: logging.info(project) - - diff --git a/configure.py b/configure.py index 808698c..cd4601f 100755 --- a/configure.py +++ b/configure.py @@ -1,13 +1,11 @@ #!/usr/bin/env python3 -import re, glob, json, csv from orders import Orders import datetime import yaml -import subprocess -import argparse, logging, shutil, sys, os, collections -from git_utils import install_artifacts, get_latest_action_url, unique -import git +import argparse, logging, sys, os, collections from project import Project +from documentation import Docs +from caravel import CaravelConfig # pipe handling @@ -66,7 +64,6 @@ class Projects(): except KeyError: email = 'none' """ - git_url = self.project_config[index]['url'] filler = self.project_config[index]['fill'] @@ -126,7 +123,6 @@ class Projects(): all_gds_files = [project.get_macro_gds_filename() for project in self.projects if not project.is_fill()] self.assert_unique(all_gds_files) - def assert_unique(self, check): duplicates = [item for item, count in collections.Counter(check).items() if count > 1] if duplicates: @@ -175,7 +171,6 @@ class Projects(): if util < min_util: min_util = util - logging.info(f"build time for all projects {total_seconds / 3600} hrs") logging.info(f"total wire length {total_wire_length} um") logging.info(f"total cells {total_physical_cells}") @@ -230,8 +225,8 @@ if __name__ == '__main__': projects = Projects(config, args) -# docs = Docs(projects.projects, args=args) -# caravel = CaravelConfig(projects.projects, num_projects=args.limit_num_projects) + docs = Docs(projects.projects, args=args) + caravel = CaravelConfig(projects.projects, num_projects=args.limit_num_projects) if args.list: caravel.list() @@ -243,7 +238,7 @@ if __name__ == '__main__': orders = Orders(config) orders.fetch_orders() orders.update_project_list() - + if args.update_caravel: caravel.create_macro_config() caravel.instantiate() diff --git a/documentation.py b/documentation.py index ec16fad..2ead7e3 100644 --- a/documentation.py +++ b/documentation.py @@ -1,3 +1,5 @@ +import os, logging, subprocess, shutil, json + class Docs(): @@ -30,9 +32,9 @@ class Docs(): design = project.get_yaml() designs.append(design) - with open(args.dump_json, "w") as fh: + with open(self.args.dump_json, "w") as fh: fh.write(json.dumps(designs, indent=4)) - logging.info(f'wrote json to {args.dump_json}') + logging.info(f'wrote json to {self.args.dump_json}') def dump_markdown(self): @@ -51,7 +53,7 @@ class Docs(): with open("CREDITS.md") as fh: doc_credits = fh.read() - with open(args.dump_markdown, 'w') as fh: + with open(self.args.dump_markdown, 'w') as fh: fh.write(doc_header) for project in self.projects: @@ -86,17 +88,17 @@ class Docs(): fh.write("\n\pagebreak\n") fh.write(doc_credits) - logging.info(f'wrote markdown to {args.dump_markdown}') + logging.info(f'wrote markdown to {self.args.dump_markdown}') - if args.dump_pdf: - pdf_cmd = f'pandoc --toc --toc-depth 2 --pdf-engine=xelatex -i {args.dump_markdown} -o {args.dump_pdf}' + if self.args.dump_pdf: + pdf_cmd = f'pandoc --toc --toc-depth 2 --pdf-engine=xelatex -i {self.args.dump_markdown} -o {self.args.dump_pdf}' logging.info(pdf_cmd) p = subprocess.run(pdf_cmd, shell=True) if p.returncode != 0: logging.error("pdf command failed") def build_hugo_content(self): - hugo_root = args.build_hugo_content + hugo_root = self.args.build_hugo_content hugo_images = os.path.join(hugo_root, 'images') shutil.rmtree(hugo_root) os.makedirs(hugo_root) @@ -144,4 +146,3 @@ class Docs(): doc = doc_template.format(**yaml_data) with open(os.path.join(project_dir, '_index.md'), 'w') as pfh: pfh.write(doc) - diff --git a/lint.sh b/lint.sh index 983de92..b92498a 100755 --- a/lint.sh +++ b/lint.sh @@ -1 +1 @@ -flake8 --ignore E203,E123,E501,E221,E401,W605 documentation.py project.py tt_tool.py reports.py +flake8 --ignore E203,E123,E501,E221,E401,W605 configure.py project.py tt_tool.py orders.py documentation.py caravel.py diff --git a/orders.py b/orders.py index 476e143..1b0c3b8 100644 --- a/orders.py +++ b/orders.py @@ -1,8 +1,8 @@ -import stripe, os, json +import stripe, os from datetime import datetime -from collections import OrderedDict import yaml + class Orders(): def __init__(self, config): @@ -11,7 +11,6 @@ class Orders(): stripe.api_key = os.environ['STRIPE_TOKEN'] self.orders = [] - def fetch_orders(self): start_id = None after_open_date = True @@ -35,29 +34,27 @@ class Orders(): # pagination start_id = checkout['id'] - + # put in date order self.orders.reverse() return self.orders - def update_project_list(self): project_list = {} # first project is the filler test project index = 0 - project = { 'url': self.config['filler_project'], 'status': 'active', 'fill': False } + project = {'url': self.config['filler_project'], 'status': 'active', 'fill': False} project_list[index] = project # add all the real orders for index, order in enumerate(self.orders): - project = { 'url': order['git_url'], 'status': 'active', 'fill': False } + project = {'url': order['git_url'], 'status': 'active', 'fill': False} project_list[index + 1] = project # fill with the filler for index in range(len(project_list), self.config['num_projects']): - project = { 'url': self.config['filler_project'], 'status': 'active', 'fill': True } + project = {'url': self.config['filler_project'], 'status': 'active', 'fill': True} project_list[index] = project with open('projects.yaml', 'w') as fh: fh.write(yaml.dump(project_list, sort_keys=False)) - diff --git a/project.py b/project.py index 87edbb0..837ee95 100644 --- a/project.py +++ b/project.py @@ -1,22 +1,16 @@ import yaml -import logging -import requests +import glob import os -import subprocess import logging import re import subprocess -import os -import glob import gdstk import cairosvg -import os import csv import json -import re -import glob import git_utils import git +import shutil # can't find a google version CELL_URL = 'https://antmicro-skywater-pdk-docs.readthedocs.io/en/test-submodules-in-rtd/contents/libraries/sky130_fd_sc_ls/cells/' @@ -43,7 +37,6 @@ class Project(): except FileNotFoundError: self.metrics = {} - def check_ports(self): top = self.get_macro_name() sources = [os.path.join(self.local_dir, 'src', src) for src in self.src_files] @@ -69,7 +62,6 @@ class Project(): logging.error(f"{self} {port} doesn't have 8 bits") exit(1) - def check_num_cells(self): num_cells = self.get_cell_count_from_gl() if self.is_hdl(): @@ -79,16 +71,13 @@ class Project(): if num_cells < 11: logging.warning(f"{self} only has {num_cells} cells") - def is_fill(self): return self.fill - def is_wokwi(self): if self.wokwi_id != 0: return True - def is_hdl(self): return not self.is_wokwi() @@ -100,7 +89,6 @@ class Project(): logging.error(f"yaml file not found for {self} - do you need to --clone the project repos?") exit(1) - # find and save the location of the source files # get name of top module and the verilog module that contains the top def setup_source_files(self): @@ -125,13 +113,10 @@ class Project(): self.macro_instance = f"{self.top_module}_{self.index :03}" self.scanchain_instance = f"scanchain_{self.index :03}" - def get_wokwi_source(self): src_file = "user_module_{}.v".format(self.wokwi_id) return [src_file, 'cells.v'] - - def get_hdl_source(self): if 'source_files' not in self.yaml['project']: logging.error("source files must be provided if wokwi_id is set to 0") @@ -164,7 +149,6 @@ class Project(): return source_files - def get_yaml(self): return self.yaml @@ -224,18 +208,19 @@ class Project(): o = repo.remotes.origin o.pull() - def __str__(self): -# if self.args.log_email: -# return f"[{self.index:03} : {self.email} : {self.git_url}]" -# else: + """ + if self.args.log_email: + return f"[{self.index:03} : {self.email} : {self.git_url}]" + else: + """ return f"[{self.index:03} : {self.git_url}]" def fetch_gds(self): - install_artifacts(self.git_url, self.local_dir) + git_utils.install_artifacts(self.git_url, self.local_dir) def get_latest_action_url(self): - return get_latest_action_url(self.git_url, self.local_dir) + return git_utils.get_latest_action_url(self.git_url, self.local_dir) def get_macro_name(self): return self.top_module @@ -296,13 +281,12 @@ class Project(): logging.debug(f"copy {from_path} to {to_path}") shutil.copyfile(from_path, to_path) - def print_wokwi_id(self): print(self.wokwi_id) - def fetch_wokwi_files(self): logging.info("fetching wokwi files") + src_file = self.src_files[0] url = "https://wokwi.com/api/projects/{}/verilog".format(self.wokwi_id) git_utils.fetch_file(url, os.path.join(self.local_dir, "src", src_file)) @@ -311,7 +295,6 @@ class Project(): diagram_file = "wokwi_diagram.json" git_utils.fetch_file(url, os.path.join(self.local_dir, "src", diagram_file)) - def create_user_config(self): self.fetch_wokwi_files() @@ -326,7 +309,6 @@ class Project(): fh.write(' \\\n') fh.write('"\n') - """ def harden(self): logging.info(f"hardening {self}") @@ -392,7 +374,6 @@ class Project(): logging.error('Invalid format for discord username') exit(1) - # use pandoc to create a single page PDF preview def create_pdf(self): yaml = self.yaml @@ -430,7 +411,6 @@ class Project(): if p.returncode != 0: logging.error("pdf command failed") - # SVG and PNG renders of the GDS def create_svg(self): gds = glob.glob(os.path.join(self.local_dir, 'runs/wokwi/results/final/gds/*gds')) @@ -441,7 +421,6 @@ class Project(): if self.args.create_png: cairosvg.svg2png(url='gds_render.svg', write_to='gds_render.png') - def print_warnings(self): warnings = [] with open(os.path.join(self.local_dir, 'runs/wokwi/logs/synthesis/1-synthesis.log')) as f: @@ -456,7 +435,6 @@ class Project(): for warning in warnings: print(f'* {warning}') - def print_stats(self): print('# Routing stats') print() @@ -464,7 +442,6 @@ class Project(): print('|-------------|------------------|') print('| {} | {} |'.format(self.metrics['OpenDP_Util'], self.metrics['wire_length'])) - # Print the summaries def summarize(self): cell_count = self.get_cell_count_from_gl() @@ -509,7 +486,6 @@ class Project(): cell_links = [f'[{name}]({CELL_URL}{name})' for name in cat_dict['examples']] print(f'|{cat_name} | {" ".join(cell_links)} | {cat_dict["count"]}|') - # Parse the lib, cell and drive strength an OpenLane gate-level Verilog file def get_cell_count_from_gl(self): cell_count = {} @@ -531,7 +507,6 @@ class Project(): cell_count[cell_name] = 1 return (cell_count) - # Load all the json defs and combine into one big dict, keyed by cellname # Needs access to the PDK def create_defs(self): diff --git a/tt_tool.py b/tt_tool.py index b5a66d7..55f7ed3 100755 --- a/tt_tool.py +++ b/tt_tool.py @@ -60,7 +60,7 @@ if __name__ == '__main__': elif args.print_wokwi_id: project.print_wokwi_id() - + if args.create_user_config: project.create_user_config() From c903467adb69730d48d4cd4388d2c63a2dbdf8ec Mon Sep 17 00:00:00 2001 From: matt venn Date: Mon, 13 Mar 2023 18:06:49 +0100 Subject: [PATCH 03/45] ignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5292f45 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*swp +setuptokens.sh +__pycache__ From ada59f5679530807510393e24088694a374bc793 Mon Sep 17 00:00:00 2001 From: matt venn Date: Tue, 14 Mar 2023 13:26:19 +0100 Subject: [PATCH 04/45] tested user project stuff, docs, metrics, working on caravel --- caravel.py | 1 + configure.py | 14 +++++++++++--- project.py | 20 ++++++++++++++++++-- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/caravel.py b/caravel.py index 2692b4f..fe4b69d 100644 --- a/caravel.py +++ b/caravel.py @@ -52,6 +52,7 @@ class CaravelConfig(): if row == 0 and col <= 1: continue + logging.debug(self.num_projects) if num_macros_placed < self.num_projects: if orientation == 'N': # scanchain first diff --git a/configure.py b/configure.py index cd4601f..e801fe6 100755 --- a/configure.py +++ b/configure.py @@ -56,17 +56,21 @@ class Projects(): continue if args.end_at != 0 and index > args.end_at: continue - if self.project_config[index]['status'] == 'disable': - continue + """ try: email = self.git_url_to_email_map[git_url] except KeyError: email = 'none' """ + git_url = self.project_config[index]['url'] filler = self.project_config[index]['fill'] + # not sure if this is a good idea + if self.project_config[index]['status'] == 'disable': + filler = True + # fill projects don't have their own directory, reuse the first project which is always fill if filler: project_dir = os.path.join(os.path.join(self.project_dir, f'{0 :03}')) @@ -123,6 +127,8 @@ class Projects(): all_gds_files = [project.get_macro_gds_filename() for project in self.projects if not project.is_fill()] self.assert_unique(all_gds_files) + logging.info(f"loaded {len(self.projects)} projects") + def assert_unique(self, check): duplicates = [item for item, count in collections.Counter(check).items() if count > 1] if duplicates: @@ -136,8 +142,10 @@ class Projects(): total_physical_cells = 0 max_cells = 0 min_cells = 1000 + max_cell_project = None max_util = 0 min_util = 100 + max_util_project = None languages = {} for project in self.projects: @@ -149,7 +157,7 @@ class Projects(): total_wire_length += int(project.metrics['wire_length']) total_wires_count += int(project.metrics['wires_count']) util = float(project.metrics['OpenDP_Util']) - num_cells = project.check_num_cells() + num_cells = project.get_cell_count_from_synth() total_physical_cells += num_cells yaml_data = project.get_project_doc_yaml() diff --git a/project.py b/project.py index 837ee95..395eb2b 100644 --- a/project.py +++ b/project.py @@ -63,7 +63,7 @@ class Project(): exit(1) def check_num_cells(self): - num_cells = self.get_cell_count_from_gl() + num_cells = self.get_cell_count_from_synth() if self.is_hdl(): if num_cells < 20: logging.warning(f"{self} only has {num_cells} cells") @@ -486,8 +486,24 @@ class Project(): cell_links = [f'[{name}]({CELL_URL}{name})' for name in cat_dict['examples']] print(f'|{cat_name} | {" ".join(cell_links)} | {cat_dict["count"]}|') + # get cell count from synth report + def get_cell_count_from_synth(self): + num_cells = 0 + try: + yosys_report = glob.glob(f'{self.local_dir}/runs/wokwi/reports/synthesis/1-synthesis.*0.stat.rpt')[0] # can't open a file with \ in the path + with open(yosys_report) as fh: + for line in fh.readlines(): + m = re.search(r'Number of cells:\s+(\d+)', line) + if m is not None: + num_cells = int(m.group(1)) + + except IndexError: + logging.warning(f"couldn't open yosys cell report for cell checking {self}") + + return num_cells + # Parse the lib, cell and drive strength an OpenLane gate-level Verilog file - def get_cell_count_from_gl(self): + def get_cell_counts_from_gl(self): cell_count = {} total = 0 gl_files = glob.glob(os.path.join(self.local_dir, 'runs/wokwi/results/final/verilog/gl/*.nl.v')) From efb4dc5959980d89d57d13dd6cf2f416ab9f8573 Mon Sep 17 00:00:00 2001 From: matt venn Date: Tue, 14 Mar 2023 15:13:34 +0100 Subject: [PATCH 05/45] print total non tap or fill cells --- project.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/project.py b/project.py index 395eb2b..50de14a 100644 --- a/project.py +++ b/project.py @@ -444,7 +444,7 @@ class Project(): # Print the summaries def summarize(self): - cell_count = self.get_cell_count_from_gl() + cell_count = self.get_cell_counts_from_gl() script_dir = os.path.dirname(os.path.realpath(__file__)) with open(os.path.join(script_dir, 'categories.json')) as fh: @@ -469,6 +469,7 @@ class Project(): if self.args.print_cell_category: by_category = {} + total = 0 for cell_name in cell_count: cat_index = categories['map'][cell_name] cat_name = categories['categories'][cat_index] @@ -478,6 +479,9 @@ class Project(): else: by_category[cat_name] = {'count' : cell_count[cell_name], 'examples' : [cell_name]} + if cat_name not in ['Fill', 'Tap']: + total += cell_count[cell_name] + print('# Cell usage by Category') print() print('| Category | Cells | Count |') @@ -486,6 +490,8 @@ class Project(): cell_links = [f'[{name}]({CELL_URL}{name})' for name in cat_dict['examples']] print(f'|{cat_name} | {" ".join(cell_links)} | {cat_dict["count"]}|') + print(f'## {total} total cells (excluding fill and tap cells)') + # get cell count from synth report def get_cell_count_from_synth(self): num_cells = 0 @@ -501,7 +507,7 @@ class Project(): logging.warning(f"couldn't open yosys cell report for cell checking {self}") return num_cells - + # Parse the lib, cell and drive strength an OpenLane gate-level Verilog file def get_cell_counts_from_gl(self): cell_count = {} From e882cdf915f167fab4626ca3c04137ddfefe7777 Mon Sep 17 00:00:00 2001 From: matt venn Date: Thu, 16 Mar 2023 09:41:11 +0100 Subject: [PATCH 06/45] working for mpw-9a --- caravel.py | 68 +++++++++++------------------- config.yaml | 1 + configure.py | 6 +-- project.py | 2 +- projects.yaml | 6 +-- upw_config.json | 107 ++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 140 insertions(+), 50 deletions(-) create mode 100644 upw_config.json diff --git a/caravel.py b/caravel.py index fe4b69d..80fbdb1 100644 --- a/caravel.py +++ b/caravel.py @@ -1,4 +1,5 @@ import logging +import json class CaravelConfig(): @@ -86,53 +87,34 @@ class CaravelConfig(): logging.info(f"total user macros placed: {num_macros_placed}") - # macro_power.tcl: extra file for macro power hooks - logging.info("creating macro_power.tcl") - with open("openlane/user_project_wrapper/macro_power.tcl", 'w') as fh: - fh.write('set ::env(FP_PDN_MACRO_HOOKS) "\\\n') - fh.write(" ") - fh.write("scan_controller") - fh.write(" vccd1 vssd1 vccd1 vssd1") - fh.write(", \\\n") - for i in range(self.num_projects): - fh.write(" ") - fh.write(self.projects[i].get_scanchain_instance()) - fh.write(" vccd1 vssd1 vccd1 vssd1, \\\n") - fh.write(" ") - fh.write(self.projects[i].get_macro_instance()) - fh.write(" vccd1 vssd1 vccd1 vssd1") - if i != self.num_projects - 1: - fh.write(", \\\n") - fh.write('"\n') + with open("upw_config.json") as fh: + caravel_config = json.load(fh) - # extra_lef_gds.tcl - lefs = [] - gdss = [] - logging.info("creating extra_lef_gds.tcl") + power_domains = "vccd1 vssd1 vccd1 vssd1" + power_config = [] + logging.info("creating macro hooks") + + for i in range(self.num_projects): + power_config.append(f"{self.projects[i].get_scanchain_instance()} {power_domains},") + power_config.append(f"{self.projects[i].get_macro_instance()} {power_domains},") + power_config.append(f"scan_controller {power_domains}") # no trailing comma + + caravel_config["FP_PDN_MACRO_HOOKS"] = power_config + + logging.info("GDS and LEF") + lef_prefix = "dir::../../lef/" + gds_prefix = "dir::../../gds/" + caravel_config["EXTRA_LEFS"].append(lef_prefix + "scan_controller.lef") + caravel_config["EXTRA_LEFS"].append(lef_prefix + "scanchain.lef") + caravel_config["EXTRA_GDS_FILES"].append(gds_prefix + "scan_controller.gds") + caravel_config["EXTRA_GDS_FILES"].append(gds_prefix + "scanchain.gds") for project in self.projects: if not project.is_fill(): - lefs.append(project.get_macro_lef_filename()) - gdss.append(project.get_macro_gds_filename()) + caravel_config["EXTRA_LEFS"].append(lef_prefix + project.get_macro_lef_filename()) + caravel_config["EXTRA_GDS_FILES"].append(gds_prefix + project.get_macro_gds_filename()) - with open("openlane/user_project_wrapper/extra_lef_gds.tcl", 'w') as fh: - fh.write('set ::env(EXTRA_LEFS) "\\\n') - fh.write("$script_dir/../../lef/scan_controller.lef \\\n") - fh.write("$script_dir/../../lef/scanchain.lef \\\n") - for i, lef in enumerate(lefs): - fh.write("$script_dir/../../lef/{}".format(lef)) - if i != len(lefs) - 1: - fh.write(" \\\n") - else: - fh.write('"\n') - fh.write('set ::env(EXTRA_GDS_FILES) "\\\n') - fh.write("$script_dir/../../gds/scan_controller.gds \\\n") - fh.write("$script_dir/../../gds/scanchain.gds \\\n") - for i, gds in enumerate(gdss): - fh.write("$script_dir/../../gds/{}".format(gds)) - if i != len(gdss) - 1: - fh.write(" \\\n") - else: - fh.write('"\n') + with open("openlane/user_project_wrapper/config.json", 'w') as fh: + json.dump(caravel_config, fh, indent=4) # instantiate inside user_project_wrapper def instantiate(self): diff --git a/config.yaml b/config.yaml index 3aa256d..0a52afc 100644 --- a/config.yaml +++ b/config.yaml @@ -1,4 +1,5 @@ start_date: '2023-03-01' +name: 'tt03' # used for university orders filler_project: https://github.com/TinyTapeout/tt03-test-invert project_dir: 'projects' tmp_dir: '/tmp/tt' diff --git a/configure.py b/configure.py index e801fe6..266fb2a 100755 --- a/configure.py +++ b/configure.py @@ -111,9 +111,9 @@ class Projects(): if filler is False: project.copy_files_to_caravel() - # check all top level module ports are correct - project.check_ports() - project.check_num_cells() + # check all top level module ports are correct + project.check_ports() + project.check_num_cells() self.projects.append(project) diff --git a/project.py b/project.py index 50de14a..b14d341 100644 --- a/project.py +++ b/project.py @@ -198,7 +198,7 @@ class Project(): logging.error(f"{self.git_url} != {git_remote}") exit(1) else: - logging.info("clone") + logging.debug("clone") git.Repo.clone_from(self.git_url, self.local_dir, recursive=True) def pull(self): diff --git a/projects.yaml b/projects.yaml index f024b89..17b971c 100644 --- a/projects.yaml +++ b/projects.yaml @@ -24,12 +24,12 @@ fill: false 6: url: https://github.com/dgarrett/tt03-num-latch - status: disable + status: active fill: false 7: - url: https://github.com/TinyTapeout/tt03-test-invert + url: https://github.com/MichaelBell/tt03-hovalaag status: active - fill: true + fill: false 8: url: https://github.com/TinyTapeout/tt03-test-invert status: active diff --git a/upw_config.json b/upw_config.json new file mode 100644 index 0000000..68f4797 --- /dev/null +++ b/upw_config.json @@ -0,0 +1,107 @@ +{ + "DESIGN_NAME": "user_project_wrapper", + "VERILOG_FILES": [ + "dir::../../verilog/rtl/defines.v", + "dir::../../verilog/rtl/user_project_wrapper.v" + ], + "ROUTING_CORES": 8, + "CLOCK_PERIOD": 25, + "CLOCK_PORT": "wb_clk_i", + "CLOCK_NET": "mprj.clk", + "FP_PDN_MACRO_HOOKS": [], + "MACRO_PLACEMENT_CFG": "dir::macro.cfg", + "PDN_CFG": "dir::pdn_cfg.tcl", + "MAGIC_DEF_LABELS": 0, + "VERILOG_FILES_BLACKBOX": [ + "dir::../../verilog/rtl/defines.v", + "dir::../../verilog/blackbox_project_includes.v" + ], + "EXTRA_LEFS": [], + "EXTRA_GDS_FILES": [], + "BASE_SDC_FILE": "dir::base_user_project_wrapper.sdc", + "RUN_KLAYOUT_XOR": 0, + "RUN_KLAYOUT_DRC": 0, + "RUN_MAGIC_DRC": 0, + "IO_SYNC": 0, + "FP_PDN_CHECK_NODES": 0, + "SYNTH_ELABORATE_ONLY": 1, + "PL_RANDOM_GLB_PLACEMENT": 1, + "PL_RESIZER_DESIGN_OPTIMIZATIONS": 0, + "PL_RESIZER_TIMING_OPTIMIZATIONS": 0, + "PL_RESIZER_BUFFER_INPUT_PORTS": 0, + "FP_PDN_ENABLE_RAILS": 0, + "DIODE_INSERTION_STRATEGY": 0, + "RUN_FILL_INSERTION": 0, + "RUN_TAP_DECAP_INSERTION": 0, + "FP_PDN_VPITCH": 140, + "FP_PDN_VOFFSET": 186, + "FP_PDN_HPITCH": 185, + "FP_PDN_HOFFSET": 130, + "CLOCK_TREE_SYNTH": 0, + "MAGIC_ZEROIZE_ORIGIN": 0, + "FP_SIZING": "absolute", + "RUN_CVC": 0, + "UNIT": 2.4, + "FP_IO_VEXTEND": "expr::2 * $UNIT", + "FP_IO_HEXTEND": "expr::2 * $UNIT", + "FP_IO_VLENGTH": "expr::$UNIT", + "FP_IO_HLENGTH": "expr::$UNIT", + "FP_IO_VTHICKNESS_MULT": 4, + "FP_IO_HTHICKNESS_MULT": 4, + "FP_PDN_CORE_RING": 1, + "FP_PDN_CORE_RING_VWIDTH": 3.1, + "FP_PDN_CORE_RING_HWIDTH": 3.1, + "FP_PDN_CORE_RING_VOFFSET": 12.45, + "FP_PDN_CORE_RING_HOFFSET": 12.45, + "FP_PDN_CORE_RING_VSPACING": 1.7, + "FP_PDN_CORE_RING_HSPACING": 1.7, + "FP_PDN_VWIDTH": 3.1, + "FP_PDN_HWIDTH": 3.1, + "FP_PDN_VSPACING": "expr::(5 * $FP_PDN_CORE_RING_VWIDTH)", + "FP_PDN_HSPACING": "expr::(5 * $FP_PDN_CORE_RING_HWIDTH)", + "VDD_NETS": [ + "vccd1", + "vccd2", + "vdda1", + "vdda2" + ], + "GND_NETS": [ + "vssd1", + "vssd2", + "vssa1", + "vssa2" + ], + "SYNTH_USE_PG_PINS_DEFINES": "USE_POWER_PINS", + "pdk::sky130*": { + "RT_MAX_LAYER": "met4", + "DIE_AREA": "0 0 2920 3520", + "FP_DEF_TEMPLATE": "dir::fixed_dont_change/user_project_wrapper.def", + "scl::sky130_fd_sc_hd": { + "CLOCK_PERIOD": 25 + }, + "scl::sky130_fd_sc_hdll": { + "CLOCK_PERIOD": 10 + }, + "scl::sky130_fd_sc_hs": { + "CLOCK_PERIOD": 8 + }, + "scl::sky130_fd_sc_ls": { + "CLOCK_PERIOD": 10, + "SYNTH_MAX_FANOUT": 5 + }, + "scl::sky130_fd_sc_ms": { + "CLOCK_PERIOD": 10 + } + }, + "pdk::gf180mcuC": { + "STD_CELL_LIBRARY": "gf180mcu_fd_sc_mcu7t5v0", + "FP_PDN_CHECK_NODES": 0, + "FP_PDN_ENABLE_RAILS": 0, + "RT_MAX_LAYER": "Metal4", + "DIE_AREA": "0 0 3000 3000", + "FP_DEF_TEMPLATE": "dir::fixed_dont_change/user_project_wrapper_gf180mcu.def", + "PL_OPENPHYSYN_OPTIMIZATIONS": 0, + "DIODE_INSERTION_STRATEGY": 0, + "MAGIC_WRITE_FULL_LEF": 0 + } +} From 5af18b41784a7a68fc438c3c91cf801a87f7d498 Mon Sep 17 00:00:00 2001 From: matt venn Date: Thu, 16 Mar 2023 10:08:37 +0100 Subject: [PATCH 07/45] handle uni orders --- configure.py | 10 +++++----- orders.py | 31 ++++++++++++++++++++++++++++--- projects.yaml | 20 ++++++++++---------- 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/configure.py b/configure.py index 266fb2a..5d37861 100755 --- a/configure.py +++ b/configure.py @@ -231,6 +231,11 @@ if __name__ == '__main__': ch.setFormatter(log_format) log.addHandler(ch) + if args.update_orders: + orders = Orders(config) + orders.fetch_orders() + orders.update_project_list() + projects = Projects(config, args) docs = Docs(projects.projects, args=args) @@ -242,11 +247,6 @@ if __name__ == '__main__': if args.metrics: projects.build_metrics() - if args.update_orders: - orders = Orders(config) - orders.fetch_orders() - orders.update_project_list() - if args.update_caravel: caravel.create_macro_config() caravel.instantiate() diff --git a/orders.py b/orders.py index 1b0c3b8..c5df24b 100644 --- a/orders.py +++ b/orders.py @@ -1,6 +1,9 @@ import stripe, os from datetime import datetime import yaml +import requests +import pytz +import logging class Orders(): @@ -12,8 +15,30 @@ class Orders(): self.orders = [] def fetch_orders(self): + orders = self.fetch_stripe_orders() + self.fetch_university_orders() + self.orders = sorted(orders, key=lambda d: d['time']) + + def fetch_university_orders(self): + name = self.config['name'] + url = f'https://app.tinytapeout.com/api/submissions?shuttle={name}' + r = requests.get(url) + if r.status_code != 200: + logging.warning("couldn't download {}".format(url)) + exit(1) + + orders = [] + for item in r.json()['items']: + orders.append({ + 'git_url' : item['repo'], + 'email' : None, + 'time' : datetime.strptime(item['time'], '%Y-%m-%dT%H:%M:%S.%f%z').replace(tzinfo=pytz.UTC) + }) + return orders + + def fetch_stripe_orders(self): start_id = None after_open_date = True + orders = [] while after_open_date: checkouts = stripe.checkout.Session.list(limit=10, starting_after=start_id) @@ -29,15 +54,15 @@ class Orders(): order = { 'git_url' : git_url, 'email' : checkout['customer_details']['email'], + 'time' : datetime.fromtimestamp(checkout['created']).replace(tzinfo=pytz.UTC), } - self.orders.append(order) + orders.append(order) # pagination start_id = checkout['id'] # put in date order - self.orders.reverse() - return self.orders + return orders def update_project_list(self): project_list = {} diff --git a/projects.yaml b/projects.yaml index 17b971c..b754ed1 100644 --- a/projects.yaml +++ b/projects.yaml @@ -3,15 +3,15 @@ status: active fill: false 1: - url: https://github.com/WallieEverest/tt03 + url: https://github.com/MichaelBell/tt03-hovalaag status: active fill: false 2: - url: https://github.com/icegoat9/tinytapeout03-7seglife + url: https://github.com/dgarrett/tt03-num-latch status: active fill: false 3: - url: https://github.com/meiniKi/tt03-another-piece-of-pi + url: https://github.com/KolosKoblasz/tt03-knight_rider status: active fill: false 4: @@ -19,25 +19,25 @@ status: active fill: false 5: - url: https://github.com/KolosKoblasz/tt03-knight_rider + url: https://github.com/meiniKi/tt03-another-piece-of-pi status: active fill: false 6: - url: https://github.com/dgarrett/tt03-num-latch + url: https://github.com/icegoat9/tinytapeout03-7seglife status: active fill: false 7: - url: https://github.com/MichaelBell/tt03-hovalaag + url: https://github.com/WallieEverest/tt03 status: active fill: false 8: - url: https://github.com/TinyTapeout/tt03-test-invert + url: https://github.com/yannickreiss/TT3_Memory status: active - fill: true + fill: false 9: - url: https://github.com/TinyTapeout/tt03-test-invert + url: https://github.com/yannickreiss/TT3_KS-Signal status: active - fill: true + fill: false 10: url: https://github.com/TinyTapeout/tt03-test-invert status: active From 9e6891f111c008373bdb539467ad0379f3d6e10e Mon Sep 17 00:00:00 2001 From: matt venn Date: Thu, 16 Mar 2023 10:16:07 +0100 Subject: [PATCH 08/45] tt 03 update to docs --- docs/README_init.md | 8 ++++---- docs/doc_header.md | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/README_init.md b/docs/README_init.md index fad6438..d665d14 100644 --- a/docs/README_init.md +++ b/docs/README_init.md @@ -1,8 +1,8 @@ -[![tinytapeout](https://github.com/tinytapeout/tinytapeout-02/actions/workflows/gds.yaml/badge.svg)](https://github.com/tinytapeout/tinytapeout-02/actions/workflows/gds.yaml) -[![verification](https://github.com/tinytapeout/tinytapeout-02/actions/workflows/verification.yaml/badge.svg)](https://github.com/tinytapeout/tinytapeout-02/actions/workflows/verification.yaml) -[![tt_datasheet](https://github.com/tinytapeout/tinytapeout-02/actions/workflows/tt_datasheet.yaml/badge.svg)](https://github.com/tinytapeout/tinytapeout-02/actions/workflows/tt_datasheet.yaml) +[![tinytapeout](https://github.com/tinytapeout/tinytapeout-03/actions/workflows/gds.yaml/badge.svg)](https://github.com/tinytapeout/tinytapeout-03/actions/workflows/gds.yaml) +[![verification](https://github.com/tinytapeout/tinytapeout-03/actions/workflows/verification.yaml/badge.svg)](https://github.com/tinytapeout/tinytapeout-03/actions/workflows/verification.yaml) +[![tt_datasheet](https://github.com/tinytapeout/tinytapeout-03/actions/workflows/tt_datasheet.yaml/badge.svg)](https://github.com/tinytapeout/tinytapeout-03/actions/workflows/tt_datasheet.yaml) -# TinyTapeout +# TinyTapeout 03 * Visit https://tinytapeout.com for more information on the project and how to get involved. * Read [INFO](INFO.md) for how the project is built and technical project notes. diff --git a/docs/doc_header.md b/docs/doc_header.md index e1d375b..aad5897 100644 --- a/docs/doc_header.md +++ b/docs/doc_header.md @@ -1,7 +1,7 @@ --- title: | ![](ttlogo.png){width=15cm} - Tiny Tapeout 02 Datasheet + Tiny Tapeout 03 Datasheet subtitle: Project Repository [https://github.com/TinyTapeout/tinytapeout-02](https://github.com/TinyTapeout/tinytapeout-02) documentclass: scrartcl date: \today{} From b5745ccf4d4127b164643579838e55df857c89e5 Mon Sep 17 00:00:00 2001 From: matt venn Date: Thu, 16 Mar 2023 15:41:54 +0100 Subject: [PATCH 09/45] template files and paths --- caravel.py | 8 +- caravel_template/caravel.lyp | 8226 +++++++++++++++++ caravel_template/dump_pic.rb | 1 + caravel_template/klayoutrc | 247 + .../upw_config.json | 0 caravel_template/upw_post.v | 4 + caravel_template/upw_pre.v | 84 + configure.py | 10 - documentation.py | 25 +- 9 files changed, 8581 insertions(+), 24 deletions(-) create mode 100644 caravel_template/caravel.lyp create mode 100644 caravel_template/dump_pic.rb create mode 100644 caravel_template/klayoutrc rename upw_config.json => caravel_template/upw_config.json (100%) create mode 100644 caravel_template/upw_post.v create mode 100644 caravel_template/upw_pre.v diff --git a/caravel.py b/caravel.py index 80fbdb1..a0b3519 100644 --- a/caravel.py +++ b/caravel.py @@ -1,5 +1,6 @@ import logging import json +import os class CaravelConfig(): @@ -7,6 +8,7 @@ class CaravelConfig(): def __init__(self, projects, num_projects): self.projects = projects self.num_projects = num_projects + self.script_dir = os.path.dirname(os.path.realpath(__file__)) # create macro file & positions, power hooks def create_macro_config(self): @@ -87,7 +89,7 @@ class CaravelConfig(): logging.info(f"total user macros placed: {num_macros_placed}") - with open("upw_config.json") as fh: + with open(os.path.join(self.script_dir, 'caravel_template', 'upw_config.json')) as fh: caravel_config = json.load(fh) power_domains = "vccd1 vssd1 vccd1 vssd1" @@ -215,12 +217,12 @@ class CaravelConfig(): # Write to file with open('verilog/rtl/user_project_wrapper.v', 'w') as fh: # Insert the Caravel preamble - with open("upw_pre.v", "r") as fh_pre: + with open(os.path.join(self.script_dir, 'caravel_template', 'upw_pre.v')) as fh_pre: fh.write(fh_pre.read()) # Indent, join, and insert the module instances fh.write("\n".join([(" " + x).rstrip() for x in body])) # Insert the Caravel postamble - with open("upw_post.v", "r") as fh_post: + with open(os.path.join(self.script_dir, 'caravel_template', 'upw_post.v')) as fh_post: fh.write(fh_post.read()) # build the blackbox_project_includes.v file - used for blackboxing when building the GDS diff --git a/caravel_template/caravel.lyp b/caravel_template/caravel.lyp new file mode 100644 index 0000000..44b67d1 --- /dev/null +++ b/caravel_template/caravel.lyp @@ -0,0 +1,8226 @@ + + + + #ccccd9 + #ccccd9 + 0 + 0 + C7 + C0 + true + false + false + 1 + false + false + 0 + prBoundary.boundary - 235/4 + 235/4@1 + + + #00ffff + #00ffff + 0 + 0 + C20 + C0 + true + true + false + 1 + false + false + 0 + pwell.drawing - 64/44 + 64/44@1 + + + #ffbff2 + #ffbff2 + 0 + 0 + C38 + C0 + true + false + false + 1 + false + false + 0 + pwell.pin - 122/16 + 122/16@1 + + + #9900e6 + #9900e6 + 0 + 0 + C1 + C0 + true + false + false + 1 + false + false + 0 + pwell.label - 64/59 + 64/59@1 + + + #ffbff2 + #ffbff2 + 0 + 0 + I1 + C0 + true + true + false + 1 + false + false + 0 + pwell.res - 64/13 + 64/13@1 + + + #ffbff2 + #ffbff2 + 0 + 0 + C1 + C0 + true + true + false + 1 + false + false + 0 + pwell.cut - 64/14 + 64/14@1 + + + #ffffff + #96c8ff + 0 + 0 + C38 + C0 + true + true + false + 1 + false + false + 0 + pwelliso.pin - 44/16 + 44/16@1 + + + #9900e6 + #9900e6 + 0 + 0 + C1 + C0 + true + true + false + 1 + false + false + 0 + pwelliso.label - 44/5 + 44/5@1 + + + #00cc66 + #00cc66 + 0 + 0 + I0 + C0 + true + false + false + 1 + false + false + 0 + nwell.drawing - 64/20 + 64/20@1 + + + #ff00ff + #ff00ff + 0 + 0 + C2 + C0 + true + true + false + 1 + false + false + 0 + nwell.net - 84/23 + 84/23@1 + + + #268c6b + #268c6b + 0 + 0 + C36 + C0 + true + false + false + 1 + false + false + 0 + nwell.pin - 64/16 + 64/16@1 + + + #333399 + #333399 + 0 + 0 + C1 + C0 + true + false + false + 1 + false + false + 0 + nwell.label - 64/5 + 64/5@1 + + + #c8ffc8 + #c8ffc8 + 0 + 0 + C47 + C0 + true + true + false + 1 + false + false + 0 + dnwell.drawing - 64/18 + 64/18@1 + + + #00ffff + #00ffff + 0 + 0 + C6 + C0 + true + true + false + 1 + false + false + 0 + vhvi.drawing - 74/21 + 74/21@1 + + + #00ff00 + #00ff00 + 0 + 0 + C34 + C0 + true + false + false + 1 + false + false + 0 + diff.drawing - 65/20 + 65/20@1 + + + #00ff00 + #00ff00 + 0 + 0 + I1 + C0 + true + true + false + 1 + false + false + 0 + diff.res - 65/13 + 65/13@1 + + + #00ff00 + #00ff00 + 0 + 0 + C1 + C0 + true + false + false + 1 + false + false + 0 + diff.cut - 65/14 + 65/14@1 + + + #268c6b + #268c6b + 0 + 0 + C36 + C0 + false + false + false + 1 + false + false + 0 + diff.pin - 65/16 + 65/16@1 + + + #c8ffc8 + #c8ffc8 + 0 + 0 + C1 + C0 + false + true + false + 1 + false + false + 0 + diff.label - 65/6 + 65/6@1 + + + #00ff00 + #00ff00 + 0 + 0 + C5 + C0 + false + true + false + 1 + false + false + 0 + diff.net - 65/23 + 65/23@1 + + + #00ff00 + #00ff00 + 0 + 0 + C0 + C0 + false + true + false + 1 + false + false + 0 + diff.boundary - 65/4 + 65/4@1 + + + #9900e6 + #9900e6 + 0 + 0 + C36 + C0 + true + true + false + 1 + false + false + 0 + diff.hv - 65/8 + 65/8@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C34 + C0 + true + false + false + 1 + false + false + 0 + tap.drawing - 65/44 + 65/44@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C6 + C0 + false + true + false + 1 + false + false + 0 + tap.pin - 65/48 + 65/48@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C5 + C0 + false + true + false + 1 + false + false + 0 + tap.net - 65/41 + 65/41@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C0 + C0 + false + true + false + 1 + false + false + 0 + tap.boundary - 65/60 + 65/60@1 + + + #fff464 + #fff464 + 0 + 0 + C1 + C0 + false + true + false + 1 + false + false + 0 + tap.label - 65/5 + 65/5@1 + + + #00ffff + #00ffff + 0 + 0 + I2 + C0 + true + false + false + 1 + false + false + 0 + psdm.drawing - 94/20 + 94/20@1 + + + #afff80 + #afff80 + 0 + 0 + I2 + C0 + true + false + false + 1 + false + false + 0 + nsdm.drawing - 93/44 + 93/44@1 + + + #ff9d9d + #ff9d9d + 0 + 0 + I5 + C0 + true + false + false + 1 + false + false + 0 + poly.drawing - 66/20 + 66/20@1 + + + #ff8000 + #ff8000 + 0 + 0 + C38 + C0 + true + false + false + 1 + false + false + 0 + poly.pin - 66/16 + 66/16@1 + + + #ff0000 + #ff0000 + 0 + 0 + I1 + C0 + true + true + false + 1 + false + false + 0 + poly.res - 66/13 + 66/13@1 + + + #ff0000 + #ff0000 + 0 + 0 + C1 + C0 + true + false + false + 1 + false + false + 0 + poly.cut - 66/14 + 66/14@1 + + + #ff0000 + #ff0000 + 0 + 0 + I1 + C0 + true + true + false + 1 + false + false + 0 + poly.gate - 66/9 + 66/9@1 + + + #ffafaf + #ffafaf + 0 + 0 + C1 + C0 + true + true + false + 1 + false + false + 0 + poly.label - 66/5 + 66/5@1 + + + #ff0000 + #ff0000 + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + poly.boundary - 66/4 + 66/4@1 + + + #ff0000 + #ff0000 + 0 + 0 + C1 + C0 + true + true + false + 1 + false + false + 0 + poly.probe - 66/25 + 66/25@1 + + + #ff0000 + #ff0000 + 0 + 0 + C36 + C0 + true + false + false + 1 + false + false + 0 + poly.short - 66/15 + 66/15@1 + + + #ff0000 + #ff0000 + 0 + 0 + C5 + C0 + true + true + false + 1 + false + false + 0 + poly.net - 66/23 + 66/23@1 + + + #ff0000 + #ff0000 + 0 + 0 + C1 + C0 + true + false + false + 1 + false + false + 0 + poly.model - 66/83 + 66/83@1 + + + #00cc66 + #00cc66 + 0 + 0 + C50 + C0 + true + true + false + 1 + false + false + 0 + ldntm.drawing - 11/44 + 11/44@1 + + + #96c8ff + #ffffff + 0 + 0 + C15 + C0 + true + true + false + 1 + false + false + 0 + lvtn.drawing - 125/44 + 125/44@1 + + + #ff8000 + #ffffff + 0 + 0 + C14 + C0 + true + false + false + 1 + false + false + 0 + hvtp.drawing - 78/44 + 78/44@1 + + + #ff0000 + #e61f0d + 0 + 0 + C14 + C0 + false + true + false + 1 + false + false + 0 + hvtr.drawing - 18/20 + 18/20@1 + + + #9900e6 + #9900e6 + 0 + 0 + C41 + C0 + true + true + false + 1 + false + false + 0 + tunm.drawing - 80/20 + 80/20@1 + + + #008050 + #008050 + 0 + 0 + I0 + C0 + true + false + false + 1 + false + false + 0 + licon1.drawing - 66/44 + 66/44@1 + + + #ffffcc + #ffffcc + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + licon1.boundary - 66/60 + 66/60@1 + + + #ffe6bf + #c8ffff + 0 + 0 + C6 + C0 + false + true + false + 1 + false + false + 0 + licon1.pin - 66/58 + 66/58@1 + + + #ffffcc + #ffffcc + 0 + 0 + C5 + C0 + true + true + false + 1 + false + false + 0 + licon1.net - 66/41 + 66/41@1 + + + #bf4026 + #bf4026 + 0 + 0 + C0 + C0 + true + false + false + 1 + false + false + 0 + npc.drawing - 95/20 + 95/20@1 + + + #ff9d9d + #ff9d9d + 16 + 16 + I0 + C0 + true + false + false + 1 + false + false + 0 + li1.drawing - 67/20 + 67/20@1 + + + #bf4026 + #bf4026 + 0 + 0 + I0 + C0 + true + false + false + 1 + false + false + 0 + li1.pin - 67/16 + 67/16@1 + + + #ffe6bf + #ffe6bf + 0 + 0 + I1 + C0 + false + true + false + 1 + false + false + 0 + li1.res - 67/13 + 67/13@1 + + + #ffe6bf + #ffe6bf + 0 + 0 + C1 + C0 + false + true + false + 1 + false + false + 0 + li1.cut - 67/14 + 67/14@1 + + + #bf4026 + #bf4026 + 0 + 0 + C1 + C0 + true + false + false + 1 + false + false + 0 + li1.label - 67/5 + 67/5@1 + + + #ffe6bf + #ffe6bf + 0 + 0 + C53 + C0 + true + true + false + 1 + false + false + 0 + li1.net - 67/23 + 67/23@1 + + + #d9e6ff + #d9e6ff + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + li1.boundary - 67/4 + 67/4@1 + + + #bf4026 + #bf4026 + 0 + 0 + C53 + C0 + true + true + false + 1 + false + false + 0 + li1.blockage - 67/10 + 67/10@1 + + + #ffe6bf + #ffe6bf + 0 + 0 + C36 + C0 + true + false + false + 1 + false + false + 0 + li1.short - 67/15 + 67/15@1 + + + #ffe6bf + #ffe6bf + 0 + 0 + C1 + C0 + true + true + false + 1 + false + false + 0 + li1.probe - 67/25 + 67/25@1 + + + #ccccd9 + #ccccd9 + 0 + 0 + C1 + C0 + true + false + false + 1 + false + false + 0 + mcon.drawing - 67/44 + 67/44@1 + + + #ccccd9 + #ccccd9 + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + mcon.boundary - 67/60 + 67/60@1 + + + #ffffcc + #d9e6ff + 0 + 0 + C6 + C0 + false + true + false + 1 + false + false + 0 + mcon.pin - 67/48 + 67/48@1 + + + #ccccd9 + #ccccd9 + 0 + 0 + C5 + C0 + true + true + false + 1 + false + false + 0 + mcon.net - 67/41 + 67/41@1 + + + #80a8ff + #80a8ff + 0 + 0 + I2 + C0 + true + true + false + 1 + false + false + 0 + met1.drawing - 68/20 + 68/20@1 + + + #0000ff + #0000ff + 0 + 0 + I1 + C0 + false + true + false + 1 + false + false + 0 + met1.res - 68/13 + 68/13@1 + + + #0000ff + #0000ff + 0 + 0 + C1 + C0 + false + true + false + 1 + false + false + 0 + met1.cut - 68/14 + 68/14@1 + + + #5e00e6 + #5e00e6 + 0 + 0 + C6 + C0 + true + false + false + 1 + false + false + 0 + met1.pin - 68/16 + 68/16@1 + + + #96c8ff + #96c8ff + 0 + 0 + C1 + C0 + true + false + false + 1 + false + false + 0 + met1.label - 68/5 + 68/5@1 + + + #5e00e6 + #5e00e6 + 0 + 0 + C5 + C0 + true + true + false + 1 + false + false + 0 + met1.net - 68/23 + 68/23@1 + + + #0000ff + #0000ff + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + met1.boundary - 68/4 + 68/4@1 + + + #0000ff + #0000ff + 0 + 0 + C53 + C0 + true + true + false + 1 + false + false + 0 + met1.blockage - 68/10 + 68/10@1 + + + #0000ff + #0000ff + 0 + 0 + C36 + C0 + true + false + false + 1 + false + false + 0 + met1.short - 68/15 + 68/15@1 + + + #0000ff + #0000ff + 0 + 0 + C1 + C0 + true + true + false + 1 + false + false + 0 + met1.probe - 68/25 + 68/25@1 + + + #0000ff + #0000ff + 0 + 0 + C25 + C0 + false + true + false + 1 + false + false + 0 + met1.option1 - 68/32 + 68/32@1 + + + #0000ff + #0000ff + 0 + 0 + C26 + C0 + false + true + false + 1 + false + false + 0 + met1.option2 - 68/33 + 68/33@1 + + + #0000ff + #0000ff + 0 + 0 + C27 + C0 + false + true + false + 1 + false + false + 0 + met1.option3 - 68/34 + 68/34@1 + + + #0000ff + #0000ff + 0 + 0 + C28 + C0 + false + true + false + 1 + false + false + 0 + met1.option4 - 68/35 + 68/35@1 + + + #0000ff + #0000ff + 0 + 0 + C29 + C0 + false + true + false + 1 + false + false + 0 + met1.option5 - 68/36 + 68/36@1 + + + #0000ff + #0000ff + 0 + 0 + C30 + C0 + false + true + false + 1 + false + false + 0 + met1.option6 - 68/37 + 68/37@1 + + + #0000ff + #0000ff + 0 + 0 + C31 + C0 + false + true + false + 1 + false + false + 0 + met1.option7 - 68/38 + 68/38@1 + + + #0000ff + #0000ff + 0 + 0 + C32 + C0 + false + true + false + 1 + false + false + 0 + met1.option8 - 68/39 + 68/39@1 + + + #5e00e6 + #5e00e6 + 0 + 0 + C1 + C0 + true + false + false + 1 + false + false + 0 + via.drawing - 68/44 + 68/44@1 + + + #5e00e6 + #5e00e6 + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + via.boundary - 68/60 + 68/60@1 + + + #5e00e6 + #5e00e6 + 0 + 0 + C5 + C0 + true + true + false + 1 + false + false + 0 + via.net - 68/41 + 68/41@1 + + + #ae7dff + #ae7dff + 0 + 0 + C6 + C0 + false + true + false + 1 + false + false + 0 + via.pin - 68/58 + 68/58@1 + + + #c080ff + #c080ff + 0 + 0 + I11 + C0 + true + true + false + 1 + false + false + 0 + met2.drawing - 69/20 + 69/20@1 + + + #ff00ff + #ff00ff + 0 + 0 + I1 + C0 + false + true + false + 1 + false + false + 0 + met2.res - 69/13 + 69/13@1 + + + #ff00ff + #ff00ff + 0 + 0 + C1 + C0 + false + true + false + 1 + false + false + 0 + met2.cut - 69/14 + 69/14@1 + + + #ff00ff + #ff00ff + 0 + 0 + C45 + C0 + true + false + false + 1 + false + false + 0 + met2.pin - 69/16 + 69/16@1 + + + #ffc8ff + #ffc8ff + 0 + 0 + C1 + C0 + true + false + false + 1 + false + false + 0 + met2.label - 69/5 + 69/5@1 + + + #ff00ff + #ff00ff + 0 + 0 + C5 + C0 + true + true + false + 1 + false + false + 0 + met2.net - 69/23 + 69/23@1 + + + #ff00ff + #ff00ff + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + met2.boundary - 69/4 + 69/4@1 + + + #ff00ff + #ff00ff + 0 + 0 + C53 + C0 + true + true + false + 1 + false + false + 0 + met2.blockage - 69/10 + 69/10@1 + + + #ff00ff + #ff00ff + 0 + 0 + C36 + C0 + true + false + false + 1 + false + false + 0 + met2.short - 69/15 + 69/15@1 + + + #ff00ff + #ff00ff + 0 + 0 + C1 + C0 + true + true + false + 1 + false + false + 0 + met2.probe - 69/25 + 69/25@1 + + + #ff00ff + #ff00ff + 0 + 0 + C25 + C0 + false + true + false + 1 + false + false + 0 + met2.option1 - 69/32 + 69/32@1 + + + #ff00ff + #ff00ff + 0 + 0 + C26 + C0 + false + true + false + 1 + false + false + 0 + met2.option2 - 69/33 + 69/33@1 + + + #ff00ff + #ff00ff + 0 + 0 + C27 + C0 + false + true + false + 1 + false + false + 0 + met2.option3 - 69/34 + 69/34@1 + + + #ff00ff + #ff00ff + 0 + 0 + C28 + C0 + false + true + false + 1 + false + false + 0 + met2.option4 - 69/35 + 69/35@1 + + + #ff00ff + #ff00ff + 0 + 0 + C29 + C0 + false + true + false + 1 + false + false + 0 + met2.option5 - 69/36 + 69/36@1 + + + #ff00ff + #ff00ff + 0 + 0 + C30 + C0 + false + true + false + 1 + false + false + 0 + met2.option6 - 69/37 + 69/37@1 + + + #ff00ff + #ff00ff + 0 + 0 + C31 + C0 + false + true + false + 1 + false + false + 0 + met2.option7 - 69/38 + 69/38@1 + + + #ff00ff + #ff00ff + 0 + 0 + C32 + C0 + false + true + false + 1 + false + false + 0 + met2.option8 - 69/39 + 69/39@1 + + + #ff8000 + #ff8000 + 0 + 0 + I1 + C0 + true + true + false + 3 + false + false + 0 + via2.drawing - 69/44 + 69/44@1 + + + #ff8000 + #ff8000 + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + via2.boundary - 69/60 + 69/60@1 + + + #ff8000 + #ff8000 + 0 + 0 + C6 + C0 + false + true + false + 1 + false + false + 0 + via2.pin - 69/58 + 69/58@1 + + + #ff8000 + #ff8000 + 0 + 0 + C5 + C0 + true + true + false + 1 + false + false + 0 + via2.net - 69/41 + 69/41@1 + + + #8000ff + #8000ff + 0 + 0 + I5 + C0 + true + true + false + 1 + false + false + 0 + met3.drawing - 70/20 + 70/20@1 + + + #00ffff + #00ffff + 0 + 0 + I1 + C0 + false + true + false + 1 + false + false + 0 + met3.res - 70/13 + 70/13@1 + + + #00ffff + #00ffff + 0 + 0 + C1 + C0 + false + true + false + 1 + false + false + 0 + met3.cut - 70/14 + 70/14@1 + + + #00ffff + #00ffff + 0 + 0 + C34 + C0 + true + false + false + 1 + false + false + 0 + met3.pin - 70/16 + 70/16@1 + + + #c8ffff + #c8ffff + 0 + 0 + C1 + C0 + true + false + false + 1 + false + false + 0 + met3.label - 70/5 + 70/5@1 + + + #00ffff + #00ffff + 0 + 0 + C5 + C0 + true + true + false + 1 + false + false + 0 + met3.net - 70/23 + 70/23@1 + + + #00ffff + #00ffff + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + met3.boundary - 70/4 + 70/4@1 + + + #00ffff + #00ffff + 0 + 0 + C53 + C0 + true + true + false + 1 + false + false + 0 + met3.blockage - 70/10 + 70/10@1 + + + #00ffff + #00ffff + 0 + 0 + C36 + C0 + true + true + false + 1 + false + false + 0 + met3.short - 70/15 + 70/15@1 + + + #00ffff + #00ffff + 0 + 0 + I1 + C0 + false + true + false + 1 + false + false + 0 + met3.fuse - 70/17 + 70/17@1 + + + #00ffff + #00ffff + 0 + 0 + C1 + C0 + true + true + false + 1 + false + false + 0 + met3.probe - 70/25 + 70/25@1 + + + #00ffff + #00ffff + 0 + 0 + C25 + C0 + false + true + false + 1 + false + false + 0 + met3.option1 - 70/32 + 70/32@1 + + + #00ffff + #00ffff + 0 + 0 + C26 + C0 + false + true + false + 1 + false + false + 0 + met3.option2 - 70/33 + 70/33@1 + + + #00ffff + #00ffff + 0 + 0 + C27 + C0 + false + true + false + 1 + false + false + 0 + met3.option3 - 70/34 + 70/34@1 + + + #00ffff + #00ffff + 0 + 0 + C28 + C0 + false + true + false + 1 + false + false + 0 + met3.option4 - 70/35 + 70/35@1 + + + #00ffff + #00ffff + 0 + 0 + C29 + C0 + false + true + false + 1 + false + false + 0 + met3.option5 - 70/36 + 70/36@1 + + + #00ffff + #00ffff + 0 + 0 + C30 + C0 + false + true + false + 1 + false + false + 0 + met3.option6 - 70/37 + 70/37@1 + + + #00ffff + #00ffff + 0 + 0 + C31 + C0 + false + true + false + 1 + false + false + 0 + met3.option7 - 70/38 + 70/38@1 + + + #00ffff + #00ffff + 0 + 0 + C32 + C0 + false + true + false + 1 + false + false + 0 + met3.option8 - 70/39 + 70/39@1 + + + #268c6b + #268c6b + 0 + 0 + I1 + C0 + true + false + false + 3 + false + false + 0 + via3.drawing - 70/44 + 70/44@1 + + + #268c6b + #268c6b + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + via3.boundary - 70/60 + 70/60@1 + + + #268c6b + #268c6b + 0 + 0 + C6 + C0 + false + true + false + 1 + false + false + 0 + via3.pin - 70/48 + 70/48@1 + + + #268c6b + #268c6b + 0 + 0 + C5 + C0 + true + true + false + 1 + false + false + 0 + via3.net - 70/41 + 70/41@1 + + + #c080ff + #c080ff + 0 + 0 + I2 + C0 + true + false + false + 1 + false + false + 0 + met4.drawing - 71/20 + 71/20@1 + + + #5e00e6 + #5e00e6 + 0 + 0 + I1 + C0 + false + true + false + 1 + false + false + 0 + met4.res - 71/13 + 71/13@1 + + + #5e00e6 + #5e00e6 + 0 + 0 + C1 + C0 + false + true + false + 1 + false + false + 0 + met4.cut - 71/14 + 71/14@1 + + + #5e00e6 + #5e00e6 + 0 + 0 + C34 + C0 + true + false + false + 1 + false + false + 0 + met4.pin - 71/16 + 71/16@1 + + + #ae7dff + #ae7dff + 0 + 0 + C1 + C0 + true + false + false + 1 + false + false + 0 + met4.label - 71/5 + 71/5@1 + + + #5e00e6 + #5e00e6 + 0 + 0 + C5 + C0 + true + true + false + 1 + false + false + 0 + met4.net - 71/23 + 71/23@1 + + + #5e00e6 + #5e00e6 + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + met4.boundary - 71/4 + 71/4@1 + + + #5e00e6 + #5e00e6 + 0 + 0 + C53 + C0 + true + true + false + 1 + false + false + 0 + met4.blockage - 71/10 + 71/10@1 + + + #5e00e6 + #5e00e6 + 0 + 0 + C36 + C0 + true + false + false + 1 + false + false + 0 + met4.short - 71/15 + 71/15@1 + + + #5e00e6 + #5e00e6 + 0 + 0 + I1 + C0 + false + true + false + 1 + false + false + 0 + met4.fuse - 71/17 + 71/17@1 + + + #5e00e6 + #5e00e6 + 0 + 0 + C1 + C0 + true + false + false + 1 + false + false + 0 + met4.probe - 71/25 + 71/25@1 + + + #5e00e6 + #5e00e6 + 0 + 0 + C25 + C0 + false + true + false + 1 + false + false + 0 + met4.option1 - 71/32 + 71/32@1 + + + #5e00e6 + #5e00e6 + 0 + 0 + C26 + C0 + false + true + false + 1 + false + false + 0 + met4.option2 - 71/33 + 71/33@1 + + + #5e00e6 + #5e00e6 + 0 + 0 + C27 + C0 + false + true + false + 1 + false + false + 0 + met4.option3 - 71/34 + 71/34@1 + + + #5e00e6 + #5e00e6 + 0 + 0 + C28 + C0 + false + true + false + 1 + false + false + 0 + met4.option4 - 71/35 + 71/35@1 + + + #5e00e6 + #5e00e6 + 0 + 0 + C29 + C0 + false + true + false + 1 + false + false + 0 + met4.option5 - 71/36 + 71/36@1 + + + #5e00e6 + #5e00e6 + 0 + 0 + C30 + C0 + false + true + false + 1 + false + false + 0 + met4.option6 - 71/37 + 71/37@1 + + + #5e00e6 + #5e00e6 + 0 + 0 + C31 + C0 + false + true + false + 1 + false + false + 0 + met4.option7 - 71/38 + 71/38@1 + + + #5e00e6 + #5e00e6 + 0 + 0 + C32 + C0 + false + true + false + 1 + false + false + 0 + met4.option8 - 71/39 + 71/39@1 + + + #500080 + #500080 + 0 + 0 + I0 + + true + false + false + 3 + false + false + 0 + via4.drawing - 71/44 + 71/44@1 + + + #ffff00 + #ffff00 + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + via4.boundary - 71/60 + 71/60@1 + + + #ffff00 + #ffff00 + 0 + 0 + C6 + C0 + false + true + false + 1 + false + false + 0 + via4.pin - 71/48 + 71/48@1 + + + #ffff00 + #ffff00 + 0 + 0 + C5 + C0 + true + true + false + 1 + false + false + 0 + via4.net - 71/41 + 71/41@1 + + + #004080 + #004080 + 0 + 0 + I9 + C0 + true + false + false + 1 + false + false + 0 + met5.drawing - 72/20 + 72/20@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + I1 + C0 + false + true + false + 1 + false + false + 0 + met5.res - 72/13 + 72/13@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C1 + C0 + false + true + false + 1 + false + false + 0 + met5.cut - 72/14 + 72/14@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C34 + C0 + true + false + false + 1 + false + false + 0 + met5.pin - 72/16 + 72/16@1 + + + #fff464 + #fff464 + 0 + 0 + C1 + C0 + true + false + false + 1 + false + false + 0 + met5.label - 72/5 + 72/5@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C5 + C0 + true + true + false + 1 + false + false + 0 + met5.net - 72/23 + 72/23@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + met5.boundary - 72/4 + 72/4@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C53 + C0 + true + true + false + 1 + false + false + 0 + met5.blockage - 72/10 + 72/10@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C36 + C0 + true + false + false + 1 + false + false + 0 + met5.short - 72/15 + 72/15@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + I1 + C0 + false + true + false + 1 + false + false + 0 + met5.fuse - 72/17 + 72/17@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C1 + C0 + true + true + false + 1 + false + false + 0 + met5.probe - 72/25 + 72/25@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C25 + C0 + false + true + false + 1 + false + false + 0 + met5.option1 - 72/32 + 72/32@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C26 + C0 + false + true + false + 1 + false + false + 0 + met5.option2 - 72/33 + 72/33@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C27 + C0 + false + true + false + 1 + false + false + 0 + met5.option3 - 72/34 + 72/34@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C28 + C0 + false + true + false + 1 + false + false + 0 + met5.option4 - 72/35 + 72/35@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C29 + C0 + false + true + false + 1 + false + false + 0 + met5.option5 - 72/36 + 72/36@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C30 + C0 + false + true + false + 1 + false + false + 0 + met5.option6 - 72/37 + 72/37@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C31 + C0 + false + true + false + 1 + false + false + 0 + met5.option7 - 72/38 + 72/38@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C32 + C0 + false + true + false + 1 + false + false + 0 + met5.option8 - 72/39 + 72/39@1 + + + #00cc66 + #00cc66 + 0 + 0 + C50 + C0 + true + true + false + 1 + false + false + 0 + nsm.drawing - 61/20 + 61/20@1 + + + #ffffcc + #ffffcc + 0 + 0 + C20 + C0 + true + true + false + 1 + false + false + 0 + pad.drawing - 76/20 + 76/20@1 + + + #d9e6ff + #d9e6ff + 0 + 0 + C1 + C0 + true + true + false + 1 + false + false + 0 + pad.label - 76/5 + 76/5@1 + + + #d9e6ff + #d9e6ff + 0 + 0 + C34 + C0 + true + true + false + 1 + false + false + 0 + pad.pin - 76/16 + 76/16@1 + + + #ff00ff + #ff00ff + 0 + 0 + I1 + C0 + true + true + false + 1 + false + false + 0 + pnp.drawing - 82/44 + 82/44@1 + + + #ffc8ff + #ffc8ff + 0 + 0 + C1 + C0 + true + true + false + 1 + false + false + 0 + pnp.label - 82/59 + 82/59@1 + + + #00ffff + #00ffff + 0 + 0 + I1 + C0 + true + true + false + 1 + false + false + 0 + npn.drawing - 82/20 + 82/20@1 + + + #c8ffff + #c8ffff + 0 + 0 + C1 + C0 + true + true + false + 1 + false + false + 0 + npn.label - 82/5 + 82/5@1 + + + #ffffff + #96c8ff + 0 + 0 + C38 + C0 + true + true + false + 1 + false + false + 0 + rpm.drawing - 86/20 + 86/20@1 + + + #9900e6 + #9900e6 + 0 + 0 + C4 + C0 + true + false + false + 1 + false + false + 0 + hvi.drawing - 75/20 + 75/20@1 + + + #ffb232 + #ffb232 + 0 + 0 + C0 + C0 + true + true + false + 3 + false + false + 0 + capacitor.drawing - 82/64 + 82/64@1 + + + #ffe6bf + #ffe6bf + 0 + 0 + C14 + C0 + true + true + false + 1 + false + false + 0 + ncm.drawing - 92/44 + 92/44@1 + + + #ff8000 + #ff8000 + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + cncm.drawing - 96/44 + 96/44@1 + + + #ff0000 + #ff0000 + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + cncm.mask - 17/0 + 17/0@1 + + + #ffe6bf + #ffe6bf + 0 + 0 + C20 + C1 + true + true + false + 1 + false + false + 0 + pmm.drawing - 85/44 + 85/44@1 + + + #bf4026 + #bf4026 + 0 + 0 + C40 + C0 + true + true + false + 1 + false + false + 0 + pmm2.drawing - 77/20 + 77/20@1 + + + #e61f0d + #e61f0d + 0 + 0 + C47 + C0 + true + true + false + 1 + false + false + 0 + rdl.drawing - 74/20 + 74/20@1 + + + #e61f0d + #e61f0d + 0 + 0 + C34 + C0 + true + true + false + 1 + false + false + 0 + rdl.pin - 74/16 + 74/16@1 + + + #ff6464 + #ff6464 + 0 + 0 + C1 + C0 + true + false + false + 1 + false + false + 0 + rdl.label - 74/5 + 74/5@1 + + + #ffbff2 + #ffbff2 + 0 + 0 + I1 + C0 + true + true + false + 1 + false + false + 0 + rdl.res - 74/13 + 74/13@1 + + + #ffbff2 + #ffbff2 + 0 + 0 + C1 + C0 + true + true + false + 1 + false + false + 0 + rdl.cut - 74/14 + 74/14@1 + + + #e61f0d + #e61f0d + 0 + 0 + C36 + C0 + false + true + false + 1 + false + false + 0 + rdl.short - 74/15 + 74/15@1 + + + #e61f0d + #e61f0d + 0 + 0 + C25 + C0 + false + true + false + 1 + false + false + 0 + rdl.option1 - 89/32 + 89/32@1 + + + #e61f0d + #e61f0d + 0 + 0 + C26 + C0 + false + true + false + 1 + false + false + 0 + rdl.option2 - 89/33 + 89/33@1 + + + #e61f0d + #e61f0d + 0 + 0 + C27 + C0 + false + true + false + 1 + false + false + 0 + rdl.option3 - 89/34 + 89/34@1 + + + #e61f0d + #e61f0d + 0 + 0 + C28 + C0 + false + true + false + 1 + false + false + 0 + rdl.option4 - 89/35 + 89/35@1 + + + #e61f0d + #e61f0d + 0 + 0 + C29 + C0 + false + true + false + 1 + false + false + 0 + rdl.option5 - 89/36 + 89/36@1 + + + #e61f0d + #e61f0d + 0 + 0 + C30 + C0 + false + true + false + 1 + false + false + 0 + rdl.option6 - 89/37 + 89/37@1 + + + #e61f0d + #e61f0d + 0 + 0 + C31 + C0 + false + true + false + 1 + false + false + 0 + rdl.option7 - 89/38 + 89/38@1 + + + #e61f0d + #e61f0d + 0 + 0 + C32 + C0 + false + true + false + 1 + false + false + 0 + rdl.option8 - 89/39 + 89/39@1 + + + #ccccd9 + #333399 + 0 + 0 + C7 + C0 + true + true + false + 1 + false + false + 0 + ubm.drawing - 127/21 + 127/21@1 + + + #ffbff2 + #ffbff2 + 0 + 0 + C20 + C0 + true + true + false + 1 + false + false + 0 + bump.drawing - 127/22 + 127/22@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + inductor.drawing - 82/24 + 82/24@1 + + + #fff464 + #fff464 + 0 + 0 + C1 + C0 + true + true + false + 1 + false + false + 0 + inductor.label - 82/25 + 82/25@1 + + + #333399 + #ffffff + 0 + 0 + C25 + C0 + true + true + false + 1 + false + false + 0 + inductor.term1 - 82/26 + 82/26@1 + + + #333399 + #ffffff + 0 + 0 + C26 + C0 + true + true + false + 1 + false + false + 0 + inductor.term2 - 82/27 + 82/27@1 + + + #333399 + #ffffff + 0 + 0 + C27 + C0 + true + true + false + 1 + false + false + 0 + inductor.term3 - 82/28 + 82/28@1 + + + #ccccd9 + #ccccd9 + 0 + 0 + C20 + C0 + true + true + false + 1 + false + false + 0 + cfom.drawing - 22/20 + 22/20@1 + + + #ccccd9 + #ccccd9 + 0 + 0 + C13 + C0 + true + true + false + 1 + false + false + 0 + cfom.mask - 23/0 + 23/0@1 + + + #ccccd9 + #ccccd9 + 0 + 0 + C14 + C0 + true + false + false + 1 + false + false + 0 + cfom.maskAdd - 22/21 + 22/21@1 + + + #ccccd9 + #ccccd9 + 0 + 0 + C11 + C0 + true + false + false + 1 + false + false + 0 + cfom.maskDrop - 22/22 + 22/22@1 + + + #268c6b + #268c6b + 0 + 0 + I1 + C0 + true + true + false + 1 + false + false + 0 + cfom.waffleDrop - 22/24 + 22/24@1 + + + #ccccd9 + #ccccd9 + 0 + 0 + C20 + C0 + false + true + false + 1 + false + false + 0 + fom.dummy - 22/23 + 22/23@1 + + + #268c6b + #268c6b + 0 + 0 + C20 + C0 + false + true + false + 1 + false + false + 0 + cnwm.drawing - 109/44 + 109/44@1 + + + #268c6b + #268c6b + 0 + 0 + C13 + C0 + true + true + false + 1 + false + false + 0 + cnwm.mask - 21/0 + 21/0@1 + + + #268c6b + #268c6b + 0 + 0 + C14 + C0 + false + true + false + 1 + false + false + 0 + cnwm.maskAdd - 109/43 + 109/43@1 + + + #268c6b + #268c6b + 0 + 0 + C11 + C0 + false + true + false + 1 + false + false + 0 + cnwm.maskDrop - 109/42 + 109/42@1 + + + #ffffff + #ffffff + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + cdnm.drawing - 110/20 + 110/20@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C17 + C0 + true + true + false + 1 + false + false + 0 + cdnm.mask - 48/0 + 48/0@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C14 + C0 + true + true + false + 1 + false + false + 0 + cdnm.maskAdd - 110/21 + 110/21@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C3 + C0 + true + true + false + 1 + false + false + 0 + cdnm.maskDrop - 110/22 + 110/22@1 + + + #96c8ff + #ffffcc + 0 + 0 + C0 + C0 + false + true + false + 1 + false + false + 0 + clvtnm.drawing - 25/44 + 25/44@1 + + + #96c8ff + #ffffff + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + clvtnm.mask - 25/0 + 25/0@1 + + + #96c8ff + #ffffff + 0 + 0 + C14 + C0 + false + true + false + 1 + false + false + 0 + clvtnm.maskAdd - 25/43 + 25/43@1 + + + #96c8ff + #0000ff + 0 + 0 + C3 + C0 + false + true + false + 1 + false + false + 0 + clvtnm.maskDrop - 25/42 + 25/42@1 + + + #ff8000 + #ffffcc + 0 + 0 + C0 + C0 + false + true + false + 1 + false + false + 0 + chvtpm.drawing - 88/44 + 88/44@1 + + + #ff8000 + #ffffff + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + chvtpm.mask - 97/0 + 97/0@1 + + + #ff8000 + #ffffff + 0 + 0 + C14 + C0 + false + true + false + 1 + false + false + 0 + chvtpm.maskAdd - 97/43 + 97/43@1 + + + #ff8000 + #0000ff + 0 + 0 + C3 + C0 + false + true + false + 1 + false + false + 0 + chvtpm.maskDrop - 97/42 + 97/42@1 + + + #ff0000 + #d9e6ff + 0 + 0 + C0 + C0 + false + true + false + 1 + false + false + 0 + chvtrm.drawing - 98/44 + 98/44@1 + + + #ff0000 + #e61f0d + 0 + 0 + C0 + C0 + false + true + false + 1 + false + false + 0 + chvtrm.mask - 98/0 + 98/0@1 + + + #ff0000 + #e61f0d + 0 + 0 + C14 + C0 + false + true + false + 1 + false + false + 0 + chvtrm.maskAdd - 98/43 + 98/43@1 + + + #ff0000 + #d9e6ff + 0 + 0 + C3 + C0 + false + true + false + 1 + false + false + 0 + chvtrm.maskDrop - 98/42 + 98/42@1 + + + #ffffff + #ffffff + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + ctunm.drawing - 96/20 + 96/20@1 + + + #8c8ca6 + #8c8ca6 + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + ctunm.mask - 20/0 + 20/0@1 + + + #ffffff + #ffffff + 0 + 0 + C14 + C0 + true + true + false + 1 + false + false + 0 + ctunm.maskAdd - 96/21 + 96/21@1 + + + #ffffff + #ffffff + 0 + 0 + C3 + C0 + true + true + false + 1 + false + false + 0 + ctunm.maskDrop - 96/22 + 96/22@1 + + + #ff0000 + #ff0000 + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + conom.drawing - 87/44 + 87/44@1 + + + #ff0000 + #ff0000 + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + conom.mask - 88/0 + 88/0@1 + + + #ff0000 + #ff0000 + 0 + 0 + C14 + C0 + true + true + false + 1 + false + false + 0 + conom.maskAdd - 87/43 + 87/43@1 + + + #ff0000 + #ff0000 + 0 + 0 + C3 + C0 + true + true + false + 1 + false + false + 0 + conom.maskDrop - 87/42 + 87/42@1 + + + #00cc66 + #00cc66 + 0 + 0 + C20 + C0 + true + true + false + 1 + false + false + 0 + cnsdm.drawing - 29/20 + 29/20@1 + + + #00cc66 + #00cc66 + 0 + 0 + C13 + C0 + true + true + false + 1 + false + false + 0 + cnsdm.mask - 30/0 + 30/0@1 + + + #00cc66 + #00cc66 + 0 + 0 + C14 + C0 + true + true + false + 1 + false + false + 0 + cnsdm.maskAdd - 29/21 + 29/21@1 + + + #00cc66 + #00cc66 + 0 + 0 + C11 + C0 + true + true + false + 1 + false + false + 0 + cnsdm.maskDrop - 29/22 + 29/22@1 + + + #ffff00 + #ffff00 + 0 + 0 + C20 + C0 + true + true + false + 1 + false + false + 0 + cpsdm.drawing - 31/20 + 31/20@1 + + + #ffff00 + #ffff00 + 0 + 0 + C13 + C0 + true + true + false + 1 + false + false + 0 + cpsdm.mask - 32/0 + 32/0@1 + + + #ffff00 + #ffff00 + 0 + 0 + C14 + C0 + true + true + false + 1 + false + false + 0 + cpsdm.maskAdd - 31/21 + 31/21@1 + + + #ffff00 + #ffff00 + 0 + 0 + C11 + C0 + true + true + false + 1 + false + false + 0 + cpsdm.maskDrop - 31/22 + 31/22@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C20 + C0 + true + true + false + 1 + false + false + 0 + cntm.drawing - 26/20 + 26/20@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C13 + C0 + true + true + false + 1 + false + false + 0 + cntm.mask - 27/0 + 27/0@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C14 + C0 + false + true + false + 1 + false + false + 0 + cntm.maskAdd - 26/21 + 26/21@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C11 + C0 + false + true + false + 1 + false + false + 0 + cntm.maskDrop - 26/22 + 26/22@1 + + + #ffff00 + #ffff00 + 0 + 0 + C20 + C0 + true + true + false + 1 + false + false + 0 + hvntm.drawing - 125/20 + 125/20@1 + + + #fff5e6 + #fff5e6 + 0 + 0 + C20 + C0 + true + true + false + 1 + false + false + 0 + chvntm.drawing - 38/20 + 38/20@1 + + + #fff5e6 + #fff5e6 + 0 + 0 + C13 + C0 + true + true + false + 1 + false + false + 0 + chvntm.mask - 39/0 + 39/0@1 + + + #fff5e6 + #fff5e6 + 0 + 0 + C14 + C0 + false + true + false + 1 + false + false + 0 + chvntm.maskAdd - 38/21 + 38/21@1 + + + #fff5e6 + #fff5e6 + 0 + 0 + C11 + C0 + false + true + false + 1 + false + false + 0 + chvntm.maskDrop - 38/22 + 38/22@1 + + + #00cc66 + #00cc66 + 0 + 0 + C20 + C0 + false + true + false + 1 + false + false + 0 + cldntm.drawing - 11/20 + 11/20@1 + + + #00cc66 + #00cc66 + 0 + 0 + C13 + C0 + true + true + false + 1 + false + false + 0 + cldntm.mask - 11/0 + 11/0@1 + + + #ff8000 + #ff8000 + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + clvom.drawing - 45/20 + 45/20@1 + + + #268c6b + #268c6b + 0 + 0 + C17 + C0 + true + true + false + 1 + false + false + 0 + clvom.mask - 46/0 + 46/0@1 + + + #268c6b + #268c6b + 0 + 0 + C14 + C0 + false + true + false + 1 + false + false + 0 + clvom.maskAdd - 45/21 + 45/21@1 + + + #8c8ca6 + #8c8ca6 + 0 + 0 + C3 + C0 + false + true + false + 1 + false + false + 0 + clvom.maskDrop - 45/22 + 45/22@1 + + + #ff8000 + #ff8000 + 0 + 0 + C20 + C0 + false + true + false + 1 + false + false + 0 + cp1m.drawing - 33/44 + 33/44@1 + + + #ff8000 + #ff8000 + 0 + 0 + C13 + C0 + true + true + false + 1 + false + false + 0 + cp1m.mask - 28/0 + 28/0@1 + + + #ff8000 + #ff8000 + 0 + 0 + C14 + C0 + true + false + false + 1 + false + false + 0 + cp1m.maskAdd - 33/43 + 33/43@1 + + + #9900e6 + #9900e6 + 0 + 0 + I1 + C0 + true + true + false + 1 + false + false + 0 + cp1m.waffleDrop - 33/24 + 33/24@1 + + + #ff8000 + #ff8000 + 0 + 0 + C11 + C0 + true + false + false + 1 + false + false + 0 + cp1m.maskDrop - 33/42 + 33/42@1 + + + #ffffff + #ffffff + 0 + 0 + C0 + C0 + false + true + false + 1 + false + false + 0 + cli1m.drawing - 115/44 + 115/44@1 + + + #00ffff + #00ffff + 0 + 0 + C11 + C0 + true + true + false + 1 + false + false + 0 + cli1m.mask - 56/0 + 56/0@1 + + + #00ffff + #00ffff + 0 + 0 + C14 + C0 + true + true + false + 1 + false + false + 0 + cli1m.maskAdd - 115/43 + 115/43@1 + + + #00ffff + #00ffff + 0 + 0 + C3 + C0 + true + true + false + 1 + false + false + 0 + cli1m.maskDrop - 115/42 + 115/42@1 + + + #ffffff + #ffffff + 0 + 0 + C0 + C0 + false + true + false + 1 + false + false + 0 + clicm1.drawing - 106/44 + 106/44@1 + + + #d9e6ff + #d9e6ff + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + clicm1.mask - 43/0 + 43/0@1 + + + #d9e6ff + #d9e6ff + 0 + 0 + C14 + C0 + true + true + false + 1 + false + false + 0 + clicm1.maskAdd - 106/43 + 106/43@1 + + + #d9e6ff + #d9e6ff + 0 + 0 + C3 + C0 + true + true + false + 1 + false + false + 0 + clicm1.maskDrop - 106/42 + 106/42@1 + + + #0000ff + #0000ff + 0 + 0 + C20 + C0 + false + true + false + 1 + false + false + 0 + cmm1.drawing - 62/20 + 62/20@1 + + + #0000ff + #0000ff + 0 + 0 + C13 + C0 + true + true + false + 1 + false + false + 0 + cmm1.mask - 36/0 + 36/0@1 + + + #0000ff + #0000ff + 0 + 0 + C14 + C0 + false + true + false + 1 + false + false + 0 + cmm1.maskAdd - 62/21 + 62/21@1 + + + #0000ff + #0000ff + 0 + 0 + C11 + C0 + false + true + false + 1 + false + false + 0 + cmm1.maskDrop - 62/22 + 62/22@1 + + + #0000ff + #0000ff + 0 + 0 + I1 + C0 + true + true + false + 1 + false + false + 0 + cmm1.waffleDrop - 62/24 + 62/24@1 + + + #ffffcc + #ffffcc + 0 + 0 + C0 + C0 + false + true + false + 1 + false + false + 0 + cviam.drawing - 105/20 + 105/20@1 + + + #ffffcc + #ffffcc + 0 + 0 + C17 + C0 + true + true + false + 1 + false + false + 0 + cviam.mask - 40/0 + 40/0@1 + + + #ffffcc + #ffffcc + 0 + 0 + C14 + C0 + false + true + false + 1 + false + false + 0 + cviam.maskAdd - 105/21 + 105/21@1 + + + #ffffcc + #ffffcc + 0 + 0 + C3 + C0 + false + true + false + 1 + false + false + 0 + cviam.maskDrop - 105/22 + 105/22@1 + + + #ffbff2 + #ffbff2 + 0 + 0 + C20 + C0 + false + true + false + 1 + false + false + 0 + cmm2.drawing - 105/44 + 105/44@1 + + + #ffbff2 + #ffbff2 + 0 + 0 + C13 + C0 + true + true + false + 1 + false + false + 0 + cmm2.mask - 41/0 + 41/0@1 + + + #ffbff2 + #ffbff2 + 0 + 0 + C14 + C0 + false + true + false + 1 + false + false + 0 + cmm2.maskAdd - 105/43 + 105/43@1 + + + #ffbff2 + #ffbff2 + 0 + 0 + C11 + C0 + false + true + false + 1 + false + false + 0 + cmm2.maskDrop - 105/42 + 105/42@1 + + + #ffbff2 + #ffbff2 + 0 + 0 + I1 + C0 + true + true + false + 1 + false + false + 0 + cmm2.waffleDrop - 105/52 + 105/52@1 + + + #333399 + #333399 + 0 + 0 + C0 + C0 + false + true + false + 1 + false + false + 0 + cviam2.drawing - 108/20 + 108/20@1 + + + #333399 + #333399 + 0 + 0 + C17 + C0 + true + true + false + 1 + false + false + 0 + cviam2.mask - 44/0 + 44/0@1 + + + #333399 + #333399 + 0 + 0 + C14 + C0 + false + true + false + 1 + false + false + 0 + cviam2.maskAdd - 108/21 + 108/21@1 + + + #333399 + #333399 + 0 + 0 + C3 + C0 + false + true + false + 1 + false + false + 0 + cviam2.maskDrop - 108/22 + 108/22@1 + + + #5e00e6 + #5e00e6 + 0 + 0 + C20 + C0 + false + true + false + 1 + false + false + 0 + cmm3.drawing - 107/20 + 107/20@1 + + + #5e00e6 + #5e00e6 + 0 + 0 + C13 + C0 + true + true + false + 1 + false + false + 0 + cmm3.mask - 34/0 + 34/0@1 + + + #5e00e6 + #5e00e6 + 0 + 0 + C14 + C0 + false + true + false + 1 + false + false + 0 + cmm3.maskAdd - 107/21 + 107/21@1 + + + #5e00e6 + #5e00e6 + 0 + 0 + C11 + C0 + false + true + false + 1 + false + false + 0 + cmm3.maskDrop - 107/22 + 107/22@1 + + + #5e00e6 + #5e00e6 + 0 + 0 + I1 + C0 + true + true + false + 1 + false + false + 0 + cmm3.waffleDrop - 107/24 + 107/24@1 + + + #ffffff + #ffffff + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + cnpc.drawing - 44/20 + 44/20@1 + + + #e61f0d + #e61f0d + 0 + 0 + C39 + C0 + true + true + false + 1 + false + false + 0 + cnpc.mask - 49/0 + 49/0@1 + + + #e61f0d + #e61f0d + 0 + 0 + C14 + C0 + false + true + false + 1 + false + false + 0 + cnpc.maskAdd - 44/43 + 44/43@1 + + + #e61f0d + #e61f0d + 0 + 0 + C3 + C0 + false + true + false + 1 + false + false + 0 + cnpc.maskDrop - 44/42 + 44/42@1 + + + #268c6b + #268c6b + 0 + 0 + C0 + C0 + false + true + false + 1 + false + false + 0 + cviam3.drawing - 112/20 + 112/20@1 + + + #268c6b + #268c6b + 0 + 0 + C17 + C0 + true + true + false + 1 + false + false + 0 + cviam3.mask - 50/0 + 50/0@1 + + + #268c6b + #268c6b + 0 + 0 + C14 + C0 + false + true + false + 1 + false + false + 0 + cviam3.maskAdd - 112/21 + 112/21@1 + + + #268c6b + #268c6b + 0 + 0 + C3 + C0 + false + true + false + 1 + false + false + 0 + cviam3.maskDrop - 112/22 + 112/22@1 + + + #00cc66 + #00cc66 + 0 + 0 + C20 + C0 + true + true + false + 1 + false + false + 0 + cnsm.mask - 22/0 + 22/0@1 + + + #ffe6bf + #ffe6bf + 0 + 0 + C20 + C0 + false + true + false + 1 + false + false + 0 + cpdm.drawing - 104/44 + 104/44@1 + + + #ffe6bf + #ffe6bf + 0 + 0 + C13 + C0 + true + true + false + 1 + false + false + 0 + cpdm.mask - 37/0 + 37/0@1 + + + #ffe6bf + #ffe6bf + 0 + 0 + C14 + C0 + false + true + false + 1 + false + false + 0 + cpdm.maskAdd - 104/43 + 104/43@1 + + + #ffe6bf + #ffe6bf + 0 + 0 + C11 + C0 + false + true + false + 1 + false + false + 0 + cpdm.maskDrop - 104/42 + 104/42@1 + + + #8c8ca6 + #0000ff + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + cpmm.drawing - 91/44 + 91/44@1 + + + #8c8ca6 + #8c8ca6 + 0 + 0 + C13 + C0 + true + true + false + 1 + false + false + 0 + cpbo.mask - 99/0 + 99/0@1 + + + #00cc66 + #00cc66 + 0 + 0 + C13 + C0 + true + true + false + 1 + false + false + 0 + cmm4.mask - 51/0 + 51/0@1 + + + #00cc66 + #00cc66 + 0 + 0 + C14 + C0 + true + true + false + 1 + false + false + 0 + cmm4.maskAdd - 112/43 + 112/43@1 + + + #00cc66 + #00cc66 + 0 + 0 + C11 + C0 + true + true + false + 1 + false + false + 0 + cmm4.maskDrop - 112/42 + 112/42@1 + + + #00cc66 + #00cc66 + 0 + 0 + I1 + C0 + true + true + false + 1 + false + false + 0 + cmm4.waffleDrop - 112/4 + 112/4@1 + + + #00ffff + #00ffff + 0 + 0 + C0 + C0 + false + true + false + 1 + false + false + 0 + cviam4.drawing - 117/20 + 117/20@1 + + + #00ffff + #00ffff + 0 + 0 + C17 + C0 + true + true + false + 1 + false + false + 0 + cviam4.mask - 58/0 + 58/0@1 + + + #00ffff + #00ffff + 0 + 0 + C14 + C0 + false + true + false + 1 + false + false + 0 + cviam4.maskAdd - 117/21 + 117/21@1 + + + #00ffff + #00ffff + 0 + 0 + C3 + C0 + false + true + false + 1 + false + false + 0 + cviam4.maskDrop - 117/22 + 117/22@1 + + + #ff8000 + #ff8000 + 0 + 0 + C13 + C0 + true + true + false + 1 + false + false + 0 + cmm5.mask - 59/0 + 59/0@1 + + + #ff8000 + #ff8000 + 0 + 0 + I1 + C0 + true + true + false + 1 + false + false + 0 + cmm5.waffleDrop - 117/4 + 117/4@1 + + + #0000ff + #0000ff + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + target.drawing - 76/44 + 76/44@1 + + + #ffffff + #ffffff + 0 + 0 + C0 + C0 + false + true + false + 1 + false + false + 0 + cctm1.drawing - 101/44 + 101/44@1 + + + #ffffff + #ffffff + 0 + 0 + C17 + C0 + true + true + false + 1 + false + false + 0 + cctm1.mask - 35/0 + 35/0@1 + + + #ffffff + #ffffff + 0 + 0 + C14 + C0 + false + true + false + 1 + false + false + 0 + cctm1.maskAdd - 101/43 + 101/43@1 + + + #ffffff + #ffffff + 0 + 0 + C3 + C0 + false + true + false + 1 + false + false + 0 + cctm1.maskDrop - 101/42 + 101/42@1 + + + #ffffff + #ffffff + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + crpm.drawing - 53/44 + 53/44@1 + + + #ffffff + #ffffff + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + crpm.mask - 96/0 + 96/0@1 + + + #ffffff + #ffffff + 0 + 0 + C14 + C0 + true + true + false + 1 + false + false + 0 + crpm.maskAdd - 53/43 + 53/43@1 + + + #ffffff + #ffffff + 0 + 0 + C3 + C0 + true + true + false + 1 + false + false + 0 + crpm.maskDrop - 53/42 + 53/42@1 + + + #e61f0d + #e61f0d + 0 + 0 + C47 + C0 + false + true + false + 1 + false + false + 0 + ccu1m.mask - 93/0 + 93/0@1 + + + #bf4026 + #bf4026 + 0 + 0 + C40 + C0 + false + true + false + 1 + false + false + 0 + cpmm2.mask - 94/0 + 94/0@1 + + + #ccccd9 + #333399 + 0 + 0 + C7 + C0 + false + true + false + 1 + false + false + 0 + cubm.mask - 100/0 + 100/0@1 + + + #ffbff2 + #ffbff2 + 0 + 0 + C20 + C0 + false + true + false + 1 + false + false + 0 + cbump.mask - 101/0 + 101/0@1 + + + #00bfff + #00bfff + 0 + 0 + C0 + C0 + true + true + false + 3 + false + false + 0 + overlap.drawing - 90/20 + 90/20@1 + + + #ff7f50 + #ff7f50 + 0 + 0 + C0 + C0 + true + true + false + 3 + false + false + 0 + overlap.boundary - 90/4 + 90/4@1 + + + #ff8000 + #ff8000 + 0 + 0 + C0 + C0 + true + false + false + 3 + false + false + 0 + areaid.lowTapDensity - 81/14 + 81/14@1 + + + #ffbff2 + #ffbff2 + 0 + 0 + C0 + C0 + true + true + false + 3 + false + false + 0 + areaid.notCritSide - 81/15 + 81/15@1 + + + #adff2f + #adff2f + 0 + 0 + C2 + C0 + true + true + false + 3 + false + false + 0 + areaid.injection - 81/17 + 81/17@1 + + + #bebed8 + #bebed8 + 0 + 0 + C2 + C0 + true + true + false + 3 + false + false + 0 + areaid.rfdiode - 81/125 + 81/125@1 + + + #ffffff + #ffffff + 0 + 0 + C0 + C0 + true + true + false + 3 + false + false + 0 + areaid.seal - 81/1 + 81/1@1 + + + #d9e6ff + #d9e6ff + 0 + 0 + C0 + C0 + true + false + false + 3 + false + false + 0 + areaid.core - 81/2 + 81/2@1 + + + #ffffcc + #ffffcc + 0 + 0 + C0 + C0 + true + true + false + 3 + false + false + 0 + areaid.frame - 81/3 + 81/3@1 + + + #d9cc00 + #d9cc00 + 0 + 0 + C0 + C0 + true + false + false + 3 + false + false + 0 + areaid.esd - 81/19 + 81/19@1 + + + #ffe6bf + #ffe6bf + 0 + 0 + C0 + C0 + true + true + false + 3 + false + false + 0 + areaid.dieCut - 81/11 + 81/11@1 + + + #00ff00 + #00ff00 + 0 + 0 + C0 + C0 + true + true + false + 3 + false + false + 0 + areaid.moduleCut - 81/10 + 81/10@1 + + + #00ffff + #00ffff + 0 + 0 + C0 + C0 + true + true + false + 3 + false + false + 0 + areaid.frameRect - 81/12 + 81/12@1 + + + #333399 + #ccccd9 + 0 + 0 + C22 + C0 + true + true + false + 1 + false + false + 0 + areaid.substrateCut - 81/53 + 81/53@1 + + + #ffff00 + #ffff00 + 0 + 0 + C0 + C0 + true + false + false + 3 + false + false + 0 + areaid.diode - 81/23 + 81/23@1 + + + #ff00ff + #ff00ff + 0 + 0 + C0 + C0 + true + false + false + 3 + false + false + 0 + areaid.standardc - 81/4 + 81/4@1 + + + #ffe6bf + #ffe6bf + 0 + 0 + C0 + C5 + true + true + false + 1 + false + false + 0 + areaid.deadZon - 81/50 + 81/50@1 + + + #ff8000 + #ff8000 + 0 + 0 + C0 + C5 + true + true + false + 1 + false + false + 0 + areaid.critCorner - 81/51 + 81/51@1 + + + #ffffcc + #ffffcc + 0 + 0 + C0 + C5 + true + true + false + 1 + false + false + 0 + areaid.critSid - 81/52 + 81/52@1 + + + #8c8ca6 + #c8ffc8 + 0 + 0 + C20 + C0 + true + true + false + 3 + false + false + 0 + areaid.opcDrop - 81/54 + 81/54@1 + + + #00bfff + #00ffe7 + 0 + 0 + C1 + C0 + true + true + false + 3 + false + false + 0 + areaid.waffleWindow - 81/13 + 81/13@1 + + + #daa520 + #daa520 + 0 + 0 + C0 + C0 + true + true + false + 3 + false + false + 0 + areaid.extendedDrain - 81/57 + 81/57@1 + + + #ffbff2 + #ffbff2 + 0 + 0 + C0 + C0 + true + true + false + 3 + false + false + 0 + areaid.lvNative - 81/60 + 81/60@1 + + + #9900e6 + #9900e6 + 0 + 0 + C0 + C0 + true + true + false + 3 + false + false + 0 + areaid.photo - 81/81 + 81/81@1 + + + #ff00ff + #00ff00 + 0 + 0 + C0 + C0 + true + true + false + 3 + false + false + 0 + areaid.etest - 81/101 + 81/101@1 + + + #ff0000 + #ff0000 + 0 + 0 + C0 + C0 + true + true + false + 3 + false + false + 0 + areaid.hvnwell - 81/63 + 81/63@1 + + + #9900e6 + #9900e6 + 0 + 0 + C0 + C0 + true + true + false + 3 + false + false + 0 + areaid.rdlprobepad - 81/27 + 81/27@1 + + + #00bfff + #00bfff + 0 + 0 + C0 + C0 + true + false + false + 3 + false + false + 0 + areaid.sigPadDiff - 81/6 + 81/6@1 + + + #c8ffc8 + #c8ffc8 + 0 + 0 + C0 + C0 + true + true + false + 3 + false + false + 0 + areaid.sigPadWell - 81/7 + 81/7@1 + + + #ff7f50 + #ff7f50 + 0 + 0 + C0 + C0 + true + false + false + 3 + false + false + 0 + areaid.sigPadMetNtr - 81/8 + 81/8@1 + + + #ff6464 + #ff6464 + 0 + 0 + C0 + C0 + true + true + false + 3 + false + false + 0 + areaid.analog - 81/79 + 81/79@1 + + + #ffff00 + #ffffff + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + prune.drawing - 84/44 + 84/44@1 + + + #0000ff + #0000ff + 0 + 0 + C0 + C0 + true + true + false + 1 + false + false + 0 + padCenter.drawing - 81/20 + 81/20@1 + + + #8c8ca6 + #8c8ca6 + 0 + 0 + C7 + C0 + true + true + false + 1 + false + false + 0 + met1.psa1 - 68/88 + 68/88@1 + + + #8c8ca6 + #8c8ca6 + 0 + 0 + C37 + C0 + true + true + false + 1 + false + false + 0 + met2.psa1 - 69/88 + 69/88@1 + + + #8c8ca6 + #8c8ca6 + 0 + 0 + C49 + C0 + true + true + false + 1 + false + false + 0 + met3.psa1 - 70/88 + 70/88@1 + + + #8c8ca6 + #8c8ca6 + 0 + 0 + C15 + C0 + true + true + false + 1 + false + false + 0 + met4.psa1 - 71/88 + 71/88@1 + + + #8c8ca6 + #8c8ca6 + 0 + 0 + C42 + C0 + true + true + false + 1 + false + false + 0 + met5.psa1 - 72/88 + 72/88@1 + + + #00ffe7 + #00ffe7 + 0 + 0 + C7 + C0 + true + true + false + 1 + false + false + 0 + met1.psa2 - 68/89 + 68/89@1 + + + #00ffe7 + #00ffe7 + 0 + 0 + C37 + C0 + true + true + false + 1 + false + false + 0 + met2.psa2 - 69/89 + 69/89@1 + + + #00ffe7 + #00ffe7 + 0 + 0 + C49 + C0 + true + true + false + 1 + false + false + 0 + met3.psa2 - 70/89 + 70/89@1 + + + #00ffe7 + #00ffe7 + 0 + 0 + C15 + C0 + true + true + false + 1 + false + false + 0 + met4.psa2 - 71/89 + 71/89@1 + + + #00ffe7 + #00ffe7 + 0 + 0 + C42 + C0 + true + true + false + 1 + false + false + 0 + met5.psa2 - 72/89 + 72/89@1 + + + #ffffcc + #ffffcc + 0 + 0 + C7 + C0 + true + true + false + 1 + false + false + 0 + met1.psa3 - 68/90 + 68/90@1 + + + #ffffcc + #ffffcc + 0 + 0 + C37 + C0 + true + true + false + 1 + false + false + 0 + met2.psa3 - 69/90 + 69/90@1 + + + #ffffcc + #ffffcc + 0 + 0 + C49 + C0 + true + true + false + 1 + false + false + 0 + met3.psa3 - 70/90 + 70/90@1 + + + #ffffcc + #ffffcc + 0 + 0 + C15 + C0 + true + true + false + 1 + false + false + 0 + met4.psa3 - 71/90 + 71/90@1 + + + #ffffcc + #ffffcc + 0 + 0 + C42 + C0 + true + true + false + 1 + false + false + 0 + met5.psa3 - 72/90 + 72/90@1 + + + #802626 + #802626 + 0 + 0 + C7 + C0 + true + true + false + 1 + false + false + 0 + met1.psa4 - 68/91 + 68/91@1 + + + #802626 + #802626 + 0 + 0 + C37 + C0 + true + true + false + 1 + false + false + 0 + met2.psa4 - 69/91 + 69/91@1 + + + #802626 + #802626 + 0 + 0 + C49 + C0 + true + true + false + 1 + false + false + 0 + met3.psa4 - 70/91 + 70/91@1 + + + #802626 + #802626 + 0 + 0 + C15 + C0 + true + true + false + 1 + false + false + 0 + met4.psa4 - 71/91 + 71/91@1 + + + #802626 + #802626 + 0 + 0 + C42 + C0 + true + true + false + 1 + false + false + 0 + met5.psa4 - 72/91 + 72/91@1 + + + #333399 + #333399 + 0 + 0 + C7 + C0 + true + true + false + 1 + false + false + 0 + met1.psa5 - 68/92 + 68/92@1 + + + #333399 + #333399 + 0 + 0 + C37 + C0 + true + true + false + 1 + false + false + 0 + met2.psa5 - 69/92 + 69/92@1 + + + #333399 + #333399 + 0 + 0 + C49 + C0 + true + true + false + 1 + false + false + 0 + met3.psa5 - 70/92 + 70/92@1 + + + #333399 + #333399 + 0 + 0 + C15 + C0 + true + true + false + 1 + false + false + 0 + met4.psa5 - 71/92 + 71/92@1 + + + #333399 + #333399 + 0 + 0 + C42 + C0 + true + true + false + 1 + false + false + 0 + met5.psa5 - 72/92 + 72/92@1 + + + #fa8072 + #fa8072 + 0 + 0 + C7 + C0 + true + true + false + 1 + false + false + 0 + met1.psa6 - 68/93 + 68/93@1 + + + #fa8072 + #fa8072 + 0 + 0 + C37 + C0 + true + true + false + 1 + false + false + 0 + met2.psa6 - 69/93 + 69/93@1 + + + #fa8072 + #fa8072 + 0 + 0 + C49 + C0 + true + true + false + 1 + false + false + 0 + met3.psa6 - 70/93 + 70/93@1 + + + #fa8072 + #fa8072 + 0 + 0 + C15 + C0 + true + true + false + 1 + false + false + 0 + met4.psa6 - 71/93 + 71/93@1 + + + #fa8072 + #fa8072 + 0 + 0 + C42 + C0 + true + true + false + 1 + false + false + 0 + met5.psa6 - 72/93 + 72/93@1 + + + #ff0000 + #ff0000 + 0 + 0 + C10 + C0 + true + true + false + 1 + false + false + 0 + rdl.psa1 - 74/88 + 74/88@1 + + + #0000ff + #0000ff + 0 + 0 + C10 + C0 + true + true + false + 1 + false + false + 0 + rdl.psa2 - 74/89 + 74/89@1 + + + #00cc66 + #00cc66 + 0 + 0 + C10 + C0 + true + true + false + 1 + false + false + 0 + rdl.psa3 - 74/90 + 74/90@1 + + + #ffffff + #ffffff + 0 + 0 + C10 + C0 + true + true + false + 1 + false + false + 0 + rdl.psa4 - 74/91 + 74/91@1 + + + #ffff00 + #ffff00 + 0 + 0 + C10 + C0 + true + true + false + 1 + false + false + 0 + rdl.psa5 - 74/92 + 74/92@1 + + + #bf4026 + #bf4026 + 0 + 0 + C10 + C0 + true + true + false + 1 + false + false + 0 + rdl.psa6 - 74/93 + 74/93@1 + + + #ffe6bf + #ffe6bf + 0 + 0 + C20 + C1 + false + true + false + 1 + false + false + 0 + blanking.drawing - 124/40 + 124/40@1 + + + + + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + + 1 + blank + + + + **************** + **************** + **************** + **************** + **************** + **************** + **************** + **************** + **************** + **************** + **************** + **************** + **************** + **************** + **************** + **************** + + 2 + solid + + + + ..*...*...*...*. + ................ + *...*...*...*... + ................ + ..*...*...*...*. + ................ + *...*...*...*... + ................ + ..*...*...*...*. + ................ + *...*...*...*... + ................ + ..*...*...*...*. + ................ + *...*...*...*... + ................ + + 3 + dots + + + + ................ + ................ + ................ + **************** + ................ + ................ + ................ + **************** + ................ + ................ + ................ + **************** + ................ + ................ + ................ + **************** + + 4 + hLine + + + + ..*...*...*...*. + ..*...*...*...*. + ..*...*...*...*. + ..*...*...*...*. + ..*...*...*...*. + ..*...*...*...*. + ..*...*...*...*. + ..*...*...*...*. + ..*...*...*...*. + ..*...*...*...*. + ..*...*...*...*. + ..*...*...*...*. + ..*...*...*...*. + ..*...*...*...*. + ..*...*...*...*. + ..*...*...*...*. + + 5 + vLine + + + + *...*...*...*... + .*.*.*.*.*.*.*.* + ..*...*...*...*. + .*.*.*.*.*.*.*.* + *...*...*...*... + .*.*.*.*.*.*.*.* + ..*...*...*...*. + .*.*.*.*.*.*.*.* + *...*...*...*... + .*.*.*.*.*.*.*.* + ..*...*...*...*. + .*.*.*.*.*.*.*.* + *...*...*...*... + .*.*.*.*.*.*.*.* + ..*...*...*...*. + .*.*.*.*.*.*.*.* + + 6 + cross + + + + ...*...*...*...* + ...*...*...*...* + ...*...*...*...* + **************** + ...*...*...*...* + ...*...*...*...* + ...*...*...*...* + **************** + ...*...*...*...* + ...*...*...*...* + ...*...*...*...* + **************** + ...*...*...*...* + ...*...*...*...* + ...*...*...*...* + **************** + + 7 + grid + + + + ...*...*...*...* + ..*...*...*...*. + .*...*...*...*.. + *...*...*...*... + ...*...*...*...* + ..*...*...*...*. + .*...*...*...*.. + *...*...*...*... + ...*...*...*...* + ..*...*...*...*. + .*...*...*...*.. + *...*...*...*... + ...*...*...*...* + ..*...*...*...*. + .*...*...*...*.. + *...*...*...*... + + 8 + slash + + + + *...*...*...*... + .*...*...*...*.. + ..*...*...*...*. + ...*...*...*...* + *...*...*...*... + .*...*...*...*.. + ..*...*...*...*. + ...*...*...*...* + *...*...*...*... + .*...*...*...*.. + ..*...*...*...*. + ...*...*...*...* + *...*...*...*... + .*...*...*...*.. + ..*...*...*...*. + ...*...*...*...* + + 9 + backSlash + + + + **......**...... + ..*.......*..... + ...**......**... + .....*.......*.. + ......**......** + *.......*....... + .**......**..... + ...*.......*.... + ....**......**.. + ......*.......*. + *......**......* + .*.......*...... + ..**......**.... + ....*.......*... + .....**......**. + .......*.......* + + 10 + hZigZag + + + + *....*....*..... + *.....*....*.... + .*....*.....*... + ..*....*....*... + ..*.....*....*.. + ...*....*.....*. + ....*....*....*. + ....*.....*....* + *....*....*..... + *.....*....*.... + .*....*.....*... + ..*....*....*... + ..*.....*....*.. + ...*....*.....*. + ....*....*....*. + ....*.....*....* + + 11 + vZigZag + + + + ................ + ................ + ...*****...***** + ...*...*...*...* + ...*...*...*...* + ****...*****...* + ................ + ................ + ................ + ................ + ...*****...***** + ...*...*...*...* + ...*...*...*...* + ****...*****...* + ................ + ................ + + 12 + hCurb + + + + .....*.......*.. + .....*.......*.. + .....*.......*.. + ..****....****.. + ..*.......*..... + ..*.......*..... + ..*.......*..... + ..****....****.. + .....*.......*.. + .....*.......*.. + .....*.......*.. + ..****....****.. + ..*.......*..... + ..*.......*..... + ..*.......*..... + ..****....****.. + + 13 + vCurb + + + + **************** + ..*.......*..... + ..*.......*..... + ..*.......*..... + **************** + ......*.......*. + ......*.......*. + ......*.......*. + **************** + ..*.......*..... + ..*.......*..... + ..*.......*..... + **************** + ......*.......*. + ......*.......*. + ......*.......*. + + 14 + brick + + + + ................ + ..*.......*..... + ..*.......*..... + ..*.......*..... + *****...*****... + ..*.......*..... + ..*.......*..... + ..*.......*..... + ................ + .....*.......*.. + .....*.......*.. + .....*.......*.. + ...*****...***** + .....*.......*.. + .....*.......*.. + .....*.......*.. + + 15 + dagger + + + + ................ + ..*............. + ..*............. + ..*............. + *****........... + ..*............. + ..*............. + ..*............. + ................ + .............*.. + .............*.. + .............*.. + ...........***** + .............*.. + .............*.. + .............*.. + + 16 + sparseDagger + + + + ................ + ..........*..... + ..........*..... + ..........*..... + ........*****... + ..........*..... + ..........*..... + ..........*..... + ................ + .....*.......... + .....*.......... + .....*.......... + ...*****........ + .....*.......... + .....*.......... + .....*.......... + + 17 + sparseDagger2 + + + + ................ + ....*........... + ...*.*.......... + ..*...*......... + .*.....*........ + *********....... + ................ + ................ + ................ + ...........*.... + ..........*.*... + .........*...*.. + ........*.....*. + .......********* + ................ + ................ + + 18 + triangle + + + + ................ + .*.....*.....*.. + ..*...**....*... + ...*.*.*...*.... + ....*..*..*..... + .....*.*.*...... + ......***....... + .......*........ + ......***....... + .....*.*.*...... + ....*..*..*..... + ...*...*...*.... + ..*....*....*... + .*..*******..*.. + ................ + ................ + + 19 + Xone + + + + ................ + .*....**.....*.. + ..*..*..*...*... + ...**....*.*.... + ....*....**..... + .....*...*...... + ......*.*....... + .......*........ + ......*.*....... + .....*...*...... + ....*.....*..... + ...**......*.... + ..*.*.......*... + .*..******...*.. + ................ + ................ + + 20 + Xtwo + + + + ................ + ................ + ......*.......*. + ................ + ................ + ................ + ..*.......*..... + ................ + ................ + ................ + ......*.......*. + ................ + ................ + ................ + ..*.......*..... + ................ + + 21 + spareDots + + + + ................ + ................ + ................ + ................ + ................ + ......*.......*. + ................ + ................ + ................ + ................ + ................ + ................ + ..*.......*..... + ................ + ................ + ................ + + 22 + spareDots21 + + + + ................ + ................ + ......*.......*. + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ..*.......*..... + ................ + ................ + ................ + ................ + ................ + + 23 + spareDots22 + + + + ****....****.... + ****....****.... + ****....****.... + ****....****.... + ....****....**** + ....****....**** + ....****....**** + ....****....**** + ****....****.... + ****....****.... + ****....****.... + ****....****.... + ....****....**** + ....****....**** + ....****....**** + ....****....**** + + 24 + checker + + + + ....****....**** + ....****....**** + ....****....**** + ....****....**** + ****....****.... + ****....****.... + ****....****.... + ****....****.... + ....****....**** + ....****....**** + ....****....**** + ....****....**** + ****....****.... + ****....****.... + ****....****.... + ****....****.... + + 25 + checker2 + + + + ................ + ................ + ................ + ................ + .......*........ + ......**........ + .......*........ + .......*........ + .......*........ + .......*........ + ......***....... + ................ + ................ + ................ + ................ + ................ + + 26 + one + + + + ................ + ................ + ................ + ................ + ................ + .......**....... + ......*..*...... + .........*...... + ........*....... + .......*........ + ......*......... + ......****...... + ................ + ................ + ................ + ................ + + 27 + two + + + + ................ + ................ + ................ + ................ + ................ + ......***....... + .....*...*...... + .........*...... + .......**....... + .........*...... + .....*...*...... + ......***....... + ................ + ................ + ................ + ................ + + 28 + three + + + + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + .............*.. + ............**.. + ...........*.*.. + ..........*..*.. + .........******. + .............*.. + .............*.. + ................ + + 29 + four + + + + ................ + ................ + ................ + ......***....... + .....*.......... + .....*.......... + .....***........ + ........*....... + ........*....... + .....***........ + ................ + ................ + ................ + ................ + ................ + ................ + + 30 + five + + + + ................ + ................ + ................ + ................ + .......***...... + ......*......... + ......*......... + ......****...... + ......*...*..... + ......*...*..... + .......***...... + ................ + ................ + ................ + ................ + ................ + + 31 + six + + + + ................ + ................ + ................ + .....******..... + ..........*..... + .........*...... + .........*...... + ........*....... + ........*....... + .......*........ + ......*......... + .....*.......... + ................ + ................ + ................ + ................ + + 32 + seven + + + + ................ + ................ + ................ + ................ + ......***....... + .....*...*...... + .....*...*...... + ......***....... + .....*...*...... + .....*...*...... + ......***....... + ................ + ................ + ................ + ................ + ................ + + 33 + eight + + + + *..............* + .*............*. + ..*..........*.. + ...*........*... + ....*......*.... + .....*....*..... + ......*..*...... + .......**....... + .......**....... + ......*..*...... + .....*....*..... + ....*......*.... + ...*........*... + ..*..........*.. + .*............*. + *..............* + + 34 + box45 + + + + .*.*.*.*.*.*.*.* + *.*.*.*.*.*.*.*. + .*.*.*.*.*.*.*.* + *.*.*.*.*.*.*.*. + .*.*.*.*.*.*.*.* + *.*.*.*.*.*.*.*. + .*.*.*.*.*.*.*.* + *.*.*.*.*.*.*.*. + .*.*.*.*.*.*.*.* + *.*.*.*.*.*.*.*. + .*.*.*.*.*.*.*.* + *.*.*.*.*.*.*.*. + .*.*.*.*.*.*.*.* + *.*.*.*.*.*.*.*. + .*.*.*.*.*.*.*.* + *.*.*.*.*.*.*.*. + + 35 + gray50 + + + + *...*...*...*... + ................ + ..*...*...*...*. + ................ + *...*...*...*... + ................ + ..*...*...*...*. + ................ + *...*...*...*... + ................ + ..*...*...*...*. + ................ + *...*...*...*... + ................ + ..*...*...*...*. + ................ + + 36 + gray25 + + + + .*.*.*...*.*.*.. + ................ + ...*...*...*...* + ................ + .*...*.*.*...*.* + ................ + ...*...*...*...* + ................ + .*.*.*...*.*.*.. + ................ + ...*...*...*...* + ................ + .*...*.*.*...*.* + ................ + ...*...*...*...* + ................ + + 37 + snow + + + + ..*.......*..... + ...*.......*.... + ....*.......*... + .....*.......*.. + ......*.......*. + .......*.......* + *.......*....... + .*.......*...... + ..*.......*..... + ...*.......*.... + ....*.......*... + .....*.......*.. + ......*.......*. + .......*.......* + *.......*....... + .*.......*...... + + 38 + backSlash2 + + + + *.....*.*.....*. + .*...*...*...*.. + ..*.*.....*.*... + ...*...*...*...* + ..*.*.....*.*... + .*...*...*...*.. + *.....*.*.....*. + ...*...*...*...* + *.....*.*.....*. + .*...*...*...*.. + ..*.*.....*.*... + ...*...*...*...* + ..*.*.....*.*... + .*...*...*...*.. + *.....*.*.....*. + ...*...*...*...* + + 39 + lattice + + + + **..**..**..**.. + **..**..**..**.. + ..**..**..**..** + ..**..**..**..** + **..**..**..**.. + **..**..**..**.. + ..**..**..**..** + ..**..**..**..** + **..**..**..**.. + **..**..**..**.. + ..**..**..**..** + ..**..**..**..** + **..**..**..**.. + **..**..**..**.. + ..**..**..**..** + ..**..**..**..** + + 40 + smallChecker + + + + ....*........... + ...*.*.......... + ..*...*......... + .*.....*........ + *.......*....... + .*.......*...... + ..*.......*..... + ...*.......*.... + ....*.......*... + .....*.......*.. + ......*.......*. + .......*.......* + ........*.....*. + .........*...*.. + ..........*.*... + ...........*.... + + 41 + slantBox + + + + .*...*...*...*.. + *...*...*...*... + ...*...*...*...* + ..*...*...*...*. + .*...*...*...*.. + *...*...*...*... + ...*...*...*...* + ..*...*...*...*. + .*...*...*...*.. + *...*...*...*... + ...*...*...*...* + ..*...*...*...*. + .*...*...*...*.. + *...*...*...*... + ...*...*...*...* + ..*...*...*...*. + + 42 + slash2 + + + + ...*.......*.... + ..*.......*..... + .*.......*...... + *.......*....... + .......*.......* + ......*.......*. + .....*.......*.. + ....*.......*... + ...*.......*.... + ..*.......*..... + .*.......*...... + *.......*....... + .......*.......* + ......*.......*. + .....*.......*.. + ....*.......*... + + 43 + bigSlash + + + + ****....****.... + *..*....*..*.... + *..*....*..*.... + ****....****.... + ................ + ................ + ................ + ................ + ****....****.... + *..*....*..*.... + *..*....*..*.... + ****....****.... + ................ + ................ + ................ + ................ + + 44 + boxes + + + + ..**......**.... + .*..*....*..*... + *....*..*....*.. + *....*..*....*.. + .*..*....*..*... + ..**......**.... + ................ + ................ + ..**......**.... + .*..*....*..*... + *....*..*....*.. + *....*..*....*.. + .*..*....*..*... + ..**......**.... + ................ + ................ + + 45 + circles + + + + .**..**..**..**. + ...*...*...*...* + *...*...*...*... + .**..**..**..**. + .**..**..**..**. + *...*...*...*... + ...*...*...*...* + .**..**..**..**. + .**..**..**..**. + ...*...*...*...* + *...*...*...*... + .**..**..**..**. + .**..**..**..**. + *...*...*...*... + ...*...*...*...* + .**..**..**..**. + + 46 + zigzag + + + + ................ + *.*.*.*.*.*.*.*. + ................ + ...*....*....*.. + ................ + *.*.*.*.*.*.*.*. + ................ + ...*....*....*.. + ................ + *.*.*.*.*.*.*.*. + ................ + ...*....*....*.. + ................ + *.*.*.*.*.*.*.*. + ................ + ...*....*....*.. + + 47 + lightMesh + + + + ...............* + ..............*. + .............*.. + ............*... + ...........*.... + ..........*..... + .........*...... + ........*....... + .......*........ + ......*......... + .....*.......... + ....*........... + ...*............ + ..*............. + .*.............. + *............... + + 48 + hugeSlash + + + + *............... + .*.............. + ..*............. + ...*............ + ....*........... + .....*.......... + ......*......... + .......*........ + ........*....... + .........*...... + ..........*..... + ...........*.... + ............*... + .............*.. + ..............*. + ...............* + + 49 + hugeSlash2 + + + + *.........*..... + .....*.......... + ..*.........*... + .......*........ + ....*.........*. + .*.......*...... + ......*......... + ...*.......*.... + ........*....... + .....*.......*.. + *.........*..... + .......*........ + ..*.........*... + .........*...... + ....*.........*. + ...........*.... + + 50 + curve + + + + ....*....*....*. + ..*............. + *.......*....... + .............*.. + .......*........ + ............*... + .....*.......... + ...*.......*.... + *............... + ................ + .........*...... + ................ + ......*........* + ...*............ + *............*.. + ..........*..... + + 51 + curve2 + + + + ...........*.... + ..........*.*... + .........*...*.. + ........*.....*. + .........*...*.. + ..........*.*... + ...........*.... + ................ + ................ + ...*............ + ..*.*........... + .*...*.......... + *.....*......... + .*...*.......... + ..*.*........... + ...*............ + + 52 + diams + + + + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ................ + ...*............ + ..*.*........... + .*...*.......... + *.....*......... + .*...*.......... + ..*.*........... + ...*............ + + 53 + sparsediam + + + + .......*.......* + ......*......... + .....*.......... + ................ + ................ + ................ + .*.............. + *............... + .......*.......* + ..............*. + .............*.. + ................ + ................ + ................ + .........*...... + ........*....... + + 54 + rain + + + + * + + 55 + + + + *** + 1 + solid + + + ****.. + 2 + dashed + + + *.. + 3 + dots + + + ***..*.. + 4 + dashDot + + + **.. + 5 + shortDash + + + ****..**.. + 6 + doubleDash + + + *... + 7 + hidden + + + + 8 + + + diff --git a/caravel_template/dump_pic.rb b/caravel_template/dump_pic.rb new file mode 100644 index 0000000..7bdf5c9 --- /dev/null +++ b/caravel_template/dump_pic.rb @@ -0,0 +1 @@ +RBA::Application.instance.main_window.current_view.save_image("./tinytapeout.png",2000,2000) diff --git a/caravel_template/klayoutrc b/caravel_template/klayoutrc new file mode 100644 index 0000000..f08f5ef --- /dev/null +++ b/caravel_template/klayoutrc @@ -0,0 +1,247 @@ + + + false + false + 10.0 + true + auto + true + 1 + by-name + auto + 50 + false + false + + any-top + 1000 + 1.0 + fit-marker + + 32 + false + + add + auto + 50 + false + + 0 + + false + + false + 0 + 0.01,0.005,0.001 + + 0.1 + false + false + false + false + false + 2 + 5 + false + 1 + false + 0 + 10 + any + global + -1 + 0 + false + + 0 + 0 + 1 + + false + + false + 0 + 0 + 1 + 1 + 1000 + false + any + flush + 0.0 + 0.0 + 0.1 + true + false + left + 0 + ABC + bottom + false + true + false + true + r0 + auto + auto + auto + 0.001 + auto + true + invisible + dots + tenths-dotted-lines + true + auto + 1 + 5 + true + false + 1 + 1 + + auto + 0 + true + true + + CIRCUIT_ + DEVICE_ + NET_ + + false + false + 1000 + auto + 255,0,0 0,255,0 0,0,255 255,255,0 255,0,255 0,255,255 160,80,255 255,160,0 + false + -1 + -1 + 50 + -1 + false + -1 + 10000 + true + 1.0 + fit-net + + false + true + false + true + + + + + + + true + true + + + 2 + + false + true + + 8 + + + true + + 16 + 0 + '/home/matt/work/asic-workshop/shuttle6/zero_to_asic_mpw6/gds/user_project_wrapper.gds.gz' '/home/matt/work/asic-workshop/shuttle7/zero_to_asic_mpw7/gds/user_project_wrapper.gds.gz' '/home/matt/work/asic-workshop/shuttle7/tinytapeout-02/gds/user_project_wrapper.gds' '/home/matt/work/asic-workshop/shuttle8/tinytapeout-02/gds/user_project_wrapper.gds' '/home/matt/work/asic-workshop/shuttle9/caravel_user_project/gds/user_project_wrapper.gds' + false + true + false + auto + 255,0,0 0,255,0 0,0,255 255,255,0 255,0,255 0,255,255 160,80,255 255,160,0 + false + -1 + -1 + 50 + -1 + -1 + 10000 + 1.0 + fit-net + 0.15 + 2 + + database-top + auto + -1 + -1 + -1 + -1 + 1000 + 1.0 + fit-marker + + false + auto + false + true + true + any + 8 + mode=normal,title=Ruler,category=_ruler,fmt=$D,fmt_x=$X,fmt_y=$Y,position=auto,xalign=auto,yalign=auto,xlabel_xalign=auto,xlabel_yalign=auto,ylabel_xalign=auto,ylabel_yalign=auto,style=ruler,outline=diag,snap=true,angle_constraint=global;mode=single_click,title=Cross,category=_cross,fmt='$U,$V',fmt_x='',fmt_y='',position=auto,xalign=auto,yalign=auto,xlabel_xalign=auto,xlabel_yalign=auto,ylabel_xalign=auto,ylabel_yalign=auto,style=cross_both,outline=diag,snap=true,angle_constraint=global;mode=auto_metric,title=Measure,category=_measure,fmt=$D,fmt_x=$X,fmt_y=$Y,position=auto,xalign=auto,yalign=auto,xlabel_xalign=auto,xlabel_yalign=auto,ylabel_xalign=auto,ylabel_yalign=auto,style=ruler,outline=diag,snap=true,angle_constraint=global;mode=normal,title=Ellipse,category='',fmt='',fmt_x='W=$(abs(X))',fmt_y='H=$(abs(Y))',position=auto,xalign=auto,yalign=auto,xlabel_xalign=auto,xlabel_yalign=auto,ylabel_xalign=auto,ylabel_yalign=auto,style=line,outline=ellipse,snap=true,angle_constraint=global;mode=normal,title=Box,category='',fmt='',fmt_x='W=$(abs(X))',fmt_y='H=$(abs(Y))',position=auto,xalign=auto,yalign=auto,xlabel_xalign=auto,xlabel_yalign=auto,ylabel_xalign=auto,ylabel_yalign=auto,style=line,outline=box,snap=true,angle_constraint=global;mode=normal,title=XSection,category=XS,fmt=XS,fmt_x='',fmt_y='',position=auto,xalign=auto,yalign=auto,xlabel_xalign=auto,xlabel_yalign=auto,ylabel_xalign=auto,ylabel_yalign=auto,style=arrow_both,outline=diag,snap=true,angle_constraint=global + -1 + + 5 + auto + 1 + true + false + 1 + true + 3 + + any-top + 1000 + 1000 + 1.0 + fit-marker + + false + true + true + true + true + false + false + true + false + false + 1000 + 1.0 + fit-marker + + true + + + false + + + false + auto + 0 + true + true + editor-mode=4,editor-mode=0 + + + false + true + false + all + + 1 + rdb + all + false + + + diff --git a/upw_config.json b/caravel_template/upw_config.json similarity index 100% rename from upw_config.json rename to caravel_template/upw_config.json diff --git a/caravel_template/upw_post.v b/caravel_template/upw_post.v new file mode 100644 index 0000000..cffc17f --- /dev/null +++ b/caravel_template/upw_post.v @@ -0,0 +1,4 @@ + // end of module instantiation + +endmodule // user_project_wrapper +`default_nettype wire diff --git a/caravel_template/upw_pre.v b/caravel_template/upw_pre.v new file mode 100644 index 0000000..91a0446 --- /dev/null +++ b/caravel_template/upw_pre.v @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: 2020 Efabless Corporation +// +// 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. +// SPDX-License-Identifier: Apache-2.0 + +`default_nettype none +/* + *------------------------------------------------------------- + * + * user_project_wrapper + * + * This wrapper enumerates all of the pins available to the + * user for the user project. + * + * An example user project is provided in this wrapper. The + * example should be removed and replaced with the actual + * user project. + * + * THIS FILE HAS BEEN GENERATED USING multi_tools_project CODEGEN + * IF YOU NEED TO MAKE EDITS TO IT, EDIT codegen/caravel_iface_header.txt + * + *------------------------------------------------------------- + */ + +module user_project_wrapper #( + parameter BITS = 32 +)( +`ifdef USE_POWER_PINS + inout vdda1, // User area 1 3.3V supply + inout vdda2, // User area 2 3.3V supply + inout vssa1, // User area 1 analog ground + inout vssa2, // User area 2 analog ground + inout vccd1, // User area 1 1.8V supply + inout vccd2, // User area 2 1.8v supply + inout vssd1, // User area 1 digital ground + inout vssd2, // User area 2 digital ground +`endif + + // Wishbone Slave ports (WB MI A) + input wb_clk_i, + input wb_rst_i, + input wbs_stb_i, + input wbs_cyc_i, + input wbs_we_i, + input [3:0] wbs_sel_i, + input [31:0] wbs_dat_i, + input [31:0] wbs_adr_i, + output wbs_ack_o, + output [31:0] wbs_dat_o, + + // Logic Analyzer Signals + input [127:0] la_data_in, + output [127:0] la_data_out, + input [127:0] la_oenb, + + // IOs + input [`MPRJ_IO_PADS-1:0] io_in, + output [`MPRJ_IO_PADS-1:0] io_out, + output [`MPRJ_IO_PADS-1:0] io_oeb, + + // Analog (direct connection to GPIO pad---use with caution) + // Note that analog I/O is not available on the 7 lowest-numbered + // GPIO pads, and so the analog_io indexing is offset from the + // GPIO indexing by 7 (also upper 2 GPIOs do not have analog_io). + inout [`MPRJ_IO_PADS-10:0] analog_io, + + // Independent clock (on independent integer divider) + input user_clock2, + + // User maskable interrupt signals + output [2:0] user_irq +); + + // start of module instantiation diff --git a/configure.py b/configure.py index 5d37861..5bd396b 100755 --- a/configure.py +++ b/configure.py @@ -13,16 +13,6 @@ from signal import signal, SIGPIPE, SIG_DFL signal(SIGPIPE, SIG_DFL) -""" -# if the project is a filler, then use the config from the first fill project -def gen_local_dir(self, fill_id): - if self.fill: - return os.path.join(os.path.join(self.project_dir, f'{fill_id :03}')) - else: - return os.path.join(os.path.join(self.project_dir, f'{self.index :03}')) -""" - - class Projects(): def __init__(self, config, args): diff --git a/documentation.py b/documentation.py index 2ead7e3..cc0c460 100644 --- a/documentation.py +++ b/documentation.py @@ -6,11 +6,12 @@ class Docs(): def __init__(self, projects, args): self.projects = projects self.args = args + self.script_dir = os.path.dirname(os.path.realpath(__file__)) # stuff related to docs def build_index(self): logging.info("building doc index") - with open("README_init.md") as fh: + with open(os.path.join(self.script_dir, 'docs', 'README_init.md')) as fh: readme = fh.read() with open("README.md", 'w') as fh: fh.write(readme) @@ -21,7 +22,10 @@ class Docs(): fh.write(project.get_index_row()) def update_image(self): - cmd = "klayout -l caravel.lyp gds/user_project_wrapper.gds -r dump_pic.rb -c klayoutrc" + ruby = os.path.join(self.script_dir, 'caravel_template', 'dump_pic.rb') + klayoutrc = os.path.join(self.script_dir, 'caravel_template', 'klayoutrc') + lyp = os.path.join(self.script_dir, 'caravel_template', 'caravel.lyp') + cmd = f'klayout -l {lyp} gds/user_project_wrapper.gds -r {ruby} -c {klayoutrc}' logging.info(cmd) os.system(cmd) @@ -32,25 +36,24 @@ class Docs(): design = project.get_yaml() designs.append(design) - with open(self.args.dump_json, "w") as fh: + with open(self.args.dump_json, 'w') as fh: fh.write(json.dumps(designs, indent=4)) logging.info(f'wrote json to {self.args.dump_json}') def dump_markdown(self): - - with open("doc_header.md") as fh: + with open(os.path.join(self.script_dir, 'docs', 'doc_header.md')) as fh: doc_header = fh.read() - with open("doc_template.md") as fh: + with open(os.path.join(self.script_dir, 'docs', 'doc_template.md')) as fh: doc_template = fh.read() - with open("INFO.md") as fh: + with open(os.path.join(self.script_dir, 'docs', 'INFO.md')) as fh: doc_info = fh.read() - with open("VERIFICATION.md") as fh: + with open(os.path.join(self.script_dir, 'docs', 'VERIFICATION.md')) as fh: doc_verification = fh.read() - with open("CREDITS.md") as fh: + with open(os.path.join(self.script_dir, 'docs', 'CREDITS.md')) as fh: doc_credits = fh.read() with open(self.args.dump_markdown, 'w') as fh: @@ -104,10 +107,10 @@ class Docs(): os.makedirs(hugo_root) os.makedirs(hugo_images) - with open("hugo_template.md") as fh: + with open(os.path.join(self.script_dir, 'docs', 'hugo_template.md')) as fh: doc_template = fh.read() - with open("hugo_index_template.md") as fh: + with open(os.path.join(self.script_dir, 'docs', 'hugo_index_template.md')) as fh: index_template = fh.read() # copy image From fd0d177a56d939572b25fab9f5f2e0e70c83c8e4 Mon Sep 17 00:00:00 2001 From: matt venn Date: Thu, 16 Mar 2023 15:55:52 +0100 Subject: [PATCH 10/45] move macro pos to config.yaml --- caravel.py | 42 ++++++++++++++---------------------------- config.yaml | 21 +++++++++++++++++++++ configure.py | 2 +- 3 files changed, 36 insertions(+), 29 deletions(-) diff --git a/caravel.py b/caravel.py index a0b3519..d27802a 100644 --- a/caravel.py +++ b/caravel.py @@ -5,7 +5,8 @@ import os class CaravelConfig(): - def __init__(self, projects, num_projects): + def __init__(self, config, projects, num_projects): + self.config = config self.projects = projects self.num_projects = num_projects self.script_dir = os.path.dirname(os.path.realpath(__file__)) @@ -13,42 +14,27 @@ class CaravelConfig(): # create macro file & positions, power hooks def create_macro_config(self): # array size - rows = 18 - cols = 14 - - # start point (lower left) - start_x = 50 - start_y = 95 - - # module block sizes - scanchain_w = 30 - scanchain_spc = 6 - module_w = 150 - module_h = 170 - - # how much x & y space to leave between blocks - space_x = 15 - space_y = 15 + c = self.config['layout'] # step sizes - step_x = scanchain_w + module_w + scanchain_spc + space_x - step_y = module_h + space_y + step_x = c['scanchain_w'] + c['module_w'] + c['scanchain_spc'] +c['space_x'] + step_y = c['module_h'] + c['space_y'] - logging.info(f"start_x {start_x} start_y {start_y} step_x {step_x} step_y {step_y }") + logging.info(f"start_x {c['start_x']} start_y {c['start_y']} step_x {step_x} step_y {step_y}") num_macros_placed = 0 # macro.cfg: where macros are placed logging.info("creating macro.cfg") with open("openlane/user_project_wrapper/macro.cfg", 'w') as fh: - fh.write("scan_controller 100 100 N\n") - for row in range(rows): + fh.write(f"scan_controller {c['scan_control_x']} {c['scan_control_y']} N\n") + for row in range(c['rows']): if row % 2 == 0: - col_order = range(cols) + col_order = range(c['cols']) orientation = 'N' else: # reverse odd rows to place instances in a zig zag pattern, shortening the scan chain wires - col_order = range(cols - 1, -1, -1) + col_order = range(c['cols'] - 1, -1, -1) orientation = 'S' for col in col_order: # skip the space where the scan controller goes on the first row @@ -62,26 +48,26 @@ class CaravelConfig(): # co-ords are bottom left corner macro_instance = self.projects[num_macros_placed].get_scanchain_instance() instance = "{} {:<4} {:<4} {}\n".format( - macro_instance, start_x + col * step_x, start_y + row * step_y, orientation + macro_instance, c['start_x'] + col * step_x, c['start_y'] + row * step_y, orientation ) fh.write(instance) macro_instance = self.projects[num_macros_placed].get_macro_instance() instance = "{} {:<4} {:<4} {}\n".format( - macro_instance, start_x + scanchain_spc + scanchain_w + col * step_x, start_y + row * step_y, orientation + macro_instance, c['start_x'] + c['scanchain_spc'] + c['scanchain_w'] + col * step_x, c['start_y'] + row * step_y, orientation ) fh.write(instance) else: # macro first macro_instance = self.projects[num_macros_placed].get_macro_instance() instance = "{} {:<4} {:<4} {}\n".format( - macro_instance, start_x + col * step_x, start_y + row * step_y, orientation + macro_instance, c['start_x'] + col * step_x, c['start_y'] + row * step_y, orientation ) fh.write(instance) macro_instance = self.projects[num_macros_placed].get_scanchain_instance() instance = "{} {:<4} {:<4} {}\n".format( - macro_instance, start_x + module_w + scanchain_spc + col * step_x, start_y + row * step_y, orientation + macro_instance, c['start_x'] + c['module_w'] + c['scanchain_spc'] + col * step_x, c['start_y'] + row * step_y, orientation ) fh.write(instance) diff --git a/config.yaml b/config.yaml index 0a52afc..d982b0d 100644 --- a/config.yaml +++ b/config.yaml @@ -4,3 +4,24 @@ filler_project: https://github.com/TinyTapeout/tt03-test-invert project_dir: 'projects' tmp_dir: '/tmp/tt' num_projects: 250 + +layout: + rows: 18 + cols: 14 + + # start point (lower left) + start_x: 50 + start_y: 95 + + # module block sizes + scanchain_w: 30 + scanchain_spc: 6 + module_w: 150 + module_h: 170 + + # how much x & y space to leave between blocks + space_x: 15 + space_y: 15 + + scan_control_x: 100 + scan_control_y: 100 diff --git a/configure.py b/configure.py index 5bd396b..6e77c14 100755 --- a/configure.py +++ b/configure.py @@ -229,7 +229,7 @@ if __name__ == '__main__': projects = Projects(config, args) docs = Docs(projects.projects, args=args) - caravel = CaravelConfig(projects.projects, num_projects=args.limit_num_projects) + caravel = CaravelConfig(config, projects.projects, num_projects=args.limit_num_projects) if args.list: caravel.list() From e4443e21b9a006349125b98cafdd73d9dd052122 Mon Sep 17 00:00:00 2001 From: matt venn Date: Fri, 17 Mar 2023 09:59:09 +0100 Subject: [PATCH 11/45] missing reqs --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index 0d111e1..f59eebd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,6 @@ gdstk +stripe +GitPython cairosvg requests PyYAML From 2317354dd3c813f89fedc7d38fa9512c0bf0c44f Mon Sep 17 00:00:00 2001 From: matt venn Date: Fri, 17 Mar 2023 10:11:58 +0100 Subject: [PATCH 12/45] pytz req --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index f59eebd..bcbeda5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +pytz gdstk stripe GitPython From ef41c5f91312e2d2aea47c76a0128c9627e987b2 Mon Sep 17 00:00:00 2001 From: matt venn Date: Fri, 17 Mar 2023 10:20:49 +0100 Subject: [PATCH 13/45] update links for tt03 --- docs/datasheet.md | 709 --------------------------------------------- docs/doc_header.md | 2 +- docs/hardening.md | 2 +- 3 files changed, 2 insertions(+), 711 deletions(-) delete mode 100644 docs/datasheet.md diff --git a/docs/datasheet.md b/docs/datasheet.md deleted file mode 100644 index 295826e..0000000 --- a/docs/datasheet.md +++ /dev/null @@ -1,709 +0,0 @@ ---- -title: | - ![](ttlogo.png){width=15cm} - Tiny Tapeout 02 Datasheet -subtitle: Project Repository [https://github.com/TinyTapeout/tinytapeout-02](https://github.com/TinyTapeout/tinytapeout-02) -documentclass: scrartcl -date: \today{} -geometry: margin=2cm -fontsize: 14pt -mainfont: Latin Modern Sans -header-includes: -- \hypersetup{colorlinks=false, - allbordercolors={0 0 0}, - pdfborderstyle={/S/U/W 1}} ---- - -\pagebreak - -# Render of whole chip - -![Full GDS](tinytapeout.png) - -\pagebreak - -# Projects -## 0 : Test Inverter Project - - - -* Author: Matt Venn -* Description: Inverts every line. This project is also used to fill any empty design spaces. -* [GitHub repository](https://github.com/TinyTapeout/tt03-test-invert) -* [Wokwi](https://wokwi.com/projects/357464855584307201) project -* [Extra docs]() -* Clock: 0 Hz -* External hardware: - -### How it works - -Uses 8 inverters to invert every line. - - -### How to test - -Setting the input switch to on should turn the corresponding LED off. - - -### IO - -| # | Input | Output | -|---|--------------|--------------| -| 0 | a | segment a | -| 1 | b | segment b | -| 2 | c | segment c | -| 3 | d | segment d | -| 4 | e | segment e | -| 5 | f | segment f | -| 6 | g | segment g | -| 7 | dot | dot | - -\pagebreak -## 2 : 7 Segment Life - - - -* Author: icegoat9 -* Description: Simple 7-segment cellular automaton -* [GitHub repository](https://github.com/icegoat9/tinytapeout03-7seglife) -* [Wokwi](https://wokwi.com/projects/357752736742764545) project -* [Extra docs](https://github.com/icegoat9/tinytapeout03-7seglife/blob/main/README.md) -* Clock: 0 Hz -* External hardware: None. Could add debounced momentary pushbuttons in parallel with dip switches 1,2,3 to make loading in new patterns and stepping through a run easier. - -### How it works - -See the [Wokwi gate layout and simulation](https://wokwi.com/projects/357752736742764545). At a high level... - -* Seven flip-flops hold the cellular automaton's internal state, which is also displayed in the seven-segment display. -* Combinatorial logic generates the next state for each segment based on its neighbors, according to the ruleset... - * Living segments with exactly one living neighbor (another segment that touches it end to end) survive, and all others die. - * Dead segments with exactly two living neighbors come to life. -* When either the system clock or a user toggling the clock input go high, this new state is latched into the automaton's state. -* There's minor additional support logic to let the user manually shift in an initial condition and handle clock dividing. - - -### How to test - -For full details and a few 'exercises for the reader', see the [github README](https://github.com/icegoat9/tinytapeout03-7seglife) doc link. But at a high level, assuming the IC is mounted on the standard tinytapeout PCB which provides dip switches, clock, and a seven-segment display for output... - -* Set all dip switches off and the clock slide switch to the 'manual' clock side. -* Power on the system. An arbitrary state may appear on the 7-segment display. -* Set dip switch 4 on ('run mode'). -* Toggle dip switch 1 on and off to advance the automaton to the next state, you should see the 7-segment display update. - -If you want to watch it run automatically (which may quickly settle on an empty state or a static pattern, depending on start state)... - -* Set the PCB clock divider to the maximum clock division (255). With a system clock of 6.25kHz, the clock input should now be ~24.5Hz. -* Set dip switches 5 and 7 on to add a reasonable additional clock divider (see docs for more details on a higher or lower divider). -* Set dip switch 4 on. -* Switch the clock slide switch to the 'system clock' side. The display should advance at roughly 1.5Hz if I've done math correctly. - -If you want to load an initial state... - -* Set dip switch 4 off ('load mode'). -* Toggle dip switches 2 and/or 3 on and off seven times total, to shift in 0 and 1 values to the automaton internal state. -* Set dip switch 4 on and run manually or automatically as above. - - -### IO - -| # | Input | Output | -|---|--------------|--------------| -| 0 | clock | 7segmentA | -| 1 | load0 | 7segmentB | -| 2 | load1 | 7segmentC | -| 3 | runmode | 7segmentD | -| 4 | clockdiv8 | 7segmentE | -| 5 | clockdiv4 | 7segmentF | -| 6 | clockdiv2 | 7segmentG | -| 7 | unused | 7segmentDP | - -\pagebreak -## 3 : Another Piece of Pi - - - -* Author: Meinhard Kissich, EAS Group, Graz University of Technology -* Description: This design takes up the idea of James Ross [1], who submitted a circuit to Tiny Tapeout 02 that stores and outputs the first 1024 decimal digits of the number Pi (including the decimal point) to a 7-segment display. In contrast to his approach, a densely packed decimal encoding is used to store the data. With this approach, 1400 digits can be stored and output within the design area of 150um x 170um. However, at 1400 decimals and utilization of 38.99%, the limitation seems to be routing. Like James, I'm also interested to hear about better strategies to fit more information into the design with synthesizable Verilog code. [1] https://github.com/jar/tt02_freespeech -* [GitHub repository](https://github.com/meiniKi/tt03-another-piece-of-pi) -* HDL project -* [Extra docs]() -* Clock: 0 Hz -* External hardware: 7-segment display - -### How it works - -The circuit stores each triplet of decimals in a 10-bit vector encoded as densely packed decimals. An index vector selects the current digits to be output to the 7-segment display. It consists of an upper part `index[11:2]` that selects the triplet and a lower part `index[1:0]` that specifies the digit within the triplet. First, the upper part decides on the triplet, which is then decoded into three decimals. Afterwards, the lower part selects one of the three decimals to be decoded into 7-segment display logic and applied to the outputs. The index is incremented at each primary clock edge. However, when the lower part equals three, i.e., `index[1:0]==1'b10`, two is added, as the triplet consists of three (not four) digits. - -- `index == 'b0000000000|00`: triplet[0], digit 0 within triplet -- `index == 'b0000000000|01`: triplet[0], digit 1 within triplet -- `index == 'b0000000000|10`: triplet[0], digit 2 within triplet -- `index == 'b0000000001|00`: triplet[1], digit 0 within triplet -- `index == 'b0000000001|01`: triplet[1], digit 1 within triplet -- `index == 'b0000000001|10`: triplet[1], digit 2 within triplet - -There is one exception to the rule above: the decimal point. Another multiplexer at the input of the 7-segment decoder can either forward a digit from the decoded tripled or a constant -- the decimal point. Once the lower part of the index counter, i.e., `index[1:0]` reaches `2'b10` for the first time, the multiplexer selects the decimal point and pauses incrementing the index for one clock cycle. - -- `index == 'b0000000000|00`: triplet[0], digit 0 within triplet -- `index == 'b0000000000|01`: triplet[0], decimal point -- `index == 'b0000000000|01`: triplet[0], digit 1 within triplet -- `index == 'b0000000000|10`: triplet[0], digit 2 within triplet -- `index == 'b0000000001|00`: triplet[1], digit 0 within triplet - - -### How to test - -For simulation, please use the provided testbench and Makefile. It is important to run the `genmux.py` Python script first, as it generates the test vectors required by the Verilog testbench. For testing the physical chip, release the reset and compare the digits of Pi against a reference. - - -### IO - -| # | Input | Output | -|---|--------------|--------------| -| 0 | clk | segment a | -| 1 | reset | segment b | -| 2 | none | segment c | -| 3 | none | segment d | -| 4 | none | segment e | -| 5 | none | segment f | -| 6 | none | segment g | -| 7 | none | decimal LED | - -\pagebreak -## 4 : Wormy - - - -* Author: nqbit -* Description: MC Wormy Pants squirms like a worm and grows just as fast. -* [GitHub repository](https://github.com/nqbit/wormy) -* HDL project -* [Extra docs](https://github.com/nqbit/wormy) -* Clock: 300 Hz -* External hardware: - -### How it works - -Wormy is a very simple, addictive last person video game. This last person, -open-world game takes you down the path of an earthworm. Wormy's world is -made up of a 4x4 grid represented by 3x16-bit arrays: Direction[0], -Direction[1], and Occupied. The Direction[x] maps keep track of which way a -segment of worm moves, if it is on, and Occupied keeps track of if the grid -location is occupied. - -Example Occupied Grid: - -``` - _________ - | X X X X | - | X | - | X | - |_________| -``` - -In addition to direction and occupied there are also pointers to the head -and the tail. The same grid would look something like the following: - -Example Occupied Grid with head(H) and tail(T) highlighted: - -``` - _________ - | T X X X | - | X | - | H | - |_________| -``` - -BOOM! Wormy shouldn't run into itself. If its head hits any part of its -body, that causes a collision. There is a collision if the location of the -Wormy's head is occupied by another segment of the Wormy. To determine -this, we keep track of the worm location, and specifically the current and -future locations of the worm head (H) and tail (T). If the future location -of H will occupy a location that will already be occupied, this causes a -collision. - -This is made a bit trickier with growth (see below), because if the future -state of H is set to occupy the current state of T - there is only a -collision if growth is set to occur in the next cycle, so be careful if you -plan to have Wormy chase its tail! SPLAT! - -NOM NOM! Wormy eats the tasty earth around it and grows. Every so often -Wormy, after having eaten all of its lunch, grows a whole segment. During a -growth cycle, the state of T simply persists (remains occupied and heading -in the same direction as it is). - -Last Person Input - see user_input.v -To move Wormy along, the last player needs to push buttons to help Wormy -find more tasty earth: up, right, down or left. - -Buttons are nasty little bugs in general. When pushed the button generates -an analog signal that might not look exactly like a single rising edge. - -It might look something like this: - -``` - xxxxxxxxxxxxxxxxx - xx - x - x - x x - x x - x x - x x - x xx x x - x xxxx x - x x xx x - x x x x - x x x x - xx x x x - xxx x x x - x xx x x x - x x x x x - x x x xx x - x xx x x - xxxxxxxxxxxxxxxxxxx x -``` - -In order to help protect our logic from this scary looking signal that -might introduce metastability (<- WHAT?), we can filter it with a couple -flippy-floppies and keep the metastability at bay. ARGH. - -Once we have a clear pushed or not-pushed, we can suggest that Wormy move -in a specific direction. If the last player tries button mashing, Wormy -won't listen. Once a second Wormy checks what the last button press was and -tries really hard to go that way (see BOOM!). - -Earthworms don't have eyes - see multiplexer.v -The game's display is made up of a 4x4 grid of LEDs controlled by a -multiplexer. Why multiplexing? With a multiplexed LED setup, we can control -more display units (LEDs), with a limited number of outputs (8 on this -TinyTapeout project). - -To get multiplexing working the network of outputs is mapped to each -display unit. This allows us to manipulate assigned outputs to control the -state of each display unit, one at a time. We then cycle through each -display unit quickly enough to display a persistent image to the last -player. - -Wires (A1-4, B1-4) map to each location on the game arena (4x4 grid): - -``` - A1| | | | - __________________ - A2| | | | - __________________ - A3| | | | - __________________ - A4| | | | - __________________ - |B1 |B2 |B3 |B4 -``` - -When B's voltage is OFF the LED's state changes to ON if A is also ON. - - ON LED state: A(ON) — >| — B(OFF) - - OFF LED state: A(ON) — >| — B(ON) - -Example: The 3 filled squares below each represent a Wormy segment in the -ON state as controlled by the multiplexer. Notice how each is lighter than -the last. This is because the multiplexer cycles through each LED to update -the state, creating one persistent image even though the LEDS are not on -over the entire period of time. - -``` - A1| | O | | - __________________ - A2| | o | | - __________________ - A3| | . | | - __________________ - A4| | | | - __________________ - |B1 |B2 |B3 |B4 -``` - -Another Example: If you enter a dark cave and point a flashlight straight -ahead at one point on the wall you have a very small visual field that is -contained within the beam of light. However, you can expand your visual -field in the cave by waving the flashlight back and forth across the wall. -Despite the fact that the beam is moving over individual points on the -wall, the entire wall can be seen at once. This is similar to the concept -used in the Wormy display, since the multiplexer changes the state of the -worm occupied locations to ON one at a time, but in a cycle. The result is -a solid image, made up of LEDs cycling through ON states to produce a -persistent image of Wormy (that beautiful Lumbricina). - - -### How to test - -After reset, you should see a single pixel moving along the display and -it should grow every now and then. - - -### IO - -| # | Input | Output | -|---|--------------|--------------| -| 0 | clock | A0 - Multiplexer channel A to be tied to a an array of 16 multiplexed LEDs | -| 1 | reset | A1 | -| 2 | button0 | A2 | -| 3 | button1 | A3 | -| 4 | button2 | B0 - Multiplexer channel B to be tied to a an array of 16 multiplexed LEDs | -| 5 | button3 | B1 | -| 6 | none | B2 | -| 7 | none | B3 | - -\pagebreak -## 5 : Knight Rider Sensor Lights - -![picture](projects/005/KITT.jpg) - -* Author: Kolos Koblasz -* Description: The logic assertes output bits one by one, like KITT's sensor lights in Knight Rider. -* [GitHub repository](https://github.com/KolosKoblasz/tt03-knight_rider) -* HDL project -* [Extra docs]() -* Clock: 6000 Hz -* External hardware: Conect LEDs with ~1K-10K Ohm serial resistors to output pins and connect push button switches to Input[2] and Input[3] which drive the inputs with logic zeros when idle and with logic 1 when pressed. Rising edge on these inputs selects the next settings. - -### How it works - -Uses several counters, shiftregisters to create a moving light. -Input[2] and Input[3] can control speed and brightness respectively. - - -### How to test - -After reset it starts moving the switched on LED. -By creating rising edges on Input[2] and Input[3] the two config spaces can be discovered. Input[0] is clk Input[1] is reset (1=reset on, 0=Reset off). -Simulated with 6KHz clock signal. - - -### IO - -| # | Input | Output | -|---|--------------|--------------| -| 0 | clock | LED 0 | -| 1 | reset | LED 1 | -| 2 | speed control | LED 2 | -| 3 | brightness control | LED 3 | -| 4 | none | LED 4 | -| 5 | none | LED 5 | -| 6 | none | LED 6 | -| 7 | none | LED 7 | - -\pagebreak -# Technical info - -## Scan chain - -All 250 designs are joined together in a long chain similiar to JTAG. We provide the inputs and outputs of that chain (see pinout below) externally, to the Caravel logic analyser, and to an internal scan chain driver. - -The default is to use an external driver, this is in case anything goes wrong with the Caravel logic analyser or the internal driver. - -The scan chain is identical for each little project, and you can [read it here](https://github.com/mattvenn/wokwi-verilog-gds-test/blob/main/template/scan_wrapper.v). - -![block diagram](pics/block_diagram.png) - -### Updating inputs and outputs of a specified design - -A good way to see how this works is to read the FSM in the [scan controller](verilog/rtl/scan_controller/scan_controller.v). -You can also run one of the simple tests and check the waveforms. See how in the [scan chain verification](verification.md) doc. - -* Signal names are from the perspective of the scan chain driver. -* The desired project shall be called DUT (design under test) - -Assuming you want to update DUT at position 2 (0 indexed) with inputs = 0x02 and then fetch the output. -This design connects an inverter between each input and output. - -* Set scan_select low so that the data is clocked into the scan flops (rather than from the design) -* For the next 8 clocks, set scan_data_out to 0, 0, 0, 0, 0, 0, 1, 0 -* Toggle scan_clk_out 16 times to deliver the data to the DUT -* Toggle scan_latch_en to deliver the data from the scan chain to the DUT -* Set scan_select high to set the scan flop's input to be from the DUT -* Toggle the scan_clk_out to capture the DUT's data into the scan chain -* Toggle the scan_clk_out another 8 x number of remaining designs to receive the data at scan_data_in - -![update cycle](pics/update_cycle.png) - -*Notes on understanding the trace* - -* There are large wait times between the latch and scan signals to ensure no hold violations across the whole chain. For the internal scan controller, these can be configured (see section on wait states below). -* The input looks wrong (0x03) because the input is incremented by the test bench as soon as the scan controller captures the data. The input is actually 0x02. -* The output in the trace looks wrong (0xFE) because it's updated after a full refresh, the output is 0xFD. - -## Clocking - -Assuming: - -* 100MHz input clock -* 8 ins & 8 outs -* 2 clock cycles to push one bit through the scan chain (scan clock is half input clock rate) -* 250 designs -* scan controller can do a read/write cycle in one refresh - -So the max refresh rate is 100MHz / (8 * 2 * 250) = 25000Hz. - -## Clock divider - -A rising edge on the set_clk_div input will capture what is set on the input pins and use this as a divider for an internal slow clock that can be provided to the first input bit. - -The slow clock is only enabled if the set_clk_div is set, and the resulting clock is connected to input0 and also output on the slow_clk pin. - -The slow clock is synced with the scan rate. A divider of 0 mean it toggles the input0 every scan. Divider of 1 toggles it every 2 cycles. -So the resultant slow clock frequency is scan_rate / (2 * (N+1)). - -See the test_clock_div test in the [scan chain verification](verification.md). - -## Wait states - -This dictates how many wait cycle we insert in various state -of the load process. We have a sane default, but also allow -override externally. - -To override, set the wait amount on the inputs, set the driver_sel inputs both high, and then reset the chip. - -See the test_wait_state test in the [scan chain verification](verification.md). - -## Pinout - - PIN NAME DESCRIPTION - 20:12 active_select 9 bit input to set which design is active - 28:21 inputs 8 inputs - 36:29 outputs 8 outputs - 37 ready goes high for one cycle everytime the scanchain is refreshed - 10 slow_clk slow clock from internal clock divider - 11 set_clk_div enable clock divider - 9:8 driver_sel which scan chain driver: 00 = external, 01 = logic analyzer, 1x = internal - - 21 ext_scan_clk_out for external driver, clk input - 22 ext_scan_data_out data input - 23 ext_scan_select scan select - 24 ext_scan_latch_en latch - 29 ext_scan_clk_in clk output from end of chain - 30 ext_scan_data_in data output from end of chain - -## Instructions to build GDS - -To run the tool locally or have a fork's GitHub action work, you need the GH_USERNAME and GH_TOKEN set in your environment. - -GH_USERNAME should be set to your GitHub username. - -To generate your GH_TOKEN go to https://github.com/settings/tokens/new . Set the checkboxes for repo and workflow. - -To run locally, make a file like this: - - export GH_USERNAME= - export GH_TOKEN= - -And then source it before running the tool. - -### Fetch all the projects - -This goes through all the projects in project_urls.py, and fetches the latest artifact zip from GitHub. It takes the verilog, the GL verilog, and the GDS and copies -them to the correct place. - - ./configure.py --clone-all --fetch-gds - -### Configure Caravel - -Caravel needs the list of macros, how power is connected, instantiation of all the projects etc. This command builds these configs and also makes the README.md index. - - ./configure.py --update-caravel - -### Build the GDS - -To build the GDS and run the simulations, you will need to install the Sky130 PDK and OpenLane tool. -It takes about 5 minutes and needs about 3GB of disk space. - - export PDK_ROOT=/pdk - export OPENLANE_ROOT=/openlane - cd - make setup - -Then to create the GDS: - - make user_project_wrapper - -## Changing macro block size - -After working out what size you want: - -* adjust configure.py in `CaravelConfig.create_macro_config()`. -* adjust the PDN spacing to match in openlane/user_project_wrapper/config.tcl: - * ```set ::env(FP_PDN_HPITCH)``` - * ```set ::env(FP_PDN_HOFFSET)``` - - -\pagebreak -# Verification - -We are not trying to verify every single design. That is up to the person who makes it. What we want is to ensure that every design is accessible, even if some designs are broken. - -We can split the verification effort into functional testing (simulation), static tests (formal verification), timing tests (STA) and physical tests (LVS & DRC). - -See the sections below for details on each type of verification. - -## Setup - -You will need the GitHub tokens setup as described in [INFO](INFO.md#instructions-to-build-gds). - -The default of 250 projects takes a very long time to simulate, so I advise overriding the configuration: - - # fetch the test projects - ./configure.py --test --clone-all - # rebuild config with only 20 projects - ./configure.py --test --update-caravel --limit 20 - -You will also need iVerilog & cocotb. The easist way to install these are to download and install the [oss-cad-suite](https://github.com/YosysHQ/oss-cad-suite-build). - -## Simulations - -* Simulation of some test projects at RTL and GL level. -* Simulation of the whole chip with scan controller, external controller, logic analyser. -* Check wait state setting. -* Check clock divider setting. - -### Scan controller - -This test only instantiates user_project_wrapper (which contains all the small projects). It doesn't simulate the rest of the ASIC. - - cd verilog/dv/scan_controller - make test_scan_controller - -The Gate Level simulation requires scan_controller and user_project_wrapper to be re-hardened to get the correct gate level netlists: - -* Edit openlane/scan_controller/config.tcl and change NUM_DESIGNS=250 to NUM_DESIGNS=20. -* Then from the top level directory: - - - make scan_controller - make user_project_wrapper - - -* Then run the GL test - - - cd verilog/dv/scan_controller - make test_scan_controller_gl - - -#### single - -Just check one inverter module. Mainly for easy understanding of the traces. - - make test_single - -#### custom wait state - -Just check one inverter module. Set a custom wait state value. - - make test_wait_state - -#### clock divider - -Test one inverter module with an automatically generated clock on input 0. Sets the clock rate to 1/2 of the scan refresh rate. - - make test_clock_div - -## Top level tests setup - -For all the top level tests, you will also need a [RISCV compiler to build the firmware](https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.3.0-2020.04.1-x86_64-linux-ubuntu14.tar.gz). - -You will also need to install the 'management core' for the Caravel ASIC submission wrapper. This is done automatically by following the [PDK install instructions](INFO.md#build-the-gds). - -### Top level test: internal control - -Uses the scan controller, instantiated inside the whole chip. - - cd verilog/dv/scan_controller_int - make coco_test - -### Top level test: external control - -Uses external signals to control the scan chain. Simulates the whole chip. - - cd verilog/dv/scan_controller_ext - make coco_test - -### Top level test: logic analyser control - -Uses the RISCV co-processor to drive the scanchain with firmware. Simulates the whole chip. - - cd verilog/dv/scan_controller_la - make coco_test - -## Formal Verification - -* Formal verification that each small project's scan chain is correct. -* Formal verification that the correct signals are passed through for the 3 different scan chain control modes. - -### Scan chain - -Each GL netlist for each small project is proven to be equivalent to the reference scan chain implementation. -The verification is done on the GL netlist, so an RTL version of the cells used needed to be created. -See [here for more info](tinytapeout_scan/README.md). - -### Scan controller MUX - -In case the internal scan controller doesn't work, we also have ability to control the chain from external pins or the Caravel Logic Analyser. -We implement a simple MUX to achieve this and [formally prove it is correct](verilog/rtl/scan_controller/properties.v). - -## Timing constraints - -Due to limitations in OpenLane - a top level timing analyis is not possible. This would allow us to detect setup and hold violations in the scan chain. - -Instead, we design the chain and the timing constraints for each project and the scan controller with this in mind. - -* [Each small project has a negedge flop flop at the end of the shift register to reclock the data](https://github.com/mattvenn/wokwi-verilog-gds-test/blob/17f106db36f022536d013b960316bcc7f02c572c/template/scan_wrapper.v#L67). This gives more hold margin. -* [Each small project has SDC timing constraints](https://github.com/mattvenn/wokwi-verilog-gds-test/blob/main/src/base.sdc) -* [Scan controller](https://github.com/mattvenn/tinytapeout-mpw7/blob/aacae16304f4a4878943a49fd479d8a284736e32/verilog/rtl/scan_controller/scan_controller.v#L334) uses a shift register clocked with the end of the chain to ensure correct data is captured. -* [Scan controller has its own SDC timing constraints](openlane/scan_controller/base.sdc) -* Scan controller can be configured to wait for a programmable time at latching data into the design and capturing it from the design. -* External pins (by default) control the scan chain. - -## Physical tests - -* LVS -* DRC -* CVC - -### LVS - -Each project is built with OpenLane, which will check LVS for each small project. -Then when we combine all the projects together we run a top level LVS & DRC for routing, power supply and macro placement. - -The extracted netlist from the GDS is what is used in the formal scan chain proof. - -### DRC - -DRC is checked by OpenLane for each small project, and then again at the top level when we combine all the projects. - -### CVC - -Mitch Bailey' CVC checker is a device level static verification system for quickly and easily detecting common circuit errors in CDL (Circuit Definition Language) netlists. We ran the test on the final design and found no errors. - -* See [the paper here](https://woset-workshop.github.io/PDFs/2020/a05-slides.pdf). -* Github repo for the tool: https://github.com/d-m-bailey/cvc - -\pagebreak -# Sponsored by - -[![efabless](efabless.png)](https://efabless.com/) - -# Team - -Tiny Tapeout would not be possible without a lot of people helping. We would especially like to thank: - -* Uri Shaked for [wokwi](https://wokwi.com/) development and lots more -* [Sylvain Munaut](https://twitter.com/tnt) for help with scan chain improvements -* [Mike Thompson](https://www.linkedin.com/in/michael-thompson-0a581a/) for verification expertise -* [Jix](https://twitter.com/jix_) for formal verification support -* [Proppy](https://twitter.com/proppy) for help with GitHub actions -* [Maximo Balestrini](https://twitter.com/maxiborga) 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](/runs/tt01) and volunteered time to improve docs and test the flow -* The team at [YosysHQ](https://www.yosyshq.com/) and all the other open source EDA tool makers -* [Efabless](https://efabless.com/) for running the shuttles and providing OpenLane and sponsorship -* [Tim Ansell and Google](https://www.youtube.com/watch?v=EczW2IWdnOM) for supporting the open source silicon movement -* [Zero to ASIC course](https://zerotoasiccourse.com/) community for all your support diff --git a/docs/doc_header.md b/docs/doc_header.md index aad5897..170b0d6 100644 --- a/docs/doc_header.md +++ b/docs/doc_header.md @@ -2,7 +2,7 @@ title: | ![](ttlogo.png){width=15cm} Tiny Tapeout 03 Datasheet -subtitle: Project Repository [https://github.com/TinyTapeout/tinytapeout-02](https://github.com/TinyTapeout/tinytapeout-02) +subtitle: Project Repository [https://github.com/TinyTapeout/tinytapeout-03](https://github.com/TinyTapeout/tinytapeout-03) documentclass: scrartcl date: \today{} geometry: margin=2cm diff --git a/docs/hardening.md b/docs/hardening.md index 6d71a04..1ebd97b 100644 --- a/docs/hardening.md +++ b/docs/hardening.md @@ -1,6 +1,6 @@ # To rebuild all gds -Build notes are also in the tt-02-build of discord +Build notes are also in the tt-03-build of discord On git tag tapeout From 467dbb3d60df4a4ab61712c797347049ba9478bd Mon Sep 17 00:00:00 2001 From: matt venn Date: Fri, 17 Mar 2023 11:39:34 +0100 Subject: [PATCH 14/45] lint --- caravel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/caravel.py b/caravel.py index d27802a..3581584 100644 --- a/caravel.py +++ b/caravel.py @@ -17,8 +17,8 @@ class CaravelConfig(): c = self.config['layout'] # step sizes - step_x = c['scanchain_w'] + c['module_w'] + c['scanchain_spc'] +c['space_x'] - step_y = c['module_h'] + c['space_y'] + step_x = c['scanchain_w'] + c['module_w'] + c['scanchain_spc'] + c['space_x'] + step_y = c['module_h'] + c['space_y'] logging.info(f"start_x {c['start_x']} start_y {c['start_y']} step_x {step_x} step_y {step_y}") From 686b76bf2bdc54bab70df31eb8646955fcdfe216 Mon Sep 17 00:00:00 2001 From: Matt Venn Date: Fri, 17 Mar 2023 14:40:56 +0100 Subject: [PATCH 15/45] handle --test --- configure.py | 18 +++++++++++++++--- project.py | 2 -- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/configure.py b/configure.py index 6e77c14..c0d0abd 100755 --- a/configure.py +++ b/configure.py @@ -17,13 +17,20 @@ class Projects(): def __init__(self, config, args): self.args = args - self.project_dir = config['project_dir'] self.config = config + + if args.test: + self.projects_file = 'test_projects.yaml' + self.project_dir = config['test_project_dir'] + else: + self.projects_file = 'projects.yaml' + self.project_dir = config['project_dir'] + if not os.path.exists(self.project_dir): os.makedirs(self.project_dir) try: - with open('projects.yaml') as fh: + with open(self.projects_file) as fh: self.project_config = yaml.safe_load(fh) except FileNotFoundError: logging.error("projects.yaml not found, create it with --update-orders") @@ -87,11 +94,16 @@ class Projects(): project.pull() # projects should now be installed, so load all the data from the yaml files - logging.debug("post clone setup") # fill projects will load from the fill project's directory + logging.debug("post clone setup") project.post_clone_setup() logging.debug(project) + # fetch the wokwi source + if args.clone_all: + if not project.is_fill() and project.is_wokwi(): + project.fetch_wokwi_files() + if args.harden: if filler is False: project.harden() diff --git a/project.py b/project.py index b14d341..cf38671 100644 --- a/project.py +++ b/project.py @@ -296,8 +296,6 @@ class Project(): git_utils.fetch_file(url, os.path.join(self.local_dir, "src", diagram_file)) def create_user_config(self): - self.fetch_wokwi_files() - logging.info("creating include file") filename = 'user_config.tcl' with open(os.path.join(self.local_dir, 'src', filename), 'w') as fh: From aea70c98dc9b28ebe0e8bc0e18e8e75fc6dc7e19 Mon Sep 17 00:00:00 2001 From: Matt Venn Date: Fri, 17 Mar 2023 16:36:57 +0100 Subject: [PATCH 16/45] move pics to ./pics --- caravel_template/dump_pic.rb | 2 +- caravel_template/klayoutrc | 2 +- docs/CREDITS.md | 2 +- docs/doc_header.md | 4 ++-- project.py | 5 ++++- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/caravel_template/dump_pic.rb b/caravel_template/dump_pic.rb index 7bdf5c9..c486396 100644 --- a/caravel_template/dump_pic.rb +++ b/caravel_template/dump_pic.rb @@ -1 +1 @@ -RBA::Application.instance.main_window.current_view.save_image("./tinytapeout.png",2000,2000) +RBA::Application.instance.main_window.current_view.save_image("./pics/tinytapeout.png",2000,2000) diff --git a/caravel_template/klayoutrc b/caravel_template/klayoutrc index f08f5ef..d480f0f 100644 --- a/caravel_template/klayoutrc +++ b/caravel_template/klayoutrc @@ -152,7 +152,7 @@ 16 0 - '/home/matt/work/asic-workshop/shuttle6/zero_to_asic_mpw6/gds/user_project_wrapper.gds.gz' '/home/matt/work/asic-workshop/shuttle7/zero_to_asic_mpw7/gds/user_project_wrapper.gds.gz' '/home/matt/work/asic-workshop/shuttle7/tinytapeout-02/gds/user_project_wrapper.gds' '/home/matt/work/asic-workshop/shuttle8/tinytapeout-02/gds/user_project_wrapper.gds' '/home/matt/work/asic-workshop/shuttle9/caravel_user_project/gds/user_project_wrapper.gds' + '/home/matt/work/asic-workshop/shuttle6/zero_to_asic_mpw6/gds/user_project_wrapper.gds.gz' '/home/matt/work/asic-workshop/shuttle7/zero_to_asic_mpw7/gds/user_project_wrapper.gds.gz' '/home/matt/work/asic-workshop/shuttle7/tinytapeout-02/gds/user_project_wrapper.gds' '/home/matt/work/asic-workshop/shuttle8/tinytapeout-02/gds/user_project_wrapper.gds' '/home/matt/work/asic-workshop/shuttle9/caravel_user_project/gds/user_project_wrapper.gds' '/home/matt/work/asic-workshop/shuttle9/tinytapeout-03/gds/user_project_wrapper.gds' false true false diff --git a/docs/CREDITS.md b/docs/CREDITS.md index b5eefdf..7608e37 100644 --- a/docs/CREDITS.md +++ b/docs/CREDITS.md @@ -1,6 +1,6 @@ # Sponsored by -[![efabless](efabless.png)](https://efabless.com/) +[![efabless](pics/efabless.png)](https://efabless.com/) # Team diff --git a/docs/doc_header.md b/docs/doc_header.md index 170b0d6..58be003 100644 --- a/docs/doc_header.md +++ b/docs/doc_header.md @@ -1,6 +1,6 @@ --- title: | - ![](ttlogo.png){width=15cm} + ![](pics/ttlogo.png){width=15cm} Tiny Tapeout 03 Datasheet subtitle: Project Repository [https://github.com/TinyTapeout/tinytapeout-03](https://github.com/TinyTapeout/tinytapeout-03) documentclass: scrartcl @@ -18,7 +18,7 @@ header-includes: # Render of whole chip -![Full GDS](tinytapeout.png) +![Full GDS](pics/tinytapeout.png) \pagebreak diff --git a/project.py b/project.py index cf38671..5613123 100644 --- a/project.py +++ b/project.py @@ -248,6 +248,9 @@ class Project(): def get_macro_lef_filename(self): return f"{self.top_module}.lef" + def get_macro_spef_filename(self): + return f"{self.top_module}.spef" + # for GL sims & blackboxing def get_gl_verilog_filename(self): return f"{self.top_module}.v" @@ -266,10 +269,10 @@ class Project(): def get_git_url(self): return self.git_url - # todo add spef def copy_files_to_caravel(self): files = [ (f"runs/wokwi/results/final/gds/{self.get_macro_gds_filename()}", f"gds/{self.get_macro_gds_filename()}"), + (f"runs/wokwi/results/final/spef/{self.get_macro_spef_filename()}", f"spef/{self.get_macro_spef_filename()}"), (f"runs/wokwi/results/final/lef/{self.get_macro_lef_filename()}", f"lef/{self.get_macro_lef_filename()}"), (f"runs/wokwi/results/final/verilog/gl/{self.get_gl_verilog_filename()}", f"verilog/gl/{self.get_gl_verilog_filename()}"), (f"src/{self.top_verilog_filename}", f"verilog/rtl/{self.get_top_verilog_filename()}"), From 7ef78edfcbed8ca4377feff174160186f261074b Mon Sep 17 00:00:00 2001 From: Matt Venn Date: Fri, 17 Mar 2023 16:38:15 +0100 Subject: [PATCH 17/45] fix pics path --- docs/README_init.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README_init.md b/docs/README_init.md index d665d14..2da909b 100644 --- a/docs/README_init.md +++ b/docs/README_init.md @@ -16,7 +16,7 @@ For the most recent automatic build, click the tt_datasheet badge above, then do # GDS layout of all projects -![tiny tapeout](tinytapeout.png) +![tiny tapeout](pics/tinytapeout.png) # Project Index From 9b82e7721e44d39baac6fbd44f7c0c9323192e85 Mon Sep 17 00:00:00 2001 From: Matt Venn Date: Fri, 17 Mar 2023 16:50:23 +0100 Subject: [PATCH 18/45] increase margin on bottom so theres space for the pagenumber --- docs/doc_header.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/doc_header.md b/docs/doc_header.md index 58be003..0a694bf 100644 --- a/docs/doc_header.md +++ b/docs/doc_header.md @@ -5,7 +5,7 @@ title: | subtitle: Project Repository [https://github.com/TinyTapeout/tinytapeout-03](https://github.com/TinyTapeout/tinytapeout-03) documentclass: scrartcl date: \today{} -geometry: margin=2cm +geometry: "left=2cm,right=2cm,top=2cm,bottom=2.5cm" fontsize: 14pt mainfont: Latin Modern Sans header-includes: From 906cd0976287b3ad36c5194b253458952a0d8b39 Mon Sep 17 00:00:00 2001 From: Matt Venn Date: Fri, 17 Mar 2023 16:59:56 +0100 Subject: [PATCH 19/45] wip hugo docs --- docs/hugo_index_template.md | 41 +++++++++++-------------------------- documentation.py | 2 +- 2 files changed, 13 insertions(+), 30 deletions(-) diff --git a/docs/hugo_index_template.md b/docs/hugo_index_template.md index d653a60..e7b1694 100644 --- a/docs/hugo_index_template.md +++ b/docs/hugo_index_template.md @@ -1,6 +1,6 @@ --- -title: "Tiny Tapeout 02" -weight: 10 +title: "Tiny Tapeout 03" +weight: 20 description: Overview --- @@ -9,45 +9,28 @@ description: Overview # Design details -* [Github repository](https://github.com/TinyTapeout/tinytapeout-02) -* [PDF datasheet](https://github.com/TinyTapeout/tinytapeout-02/raw/tt02/datasheet.pdf) -* [Verification](https://github.com/TinyTapeout/tinytapeout-02/blob/tt02/VERIFICATION.md) -* [Efabless MPW submission](https://platform.efabless.com/projects/1410) +* [Github repository](https://github.com/TinyTapeout/tinytapeout-03) +* [PDF datasheet](https://github.com/TinyTapeout/tinytapeout-03/raw/tt03/datasheet.pdf) +* [Verification](https://github.com/TinyTapeout/tinytapeout-03/blob/tt03/VERIFICATION.md) +* [Efabless MPW submission](https://platform.efabless.com/projects/1971) # Launch stats -* Launched: 9 November 2023 -* Submission closed: 2 December 2023 -* Submitted to Efabless 2211Q chipIgnite shuttle using Skywater 130nm open source PDK +* Launched: 1 March 2023 +* Submission closed: 24 April 2023 +* Submitted to Efabless 2304C chipIgnite shuttle using Skywater 130nm open source PDK # Project statistics -* 165 projects submitted. Each project is 150 x 170um -* Youngest submitter: [4 years old](https://tinytapeout.com/runs/tt02/031) -* 64 used Wokwi graphical editor, 82 Verilog, 5 Amaranth, 1 Chisel -* 39935 standard cells used across all projects. -* Most cells used in a design was 1105, the least was 8. -* Max utilisation was 52% -* Total wire length 2024 mm. +* TBA ## Whole die render -![whole die](images/tinytapeout.png) +![whole die](images/tinytapeout-03.png) # Project Showcase -## Wokwi projects -* [UART-CC](https://tinytapeout.com/runs/tt02/057) -* [Nano-neuron](https://tinytapeout.com/runs/tt02/066) -* [Worm in a Maze](https://tinytapeout.com/runs/tt02/084) -* [Wolf sheep cabbage river crossing puzzle ASIC design](https://tinytapeout.com/runs/tt02/111) - -## HDL projects -* [Avalon Semiconductors 'TBB1143' Programmable Sound Generator](https://tinytapeout.com/runs/tt02/024/) -* [Pi (π) to 1000+ decimal places](https://tinytapeout.com/runs/tt02/036) -* [FPGA test](https://tinytapeout.com/runs/tt02/090) -* [RV8U - 8-bit RISC-V Microcore Processor](https://tinytapeout.com/runs/tt02/107) -* [8-bit CPU](https://tinytapeout.com/runs/tt02/085/) +* TBA # Testimonials / Endorsements diff --git a/documentation.py b/documentation.py index cc0c460..544a60c 100644 --- a/documentation.py +++ b/documentation.py @@ -114,7 +114,7 @@ class Docs(): index_template = fh.read() # copy image - shutil.copyfile('tinytapeout.png', os.path.join(hugo_images, 'tinytapeout.png')) + shutil.copyfile('pics/tinytapeout.png', os.path.join(hugo_images, 'tinytapeout-03.png')) #todo fix hardcoded run # index page logging.info("building pages - can take a minute as fetching latest GDS action URLs for all projects") From 8436c1186b33302b27a9bebec8af5bb195f3dfbe Mon Sep 17 00:00:00 2001 From: Matt Venn Date: Mon, 20 Mar 2023 16:01:00 +0100 Subject: [PATCH 20/45] add gl verification badge --- docs/README_init.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/README_init.md b/docs/README_init.md index 2da909b..f639493 100644 --- a/docs/README_init.md +++ b/docs/README_init.md @@ -1,6 +1,7 @@ [![tinytapeout](https://github.com/tinytapeout/tinytapeout-03/actions/workflows/gds.yaml/badge.svg)](https://github.com/tinytapeout/tinytapeout-03/actions/workflows/gds.yaml) [![verification](https://github.com/tinytapeout/tinytapeout-03/actions/workflows/verification.yaml/badge.svg)](https://github.com/tinytapeout/tinytapeout-03/actions/workflows/verification.yaml) [![tt_datasheet](https://github.com/tinytapeout/tinytapeout-03/actions/workflows/tt_datasheet.yaml/badge.svg)](https://github.com/tinytapeout/tinytapeout-03/actions/workflows/tt_datasheet.yaml) +[![gl_verification](https://github.com/TinyTapeout/tinytapeout-03/actions/workflows/gl_verification.yaml/badge.svg)](https://github.com/TinyTapeout/tinytapeout-03/actions/workflows/gl_verification.yaml) # TinyTapeout 03 From 16be4f50fa215f135ad20712575ee2f3d6633398 Mon Sep 17 00:00:00 2001 From: Matt Venn Date: Tue, 21 Mar 2023 19:09:00 +0100 Subject: [PATCH 21/45] updates for wip publishing --- docs/hugo_index_template.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/hugo_index_template.md b/docs/hugo_index_template.md index e7b1694..898fe5c 100644 --- a/docs/hugo_index_template.md +++ b/docs/hugo_index_template.md @@ -20,14 +20,17 @@ description: Overview * Submission closed: 24 April 2023 * Submitted to Efabless 2304C chipIgnite shuttle using Skywater 130nm open source PDK + -## Whole die render +# Whole die render ![whole die](images/tinytapeout-03.png) + From 6ce005a00a9a314588e986eb2d5c47ac538dc6b7 Mon Sep 17 00:00:00 2001 From: Matt Venn Date: Mon, 27 Mar 2023 14:10:31 +0200 Subject: [PATCH 22/45] loggin on number orders --- orders.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/orders.py b/orders.py index c5df24b..7435052 100644 --- a/orders.py +++ b/orders.py @@ -33,6 +33,7 @@ class Orders(): 'email' : None, 'time' : datetime.strptime(item['time'], '%Y-%m-%dT%H:%M:%S.%f%z').replace(tzinfo=pytz.UTC) }) + logging.info(f"fetched university {len(orders)} orders") return orders def fetch_stripe_orders(self): @@ -62,6 +63,7 @@ class Orders(): start_id = checkout['id'] # put in date order + logging.info(f"fetched stripe {len(orders)} orders") return orders def update_project_list(self): From f2ffaa563715946be23884bbbdf7f86349507b92 Mon Sep 17 00:00:00 2001 From: Matt Venn Date: Mon, 27 Mar 2023 14:41:02 +0200 Subject: [PATCH 23/45] fix links --- docs/hugo_index_template.md | 6 +- projects.yaml | 1000 ----------------------------------- 2 files changed, 3 insertions(+), 1003 deletions(-) delete mode 100644 projects.yaml diff --git a/docs/hugo_index_template.md b/docs/hugo_index_template.md index 898fe5c..68b9134 100644 --- a/docs/hugo_index_template.md +++ b/docs/hugo_index_template.md @@ -10,14 +10,14 @@ description: Overview # Design details * [Github repository](https://github.com/TinyTapeout/tinytapeout-03) -* [PDF datasheet](https://github.com/TinyTapeout/tinytapeout-03/raw/tt03/datasheet.pdf) -* [Verification](https://github.com/TinyTapeout/tinytapeout-03/blob/tt03/VERIFICATION.md) +* [PDF datasheet](https://github.com/TinyTapeout/tinytapeout-03/raw/main/datasheet.pdf) +* [Verification](https://github.com/TinyTapeout/tinytapeout-03/blob/main/VERIFICATION.md) * [Efabless MPW submission](https://platform.efabless.com/projects/1971) # Launch stats * Launched: 1 March 2023 -* Submission closed: 24 April 2023 +* Submission closes: 24 April 2023 * Submitted to Efabless 2304C chipIgnite shuttle using Skywater 130nm open source PDK +* 100 projects submitted, 149 projects added from [TT02](/runs/tt02) +* build time for all projects 22.68 hours +* total cells 79674 +* max cells 1287 for [project 072](./072) +* max utilisation 59.95% for [project 044](./044) +* total wire length 4079353mm +* 94 used Wokwi, 135 Verilog, 1 myhdl, 7 Amaranth, spade 1, xls 2, migen 1, systemverilog 3, mixed radix circuit synthesis (mrcs) 1, chisel 1. +* top 10 tags: + - test : 21 + - experiment: 6 + - cpu : 6 + - timer : 5 + - risc : 5 + - game : 4 + - pwm : 4 + - music : 4 + - counter : 4 # Whole die render ![whole die](images/tinytapeout-03.png) - diff --git a/documentation.py b/documentation.py index 8edc19f..0156fbb 100644 --- a/documentation.py +++ b/documentation.py @@ -119,7 +119,7 @@ class Docs(): index_template = fh.read() # copy image - shutil.copyfile('pics/tinytapeout.png', os.path.join(hugo_images, 'tinytapeout-03.png')) # TODO fix hardcoded run + shutil.copyfile('pics/tinytapeout_numbered.png', os.path.join(hugo_images, 'tinytapeout-03.png')) # TODO fix hardcoded run # index page logging.info("building pages - can take a minute as fetching latest GDS action URLs for all projects") From 40060b69d273590e0ec4b8d67e01dbc05b6ae487 Mon Sep 17 00:00:00 2001 From: Matt Venn Date: Wed, 26 Apr 2023 17:40:22 +0200 Subject: [PATCH 42/45] fix wirelength --- docs/hugo_index_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/hugo_index_template.md b/docs/hugo_index_template.md index 4ce62e8..c08317f 100644 --- a/docs/hugo_index_template.md +++ b/docs/hugo_index_template.md @@ -26,7 +26,7 @@ description: 249 designs, closed 24th April 2023 * total cells 79674 * max cells 1287 for [project 072](./072) * max utilisation 59.95% for [project 044](./044) -* total wire length 4079353mm +* total wire length 4079 mm * 94 used Wokwi, 135 Verilog, 1 myhdl, 7 Amaranth, spade 1, xls 2, migen 1, systemverilog 3, mixed radix circuit synthesis (mrcs) 1, chisel 1. * top 10 tags: - test : 21 From eb41dd0f94aa6fd5b84e60059678096fc434e5d4 Mon Sep 17 00:00:00 2001 From: Matt Venn Date: Fri, 28 Apr 2023 13:52:48 +0200 Subject: [PATCH 43/45] handle missing extra_projects.yaml --- orders.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/orders.py b/orders.py index f1abad9..6909e06 100644 --- a/orders.py +++ b/orders.py @@ -21,8 +21,12 @@ class Orders(): self.orders = sorted(orders, key=lambda d: d['time']) def add_extra_project(self, git_url): - with open(self.config['extra_projects']) as fh: - config = yaml.safe_load(fh) + try: + with open(self.config['extra_projects']) as fh: + config = yaml.safe_load(fh) + except FileNotFoundError as e: + config = [] + config.append({'project': {'url' : git_url, 'timestamp' : time.time()}}) with open(self.config['extra_projects'], 'w') as fh: yaml.safe_dump(config, fh) From 5fda2dbf3d564d4dc7f3b4ab8c18de2897719d8b Mon Sep 17 00:00:00 2001 From: Matt Venn Date: Fri, 28 Apr 2023 13:55:48 +0200 Subject: [PATCH 44/45] compare time on uni orders --- orders.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/orders.py b/orders.py index 6909e06..482d102 100644 --- a/orders.py +++ b/orders.py @@ -57,11 +57,13 @@ class Orders(): orders = [] for item in r.json()['items']: - orders.append({ + order = { 'git_url' : item['repo'], 'email' : None, 'time' : datetime.strptime(item['time'], '%Y-%m-%dT%H:%M:%S.%f%z').replace(tzinfo=pytz.UTC) - }) + } + if order['time'] > self.open_date.replace(tzinfo=pytz.UTC): + orders.append(order) logging.info(f"fetched university {len(orders)} orders") return orders From e5a26cde94f847449618d7e57dee7da8c305b784 Mon Sep 17 00:00:00 2001 From: Matt Venn Date: Fri, 28 Apr 2023 14:58:32 +0200 Subject: [PATCH 45/45] fix bad message --- configure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.py b/configure.py index 3601164..9d21285 100755 --- a/configure.py +++ b/configure.py @@ -33,7 +33,7 @@ class Projects(): with open(self.projects_file) as fh: self.project_config = yaml.safe_load(fh) except FileNotFoundError: - logging.error("projects.yaml not found, create it with --update-orders") + logging.error(f"{self.projects_file} not found, create it with --update-orders") exit(1) """