sim: add CXXRTL integration.
authorJean-François Nguyen <jf@lambdaconcept.com>
Fri, 29 Oct 2021 12:08:53 +0000 (14:08 +0200)
committerJean-François Nguyen <jf@lambdaconcept.com>
Fri, 29 Oct 2021 12:08:53 +0000 (14:08 +0200)
lambdasoc/sim/__init__.py [new file with mode: 0644]
lambdasoc/sim/blackboxes/__init__.py [new file with mode: 0644]
lambdasoc/sim/include/__init__.py [new file with mode: 0644]
lambdasoc/sim/include/util/__init__.py [new file with mode: 0644]
lambdasoc/sim/include/util/log_fmt.h [new file with mode: 0644]
lambdasoc/sim/platform.py [new file with mode: 0644]
lambdasoc/sim/top_driver.cc [new file with mode: 0644]

diff --git a/lambdasoc/sim/__init__.py b/lambdasoc/sim/__init__.py
new file mode 100644 (file)
index 0000000..c4627d4
--- /dev/null
@@ -0,0 +1,13 @@
+def collect_cxxrtl_src(package):
+    assert hasattr(package, "cxxrtl_src_files")
+    for module_name, subdirs, src_file in package.cxxrtl_src_files:
+        basedir = package.__name__.split(".")[-1]
+        yield module_name, (basedir, *subdirs), src_file
+
+
+from . import include
+
+
+cxxrtl_src_files = [
+    *collect_cxxrtl_src(include),
+]
diff --git a/lambdasoc/sim/blackboxes/__init__.py b/lambdasoc/sim/blackboxes/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/lambdasoc/sim/include/__init__.py b/lambdasoc/sim/include/__init__.py
new file mode 100644 (file)
index 0000000..6045270
--- /dev/null
@@ -0,0 +1,7 @@
+from .. import collect_cxxrtl_src
+from . import util
+
+
+cxxrtl_src_files = [
+    *collect_cxxrtl_src(util),
+]
diff --git a/lambdasoc/sim/include/util/__init__.py b/lambdasoc/sim/include/util/__init__.py
new file mode 100644 (file)
index 0000000..b387ede
--- /dev/null
@@ -0,0 +1,3 @@
+cxxrtl_src_files = [
+    (__package__, (), "log_fmt.h"),
+]
diff --git a/lambdasoc/sim/include/util/log_fmt.h b/lambdasoc/sim/include/util/log_fmt.h
new file mode 100644 (file)
index 0000000..e8d3c92
--- /dev/null
@@ -0,0 +1,23 @@
+#ifndef _FMT_LOG_H
+#define _FMT_LOG_H
+#include <cerrno>
+#include <cstring>
+#include <sstream>
+#include <string>
+
+static inline std::stringstream _fmt_msg(const std::string &msg, const std::string &file,
+        unsigned line) {
+    std::stringstream ss;
+    ss << msg << " (" << file << ":" << line << ")";
+    return ss;
+}
+
+static inline std::stringstream _fmt_errno(const std::string &msg, unsigned saved_errno,
+        const std::string &file, unsigned line) {
+    return _fmt_msg(msg + ": " + strerror(saved_errno), file, line);
+}
+
+#define fmt_msg(msg)   _fmt_msg(msg, __FILE__, __LINE__).str()
+#define fmt_errno(msg) _fmt_errno(msg, errno, __FILE__, __LINE__).str()
+
+#endif // _FMT_LOG_H
diff --git a/lambdasoc/sim/platform.py b/lambdasoc/sim/platform.py
new file mode 100644 (file)
index 0000000..f80153d
--- /dev/null
@@ -0,0 +1,185 @@
+import sys
+
+from collections import OrderedDict
+from importlib import import_module, resources
+
+from nmigen import *
+from nmigen.build import *
+
+
+__all__ = ["CXXRTLPlatform"]
+
+
+class CXXRTLPlatform(TemplatedPlatform):
+    device      = "cxxrtl"
+    default_clk = "clk"
+    default_rst = "rst"
+    resources   = [
+        Resource("clk", 0, Pins("clk", dir="i"), Clock(5e6)),
+        Resource("rst", 0, Pins("rst", dir="i")),
+    ]
+    connectors  = []
+    toolchain   = None # selected when creating platform
+
+    file_templates = {
+        **TemplatedPlatform.build_script_templates,
+        "{{name}}.il": r"""
+            # {{autogenerated}}
+            {{emit_rtlil()}}
+        """,
+        "{{name}}.ys": r"""
+            # {{autogenerated}}
+            {% for file in platform.iter_files(".v") -%}
+                read_verilog {{get_override("read_verilog_opts")|options}} {{file}}
+            {% endfor %}
+            {% for file in platform.iter_files(".sv") -%}
+                read_verilog {{get_override("read_verilog_opts")|options}} {{file}}
+            {% endfor %}
+            {% for file in platform.iter_files(".il") -%}
+                read_ilang {{file}}
+            {% endfor %}
+            read_ilang {{name}}.il
+            delete w:$verilog_initial_trigger
+            {{get_override("script_after_read")|default("# (script_after_read placeholder)")}}
+            write_cxxrtl {{get_override("write_cxxrtl_opts")|options}} -header {{name}}.cc
+        """,
+        "{{name}}.mk": r"""
+            # {{autogenerated}}
+            CC       = $(CXX)
+            CPPFLAGS = -Ilambdasoc.sim/include -include {{name}}.h -MMD \
+                       -I{{get_override("yosys_include_dir")|default("/usr/local/share/yosys/include")}} \
+                       {{get_override("additional_cpp_flags")|default("# (additional_cpp_flags placeholder)")}}
+            CXXFLAGS = {{get_override("cxx_flags")|default("-std=c++14 -Wall -O3 -mtune=native")}}
+            LDFLAGS  = {{get_override("ld_flags")|default("# (ld_flags placeholder)")}}
+            LDLIBS   = {{get_override("ld_libs")|default("# (ld_libs placeholder)")}}
+
+            SRC  = {{name}}.cc \
+            {% for file in platform.iter_files(".cc") %}
+                   {{file}} \
+            {% endfor %}
+
+            OBJS = $(SRC:.cc=.o)
+            DEPS = $(OBJS:.o=.d)
+
+            .PHONY: all clean
+
+            all: {{name}}_driver
+
+            -include DEPS
+
+            {% set tab = ""|indent(width="\t", first=True) -%}
+
+            {% for file in platform.iter_files(".v", ".sv", ".il") %}
+            {{name}}.cc {{name}}.h: {{file}}
+            {% endfor %}
+            {{name}}.cc {{name}}.h: {{name}}.ys {{name}}.il
+            {{tab}}$(YOSYS) -s {{name}}.ys
+
+            {{name}}_driver.o: CPPFLAGS += -DCXXRTL_TOP='cxxrtl_design::p_{{name}}'
+
+            $(OBJS): {{name}}.h
+
+            {{name}}_driver: $(OBJS)
+
+            clean:
+            {{tab}}rm -f {{name}}.cc {{name}}.h
+            {{tab}}rm -f $(OBJS) $(DEPS)
+            {{tab}}rm -f {{name}}_driver
+        """,
+    }
+
+    # GCC templates
+
+    _gcc_required_tools = [
+        "yosys",
+        "g++",
+        "make",
+    ]
+    _gcc_command_templates = [
+        r"""
+            YOSYS={{invoke_tool("yosys")}}
+            CXX={{invoke_tool("g++")}}
+            {{invoke_tool("make")}} -f {{name}}.mk
+        """,
+    ]
+
+    # Clang templates
+
+    _clang_required_tools = [
+        "yosys",
+        "clang++",
+        "make",
+    ]
+    _clang_command_templates = [
+        r"""
+            YOSYS={{invoke_tool("yosys")}}
+            CXX={{invoke_tool("clang++")}}
+            {{invoke_tool("make")}} -f {{name}}.mk
+        """,
+    ]
+
+    def __init__(self, *, toolchain="clang"):
+        super().__init__()
+
+        assert toolchain in ("gcc", "clang")
+        self.toolchain = toolchain
+
+    @property
+    def required_tools(self):
+        if self.toolchain == "gcc":
+            return self._gcc_required_tools
+        if self.toolchain == "clang":
+            return self._clang_required_tools
+        assert False
+
+    @property
+    def command_templates(self):
+        if self.toolchain == "gcc":
+            return self._gcc_command_templates
+        if self.toolchain == "clang":
+            return self._clang_command_templates
+        assert False
+
+    def create_missing_domain(self, name):
+        if name == "sync":
+            m = Module()
+            clk_i = self.request(self.default_clk).i
+            rst_i = self.request(self.default_rst).i
+            m.domains.sync = ClockDomain("sync")
+            m.d.comb += [
+                ClockSignal("sync").eq(clk_i),
+                ResetSignal("sync").eq(rst_i),
+            ]
+            return m
+
+    def toolchain_prepare(self, fragment, name="top", blackboxes=None, **kwargs):
+        if blackboxes is None:
+            blackboxes = OrderedDict()
+
+        def get_cxxrtl_src(package):
+            cxxrtl_src = OrderedDict()
+            assert hasattr(package, "cxxrtl_src_files")
+            for module, subdirs, src_file in package.cxxrtl_src_files:
+                src_contents = resources.read_text(module, src_file)
+                src_path = "/".join((package.__name__, *subdirs, src_file))
+                cxxrtl_src[src_path] = src_contents
+            return cxxrtl_src
+
+        cxxrtl_src = {
+            f"{name}_driver.cc": resources.read_text(__package__, "top_driver.cc"),
+            **get_cxxrtl_src(sys.modules[__package__]),
+        }
+
+        for blackbox_name, driver_name in blackboxes.items():
+            blackbox = import_module(blackbox_name)
+            blackbox_contents = resources.read_text(blackbox, "blackbox.v")
+            blackbox_path = f"{blackbox_name}/blackbox.v"
+            self.add_file(blackbox_path, blackbox_contents)
+
+            driver = import_module(f"{blackbox_name}.drivers.{driver_name}")
+            cxxrtl_src.update(get_cxxrtl_src(driver))
+
+        for cxxrtl_src_path, cxxrtl_src_contents in cxxrtl_src.items():
+            self.add_file(cxxrtl_src_path, cxxrtl_src_contents)
+
+        return super().toolchain_prepare(fragment, name=name, **kwargs)
diff --git a/lambdasoc/sim/top_driver.cc b/lambdasoc/sim/top_driver.cc
new file mode 100644 (file)
index 0000000..d09b3a4
--- /dev/null
@@ -0,0 +1,168 @@
+#include <backends/cxxrtl/cxxrtl_vcd.h>
+#include <exception>
+#include <fstream>
+#include <getopt.h>
+#include <iostream>
+#include <limits>
+#include <poll.h>
+#include <signal.h>
+#include <sstream>
+#include <stdexcept>
+#include <stdlib.h>
+#include <string>
+#include <sys/signalfd.h>
+#include <util/log_fmt.h>
+
+#ifndef CXXRTL_TOP
+#define CXXRTL_TOP cxxrtl_design::p_top
+#endif
+
+struct sim_args {
+    bool help            = false;
+    bool trace           = false;
+    std::string vcd_path = "";
+    bool trace_memories  = false;
+    uint64_t cycles      = sim_args::max_cycles();
+    bool unknown         = false;
+
+    sim_args(int argc, char *argv[]) {
+        const option long_opts[] = {
+            {"help",           no_argument,       nullptr, 'h'},
+            {"cycles",         required_argument, nullptr, 'c'},
+            {"trace",          required_argument, nullptr, 't'},
+            {"trace-memories", no_argument,       nullptr, 'm'},
+            {nullptr,          no_argument,       nullptr, 0  },
+        };
+        while (true) {
+            const int opt = getopt_long(argc, argv, "hc:t:m", long_opts, /*longindex=*/nullptr);
+            if (opt == -1) {
+                break;
+            }
+            switch (opt) {
+                case 'h':
+                    help = true;
+                    break;
+                case 'c':
+                    cycles = strtoull(optarg, /*endptr=*/nullptr, /*base=*/10);
+                    if (cycles > sim_args::max_cycles()) {
+                        std::stringstream msg;
+                        msg << "Cycles must be a positive integer lesser than or equal to "
+                            << sim_args::max_cycles();
+                        throw std::out_of_range(fmt_msg(msg.str()));
+                    }
+                    break;
+                case 't':
+                    trace = true;
+                    vcd_path = std::string(optarg);
+                    break;
+                case 'm':
+                    trace_memories = true;
+                    break;
+                default:
+                    unknown = true;
+                    break;
+            }
+        }
+    }
+
+    static constexpr uint64_t max_cycles() {
+        return std::numeric_limits<uint64_t>::max() >> 1;
+    }
+
+    static std::string usage(const std::string &name) {
+        std::stringstream msg;
+        msg << "Usage: " << name << " [-h] [--cycles CYCLES] [--trace VCD_PATH] [--trace-memories]\n"
+            << "\n"
+            << "Optional arguments:\n"
+            << "  -h, --help            show this help message and exit\n"
+            << "  -c, --cycles CYCLES   number of clock cycles (default: " << sim_args::max_cycles() << ")\n"
+            << "  -t, --trace VCD_PATH  enable tracing to a VCD file\n"
+            << "  -m, --trace-memories  also trace memories, at the cost of performance and disk usage\n";
+        return msg.str();
+    }
+};
+
+static pollfd sigint_pollfd() {
+    sigset_t mask;
+    sigemptyset(&mask);
+    sigaddset(&mask, SIGINT);
+
+    if (sigprocmask(SIG_BLOCK, &mask, /*oldset=*/nullptr) == -1) {
+        throw std::runtime_error(fmt_errno("sigprocmask"));
+    }
+
+    int sfd = signalfd(/*fd=*/-1, &mask, /*flags=*/0);
+    if (sfd == -1) {
+        throw std::runtime_error(fmt_errno("signalfd"));
+    }
+
+    pollfd pfd = {sfd, POLLIN, 0};
+    return pfd;
+}
+
+int main(int argc, char *argv[]) {
+    int rc = 0;
+
+    try {
+        const sim_args args(argc, argv);
+
+        if (args.help | args.unknown) {
+            std::cout << sim_args::usage(argv[0]);
+            if (args.unknown) {
+                rc = 1;
+            }
+        } else {
+            CXXRTL_TOP top;
+            cxxrtl::vcd_writer vcd;
+            std::ofstream vcd_file;
+            debug_items debug_items;
+
+            if (args.trace) {
+                vcd_file.open(args.vcd_path);
+                top.debug_info(debug_items);
+                vcd.timescale(1, "us");
+
+                if (args.trace_memories) {
+                    vcd.add(debug_items);
+                } else {
+                    vcd.add_without_memories(debug_items);
+                }
+            }
+
+            std::cout << "Press Enter to start simulation...";
+            std::cin.get();
+
+            pollfd sigint_pfd = sigint_pollfd();
+
+            std::cout << "Running.\n"
+                      << "Press Ctrl-C to exit simulation.\n";
+
+            for (uint64_t i = 0; i < args.cycles; i++) {
+                if (sigint_pfd.revents & POLLIN) {
+                    break;
+                }
+
+                top.p_clk__0____io.set<bool>(false);
+                top.step();
+                if (args.trace) {
+                    vcd.sample(2 * i);
+                }
+                top.p_clk__0____io.set<bool>(true);
+                top.step();
+                if (args.trace) {
+                    vcd.sample(2 * i + 1);
+                    vcd_file << vcd.buffer;
+                    vcd.buffer.clear();
+                }
+
+                poll(&sigint_pfd, /*nfds=*/1, /*timeout=*/0);
+            }
+        }
+    } catch (std::exception &e) {
+        std::cout << "ERROR: " << e.what() << "\n";
+        rc = 1;
+    }
+
+    std::cout << "\rExiting.\n";
+    return rc;
+}