--- /dev/null
+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),
+]
--- /dev/null
+from .. import collect_cxxrtl_src
+from . import util
+
+
+cxxrtl_src_files = [
+ *collect_cxxrtl_src(util),
+]
--- /dev/null
+cxxrtl_src_files = [
+ (__package__, (), "log_fmt.h"),
+]
--- /dev/null
+#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
--- /dev/null
+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)
--- /dev/null
+#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;
+}