From 3f733dbb17410d443308284fc0d9e7679c08057b Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Fri, 29 Oct 2021 14:08:53 +0200 Subject: [PATCH] sim: add CXXRTL integration. --- lambdasoc/sim/__init__.py | 13 ++ lambdasoc/sim/blackboxes/__init__.py | 0 lambdasoc/sim/include/__init__.py | 7 + lambdasoc/sim/include/util/__init__.py | 3 + lambdasoc/sim/include/util/log_fmt.h | 23 +++ lambdasoc/sim/platform.py | 185 +++++++++++++++++++++++++ lambdasoc/sim/top_driver.cc | 168 ++++++++++++++++++++++ 7 files changed, 399 insertions(+) create mode 100644 lambdasoc/sim/__init__.py create mode 100644 lambdasoc/sim/blackboxes/__init__.py create mode 100644 lambdasoc/sim/include/__init__.py create mode 100644 lambdasoc/sim/include/util/__init__.py create mode 100644 lambdasoc/sim/include/util/log_fmt.h create mode 100644 lambdasoc/sim/platform.py create mode 100644 lambdasoc/sim/top_driver.cc diff --git a/lambdasoc/sim/__init__.py b/lambdasoc/sim/__init__.py new file mode 100644 index 0000000..c4627d4 --- /dev/null +++ b/lambdasoc/sim/__init__.py @@ -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 index 0000000..e69de29 diff --git a/lambdasoc/sim/include/__init__.py b/lambdasoc/sim/include/__init__.py new file mode 100644 index 0000000..6045270 --- /dev/null +++ b/lambdasoc/sim/include/__init__.py @@ -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 index 0000000..b387ede --- /dev/null +++ b/lambdasoc/sim/include/util/__init__.py @@ -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 index 0000000..e8d3c92 --- /dev/null +++ b/lambdasoc/sim/include/util/log_fmt.h @@ -0,0 +1,23 @@ +#ifndef _FMT_LOG_H +#define _FMT_LOG_H +#include +#include +#include +#include + +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 index 0000000..f80153d --- /dev/null +++ b/lambdasoc/sim/platform.py @@ -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 index 0000000..d09b3a4 --- /dev/null +++ b/lambdasoc/sim/top_driver.cc @@ -0,0 +1,168 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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::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(false); + top.step(); + if (args.trace) { + vcd.sample(2 * i); + } + top.p_clk__0____io.set(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; +} -- 2.30.2