diff --git a/.gitmodules b/.gitmodules index 8a7369e3..58369579 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "lakeroad-private"] path = lakeroad-private url = git@github.com:uwsampl/lakeroad-private -[submodule "bitwuzla"] - path = bitwuzla - url = git@github.com:bitwuzla/bitwuzla.git [submodule "lakeroad-egglog/yosys"] path = lakeroad-egglog/yosys url = git@github.com:uwsampl/yosys diff --git a/Dockerfile b/Dockerfile index 81df6af5..c53b428c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,9 @@ # syntax=docker/dockerfile-upstream:master-labs # The above enables use of ADD of git repo. + FROM ubuntu:22.04 +ARG MAKE_JOBS=2 +SHELL ["/bin/bash", "-c"] # Update, get add-apt-repository, add PPA for Racket, update again. RUN apt update \ @@ -8,8 +11,10 @@ RUN apt update \ && add-apt-repository ppa:plt/racket \ && apt update -## Install dependencies -# apt dependencies +# Install apt dependencies +# `noninteractive` prevents the tzdata package from asking for a timezone on the +# command line. +ENV DEBIAN_FRONTEND=noninteractive RUN apt install -y \ autoconf \ bison \ @@ -19,11 +24,12 @@ RUN apt install -y \ flex \ g++ \ git \ - git \ + libboost-filesystem-dev \ libfl-dev \ libfl2 \ libgmp-dev \ libgoogle-perftools-dev \ + libreadline-dev \ libssl-dev \ libzmq3-dev \ llvm-14 \ @@ -37,6 +43,8 @@ RUN apt install -y \ python3 \ python3-pip \ racket \ + tcl \ + tcl8.6-dev \ wget \ zlib1g \ zlib1g-dev @@ -59,42 +67,51 @@ ENV PATH="/root/.local/bin:${PATH}" # Install a bunch of useful tools from prebuilt binaries. Thanks to YosysHQ for # making this available! # -# We currently use the following binaries from oss-cad-suite: -# yosys, verilator, cvc5, boolector. -# # If we get an error here, we likely just need to add other branches for other # architectures. # -# TODO(@gussmith23): Could shrink Docker image by deleting unneeded binaries. +# TODO(@gussmith23): Could shrink Docker image by deleting a bunch of uneeded +# binaries, or only taking the binaries we need. However, I found that moving +# stuff out of oss-cad-suite causes things to break. WORKDIR /root -RUN if [ "$(uname -m)" = "x86_64" ] ; then \ - wget https://github.com/YosysHQ/oss-cad-suite-build/releases/download/2023-08-06/oss-cad-suite-linux-x64-20230806.tgz -q -O oss-cad-suite.tgz; \ +ADD dependencies.sh /root/dependencies.sh +RUN source /root/dependencies.sh \ + && if [ "$(uname -m)" = "x86_64" ] ; then \ + wget https://github.com/YosysHQ/oss-cad-suite-build/releases/download/$OSS_CAD_SUITE_DATE/oss-cad-suite-linux-x64-$(echo $OSS_CAD_SUITE_DATE | tr -d "-").tgz -q -O oss-cad-suite.tgz; \ else \ exit 1; \ fi \ - && tar xf oss-cad-suite.tgz -ENV PATH="/root/oss-cad-suite/bin:${PATH}" + && tar xf oss-cad-suite.tgz \ + && rm oss-cad-suite.tgz \ + # Delete binaries we don't need (and that we explicitly build other versions + # of). + && rm oss-cad-suite/bin/yosys \ + && rm oss-cad-suite/bin/bitwuzla +# Make sure that .local/bin has precedence over oss-cad-suite/bin. I realize +# we add ./local/bin to the PATH twice, but I just want to document that we want +# things in .local/bin to take precedence, and duplicate PATH entries won't +# break anything. +ENV PATH="/root/.local/bin:/root/oss-cad-suite/bin:${PATH}" # pip dependencies WORKDIR /root/lakeroad ADD requirements.txt requirements.txt RUN pip install -r requirements.txt -# Build Bitwuzla from version tracked in submodule. +# Build Bitwuzla. WORKDIR /root -ARG MAKE_JOBS=2 -ADD bitwuzla bitwuzla -RUN cd bitwuzla \ - && ./configure.py \ +RUN source /root/dependencies.sh \ + && mkdir bitwuzla \ + && wget -qO- https://github.com/bitwuzla/bitwuzla/archive/$BITWUZLA_COMMIT_HASH.tar.gz | tar xz -C bitwuzla --strip-components=1 \ + && cd bitwuzla \ + && ./configure.py --prefix=/root/.local \ && cd build \ - && ninja -j${MAKE_JOBS} -# Put it on the path. Note that there's a bitwuzla in oss-cad-suite, so we need -# to make sure this one takes precedence. -ENV PATH="/root/bitwuzla/build/src/main/:${PATH}" + && ninja -j${MAKE_JOBS} \ + && ninja install \ + && rm -rf /root/bitwuzla # Install raco (Racket) dependencies. WORKDIR /root -ARG FMT_COMMIT_HASH=bd44477 RUN \ # First, fix https://github.com/racket/racket/issues/2691 by building the # docs. @@ -108,7 +125,8 @@ RUN \ && cd /root \ && git clone https://github.com/sorawee/fmt \ && cd fmt \ - && git checkout ${FMT_COMMIT_HASH} \ + && source /root/dependencies.sh \ + && git checkout $RACKET_FMT_COMMIT_HASH \ && raco pkg install --deps search-auto --batch # Install Rust @@ -144,37 +162,37 @@ ENV LAKEROAD_PRIVATE_DIR=/root/lakeroad/lakeroad-private # Build STP. WORKDIR /root -ENV STP_URL="https://github.com/stp/stp/archive/0510509a85b6823278211891cbb274022340fa5c.tar.gz" RUN apt-get install -y git cmake bison flex libboost-all-dev python2 perl && \ - wget ${STP_URL} -nv -O stp.tar.gz && \ - mkdir stp && \ - tar xzf stp.tar.gz -C stp --strip-components=1 && \ - cd stp && \ + source /root/dependencies.sh && \ + mkdir stp && cd stp && \ + wget -qO- https://github.com/stp/stp/archive/$STP_COMMIT_HASH.tar.gz | tar xz --strip-components=1 && \ ./scripts/deps/setup-gtest.sh && \ ./scripts/deps/setup-outputcheck.sh && \ ./scripts/deps/setup-cms.sh && \ ./scripts/deps/setup-minisat.sh && \ mkdir build && \ cd build && \ - cmake .. && \ - cmake --build . + cmake .. -DCMAKE_INSTALL_PREFIX=/root/.local && \ + make -j ${MAKE_JOBS} +# TODO(@gussmith23): Install and delete folder once +# https://github.com/stp/stp/issues/479 is fixed. +# make install && \ +# rm -rf /root/stp +# And after that we also don't need to add STP to the path. ENV PATH="/root/stp/build:${PATH}" -# Build Yices2. +# Build Yosys. WORKDIR /root -ENV YICES2_URL="https://github.com/SRI-CSL/yices2/archive/e27cf308cffb0ecc6cc7165c10e81ca65bc303b3.tar.gz" -RUN apt-get install -y gperf && \ - wget ${YICES2_URL} -nv -O yices2.tar.gz && \ - mkdir yices2 && \ - tar xvf yices2.tar.gz -C yices2 --strip-components=1 && \ - cd yices2 && \ - autoconf && \ - ./configure && \ - make && \ - # If this line fails, it's presumably because we're on a different architecture. - [ -d build/x86_64-pc-linux-gnu-release/bin ] -ENV PATH="/root/yices2/build/x86_64-pc-linux-gnu-release/bin/:${PATH}" - +RUN source /root/dependencies.sh \ + && mkdir yosys && cd yosys \ + && wget -qO- https://github.com/YosysHQ/yosys/archive/$YOSYS_COMMIT_HASH.tar.gz | tar xz --strip-components=1 \ + && PREFIX="/root/.local" CPLUS_INCLUDE_PATH="/usr/include/tcl8.6/:$CPLUS_INCLUDE_PATH" make config-gcc \ + && PREFIX="/root/.local" CPLUS_INCLUDE_PATH="/usr/include/tcl8.6/:$CPLUS_INCLUDE_PATH" make -j ${MAKE_JOBS} install \ + && rm -rf /root/yosys + +# Build Yosys plugin. +WORKDIR /root/lakeroad/yosys-plugin +RUN make -j ${MAKE_JOBS} WORKDIR /root/lakeroad CMD [ "/bin/bash", "run-tests.sh" ] diff --git a/README.md b/README.md index b827ec35..a87aa3e0 100644 --- a/README.md +++ b/README.md @@ -137,3 +137,9 @@ TODO(@gussmith23): this is unintuitive and not user-friendly. The [./import_all_primitives.sh](./import_all_primitives.sh) script uses sed, which it expects to be GNU sed. On Mac, you can install GNU sed via Homebrew and add it to your PATH temporarily or permanently. TODO(@gussmith23): Find better cross-platform regex. I thought Perl might be the answer. + +## Yosys Plugin + +Lakeroad is usable via a Yosys plugin, which can be built separately and loaded directly into your existing Yosys installation. The plugin is located in [./yosys-plugin/](./yosys-plugin/) and can be built using the Makefile in that directory. The plugin can be loaded into Yosys using Yosys's `-m` option, e.g. `yosys -m lakeroad.so ...`. An example of this can be seen in [this integration test.](./integration_tests/lakeroad/xilinx_muladd_0_stage_signed_8_bit_yosys_plugin.sv) + +Note: the Lakeroad plugin needs to be built in the same environment (i.e. same glibc version) as the Yosys executable it's being loaded into. This can be an issue e.g. when using the Yosys executable in `oss-cad-suite`. The easiest way to prevent this is to build Yosys yourself using their directions. diff --git a/bitwuzla b/bitwuzla deleted file mode 160000 index b655bc0c..00000000 --- a/bitwuzla +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b655bc0cde570258367bf8f09a113bc7b95e46e9 diff --git a/dependencies.sh b/dependencies.sh new file mode 100644 index 00000000..3ea9640a --- /dev/null +++ b/dependencies.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# This script exports a number of variables that help to pin the versions of +# various Lakeroad dependencies. +# +# To use, source this script before running relevant commands e.g. in a +# Dockerfile. +# +# This is just one possible way to implement dependency tracking. Some of the +# other options are: +# - No tracking at all. E.g. in the Dockerfile, we could clone STP at a specific +# commit, using a "magic constant" commit hash. This isn't ideal, because if +# we use STP elsewhere (e.g. in the evaluation) we have to make sure we keep +# the commit hashes in sync. +# - Git submodules. This is very similar to what we've chosen to do, but it's +# more directly supported by Git. For example, we used to have Bitwuzla be a +# submodule of Lakeroad. This avoids the need to sync commit hashes as in the +# first option. However, it's a bit overkill to add a full repository as a +# submodule when we only need the resulting binary. +# +# This option is essentially a lighter-weight version of submodules. We track +# the commit hashes of the dependencies we need, but nothing additional is +# cloned on a `git clone --recursive`. + +export STP_COMMIT_HASH="0510509a85b6823278211891cbb274022340fa5c" +export YICES2_COMMIT_HASH="e27cf308cffb0ecc6cc7165c10e81ca65bc303b3" +export BITWUZLA_COMMIT_HASH="b655bc0cde570258367bf8f09a113bc7b95e46e9" +export OSS_CAD_SUITE_DATE="2023-08-06" +export RACKET_FMT_COMMIT_HASH=bd44477 +export YOSYS_COMMIT_HASH="70d35314dbd7521870047ed607897f22dc48cbc3" diff --git a/integration_tests/lakeroad/xilinx_muladd_0_stage_signed_8_bit_yosys_plugin.sv b/integration_tests/lakeroad/xilinx_muladd_0_stage_signed_8_bit_yosys_plugin.sv new file mode 100644 index 00000000..5f018488 --- /dev/null +++ b/integration_tests/lakeroad/xilinx_muladd_0_stage_signed_8_bit_yosys_plugin.sv @@ -0,0 +1,53 @@ +// RUN: outfile=$(mktemp) +// RUN: yosys -m "$LAKEROAD_DIR/yosys-plugin/lakeroad.so" -p " \ +// RUN: read_verilog %s; \ +// RUN: hierarchy -top in_module; \ +// RUN: lakeroad in_module; \ +// RUN: rename in_module out_module; \ +// RUN: write_verilog $outfile" +// RUN: FileCheck %s < $outfile +// RUN: if [ -z ${LAKEROAD_PRIVATE_DIR+x} ]; then \ +// RUN: echo "Warning: LAKEROAD_PRIVATE_DIR is not set. Skipping simulation."; \ +// RUN: exit 0; \ +// RUN: else \ +// RUN: python3 $LAKEROAD_DIR/bin/simulate_with_verilator.py \ +// RUN: --test_module_name out_module \ +// RUN: --ground_truth_module_name in_module \ +// RUN: --max_num_tests=10000 \ +// RUN: --verilog_filepath $outfile \ +// RUN: --verilog_filepath %s \ +// RUN: --initiation_interval 0 \ +// RUN: --output_signal out:8 \ +// RUN: --input_signal a:8 \ +// RUN: --input_signal b:8 \ +// RUN: --input_signal c:8 \ +// RUN: --verilator_include_dir "$LAKEROAD_PRIVATE_DIR/DSP48E2/" \ +// RUN: --verilator_extra_arg='-DXIL_XECLIB' \ +// RUN: --verilator_extra_arg='-Wno-UNOPTFLAT' \ +// RUN: --verilator_extra_arg='-Wno-LATCH' \ +// RUN: --verilator_extra_arg='-Wno-WIDTH' \ +// RUN: --verilator_extra_arg='-Wno-STMTDLY' \ +// RUN: --verilator_extra_arg='-Wno-CASEX' \ +// RUN: --verilator_extra_arg='-Wno-TIMESCALEMOD' \ +// RUN: --verilator_extra_arg='-Wno-PINMISSING'; \ +// RUN: fi + +(* template = "dsp" *) +(* architecture = "xilinx-ultrascale-plus" *) +(* pipeline_depth = 0 *) +module in_module( + (* data *) + input signed [7:0] a, + (* data *) + input signed [7:0] b, + (* data *) + input signed [7:0] c, + (* out *) + output [7:0] out); + + assign out = (a * b) + c; +endmodule + +// CHECK: module out_module(a, b, c, out); +// CHECK: DSP48E2 #( +// CHECK: endmodule diff --git a/yosys-plugin/.gitignore b/yosys-plugin/.gitignore new file mode 100644 index 00000000..9d8d54c2 --- /dev/null +++ b/yosys-plugin/.gitignore @@ -0,0 +1,4 @@ + +lakeroad.d +lakeroad.so +lakeroad.so.dSYM/ \ No newline at end of file diff --git a/yosys-plugin/Makefile b/yosys-plugin/Makefile new file mode 100644 index 00000000..7da2ed90 --- /dev/null +++ b/yosys-plugin/Makefile @@ -0,0 +1,7 @@ +lakeroad.so: lakeroad.cc + $(CXX) $(shell yosys-config --cxxflags --ldflags) -shared -o $@ lakeroad.cc -lboost_filesystem + +clean: + rm -rfv *.d *.o lakeroad.so* + +-include *.d diff --git a/yosys-plugin/lakeroad.cc b/yosys-plugin/lakeroad.cc new file mode 100644 index 00000000..6427d29d --- /dev/null +++ b/yosys-plugin/lakeroad.cc @@ -0,0 +1,175 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/celltypes.h" +#include "kernel/ff.h" +#include "kernel/ffinit.h" +#include "kernel/log.h" +#include "kernel/register.h" +#include "kernel/sigtools.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN + + void + compileWithLakeroad(RTLIL::Module *module, RTLIL::Design *design) +{ + log_debug("Compiling module %s with Lakeroad.\n", module->name.c_str()); + + auto f = [&](std::string key) + { + if (module->attributes.count(key) != 1) + log_error("Module %s is missing attribute %s.\n", module->name.c_str(), key.c_str()); + return module->attributes[key]; + }; + + auto template_ = f("\\template").decode_string(); + auto architecture = f("\\architecture").decode_string(); + auto pipeline_depth = f("\\pipeline_depth").as_int(); + + auto find_attr = [&](std::string attr) + { + return [&, attr](IdString port) + { + auto w = module->wire(port); + assert(w); + return w->attributes.count(attr) > 0; + }; + }; + + // Clock may or may not be present. + auto clk_port_id = std::find_if(module->ports.begin(), module->ports.end(), find_attr("\\clk")); + + std::vector data_ports; + std::copy_if(module->ports.begin(), module->ports.end(), std::back_inserter(data_ports), find_attr("\\data")); + + // Out should definitely be present. + auto out_port_id = std::find_if(module->ports.begin(), module->ports.end(), find_attr("\\out")); + assert(out_port_id != module->ports.end()); + + log_debug("Template: %s\n", template_.c_str()); + log_debug("Architecture: %s\n", architecture.c_str()); + log_debug("Pipeline depth: %d\n", pipeline_depth); + if (clk_port_id != module->ports.end()) + { + log_debug("Clock port: %s\n", clk_port_id->c_str()); + } + else + { + log_debug("No clock port.\n"); + } + for (auto port : data_ports) + log_debug("Data port: %s\n", port.c_str()); + log_debug("Out port: %s\n", out_port_id->c_str()); + + auto top_module_name = module->name.substr(1); + // auto module_name = sprintf("%s_synthesized_by_lakeroad", top_module_name.c_str()); + + // Who knew getting a named temporary file was so hard in C++? This isn't a + // great solution. + auto verilog_filename = (boost::filesystem::temp_directory_path() / boost::filesystem::unique_path("%%%%-%%%%-%%%%-%%%%.v")).native(); + auto out_verilog_filename = (boost::filesystem::temp_directory_path() / boost::filesystem::unique_path("%%%%-%%%%-%%%%-%%%%.v")).native(); + std::vector write_verilog_args; + write_verilog_args.push_back("write_verilog"); + write_verilog_args.push_back(verilog_filename); + Pass::call(design, write_verilog_args); + + auto temp_module_name = top_module_name + "_temp_output_from_lakeroad"; + + std::stringstream ss; + // clang-format off + ss << getenv("LAKEROAD_DIR") << "/bin/main.rkt" + << " --verilog-module-filepath " << verilog_filename + << " --top-module-name " << top_module_name + << " --out-filepath " << out_verilog_filename + << " --out-format verilog" + << " --verilog-module-out-signal " << out_port_id->substr(1) << ":" << module->wire(*out_port_id)->width + << " --architecture " << architecture + << " --template " << template_ + << " --module-name " << temp_module_name; + if (clk_port_id != module->ports.end()){ + ss << " --clock-name " << clk_port_id->substr(1); + } + for (auto port : data_ports) + ss << " --input-signal " << port.substr(1) << ":" << module->wire(port)->width; + if (pipeline_depth != 0) + ss << " --initiation-interval " << pipeline_depth; + // clang-format on + + log("Executing Lakeroad:\n%s\n", ss.str().c_str()); + if (system(ss.str().c_str()) != 0) + log_error("Lakeroad execution failed.\n"); + + std::vector read_verilog_args; + read_verilog_args.push_back("read_verilog"); + read_verilog_args.push_back(out_verilog_filename); + Pass::call(design, read_verilog_args); + + log("Replacing module %s with the output of Lakeroad\n", top_module_name.c_str()); + design->remove(module); + auto new_module = design->module(RTLIL::escape_id(temp_module_name)); + if (new_module == nullptr) + log_error("Lakeroad returned OK, but no module named %s found.\n", top_module_name.c_str()); + design->rename(new_module, RTLIL::escape_id(top_module_name)); +} + +struct LakeroadPass : public Pass +{ + LakeroadPass() : Pass("lakeroad", "Invoke Lakeroad for technology mapping.") {} + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" lakeroad