Merge pull request #5 from proppy/info-yaml
add support for info.yaml
diff --git a/.github/workflows/user_project_ci.yml b/.github/workflows/user_project_ci.yml
index 0bb7f88..12d29e9 100644
--- a/.github/workflows/user_project_ci.yml
+++ b/.github/workflows/user_project_ci.yml
@@ -53,16 +53,20 @@
find $OPENLANE_ROOT/
find $PDK_ROOT/
- - name: harden tiny_user_project
- run: |
- make fetch
- make tiny_user_project
-
- name: setup python
uses: actions/setup-python@v4
with:
python-version: '3.10'
+ - name: fetch verilog and build config
+ run: |
+ python -m pip install requests PyYAML
+ python configure.py --create-user-config
+
+ - name: harden tiny_user_project
+ run: |
+ make tiny_user_project
+
- name: show tiny_user_project metrics
run: |
python << EOF >> $GITHUB_STEP_SUMMARY
diff --git a/Makefile b/Makefile
index 0d38dd5..7c5010f 100644
--- a/Makefile
+++ b/Makefile
@@ -318,9 +318,3 @@
@$(MAKE) -C $(TIMING_ROOT) -f $(TIMING_ROOT)/timing.mk caravel-timing-fast
@$(MAKE) -C $(TIMING_ROOT) -f $(TIMING_ROOT)/timing.mk caravel-timing-slow
@echo "You can find results for all corners in $(CUP_ROOT)/signoff/caravel/openlane-signoff/timing/"
-
-WOKWI_PROJECT_ID=334445762078310996
-
-.PHONY: fetch
-fetch:
- curl https://wokwi.com/api/projects/$(WOKWI_PROJECT_ID)/verilog | sed -e "s/user_module_$(WOKWI_PROJECT_ID)/user_module/" > verilog/rtl/user_module.v
diff --git a/README.md b/README.md
index 64e0a84..db03f76 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
1. [Generate](https://github.com/proppy/tiny_user_project/generate) a new project based on this template
-2. Create a new [Wokwi](https://wokwi.com/projects/339800239192932947) project and update [WOKWI_PROJECT_ID](Makefile#L322)
+2. Create a new [Wokwi](https://wokwi.com/projects/339800239192932947) project and update [`info.yaml`](info.yaml) with your `wokwi_id`.
3. Commit, push and check the [](https://github.com/proppy/tiny_caravel_user_project/actions/workflows/user_project_ci.yml) workflow summary (if successful a new commit including the hardened files will be automatically created).
diff --git a/configure.py b/configure.py
new file mode 100755
index 0000000..ff5d954
--- /dev/null
+++ b/configure.py
@@ -0,0 +1,180 @@
+#!/usr/bin/env python3
+import requests
+import argparse
+import os
+import glob
+import json
+import yaml
+import logging
+import sys
+import csv
+import re
+
+
+def load_yaml(yaml_file):
+ with open(yaml_file, "r") as stream:
+ return (yaml.safe_load(stream))
+
+tiny_user_project_v = '''// generated by configure.py
+`default_nettype none
+
+module tiny_user_project(
+ input [`MPRJ_IO_PADS-1:0] io_in,
+ output [`MPRJ_IO_PADS-1:0] io_out,
+ output [`MPRJ_IO_PADS-1:0] io_oeb
+);
+
+// pass input and output pins defined in user_defines.v
+{module_name} mod (
+ io_in[19:12],
+ io_out[27:20]
+);
+// all output enabled
+assign io_oeb[27:20] = 8'b0;
+
+endmodule // tiny_user_project
+
+`default_nettype wire'''
+
+def write_user_config(module_name, sources):
+ with open('verilog/rtl/tiny_user_project.v', 'w') as fh:
+ fh.write(tiny_user_project_v.format(module_name=module_name))
+ with open('openlane/tiny_user_project/config.json', 'r') as fh:
+ config_json = json.load(fh)
+ sources.append('verilog/rtl/defines.v')
+ sources.append('verilog/rtl/tiny_user_project.v')
+ config_json['VERILOG_FILES'] = [f'dir::../../{s}' for s in sources]
+ with open('openlane/tiny_user_project/config.json', 'w') as fh:
+ json.dump(config_json, fh, indent=4)
+
+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)
+
+ # it's a wokwi project
+ if wokwi_id != 0:
+ url = "https://wokwi.com/api/projects/{}/verilog".format(wokwi_id)
+ 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)
+
+ filename = "user_module_{}.v".format(wokwi_id)
+ with open(os.path.join('verilog/rtl', filename), 'wb') as fh:
+ fh.write(r.content)
+
+ # also fetch the wokwi diagram
+ url = "https://wokwi.com/api/projects/{}/diagram.json".format(wokwi_id)
+ 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(os.path.join('verilog/rtl', "wokwi_diagram.json"), 'wb') as fh:
+ fh.write(r.content)
+
+ return [f'verilog/rtl/{filename}', 'verilog/rtl/cells.v']
+
+ # else it's HDL, so check source files
+ else:
+ if 'source_files' not in yaml['project']:
+ logging.error("source files must be provided if wokwi_id is set to 0")
+ exit(1)
+
+ source_files = yaml['project']['source_files']
+ if source_files is None:
+ logging.error("must be more than 1 source file")
+ exit(1)
+
+ if len(source_files) == 0:
+ logging.error("must be more than 1 source file")
+ exit(1)
+
+ if 'top_module' not in yaml['project']:
+ logging.error("must provide a top module name")
+ exit(1)
+
+ return source_files
+
+
+# documentation
+def check_docs(yaml):
+ for key in ['author', 'title', 'description', 'how_it_works', 'how_to_test', 'language']:
+ 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(f'Invalid format for discord username')
+ exit(1)
+
+
+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_stats():
+ with open('runs/wokwi/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']))
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(description="TT setup")
+
+ parser.add_argument('--check-docs', help="check the documentation part of the yaml", action="store_const", const=True)
+ parser.add_argument('--get-stats', help="print some stats from the run", action="store_const", const=True)
+ parser.add_argument('--create-user-config', help="create the user_config.tcl file with top module and source files", 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('--yaml', help="yaml file to load", default='info.yaml')
+
+ args = parser.parse_args()
+ # setup log
+ log_format = logging.Formatter('%(asctime)s - %(module)-10s - %(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)
+
+ if args.get_stats:
+ get_stats()
+
+ elif args.check_docs:
+ logging.info("checking docs")
+ config = load_yaml(args.yaml)
+ check_docs(config)
+
+ elif args.create_user_config:
+ logging.info("creating include file")
+ config = load_yaml(args.yaml)
+ source_files = get_project_source(config)
+ top_module = get_top_module(config)
+ assert top_module != 'top'
+ write_user_config(top_module, source_files)
diff --git a/info.yaml b/info.yaml
new file mode 100644
index 0000000..a597d9e
--- /dev/null
+++ b/info.yaml
@@ -0,0 +1,46 @@
+---
+# TinyTapeout project information
+project:
+ wokwi_id: 334445762078310996 # If using wokwi, set this to your project's ID
+# source_files: # If using an HDL, set wokwi_id as 0 and uncomment and list your source files here. Source files must be in ./src
+# - counter.v
+# - decoder.v
+# top_module: "seven_segment_seconds" # put the name of your top module here, make it unique by prepending your github username
+
+# As everyone will have access to all designs, try to make it easy for someone new to your design to know what
+# it does and how to operate it.
+#
+# Here is an example: https://github.com/mattvenn/tinytapeout_m_segments/blob/main/info.yaml
+#
+# This info will be automatically collected and used to make a datasheet for the chip.
+documentation:
+ author: "" # Your name
+ discord: "" # Your discord handle - make sure to include the # part as well
+ title: "" # Project title
+ description: "" # Short description of what your project does
+ how_it_works: "" # Longer description of how the project works
+ how_to_test: "" # Instructions on how someone could test your project, include things like what buttons do what and how to set the clock if needed
+ external_hw: "" # Describe any external hardware needed
+ language: "wokwi" # other examples include Verilog, Amaranth, VHDL, etc
+ doc_link: "" # URL to longer form documentation, eg the README.md in your repository
+ clock_hz: 0 # Clock frequency in Hz (if required) we are expecting max clock frequency to be ~6khz. Provided on input 0.
+ picture: "" # relative path to a picture in your repository
+ inputs: # a description of what the inputs do
+ - clock
+ - reset
+ - none
+ - none
+ - none
+ - none
+ - none
+ - none
+ outputs:
+ - segment a # a description of what the outputs do
+ - segment b
+ - segment c
+ - segment d
+ - segment e
+ - segment f
+ - segment g
+ - none
+
diff --git a/verilog/rtl/tiny_user_project.v b/verilog/rtl/tiny_user_project.v
deleted file mode 100644
index cfea228..0000000
--- a/verilog/rtl/tiny_user_project.v
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2022 Google LLC
-//
-// 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.
-
-`default_nettype none
-
-module tiny_user_project(
- input [`MPRJ_IO_PADS-1:0] io_in,
- output [`MPRJ_IO_PADS-1:0] io_out,
- output [`MPRJ_IO_PADS-1:0] io_oeb
-);
-
-// pass input and output pins defined in user_defines.v
-user_module mod (
- .io_in (io_in[19:12]),
- .io_out(io_out[27:20])
-);
-// all output enabled
-assign io_oeb[27:20] = 8'b0;
-
-endmodule // tiny_user_project
-
-`default_nettype wire
\ No newline at end of file
diff --git a/verilog/rtl/user_module.v b/verilog/rtl/user_module.v
deleted file mode 100644
index 8b0b35c..0000000
--- a/verilog/rtl/user_module.v
+++ /dev/null
@@ -1,88 +0,0 @@
-/* Automatically generated from https://wokwi.com/projects/334445762078310996 */
-
-`default_nettype none
-
-module user_module(
- input [7:0] io_in,
- output [7:0] io_out
-);
- wire net1 = 1'b1;
- wire net2 = 1'b0;
- wire net3;
- wire net4;
- wire net5;
- wire net6;
- wire net7;
- wire net8 = 1'b1;
- wire net9 = 1'b0;
- wire net10;
- wire net11;
- wire net12 = 1'b1;
- wire net13 = 1'b0;
- wire net14;
- wire net15 = 1'b1;
- wire net16 = 1'b0;
- wire net17;
- wire net18 = 1'b0;
- wire net19 = 1'b1;
- wire net20;
- wire net21 = 1'b1;
- wire net22;
- wire net23;
- wire net24 = 1'b0;
- wire net25 = 1'b0;
-
- and_cell gate1 (
- .a (net3)
- );
- or_cell gate2 (
-
- );
- xor_cell gate3 (
-
- );
- nand_cell gate4 (
- .a (net4),
- .b (net5),
- .out (net6)
- );
- not_cell gate5 (
- .in (net7),
- .out (net5)
- );
- buffer_cell gate6 (
-
- );
- mux_cell mux1 (
- .a (net8),
- .b (net9),
- .sel (net10),
- .out (net11)
- );
- dff_cell flipflop1 (
-
- );
- mux_cell mux2 (
- .a (net12),
- .b (net13),
- .sel (net10),
- .out (net14)
- );
- mux_cell mux3 (
- .a (net15),
- .b (net16),
- .sel (net10),
- .out (net17)
- );
- mux_cell mux4 (
- .a (net18),
- .b (net19),
- .sel (net10),
- .out (net20)
- );
- and_cell gate7 (
- .a (net22),
- .b (net23),
- .out (net4)
- );
-endmodule