From cbb49d46cfcedc2eb99ab46a95329dac6c8ae6e2 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Wed, 1 May 2024 11:48:04 +0100 Subject: [PATCH] add initial version of functional C++ backend --- backends/functional/Makefile.inc | 1 + backends/functional/cxx.cc | 417 ++++++++++++++++++++++++++ backends/functional/cxx_runtime/sim.h | 366 ++++++++++++++++++++++ kernel/graphtools.h | 332 ++++++++++++++++++++ 4 files changed, 1116 insertions(+) create mode 100644 backends/functional/Makefile.inc create mode 100644 backends/functional/cxx.cc create mode 100644 backends/functional/cxx_runtime/sim.h create mode 100644 kernel/graphtools.h diff --git a/backends/functional/Makefile.inc b/backends/functional/Makefile.inc new file mode 100644 index 00000000000..011e662646c --- /dev/null +++ b/backends/functional/Makefile.inc @@ -0,0 +1 @@ +OBJS += backends/functional/cxx.o diff --git a/backends/functional/cxx.cc b/backends/functional/cxx.cc new file mode 100644 index 00000000000..46ce88bd62c --- /dev/null +++ b/backends/functional/cxx.cc @@ -0,0 +1,417 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Emily Schmidt + * + * 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/yosys.h" +#include "kernel/drivertools.h" +#include "kernel/topo_scc.h" +#include "kernel/functional.h" +#include "kernel/graphtools.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +const char *reserved_keywords[] = { + "alignas","alignof","and","and_eq","asm","atomic_cancel","atomic_commit", + "atomic_noexcept","auto","bitand","bitor","bool","break","case", + "catch","char","char16_t","char32_t","char8_t","class","co_await", + "co_return","co_yield","compl","concept","const","const_cast","consteval", + "constexpr","constinit","continue","decltype","default","delete", + "do","double","dynamic_cast","else","enum","explicit","export", + "extern","false","float","for","friend","goto","if","inline", + "int","long","mutable","namespace","new","noexcept","not","not_eq", + "nullptr","operator","or","or_eq","private","protected","public", + "reflexpr","register","reinterpret_cast","requires","return","short", + "signed","sizeof","static","static_assert","static_cast","struct", + "switch","synchronized","template","this","thread_local","throw", + "true","try","typedef","typeid","typename","union","unsigned", + "using","virtual","void","volatile","wchar_t","while","xor","xor_eq", + nullptr +}; + +struct CxxScope { + pool used_names; + dict name_map; + + CxxScope() { + for(const char **p = reserved_keywords; *p != nullptr; p++) + reserve(*p); + } + void reserve(std::string name) { + used_names.insert(name); + } + std::string insert(IdString id) { + std::string str = RTLIL::unescape_id(id); + for(size_t i = 0; i < str.size(); i++) + if(strchr("!\"#%&'()*+,-./:;<=>?@[]\\^`{|}~ ", str[i])) + str[i] = '_'; + if(used_names.count(str) == 0){ + used_names.insert(str); + name_map.insert({id, str}); + return str; + } + for (int idx = 0 ; ; idx++){ + std::string suffixed = str + "_" + std::to_string(idx); + if (used_names.count(suffixed) == 0) { + used_names.insert(suffixed); + if(name_map.count(id) == 0) + name_map.insert({id, suffixed}); + return suffixed; + } + } + } + std::string operator[](IdString id) { + if(name_map.count(id) > 0) + return name_map[id]; + else + return insert(id); + } +}; + +struct CxxWriter { + std::ostream &f; + CxxWriter(std::ostream &out) : f(out) {} + void printf(const char *fmt, ...) + { + va_list va; + va_start(va, fmt); + f << vstringf(fmt, va); + va_end(va); + } +}; + +struct CxxStruct { + std::string name; + dict types; + CxxScope scope; + CxxStruct(std::string name) : name(name) { + scope.reserve("out"); + scope.reserve("dump"); + } + void insert(IdString name, std::string type) { + scope.insert(name); + types.insert({name, type}); + } + void print(CxxWriter &f) { + f.printf("struct %s {\n", name.c_str()); + for (auto p : types) { + f.printf("\t%s %s;\n", p.second.c_str(), scope[p.first].c_str()); + } + f.printf("\n\ttemplate void dump(T &out) {\n"); + for (auto p : types) { + f.printf("\t\tout(\"%s\", %s);\n", RTLIL::unescape_id(p.first).c_str(), scope[p.first].c_str()); + } + f.printf("\t}\n};\n\n"); + } + std::string operator[](IdString field) { + return scope[field]; + } +}; + +struct CxxFunction { + IdString name; + int width; + dict parameters; + + CxxFunction(IdString name, int width) : name(name), width(width) {} + CxxFunction(IdString name, int width, dict parameters) : name(name), width(width), parameters(parameters) {} + + bool operator==(CxxFunction const &other) const { + return name == other.name && parameters == other.parameters && width == other.width; + } + + unsigned int hash() const { + return mkhash(name.hash(), parameters.hash()); + } +}; + +typedef ComputeGraph CxxComputeGraph; + +class CxxComputeGraphFactory { + CxxComputeGraph &graph; + using T = CxxComputeGraph::Ref; + static bool is_single_output(IdString type) + { + auto it = yosys_celltypes.cell_types.find(type); + return it != yosys_celltypes.cell_types.end() && it->second.outputs.size() <= 1; + } +public: + CxxComputeGraphFactory(CxxComputeGraph &g) : graph(g) {} + T slice(T a, int in_width, int offset, int out_width) { + assert(offset + out_width <= in_width); + return graph.add(CxxFunction(ID($$slice), out_width, {{ID(offset), offset}}), 0, std::array{a}); + } + T extend(T a, int in_width, int out_width, bool is_signed) { + assert(in_width < out_width); + if(is_signed) + return graph.add(CxxFunction(ID($sign_extend), out_width, {{ID(WIDTH), out_width}}), 0, std::array{a}); + else + return graph.add(CxxFunction(ID($zero_extend), out_width, {{ID(WIDTH), out_width}}), 0, std::array{a}); + } + T concat(T a, int a_width, T b, int b_width) { + return graph.add(CxxFunction(ID($$concat), a_width + b_width), 0, std::array{a, b}); + } + T add(T a, T b, int width) { return graph.add(CxxFunction(ID($add), width), 0, std::array{a, b}); } + T sub(T a, T b, int width) { return graph.add(CxxFunction(ID($sub), width), 0, std::array{a, b}); } + T bitwise_and(T a, T b, int width) { return graph.add(CxxFunction(ID($and), width), 0, std::array{a, b}); } + T bitwise_or(T a, T b, int width) { return graph.add(CxxFunction(ID($or), width), 0, std::array{a, b}); } + T bitwise_xor(T a, T b, int width) { return graph.add(CxxFunction(ID($xor), width), 0, std::array{a, b}); } + T bitwise_not(T a, int width) { return graph.add(CxxFunction(ID($not), width), 0, std::array{a}); } + T neg(T a, int width) { return graph.add(CxxFunction(ID($neg), width), 0, std::array{a}); } + T mux(T a, T b, T s, int width) { return graph.add(CxxFunction(ID($mux), width), 0, std::array{a, b, s}); } + T pmux(T a, T b, T s, int width, int) { return graph.add(CxxFunction(ID($pmux), width), 0, std::array{a, b, s}); } + T reduce_and(T a, int) { return graph.add(CxxFunction(ID($reduce_and), 1), 0, std::array{a}); } + T reduce_or(T a, int) { return graph.add(CxxFunction(ID($reduce_or), 1), 0, std::array{a}); } + T reduce_xor(T a, int) { return graph.add(CxxFunction(ID($reduce_xor), 1), 0, std::array{a}); } + T eq(T a, T b, int) { return graph.add(CxxFunction(ID($eq), 1), 0, std::array{a, b}); } + T ne(T a, T b, int) { return graph.add(CxxFunction(ID($ne), 1), 0, std::array{a, b}); } + T gt(T a, T b, int) { return graph.add(CxxFunction(ID($gt), 1), 0, std::array{a, b}); } + T ge(T a, T b, int) { return graph.add(CxxFunction(ID($ge), 1), 0, std::array{a, b}); } + T ugt(T a, T b, int) { return graph.add(CxxFunction(ID($ugt), 1), 0, std::array{a, b}); } + T uge(T a, T b, int) { return graph.add(CxxFunction(ID($uge), 1), 0, std::array{a, b}); } + T logical_shift_left(T a, T b, int y_width, int) { return graph.add(CxxFunction(ID($shl), y_width, {{ID(WIDTH), y_width}}), 0, std::array{a, b}); } + T logical_shift_right(T a, T b, int y_width, int) { return graph.add(CxxFunction(ID($shr), y_width, {{ID(WIDTH), y_width}}), 0, std::array{a, b}); } + T arithmetic_shift_right(T a, T b, int y_width, int) { return graph.add(CxxFunction(ID($asr), y_width, {{ID(WIDTH), y_width}}), 0, std::array{a, b}); } + + T constant(RTLIL::Const value) { + return graph.add(CxxFunction(ID($$const), value.size(), {{ID(value), value}}), 0); + } + T input(IdString name, int width) { return graph.add(CxxFunction(ID($$input), width, {{name, {}}}), 0); } + T state(IdString name, int width) { return graph.add(CxxFunction(ID($$state), width, {{name, {}}}), 0); } + T cell_output(T cell, IdString type, IdString name, int width) { + if (is_single_output(type)) + return cell; + else + return graph.add(CxxFunction(ID($$cell_output), width, {{name, {}}}), 0, std::array{cell}); + } + T multiple(vector args, int width) { + return graph.add(CxxFunction(ID($$multiple), width), 0, args); + } + T undriven(int width) { + return graph.add(CxxFunction(ID($$undriven), width), 0); + } + + T create_pending(int width) { + return graph.add(CxxFunction(ID($$pending), width), 0); + } + void update_pending(T pending, T node) { + assert(pending.function().name == ID($$pending)); + pending.set_function(CxxFunction(ID($$buf), pending.function().width)); + pending.append_arg(node); + } + void declare_output(T node, IdString name) { + node.assign_key(name); + } + void declare_state(T node, IdString name) { + node.assign_key(name); + } + void suggest_name(T node, IdString name) { + node.sparse_attr() = name; + } +}; + +struct FunctionalCxxBackend : public Backend +{ + FunctionalCxxBackend() : Backend("functional_cxx", "convert design to C++ using the functional backend") {} + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + } + + CxxComputeGraph calculate_compute_graph(RTLIL::Module *module) + { + CxxComputeGraph compute_graph; + CxxComputeGraphFactory factory(compute_graph); + ComputeGraphConstruction construction(factory); + construction.add_module(module); + construction.process_queue(); + + // Perform topo sort and detect SCCs + CxxComputeGraph::SccAdaptor compute_graph_scc(compute_graph); + + bool scc = false; + std::vector perm; + topo_sorted_sccs(compute_graph_scc, [&](int *begin, int *end) { + perm.insert(perm.end(), begin, end); + if (end > begin + 1) + { + log_warning("SCC:"); + for (int *i = begin; i != end; ++i) + log(" %d(%s)(%s)", *i, compute_graph[*i].function().name.c_str(), compute_graph[*i].has_sparse_attr() ? compute_graph[*i].sparse_attr().c_str() : ""); + log("\n"); + scc = true; + } + }, /* sources_first */ true); + compute_graph.permute(perm); + if(scc) log_error("combinational loops, aborting\n"); + + // Forward $$buf + std::vector alias; + perm.clear(); + + for (int i = 0; i < compute_graph.size(); ++i) + { + auto node = compute_graph[i]; + if (node.function().name == ID($$buf) && node.arg(0).index() < i) + { + int target_index = alias[node.arg(0).index()]; + auto target_node = compute_graph[perm[target_index]]; + if(!target_node.has_sparse_attr() && node.has_sparse_attr()) + target_node.sparse_attr() = node.sparse_attr(); + alias.push_back(target_index); + } + else + { + alias.push_back(GetSize(perm)); + perm.push_back(i); + } + } + compute_graph.permute(perm, alias); + return compute_graph; + } + + void printCxx(std::ostream &stream, std::string, std::string const & name, CxxComputeGraph &compute_graph) + { + dict inputs, state; + CxxWriter f(stream); + + // Dump the compute graph + for (int i = 0; i < compute_graph.size(); ++i) + { + auto ref = compute_graph[i]; + if(ref.function().name == ID($$input)) + inputs[ref.function().parameters.begin()->first] = ref.function().width; + if(ref.function().name == ID($$state)) + state[ref.function().parameters.begin()->first] = ref.function().width; + } + f.printf("#include \"sim.h\"\n"); + CxxStruct input_struct(name + "_Inputs"); + for (auto const &input : inputs) + input_struct.insert(input.first, "Signal<" + std::to_string(input.second) + ">"); + CxxStruct output_struct(name + "_Outputs"); + for (auto const &key : compute_graph.keys()) + if(state.count(key.first) == 0) + output_struct.insert(key.first, "Signal<" + std::to_string(compute_graph[key.second].function().width) + ">"); + CxxStruct state_struct(name + "_State"); + for (auto const &state_var : state) + state_struct.insert(state_var.first, "Signal<" + std::to_string(state_var.second) + ">"); + + idict node_names; + CxxScope locals; + + input_struct.print(f); + output_struct.print(f); + state_struct.print(f); + + f.printf("void %s(%s_Inputs const &input, %s_Outputs &output, %s_State const ¤t_state, %s_State &next_state)\n{\n", name.c_str(), name.c_str(), name.c_str(), name.c_str(), name.c_str()); + locals.reserve("input"); + locals.reserve("output"); + locals.reserve("current_state"); + locals.reserve("next_state"); + for (int i = 0; i < compute_graph.size(); ++i) + { + auto ref = compute_graph[i]; + int width = ref.function().width; + std::string name; + if(ref.has_sparse_attr()) + name = locals.insert(ref.sparse_attr()); + else + name = locals.insert("\\n" + std::to_string(i)); + node_names(name); + if(ref.function().name == ID($$input)) + f.printf("\tSignal<%d> %s = input.%s;\n", width, name.c_str(), input_struct[ref.function().parameters.begin()->first].c_str()); + else if(ref.function().name == ID($$state)) + f.printf("\tSignal<%d> %s = current_state.%s;\n", width, name.c_str(), state_struct[ref.function().parameters.begin()->first].c_str()); + else if(ref.function().name == ID($$buf)) + f.printf("\tSignal<%d> %s = %s;\n", width, name.c_str(), node_names[ref.arg(0).index()].c_str()); + else if(ref.function().name == ID($$cell_output)) + f.printf("\tSignal<%d> %s = %s.%s;\n", width, name.c_str(), node_names[ref.arg(0).index()].c_str(), RTLIL::unescape_id(ref.function().parameters.begin()->first).c_str()); + else if(ref.function().name == ID($$const)){ + auto c = ref.function().parameters.begin()->second; + if(c.size() <= 32){ + f.printf("\tSignal<%d> %s = $const<%d>(%#x);\n", width, name.c_str(), width, (uint32_t) c.as_int()); + }else{ + f.printf("\tSignal<%d> %s = $const<%d>({%#x", width, name.c_str(), width, (uint32_t) c.as_int()); + while(c.size() > 32){ + c = c.extract(32, c.size() - 32); + f.printf(", %#x", c.as_int()); + } + f.printf("});\n"); + } + }else if(ref.function().name == ID($$undriven)) + f.printf("\tSignal<%d> %s; //undriven\n", width, name.c_str()); + else if(ref.function().name == ID($$slice)) + f.printf("\tSignal<%d> %s = slice<%d>(%s, %d);\n", width, name.c_str(), width, node_names[ref.arg(0).index()].c_str(), ref.function().parameters.at(ID(offset)).as_int()); + else if(ref.function().name == ID($$concat)){ + f.printf("\tauto %s = concat(", name.c_str()); + for (int i = 0, end = ref.size(); i != end; ++i){ + if(i > 0) + f.printf(", "); + f.printf("%s", node_names[ref.arg(i).index()].c_str()); + } + f.printf(");\n"); + }else{ + f.printf("\t"); + if(ref.function().width > 0) + f.printf("Signal<%d>", ref.function().width); + else + f.printf("%s_Outputs", log_id(ref.function().name)); + f.printf(" %s = %s", name.c_str(), log_id(ref.function().name)); + if(ref.function().parameters.count(ID(WIDTH))){ + f.printf("<%d>", ref.function().parameters.at(ID(WIDTH)).as_int()); + } + f.printf("("); + for (int i = 0, end = ref.size(); i != end; ++i) + f.printf("%s%s", i>0?", ":"", node_names[ref.arg(i).index()].c_str()); + f.printf("); //"); + for (auto const ¶m : ref.function().parameters) + { + if (param.second.empty()) + f.printf("[%s]", log_id(param.first)); + else + f.printf("[%s=%s]", log_id(param.first), log_const(param.second)); + } + f.printf("\n"); + } + } + + for (auto const &key : compute_graph.keys()) + { + f.printf("\t%s.%s = %s;\n", state.count(key.first) > 0 ? "next_state" : "output", state_struct[key.first].c_str(), node_names[key.second].c_str()); + } + f.printf("}\n"); + } + + void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override + { + log_header(design, "Executing Functional C++ backend.\n"); + + size_t argidx = 1; + extra_args(f, filename, args, argidx, design); + + for (auto module : design->selected_modules()) { + log("Dumping module `%s'.\n", module->name.c_str()); + auto compute_graph = calculate_compute_graph(module); + printCxx(*f, filename, RTLIL::unescape_id(module->name), compute_graph); + } + } +} FunctionalCxxBackend; + +PRIVATE_NAMESPACE_END diff --git a/backends/functional/cxx_runtime/sim.h b/backends/functional/cxx_runtime/sim.h new file mode 100644 index 00000000000..4e7d9b60142 --- /dev/null +++ b/backends/functional/cxx_runtime/sim.h @@ -0,0 +1,366 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Emily Schmidt + * + * 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. + * + */ + +#ifndef SIM_H +#define SIM_H + +#include + +template +using Signal = std::array; + +template +Signal slice(Signal const& a, size_t offset) +{ + Signal ret; + + std::copy(a.begin() + offset, a.begin() + offset + n, ret.begin()); + return ret; +} + +template +Signal $const(uint32_t val) +{ + size_t i; + Signal ret; + + for(i = 0; i < n; i++) + if(i < 32) + ret[i] = val & (1< +Signal $const(std::initializer_list vals) +{ + size_t k, i; + Signal ret; + + k = 0; + for (auto val : vals) { + for(i = 0; i < 32; i++) + if(i + k < n) + ret[i + k] = val & (1< +bool as_bool(Signal sig) +{ + for(int i = 0; i < n; i++) + if(sig[i]) + return true; + return false; +} + +template +uint32_t as_int(Signal sig) +{ + uint32_t ret = 0; + for(int i = 0; i < n; i++) + if(sig[i] && i < 32) + ret |= 1< +Signal $mux(Signal const& a, Signal const &b, Signal<1> const &s) +{ + return s[0] ? b : a; +} + +template +Signal $not(Signal const& a) +{ + Signal ret; + for(size_t i = 0; i < n; i++) + ret[i] = !a[i]; + return ret; +} + +template +Signal $neg(Signal const& a) +{ + Signal ret; + bool carry = true; + for(size_t i = 0; i < n; i++) { + int r = !a[i] + carry; + ret[i] = (r & 1) != 0; + carry = (r >> 1) != 0; + } + return ret; +} + +template +Signal<1> $reduce_or(Signal const& a) +{ + return { as_bool(a) }; +} + +template +Signal<1> $reduce_and(Signal const& a) +{ + for(size_t i = 0; i < n; i++) + if(!a[i]) + return { false }; + return { true }; +} + +template +Signal<1> $reduce_bool(Signal const& a) +{ + return { as_bool(a) }; +} + +template +Signal<1> $logic_and(Signal const& a, Signal const& b) +{ + return { as_bool(a) && as_bool(b) }; +} + +template +Signal<1> $logic_or(Signal const& a, Signal const& b) +{ + return { as_bool(a) || as_bool(b) }; +} + +template +Signal<1> $logic_not(Signal const& a) +{ + return { !as_bool(a) }; +} + +template +Signal $add(Signal const& a, Signal const &b) +{ + Signal ret; + size_t i; + int x = 0; + for(i = 0; i < n; i++){ + x += (int)a[i] + (int)b[i]; + ret[i] = x & 1; + x >>= 1; + } + return ret; +} +template +Signal $sub(Signal const& a, Signal const &b) +{ + Signal ret; + int x = 1; + for(size_t i = 0; i < n; i++){ + x += (int)a[i] + (int)!b[i]; + ret[i] = x & 1; + x >>= 1; + } + return ret; +} + +template +Signal<1> $uge(Signal const& a, Signal const &b) +{ + for(size_t i = n; i-- != 0; ) + if(a[i] != b[i]) + return { a[i] }; + return { true }; +} + +template +Signal<1> $ugt(Signal const& a, Signal const &b) +{ + for(size_t i = n; i-- != 0; ) + if(a[i] != b[i]) + return { a[i] }; + return { false }; +} + +template +Signal<1> $ge(Signal const& a, Signal const &b) +{ + if(a[n-1] != b[n-1]) + return { b[n-1] }; + return $uge(a, b); +} + +template +Signal<1> $gt(Signal const& a, Signal const &b) +{ + if(a[n-1] != b[n-1]) + return { b[n-1] }; + return $ugt(a, b); +} + +template Signal<1> $ule(Signal const& a, Signal const &b) { return $uge(b, a); } +template Signal<1> $ult(Signal const& a, Signal const &b) { return $ugt(b, a); } +template Signal<1> $le(Signal const& a, Signal const &b) { return $ge(b, a); } +template Signal<1> $lt(Signal const& a, Signal const &b) { return $gt(b, a); } + +template +Signal $and(Signal const& a, Signal const &b) +{ + Signal ret; + for(size_t i = 0; i < n; i++) + ret[i] = a[i] && b[i]; + return ret; +} + +template +Signal $or(Signal const& a, Signal const &b) +{ + Signal ret; + for(size_t i = 0; i < n; i++) + ret[i] = a[i] || b[i]; + return ret; +} + +template +Signal $xor(Signal const& a, Signal const &b) +{ + Signal ret; + for(size_t i = 0; i < n; i++) + ret[i] = a[i] != b[i]; + return ret; +} + +template +Signal $shl(Signal const& a, Signal const &b) +{ + if(nb >= sizeof(int) * 8 - 1) + for(size_t i = sizeof(int) * 8 - 1; i < nb; i++) + assert(!b[i]); + size_t amount = as_int(b); + Signal ret = $const(0); + if(amount < n){ + if(amount + na > n) + std::copy(a.begin(), a.begin() + (n - amount), ret.begin() + amount); + else + std::copy(a.begin(), a.end(), ret.begin() + amount); + } + return ret; +} + +template +Signal $shr(Signal const& a, Signal const &b) +{ + if(nb >= sizeof(int) * 8 - 1) + for(size_t i = sizeof(int) * 8 - 1; i < nb; i++) + assert(!b[i]); + size_t amount = as_int(b); + Signal ret; + for (size_t i = 0; i < n; i++) { + if(i + amount < n) + ret[i] = a[i + amount]; + else + ret[i] = false; + } + return ret; +} + +template +Signal $asr(Signal const& a, Signal const &b) +{ + if(nb >= sizeof(int) * 8 - 1) + for(size_t i = sizeof(int) * 8 - 1; i < nb; i++) + assert(!b[i]); + size_t amount = as_int(b); + Signal ret; + for (size_t i = 0; i < n; i++) { + if(i + amount < n) + ret[i] = a[i + amount]; + else + ret[i] = a[n - 1]; + } + return ret; +} + +template +Signal<1> $eq(Signal const& a, Signal const &b) +{ + for(size_t i = 0; i < n; i++) + if(a[i] != b[i]) + return { false }; + return { true }; +} + +template +Signal<1> $ne(Signal const& a, Signal const &b) +{ + for(size_t i = 0; i < n; i++) + if(a[i] != b[i]) + return { true }; + return { false }; +} + +template +Signal $pmux(Signal const& a, Signal const &b, Signal const &s) +{ + bool found; + Signal ret; + + found = false; + ret = a; + for(size_t i = 0; i < ns; i++){ + if(s[i]){ + if(found) + return $const(0); + found = true; + ret = slice(b, n * i); + } + } + return ret; +} + +template +Signal concat(Signal const& a, Signal const& b) +{ + Signal ret; + std::copy(a.begin(), a.end(), ret.begin()); + std::copy(b.begin(), b.end(), ret.begin() + n); + return ret; +} + +template +Signal $zero_extend(Signal const& a) +{ + assert(n >= m); + Signal ret; + std::copy(a.begin(), a.end(), ret.begin()); + for(size_t i = m; i < n; i++) + ret[i] = false; + return ret; +} + +template +Signal $sign_extend(Signal const& a) +{ + assert(n >= m); + Signal ret; + std::copy(a.begin(), a.end(), ret.begin()); + for(size_t i = m; i < n; i++) + ret[i] = a[m-1]; + return ret; +} + +#endif diff --git a/kernel/graphtools.h b/kernel/graphtools.h new file mode 100644 index 00000000000..1a59ef1b537 --- /dev/null +++ b/kernel/graphtools.h @@ -0,0 +1,332 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Emily Schmidt + * + * 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. + * + */ + +#ifndef GRAPHTOOLS_H +#define GRAPHTOOLS_H + +#include "kernel/yosys.h" +#include "kernel/drivertools.h" +#include "kernel/functional.h" + +USING_YOSYS_NAMESPACE +YOSYS_NAMESPACE_BEGIN + +template +class CellSimplifier { + Factory &factory; + T reduce_shift_width(T b, int b_width, int y_width, int &reduced_b_width) { + log_assert(y_width > 0); + int new_width = sizeof(int) * 8 - __builtin_clz(y_width); + if (b_width <= new_width) { + reduced_b_width = b_width; + return b; + } else { + reduced_b_width = new_width; + T lower_b = factory.slice(b, b_width, 0, new_width); + T overflow = factory.gt(b, factory.constant(RTLIL::Const(y_width, b_width)), b_width); + return factory.mux(lower_b, factory.constant(RTLIL::Const(y_width, new_width)), overflow, new_width); + } + } +public: + T reduce_or(T a, int width) { + if (width == 1) + return a; + return factory.reduce_or(a, width); + } + T extend(T a, int in_width, int out_width, bool is_signed) { + if(in_width == out_width) + return a; + if(in_width > out_width) + return factory.slice(a, in_width, 0, out_width); + return factory.extend(a, in_width, out_width, is_signed); + } + T logical_shift_left(T a, T b, int y_width, int b_width) { + int reduced_b_width; + T reduced_b = reduce_shift_width(b, b_width, y_width, reduced_b_width); + return factory.logical_shift_left(a, reduced_b, y_width, reduced_b_width); + } + T logical_shift_right(T a, T b, int y_width, int b_width) { + int reduced_b_width; + T reduced_b = reduce_shift_width(b, b_width, y_width, reduced_b_width); + return factory.logical_shift_right(a, reduced_b, y_width, reduced_b_width); + } + T arithmetic_shift_right(T a, T b, int y_width, int b_width) { + int reduced_b_width; + T reduced_b = reduce_shift_width(b, b_width, y_width, reduced_b_width); + return factory.arithmetic_shift_right(a, reduced_b, y_width, reduced_b_width); + } + CellSimplifier(Factory &f) : factory(f) {} + T handle(IdString cellType, dict parameters, dict inputs) + { + int a_width = parameters.at(ID(A_WIDTH), Const(-1)).as_int(); + int b_width = parameters.at(ID(B_WIDTH), Const(-1)).as_int(); + int y_width = parameters.at(ID(Y_WIDTH), Const(-1)).as_int(); + bool a_signed = parameters.at(ID(A_SIGNED), Const(0)).as_bool(); + bool b_signed = parameters.at(ID(B_SIGNED), Const(0)).as_bool(); + if(cellType.in({ID($add), ID($sub), ID($and), ID($or), ID($xor), ID($xnor)})){ + bool is_signed = a_signed && b_signed; + T a = extend(inputs.at(ID(A)), a_width, y_width, is_signed); + T b = extend(inputs.at(ID(B)), b_width, y_width, is_signed); + if(cellType == ID($add)) + return factory.add(a, b, y_width); + else if(cellType == ID($sub)) + return factory.sub(a, b, y_width); + else if(cellType == ID($and)) + return factory.bitwise_and(a, b, y_width); + else if(cellType == ID($or)) + return factory.bitwise_or(a, b, y_width); + else if(cellType == ID($xor)) + return factory.bitwise_xor(a, b, y_width); + else if(cellType == ID($xnor)) + return factory.bitwise_not(factory.bitwise_xor(a, b, y_width), y_width); + else + log_abort(); + }else if(cellType.in({ID($eq), ID($ne), ID($eqx), ID($nex), ID($le), ID($lt), ID($ge), ID($gt)})){ + bool is_signed = a_signed && b_signed; + int width = max(a_width, b_width); + T a = extend(inputs.at(ID(A)), a_width, width, is_signed); + T b = extend(inputs.at(ID(B)), b_width, width, is_signed); + if(cellType.in({ID($eq), ID($eqx)})) + return extend(factory.eq(a, b, width), 1, y_width, false); + if(cellType.in({ID($ne), ID($nex)})) + return extend(factory.ne(a, b, width), 1, y_width, false); + else if(cellType == ID($lt)) + return extend(is_signed ? factory.gt(b, a, width) : factory.ugt(b, a, width), 1, y_width, false); + else if(cellType == ID($le)) + return extend(is_signed ? factory.ge(b, a, width) : factory.uge(b, a, width), 1, y_width, false); + else if(cellType == ID($gt)) + return extend(is_signed ? factory.gt(a, b, width) : factory.ugt(a, b, width), 1, y_width, false); + else if(cellType == ID($ge)) + return extend(is_signed ? factory.ge(a, b, width) : factory.uge(a, b, width), 1, y_width, false); + else + log_abort(); + }else if(cellType.in({ID($logic_or), ID($logic_and)})){ + T a = reduce_or(inputs.at(ID(A)), a_width); + T b = reduce_or(inputs.at(ID(B)), b_width); + T y = cellType == ID($logic_and) ? factory.bitwise_and(a, b, 1) : factory.bitwise_or(a, b, 1); + return extend(y, 1, y_width, false); + }else if(cellType == ID($not)){ + T a = extend(inputs.at(ID(A)), a_width, y_width, a_signed); + return factory.bitwise_not(a, y_width); + }else if(cellType == ID($pos)){ + return extend(inputs.at(ID(A)), a_width, y_width, a_signed); + }else if(cellType == ID($neg)){ + T a = extend(inputs.at(ID(A)), a_width, y_width, a_signed); + return factory.neg(a, y_width); + }else if(cellType == ID($logic_not)){ + T a = reduce_or(inputs.at(ID(A)), a_width); + T y = factory.bitwise_not(a, 1); + return extend(y, 1, y_width, false); + }else if(cellType.in({ID($reduce_or), ID($reduce_bool)})){ + T a = reduce_or(inputs.at(ID(A)), a_width); + return extend(a, 1, y_width, false); + }else if(cellType == ID($reduce_and)){ + T a = factory.reduce_and(inputs.at(ID(A)), a_width); + return extend(a, 1, y_width, false); + }else if(cellType.in({ID($reduce_xor), ID($reduce_xnor)})){ + T a = factory.reduce_xor(inputs.at(ID(A)), a_width); + T y = cellType == ID($reduce_xnor) ? factory.bitwise_not(a, 1) : a; + return extend(y, 1, y_width, false); + }else if(cellType == ID($shl) || cellType == ID($sshl)){ + T a = extend(inputs.at(ID(A)), a_width, y_width, a_signed); + T b = inputs.at(ID(B)); + return logical_shift_left(a, b, y_width, b_width); + }else if(cellType == ID($shr) || cellType == ID($sshr)){ + int width = max(a_width, y_width); + T a = extend(inputs.at(ID(A)), a_width, width, a_signed); + T b = inputs.at(ID(B)); + T y = a_signed && cellType == ID($sshr) ? + arithmetic_shift_right(a, b, width, b_width) : + logical_shift_right(a, b, width, b_width); + return extend(y, width, y_width, a_signed); + }else if(cellType == ID($shiftx) || cellType == ID($shift)){ + int width = max(a_width, y_width); + T a = extend(inputs.at(ID(A)), a_width, width, cellType == ID($shift) && a_signed); + T b = inputs.at(ID(B)); + T shr = logical_shift_right(a, b, width, b_width); + if(b_signed) { + T sign_b = factory.slice(b, b_width, b_width - 1, 1); + T shl = logical_shift_left(a, factory.neg(b, b_width), width, b_width); + T y = factory.mux(shr, shl, sign_b, width); + return extend(y, width, y_width, false); + } else { + return extend(shr, width, y_width, false); + } + }else if(cellType == ID($mux)){ + int width = parameters.at(ID(WIDTH)).as_int(); + return factory.mux(inputs.at(ID(A)), inputs.at(ID(B)), inputs.at(ID(S)), width); + }else if(cellType == ID($pmux)){ + int width = parameters.at(ID(WIDTH)).as_int(); + int s_width = parameters.at(ID(S_WIDTH)).as_int(); + return factory.pmux(inputs.at(ID(A)), inputs.at(ID(B)), inputs.at(ID(S)), width, s_width); + }else if(cellType == ID($concat)){ + T a = inputs.at(ID(A)); + T b = inputs.at(ID(B)); + return factory.concat(a, a_width, b, b_width); + }else if(cellType == ID($slice)){ + int offset = parameters.at(ID(OFFSET)).as_int(); + T a = inputs.at(ID(A)); + return factory.slice(a, a_width, offset, y_width); + }else{ + log_error("unhandled cell in CellSimplifier %s\n", cellType.c_str()); + } + } +}; + +template +class ComputeGraphConstruction { + std::deque queue; + dict graph_nodes; + idict cells; + DriverMap driver_map; + Factory& factory; + CellSimplifier simplifier; + + T enqueue(DriveSpec const &spec) + { + auto it = graph_nodes.find(spec); + if(it == graph_nodes.end()){ + auto node = factory.create_pending(spec.size()); + graph_nodes.insert({spec, node}); + queue.emplace_back(spec); + return node; + }else + return it->second; + } +public: + ComputeGraphConstruction(Factory &f) : factory(f), simplifier(f) {} + void add_module(Module *module) + { + driver_map.add(module); + for (auto cell : module->cells()) { + if (cell->type.in(ID($assert), ID($assume), ID($cover), ID($check))) + enqueue(DriveBitMarker(cells(cell), 0)); + } + for (auto wire : module->wires()) { + if (wire->port_output) { + T node = enqueue(DriveChunk(DriveChunkWire(wire, 0, wire->width))); + factory.declare_output(node, wire->name); + } + } + } + void process_queue() + { + for (; !queue.empty(); queue.pop_front()) { + DriveSpec spec = queue.front(); + T pending = graph_nodes.at(spec); + + if (spec.chunks().size() > 1) { + auto chunks = spec.chunks(); + T node = enqueue(chunks[0]); + int width = chunks[0].size(); + for(size_t i = 1; i < chunks.size(); i++) { + node = factory.concat(node, width, enqueue(chunks[i]), chunks[i].size()); + width += chunks[i].size(); + } + factory.update_pending(pending, node); + } else if (spec.chunks().size() == 1) { + DriveChunk chunk = spec.chunks()[0]; + if (chunk.is_wire()) { + DriveChunkWire wire_chunk = chunk.wire(); + if (wire_chunk.is_whole()) { + if (wire_chunk.wire->port_input) { + T node = factory.input(wire_chunk.wire->name, wire_chunk.width); + factory.suggest_name(node, wire_chunk.wire->name); + factory.update_pending(pending, node); + } else { + DriveSpec driver = driver_map(DriveSpec(wire_chunk)); + T node = enqueue(driver); + factory.suggest_name(node, wire_chunk.wire->name); + factory.update_pending(pending, node); + } + } else { + DriveChunkWire whole_wire(wire_chunk.wire, 0, wire_chunk.wire->width); + T node = factory.slice(enqueue(whole_wire), wire_chunk.wire->width, wire_chunk.offset, wire_chunk.width); + factory.update_pending(pending, node); + } + } else if (chunk.is_port()) { + DriveChunkPort port_chunk = chunk.port(); + if (port_chunk.is_whole()) { + if (driver_map.celltypes.cell_output(port_chunk.cell->type, port_chunk.port)) { + if (port_chunk.cell->type.in(ID($dff), ID($ff))) + { + Cell *cell = port_chunk.cell; + T node = factory.state(cell->name, port_chunk.width); + factory.suggest_name(node, port_chunk.cell->name); + factory.update_pending(pending, node); + for (auto const &conn : cell->connections()) { + if (driver_map.celltypes.cell_input(cell->type, conn.first)) { + T node = enqueue(DriveChunkPort(cell, conn)); + factory.declare_state(node, cell->name); + } + } + } + else + { + T cell = enqueue(DriveChunkMarker(cells(port_chunk.cell), 0, port_chunk.width)); + factory.suggest_name(cell, port_chunk.cell->name); + T node = factory.cell_output(cell, port_chunk.cell->type, port_chunk.port, port_chunk.width); + factory.suggest_name(node, port_chunk.cell->name.str() + "$" + port_chunk.port.str()); + factory.update_pending(pending, node); + } + } else { + DriveSpec driver = driver_map(DriveSpec(port_chunk)); + factory.update_pending(pending, enqueue(driver)); + } + } else { + DriveChunkPort whole_port(port_chunk.cell, port_chunk.port, 0, GetSize(port_chunk.cell->connections().at(port_chunk.port))); + T node = factory.slice(enqueue(whole_port), whole_port.width, port_chunk.offset, port_chunk.width); + factory.update_pending(pending, node); + } + } else if (chunk.is_constant()) { + T node = factory.constant(chunk.constant()); + factory.suggest_name(node, "$const" + std::to_string(chunk.size()) + "b" + chunk.constant().as_string()); + factory.update_pending(pending, node); + } else if (chunk.is_multiple()) { + vector args; + for (auto const &driver : chunk.multiple().multiple()) + args.push_back(enqueue(driver)); + T node = factory.multiple(args, chunk.size()); + factory.update_pending(pending, node); + } else if (chunk.is_marker()) { + Cell *cell = cells[chunk.marker().marker]; + dict connections; + for(auto const &conn : cell->connections()) { + if(driver_map.celltypes.cell_input(cell->type, conn.first)) + connections.insert({ conn.first, enqueue(DriveChunkPort(cell, conn)) }); + } + T node = simplifier.handle(cell->type, cell->parameters, connections); + factory.update_pending(pending, node); + } else if (chunk.is_none()) { + T node = factory.undriven(chunk.size()); + factory.update_pending(pending, node); + } else { + log_error("unhandled drivespec: %s\n", log_signal(chunk)); + log_abort(); + } + } else { + log_abort(); + } + } + } +}; + +YOSYS_NAMESPACE_END + +#endif