From a468cf35a403cedcb17c5c138d2f4601e63abe79 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Wed, 24 Jul 2024 17:37:17 +0100 Subject: [PATCH] add support for initializing registers and memories to the functional backend --- backends/functional/cxx.cc | 101 ++++++----- backends/functional/cxx_runtime/sim.h | 3 + backends/functional/smtlib.cc | 68 +++++--- backends/functional/test_generic.cc | 99 +++++++++++ kernel/functionalir.cc | 64 +++---- kernel/functionalir.h | 84 +++++----- tests/functional/rtlil_cells.py | 19 ++- tests/functional/smt_vcd.py | 21 +-- tests/functional/test_functional.py | 4 +- tests/functional/vcd_harness.cc | 233 +++++++++++++------------- 10 files changed, 416 insertions(+), 280 deletions(-) diff --git a/backends/functional/cxx.cc b/backends/functional/cxx.cc index 69f1973f2cb..324a8831c86 100644 --- a/backends/functional/cxx.cc +++ b/backends/functional/cxx.cc @@ -69,35 +69,48 @@ struct CxxType { using CxxWriter = FunctionalTools::Writer; struct CxxStruct { - std::string name; - dict types; - CxxScope scope; - CxxStruct(std::string name) - : name(name) { - scope.reserve("fn"); - scope.reserve("visit"); - } - void insert(IdString name, CxxType type) { - scope(name, name); - types.insert({name, type}); - } - void print(CxxWriter &f) { - f.print("\tstruct {} {{\n", name); - for (auto p : types) { - f.print("\t\t{} {};\n", p.second.to_string(), scope(p.first, p.first)); - } - f.print("\n\t\ttemplate void visit(T &&fn) {{\n"); - for (auto p : types) { - f.print("\t\t\tfn(\"{}\", {});\n", RTLIL::unescape_id(p.first), scope(p.first, p.first)); - } - f.print("\t\t}}\n"); - f.print("\t}};\n\n"); - }; - std::string operator[](IdString field) { - return scope(field, field); - } + std::string name; + dict types; + CxxScope scope; + CxxStruct(std::string name) : name(name) + { + scope.reserve("fn"); + scope.reserve("visit"); + } + void insert(IdString name, CxxType type) { + scope(name, name); + types.insert({name, type}); + } + void print(CxxWriter &f) { + f.print("\tstruct {} {{\n", name); + for (auto p : types) { + f.print("\t\t{} {};\n", p.second.to_string(), scope(p.first, p.first)); + } + f.print("\n\t\ttemplate void visit(T &&fn) {{\n"); + for (auto p : types) { + f.print("\t\t\tfn(\"{}\", {});\n", RTLIL::unescape_id(p.first), scope(p.first, p.first)); + } + f.print("\t\t}}\n"); + f.print("\t}};\n\n"); + }; + std::string operator[](IdString field) { + return scope(field, field); + } }; +std::string cxx_const(RTLIL::Const const &value) { + std::stringstream ss; + ss << "Signal<" << value.size() << ">(" << std::hex << std::showbase; + if(value.size() > 32) ss << "{"; + for(int i = 0; i < value.size(); i += 32) { + if(i > 0) ss << ", "; + ss << value.extract(i, 32).as_int(); + } + if(value.size() > 32) ss << "}"; + ss << ")"; + return ss.str(); +} + template struct CxxPrintVisitor : public FunctionalIR::AbstractVisitor { using Node = FunctionalIR::Node; CxxWriter &f; @@ -136,20 +149,7 @@ template struct CxxPrintVisitor : public FunctionalIR::Abstra void logical_shift_right(Node, Node a, Node b) override { print("{} >> {}", a, b); } void arithmetic_shift_right(Node, Node a, Node b) override { print("{}.arithmetic_shift_right({})", a, b); } void mux(Node, Node a, Node b, Node s) override { print("{2}.any() ? {1} : {0}", a, b, s); } - void constant(Node, RTLIL::Const value) override { - std::stringstream ss; - bool multiple = value.size() > 32; - ss << "Signal<" << value.size() << ">(" << std::hex << std::showbase; - if(multiple) ss << "{"; - while(value.size() > 32) { - ss << value.as_int() << ", "; - value = value.extract(32, value.size() - 32); - } - ss << value.as_int(); - if(multiple) ss << "}"; - ss << ")"; - print("{}", ss.str()); - } + void constant(Node, RTLIL::Const const & value) override { print("{}", cxx_const(value)); } void input(Node, IdString name) override { print("input.{}", input_struct[name]); } void state(Node, IdString name) override { print("current_state.{}", state_struct[name]); } void memory_read(Node, Node mem, Node addr) override { print("{}.read({})", mem, addr); } @@ -184,8 +184,24 @@ struct CxxModule { output_struct.print(f); state_struct.print(f); f.print("\tstatic void eval(Inputs const &, Outputs &, State const &, State &);\n"); + f.print("\tstatic void initialize(State &);\n"); f.print("}};\n\n"); } + void write_initial_def(CxxWriter &f) { + f.print("void {0}::initialize({0}::State &state)\n{{\n", module_name); + for (auto [name, sort] : ir.state()) { + if (sort.is_signal()) + f.print("\tstate.{} = {};\n", state_struct[name], cxx_const(ir.get_initial_state_signal(name))); + else if (sort.is_memory()) { + const auto &contents = ir.get_initial_state_memory(name); + f.print("\tstate.{}.fill({});\n", state_struct[name], cxx_const(contents.default_value())); + for(auto range : contents) + for(auto addr = range.base(); addr < range.limit(); addr++) + f.print("\tstate.{}[{}] = {};\n", state_struct[name], addr, cxx_const(range[addr])); + } + } + f.print("}}\n\n"); + } void write_eval_def(CxxWriter &f) { f.print("void {0}::eval({0}::Inputs const &input, {0}::Outputs &output, {0}::State const ¤t_state, {0}::State &next_state)\n{{\n", module_name); CxxScope locals; @@ -204,7 +220,7 @@ struct CxxModule { f.print("\tnext_state.{} = {};\n", state_struct[name], node_name(ir.get_state_next_node(name))); for (auto [name, sort] : ir.outputs()) f.print("\toutput.{} = {};\n", output_struct[name], node_name(ir.get_output_node(name))); - f.print("}}\n"); + f.print("}}\n\n"); } }; @@ -225,6 +241,7 @@ struct FunctionalCxxBackend : public Backend mod.write_header(f); mod.write_struct_def(f); mod.write_eval_def(f); + mod.write_initial_def(f); } void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override diff --git a/backends/functional/cxx_runtime/sim.h b/backends/functional/cxx_runtime/sim.h index ed1a25ed40f..6cce4e8456c 100644 --- a/backends/functional/cxx_runtime/sim.h +++ b/backends/functional/cxx_runtime/sim.h @@ -410,6 +410,9 @@ class Memory { ret._contents[addr.template as_numeric()] = data; return ret; } + // mutating methods for initializing a state + void fill(Signal data) { _contents.fill(data); } + Signal &operator[](Signal addr) { return _contents[addr.template as_numeric()]; } }; #endif diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index d40a4489f6a..0d2763d3201 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -109,6 +109,13 @@ class SmtStruct { } }; +std::string smt_const(RTLIL::Const const &c) { + std::string s = "#b"; + for(int i = c.size(); i-- > 0; ) + s += c[i] == State::S1 ? '1' : '0'; + return s; +} + struct SmtPrintVisitor : public FunctionalIR::AbstractVisitor { using Node = FunctionalIR::Node; std::function n; @@ -117,13 +124,6 @@ struct SmtPrintVisitor : public FunctionalIR::AbstractVisitor { SmtPrintVisitor(SmtStruct &input_struct, SmtStruct &state_struct) : input_struct(input_struct), state_struct(state_struct) {} - std::string literal(RTLIL::Const c) { - std::string s = "#b"; - for(int i = c.size(); i-- > 0; ) - s += c[i] == State::S1 ? '1' : '0'; - return s; - } - SExpr from_bool(SExpr &&arg) { return list("ite", std::move(arg), "#b1", "#b0"); } @@ -149,8 +149,8 @@ struct SmtPrintVisitor : public FunctionalIR::AbstractVisitor { SExpr bitwise_xor(Node, Node a, Node b) override { return list("bvxor", n(a), n(b)); } SExpr bitwise_not(Node, Node a) override { return list("bvnot", n(a)); } SExpr unary_minus(Node, Node a) override { return list("bvneg", n(a)); } - SExpr reduce_and(Node, Node a) override { return from_bool(list("=", n(a), literal(RTLIL::Const(State::S1, a.width())))); } - SExpr reduce_or(Node, Node a) override { return from_bool(list("distinct", n(a), literal(RTLIL::Const(State::S0, a.width())))); } + SExpr reduce_and(Node, Node a) override { return from_bool(list("=", n(a), smt_const(RTLIL::Const(State::S1, a.width())))); } + SExpr reduce_or(Node, Node a) override { return from_bool(list("distinct", n(a), smt_const(RTLIL::Const(State::S0, a.width())))); } SExpr reduce_xor(Node, Node a) override { vector s { "bvxor" }; for(int i = 0; i < a.width(); i++) @@ -174,7 +174,7 @@ struct SmtPrintVisitor : public FunctionalIR::AbstractVisitor { SExpr logical_shift_right(Node, Node a, Node b) override { return list("bvlshr", n(a), extend(n(b), b.width(), a.width())); } SExpr arithmetic_shift_right(Node, Node a, Node b) override { return list("bvashr", n(a), extend(n(b), b.width(), a.width())); } SExpr mux(Node, Node a, Node b, Node s) override { return list("ite", to_bool(n(s)), n(b), n(a)); } - SExpr constant(Node, RTLIL::Const value) override { return literal(value); } + SExpr constant(Node, RTLIL::Const const &value) override { return smt_const(value); } SExpr memory_read(Node, Node mem, Node addr) override { return list("select", n(mem), n(addr)); } SExpr memory_write(Node, Node mem, Node addr, Node data) override { return list("store", n(mem), n(addr), n(data)); } @@ -199,6 +199,7 @@ struct SmtModule { , output_struct(scope.unique_name(module->name.str() + "_Outputs"), scope) , state_struct(scope.unique_name(module->name.str() + "_State"), scope) { + scope.reserve(name + "-initial"); for (const auto &input : ir.inputs()) input_struct.insert(input.first, input.second); for (const auto &output : ir.outputs()) @@ -207,18 +208,8 @@ struct SmtModule { state_struct.insert(state.first, state.second); } - void write(std::ostream &out) - { - SExprWriter w(out); - - input_struct.write_definition(w); - output_struct.write_definition(w); - state_struct.write_definition(w); - - w << list("declare-datatypes", - list(list("Pair", 2)), - list(list("par", list("X", "Y"), list(list("pair", list("first", "X"), list("second", "Y")))))); - + void write_eval(SExprWriter &w) + { w.push(); w.open(list("define-fun", name, list(list("inputs", input_struct.name), @@ -245,6 +236,39 @@ struct SmtModule { state_struct.write_value(w, [&](IdString name) { return node_to_sexpr(ir.get_state_next_node(name)); }); w.pop(); } + + void write_initial(SExprWriter &w) + { + std::string initial = name + "-initial"; + w << list("declare-const", initial, state_struct.name); + for (const auto &[name, sort] : ir.state()) { + if(sort.is_signal()) + w << list("assert", list("=", state_struct.access(initial, name), smt_const(ir.get_initial_state_signal(name)))); + else if(sort.is_memory()) { + auto contents = ir.get_initial_state_memory(name); + for(int i = 0; i < 1< USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN +struct MemContentsTest { + int addr_width, data_width; + MemContents state; + using addr_t = MemContents::addr_t; + std::map reference; + MemContentsTest(int addr_width, int data_width) : addr_width(addr_width), data_width(data_width), state(addr_width, data_width, RTLIL::Const(State::S0, data_width)) {} + void check() { + state.check(); + for(auto addr = 0; addr < (1<second != state[addr]) goto error; + } else { + if(state.count_range(addr, addr + 1) != 0) goto error; + } + } + return; + error: + printf("FAIL\n"); + int digits = (data_width + 3) / 4; + + for(auto addr = 0; addr < (1<second : state.default_value(); + std::string ref_string = stringf("%.*x", digits, ref_value.as_int()); + bool sta_def = state.count_range(addr, addr + 1) == 1; + RTLIL::Const sta_value = state[addr]; + std::string sta_string = stringf("%.*x", digits, sta_value.as_int()); + if(ref_def && sta_def) { + if(ref_value == sta_value) printf("%s%s", ref_string.c_str(), string(digits, ' ').c_str()); + else printf("%s%s", ref_string.c_str(), sta_string.c_str()); + } else if(ref_def) { + printf("%s%s", ref_string.c_str(), string(digits, 'M').c_str()); + } else if(sta_def) { + printf("%s%s", sta_string.c_str(), string(digits, 'X').c_str()); + } else { + printf("%s", string(2*digits, ' ').c_str()); + } + printf(" "); + if(addr % 8 == 7) printf("\n"); + } + printf("\n"); + //log_abort(); + } + void clear_range(addr_t begin_addr, addr_t end_addr) { + for(auto addr = begin_addr; addr != end_addr; addr++) + reference.erase(addr); + state.clear_range(begin_addr, end_addr); + check(); + } + void insert_concatenated(addr_t addr, RTLIL::Const const &values) { + addr_t words = ((addr_t) values.size() + data_width - 1) / data_width; + for(addr_t i = 0; i < words; i++) { + reference.erase(addr + i); + reference.emplace(addr + i, values.extract(i * data_width, data_width)); + } + state.insert_concatenated(addr, values); + check(); + } + template void run(Rnd &rnd, int n) { + std::uniform_int_distribution addr_dist(0, (1< length_dist(10); + std::uniform_int_distribution data_dist(0, ((uint64_t)1< 0) { + addr_t low = addr_dist(rnd); + //addr_t length = std::min((1< high) std::swap(low, high); + if((rnd() & 7) == 0) { + log_debug("clear %.2x to %.2x\n", (int)low, (int)high); + clear_range(low, high + 1); + } else { + log_debug("insert %.2x to %.2x\n", (int)low, (int)high); + RTLIL::Const values; + for(addr_t addr = low; addr <= high; addr++) { + RTLIL::Const word(data_dist(rnd), data_width); + values.bits.insert(values.bits.end(), word.bits.begin(), word.bits.end()); + } + insert_concatenated(low, values); + } + } + } + +}; + struct FunctionalTestGeneric : public Pass { FunctionalTestGeneric() : Pass("test_generic", "test the generic compute graph") {} @@ -40,6 +130,14 @@ struct FunctionalTestGeneric : public Pass size_t argidx = 1; extra_args(args, argidx, design); + MemContentsTest test(8, 16); + + std::random_device seed_dev; + std::mt19937 rnd(23); //seed_dev()); + test.run(rnd, 1000); + +/* + for (auto module : design->selected_modules()) { log("Dumping module `%s'.\n", module->name.c_str()); auto fir = FunctionalIR::from_module(module); @@ -50,6 +148,7 @@ struct FunctionalTestGeneric : public Pass for(auto [name, sort] : fir.state()) std::cout << RTLIL::unescape_id(name) << " = " << RTLIL::unescape_id(fir.get_state_next_node(name).name()) << "\n"; } +*/ } } FunctionalCxxBackend; diff --git a/kernel/functionalir.cc b/kernel/functionalir.cc index 155cb3bbc53..bc21b4d3648 100644 --- a/kernel/functionalir.cc +++ b/kernel/functionalir.cc @@ -19,6 +19,8 @@ #include "kernel/functionalir.h" #include +#include "ff.h" +#include "ffinit.h" YOSYS_NAMESPACE_BEGIN @@ -70,7 +72,7 @@ struct PrintVisitor : FunctionalIR::DefaultVisitor { std::string slice(Node, Node a, int offset, int out_width) override { return "slice(" + np(a) + ", " + std::to_string(offset) + ", " + std::to_string(out_width) + ")"; } std::string zero_extend(Node, Node a, int out_width) override { return "zero_extend(" + np(a) + ", " + std::to_string(out_width) + ")"; } std::string sign_extend(Node, Node a, int out_width) override { return "sign_extend(" + np(a) + ", " + std::to_string(out_width) + ")"; } - std::string constant(Node, RTLIL::Const value) override { return "constant(" + value.as_string() + ")"; } + std::string constant(Node, RTLIL::Const const& value) override { return "constant(" + value.as_string() + ")"; } std::string input(Node, IdString name) override { return "input(" + name.str() + ")"; } std::string state(Node, IdString name) override { return "state(" + name.str() + ")"; } std::string default_handler(Node self) override { @@ -407,6 +409,8 @@ class FunctionalIRConstruction { CellSimplifier simplifier; vector memories_vector; dict memories; + SigMap sig_map; // TODO: this is only for FfInitVals, remove this once FfInitVals supports DriverMap + FfInitVals ff_initvals; Node enqueue(DriveSpec const &spec) { @@ -438,8 +442,11 @@ class FunctionalIRConstruction { return it->second; } public: - FunctionalIRConstruction(FunctionalIR::Factory &f) : factory(f), simplifier(f) {} - void add_module(Module *module) + FunctionalIRConstruction(Module *module, FunctionalIR::Factory &f) + : factory(f) + , simplifier(f) + , sig_map(module) + , ff_initvals(&sig_map, module) { driver_map.add(module); for (auto cell : module->cells()) { @@ -447,9 +454,12 @@ class FunctionalIRConstruction { queue.emplace_back(cell); } for (auto wire : module->wires()) { + if (wire->port_input) + factory.add_input(wire->name, wire->width); if (wire->port_output) { - Node node = enqueue(DriveChunk(DriveChunkWire(wire, 0, wire->width))); - factory.declare_output(node, wire->name, wire->width); + factory.add_output(wire->name, wire->width); + Node value = enqueue(DriveChunk(DriveChunkWire(wire, 0, wire->width))); + factory.set_output(wire->name, value); } } memories_vector = Mem::get_all_memories(module); @@ -487,9 +497,9 @@ class FunctionalIRConstruction { // - Since wr port j can only have priority over wr port i if j > i, if we do writes in // ascending index order the result will obey the priorty relation. vector read_results; - int addr_width = ceil_log2(mem->size); - int data_width = mem->width; - Node node = factory.state_memory(mem->cell->name, addr_width, data_width); + factory.add_state(mem->cell->name, FunctionalIR::Sort(ceil_log2(mem->size), mem->width)); + factory.set_initial_state(mem->cell->name, MemContents(mem)); + Node node = factory.get_current_state(mem->cell->name); for (size_t i = 0; i < mem->wr_ports.size(); i++) { const auto &wr = mem->wr_ports[i]; if (wr.clk_enable) @@ -513,7 +523,7 @@ class FunctionalIRConstruction { Node addr = enqueue(driver_map(DriveSpec(rd.addr))); read_results.push_back(factory.memory_read(node, addr)); } - factory.declare_state_memory(node, mem->cell->name, addr_width, data_width); + factory.set_next_state(mem->cell->name, node); return concatenate_read_results(mem, read_results); } void process_cell(Cell *cell) @@ -527,6 +537,17 @@ class FunctionalIRConstruction { } Node node = handle_memory(mem); factory.update_pending(cell_outputs.at({cell, ID(RD_DATA)}), node); + } else if (RTLIL::builtin_ff_cell_types().count(cell->type)) { + FfData ff(&ff_initvals, cell); + if (!ff.has_gclk) + log_error("The design contains a %s flip-flop at %s. This is not supported by the functional backend. " + "Call async2sync or clk2fflogic to avoid this error.\n", log_id(cell->type), log_id(cell)); + factory.add_state(ff.name, FunctionalIR::Sort(ff.width)); + Node q_value = factory.get_current_state(ff.name); + factory.suggest_name(q_value, ff.name); + factory.update_pending(cell_outputs.at({cell, ID(Q)}), q_value); + factory.set_next_state(ff.name, enqueue(ff.sig_d)); + factory.set_initial_state(ff.name, ff.val_init); } else { dict connections; IdString output_name; // for the single output case @@ -572,7 +593,7 @@ class FunctionalIRConstruction { DriveChunkWire wire_chunk = chunk.wire(); if (wire_chunk.is_whole()) { if (wire_chunk.wire->port_input) { - Node node = factory.input(wire_chunk.wire->name, wire_chunk.width); + Node node = factory.get_input(wire_chunk.wire->name); factory.suggest_name(node, wire_chunk.wire->name); factory.update_pending(pending, node); } else { @@ -590,24 +611,8 @@ class FunctionalIRConstruction { 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; - Node 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)) { - Node node = enqueue(DriveChunkPort(cell, conn)); - factory.declare_state(node, cell->name, port_chunk.width); - } - } - } - else - { - Node node = enqueue_cell(port_chunk.cell, port_chunk.port); - factory.update_pending(pending, node); - } + Node node = enqueue_cell(port_chunk.cell, port_chunk.port); + factory.update_pending(pending, node); } else { DriveSpec driver = driver_map(DriveSpec(port_chunk)); factory.update_pending(pending, enqueue(driver)); @@ -641,8 +646,7 @@ class FunctionalIRConstruction { FunctionalIR FunctionalIR::from_module(Module *module) { FunctionalIR ir; auto factory = ir.factory(); - FunctionalIRConstruction ctor(factory); - ctor.add_module(module); + FunctionalIRConstruction ctor(module, factory); ctor.process_queue(); ir.topological_sort(); ir.forward_buf(); diff --git a/kernel/functionalir.h b/kernel/functionalir.h index 852937612a7..e0af89d9f3a 100644 --- a/kernel/functionalir.h +++ b/kernel/functionalir.h @@ -189,24 +189,11 @@ class FunctionalIR { // the bool is true for next state values using Graph = ComputeGraph>; Graph _graph; - dict _inputs; - dict _outputs; - dict _state; - void add_input(IdString name, Sort sort) { - auto [it, found] = _inputs.emplace(name, std::move(sort)); - if(found) - log_assert(it->second == sort); - } - void add_state(IdString name, Sort sort) { - auto [it, found] = _state.emplace(name, std::move(sort)); - if(found) - log_assert(it->second == sort); - } - void add_output(IdString name, Sort sort) { - auto [it, found] = _outputs.emplace(name, std::move(sort)); - if(found) - log_assert(it->second == sort); - } + dict _input_sorts; + dict _output_sorts; + dict _state_sorts; + dict _initial_state_signal; + dict _initial_state_memory; public: class Factory; // Node is an immutable reference to a FunctionalIR node @@ -306,7 +293,7 @@ class FunctionalIR { virtual T logical_shift_right(Node self, Node a, Node b) = 0; virtual T arithmetic_shift_right(Node self, Node a, Node b) = 0; virtual T mux(Node self, Node a, Node b, Node s) = 0; - virtual T constant(Node self, RTLIL::Const value) = 0; + virtual T constant(Node self, RTLIL::Const const & value) = 0; virtual T input(Node self, IdString name) = 0; virtual T state(Node self, IdString name) = 0; virtual T memory_read(Node self, Node mem, Node addr) = 0; @@ -343,7 +330,7 @@ class FunctionalIR { T logical_shift_right(Node self, Node, Node) override { return default_handler(self); } T arithmetic_shift_right(Node self, Node, Node) override { return default_handler(self); } T mux(Node self, Node, Node, Node) override { return default_handler(self); } - T constant(Node self, RTLIL::Const) override { return default_handler(self); } + T constant(Node self, RTLIL::Const const &) override { return default_handler(self); } T input(Node self, IdString) override { return default_handler(self); } T state(Node self, IdString) override { return default_handler(self); } T memory_read(Node self, Node, Node) override { return default_handler(self); } @@ -452,30 +439,41 @@ class FunctionalIR { log_assert(node._ref.function() == Fn::buf && node._ref.size() == 0); log_assert(node.sort() == value.sort()); mutate(node).append_arg(value._ref); - } - Node input(IdString name, int width) { - _ir.add_input(name, Sort(width)); - return add(NodeData(Fn::input, name), Sort(width), {}); } - Node state(IdString name, int width) { - _ir.add_state(name, Sort(width)); - return add(NodeData(Fn::state, name), Sort(width), {}); + void add_input(IdString name, int width) { + auto [it, inserted] = _ir._input_sorts.emplace(name, Sort(width)); + if (!inserted) log_error("input `%s` was re-defined", name.c_str()); } - Node state_memory(IdString name, int addr_width, int data_width) { - _ir.add_state(name, Sort(addr_width, data_width)); - return add(NodeData(Fn::state, name), Sort(addr_width, data_width), {}); + void add_output(IdString name, int width) { + auto [it, inserted] = _ir._output_sorts.emplace(name, Sort(width)); + if (!inserted) log_error("output `%s` was re-defined", name.c_str()); } - void declare_output(Node node, IdString name, int width) { - _ir.add_output(name, Sort(width)); - mutate(node).assign_key({name, false}); + void add_state(IdString name, Sort sort) { + auto [it, inserted] = _ir._state_sorts.emplace(name, sort); + if (!inserted) log_error("state `%s` was re-defined", name.c_str()); } - void declare_state(Node node, IdString name, int width) { - _ir.add_state(name, Sort(width)); - mutate(node).assign_key({name, true}); + Node get_input(IdString name) { + return add(NodeData(Fn::input, name), Sort(_ir._input_sorts.at(name)), {}); } - void declare_state_memory(Node node, IdString name, int addr_width, int data_width) { - _ir.add_state(name, Sort(addr_width, data_width)); - mutate(node).assign_key({name, true}); + Node get_current_state(IdString name) { + return add(NodeData(Fn::state, name), Sort(_ir._state_sorts.at(name)), {}); + } + void set_output(IdString output, Node value) { + log_assert(_ir._output_sorts.at(output) == value.sort()); + mutate(value).assign_key({output, false}); + } + void set_initial_state(IdString state, RTLIL::Const value) { + Sort &sort = _ir._state_sorts.at(state); + value.extu(sort.width()); + _ir._initial_state_signal.emplace(state, std::move(value)); + } + void set_initial_state(IdString state, MemContents value) { + log_assert(Sort(value.addr_width(), value.data_width()) == _ir._state_sorts.at(state)); + _ir._initial_state_memory.emplace(state, std::move(value)); + } + void set_next_state(IdString state, Node value) { + log_assert(_ir._state_sorts.at(state) == value.sort()); + mutate(value).assign_key({state, true}); } void suggest_name(Node node, IdString name) { mutate(node).sparse_attr() = name; @@ -487,9 +485,11 @@ class FunctionalIR { Node operator[](int i) { return Node(_graph[i]); } void topological_sort(); void forward_buf(); - dict inputs() const { return _inputs; } - dict outputs() const { return _outputs; } - dict state() const { return _state; } + dict inputs() const { return _input_sorts; } + dict outputs() const { return _output_sorts; } + dict state() const { return _state_sorts; } + RTLIL::Const const &get_initial_state_signal(IdString name) { return _initial_state_signal.at(name); } + MemContents const &get_initial_state_memory(IdString name) { return _initial_state_memory.at(name); } Node get_output_node(IdString name) { return Node(_graph({name, false})); } Node get_state_next_node(IdString name) { return Node(_graph({name, true})); } class Iterator { diff --git a/tests/functional/rtlil_cells.py b/tests/functional/rtlil_cells.py index 90417579e5d..ed867b695b4 100644 --- a/tests/functional/rtlil_cells.py +++ b/tests/functional/rtlil_cells.py @@ -153,15 +153,17 @@ def write_rtlil_file(self, path, parameters): from test_functional import yosys_synth verilog_file = path.parent / 'verilog.v' with open(verilog_file, 'w') as f: - f.write(""" + width = parameters['WIDTH'] + f.write(f""" module gold( input wire clk, - input wire [{0}:0] D, - output reg [{0}:0] Q + input wire [{width-1}:0] D, + output reg [{width-1}:0] Q ); + initial Q = {width}'b{("101" * width)[:width]}; always @(posedge clk) Q <= D; -endmodule""".format(parameters['WIDTH'] - 1)) +endmodule""") yosys_synth(verilog_file, path) class MemCell(BaseCell): @@ -180,6 +182,10 @@ def write_rtlil_file(self, path, parameters): output reg [{0}:0] RD ); reg [{0}:0] mem[0:{2}]; + integer i; + initial + for(i = 0; i <= {2}; i = i + 1) + mem[i] = 9192 * (i + 1); always @(*) RD = mem[RA]; always @(posedge clk) @@ -211,8 +217,11 @@ def write_rtlil_file(self, path, parameters): output reg [{0}:0] RD1, output reg [{0}:0] RD2 ); - (*keep*) reg [{0}:0] mem[0:{2}]; + integer i; + initial + for(i = 0; i <= {2}; i = i + 1) + mem[i] = 9192 * (i + 1); always @(*) RD1 = mem[RA1]; always @(*) diff --git a/tests/functional/smt_vcd.py b/tests/functional/smt_vcd.py index 37d2a209f71..c23be440e3b 100644 --- a/tests/functional/smt_vcd.py +++ b/tests/functional/smt_vcd.py @@ -81,25 +81,6 @@ def handle_datatype(lst): parser.finish() assert smt_io.check_sat() == 'sat' - def initial_state(states): - mk_state_parts = [] - rv = [] - for name, width in states.items(): - if isinstance(width, int): - binary_string = format(0, '0{}b'.format(width)) - mk_state_parts.append(f"#b{binary_string}") - else: - binary_string = format(0, '0{}b'.format(width[1])) - rv.append(f"(declare-const test_state_initial_mem_{name} (Array (_ BitVec {width[0]}) (_ BitVec {width[1]})))") - rv.append(f"(assert (forall ((i (_ BitVec {width[0]}))) (= (select test_state_initial_mem_{name} i) #b{binary_string})))") - mk_state_parts.append(f"test_state_initial_mem_{name}") - if len(states) == 0: - mk_state_call = "gold_State" - else: - mk_state_call = "(gold_State {})".format(" ".join(mk_state_parts)) - rv.append(f"(define-const test_state_step_n0 gold_State {mk_state_call})\n") - return rv - def set_step(inputs, step): # This function assumes 'inputs' is a dictionary like {"A": 5, "B": 4} # and 'input_values' is a dictionary like {"A": 5, "B": 13} specifying the concrete values for each input. @@ -118,7 +99,7 @@ def set_step(inputs, step): f"(define-const test_state_step_n{step+1} gold_State (second test_results_step_n{step}))\n", ] - smt_commands = initial_state(states) + smt_commands = [f"(define-const test_state_step_n0 gold_State gold-initial)\n"] for step in range(num_steps): for step_command in set_step(inputs, step): smt_commands.append(step_command) diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index c31f4b86f5c..7bfe19fc494 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -29,14 +29,14 @@ def yosys_synth(verilog_file, rtlil_file): # simulate an rtlil file with yosys, comparing with a given vcd file, and writing out the yosys simulation results into a second vcd file def yosys_sim(rtlil_file, vcd_reference_file, vcd_out_file, preprocessing = ""): try: - yosys(f"read_rtlil {quote(rtlil_file)}; {preprocessing}; sim -r {quote(vcd_reference_file)} -scope gold -vcd {quote(vcd_out_file)} -timescale 1us -sim-gold") + yosys(f"read_rtlil {quote(rtlil_file)}; {preprocessing}; sim -r {quote(vcd_reference_file)} -scope gold -vcd {quote(vcd_out_file)} -timescale 1us -sim-gold -fst-noinit") except: # if yosys sim fails it's probably because of a simulation mismatch # since yosys sim aborts on simulation mismatch to generate vcd output # we have to re-run with a different set of flags # on this run we ignore output and return code, we just want a best-effort attempt to get a vcd subprocess.run([base_path / 'yosys', '-Q', '-p', - f'read_rtlil {quote(rtlil_file)}; sim -vcd {quote(vcd_out_file)} -a -r {quote(vcd_reference_file)} -scope gold -timescale 1us'], + f'read_rtlil {quote(rtlil_file)}; sim -vcd {quote(vcd_out_file)} -a -r {quote(vcd_reference_file)} -scope gold -timescale 1us -fst-noinit'], capture_output=True, check=False) raise diff --git a/tests/functional/vcd_harness.cc b/tests/functional/vcd_harness.cc index f01adf21832..7232172348a 100644 --- a/tests/functional/vcd_harness.cc +++ b/tests/functional/vcd_harness.cc @@ -7,140 +7,139 @@ #include "my_module_functional_cxx.cc" -std::string vcd_name_mangle(std::string name) { - std::string ret = name; - bool escape = ret.empty() || !isalpha(ret[0]) && ret[0] != '_'; - for(size_t i = 0; i < ret.size(); i++) { - if(isspace(ret[i])) ret[i] = '_'; - if(!isalnum(ret[i]) && ret[i] != '_' && ret[i] != '$') - escape = true; - } - if(escape) - return "\\" + ret; - else - return ret; -} -std::unordered_map codes; - -struct DumpHeader { - std::ofstream &ofs; - std::string code = "!"; - DumpHeader(std::ofstream &ofs) : ofs(ofs) {} - void increment_code() { - for(size_t i = 0; i < code.size(); i++) - if(code[i]++ == '~') - code[i] = '!'; - else - return; - code.push_back('!'); - } - template - void operator()(const char *name, Signal value) { - ofs << "$var wire " << n << " " << code << " " << vcd_name_mangle(name) << " $end\n"; - codes[name] = code; - increment_code(); - } - template - void operator()(const char *name, Memory value) { - } -}; - -struct Dump { - std::ofstream &ofs; - Dump(std::ofstream &ofs) : ofs(ofs) {} - template - void operator()(const char *name, Signal value) { - // Bit - if (n == 1) { - ofs << (value[0] ? '1' : '0'); - ofs << codes[name] << "\n"; - return; - } - // vector (multi-bit) signals - ofs << "b"; - for (size_t i = n; i-- > 0;) - ofs << (value[i] ? '1' : '0'); - ofs << " " << codes[name] << "\n"; - } - template - void operator()(const char *name, Memory value) { - } +class VcdFile { + std::ofstream &ofs; + std::string code_alloc = "!"; + std::unordered_map codes; + std::string name_mangle(std::string name) { + std::string ret = name; + bool escape = ret.empty() || !isalpha(ret[0]) && ret[0] != '_'; + for(size_t i = 0; i < ret.size(); i++) { + if(isspace(ret[i])) ret[i] = '_'; + if(!isalnum(ret[i]) && ret[i] != '_' && ret[i] != '$') + escape = true; + } + if(escape) + return "\\" + ret; + else + return ret; + } + std::string allocate_code() { + std::string ret = code_alloc; + for (size_t i = 0; i < code_alloc.size(); i++) + if (code_alloc[i]++ == '~') + code_alloc[i] = '!'; + else + return ret; + code_alloc.push_back('!'); + return ret; + } +public: + VcdFile(std::ofstream &ofs) : ofs(ofs) {} + struct DumpHeader { + VcdFile *file; + explicit DumpHeader(VcdFile *file) : file(file) {} + template void operator()(const char *name, Signal value) + { + auto it = file->codes.find(name); + if(it == file->codes.end()) + it = file->codes.emplace(name, file->allocate_code()).first; + file->ofs << "$var wire " << n << " " << it->second << " " << file->name_mangle(name) << " $end\n"; + } + template void operator()(const char *name, Memory value) {} + }; + struct Dump { + VcdFile *file; + explicit Dump(VcdFile *file) : file(file) {} + template void operator()(const char *name, Signal value) + { + if (n == 1) { + file->ofs << (value[0] ? '1' : '0'); + file->ofs << file->codes.at(name) << "\n"; + } else { + file->ofs << "b"; + for (size_t i = n; i-- > 0;) + file->ofs << (value[i] ? '1' : '0'); + file->ofs << " " << file->codes.at(name) << "\n"; + } + } + template void operator()(const char *name, Memory value) {} + }; + void begin_header() { + constexpr int number_timescale = 1; + const std::string units_timescale = "us"; + ofs << "$timescale " << number_timescale << " " << units_timescale << " $end\n"; + ofs << "$scope module gold $end\n"; + } + void end_header() { + ofs << "$enddefinitions $end\n$dumpvars\n"; + } + template void header(Args ...args) { + begin_header(); + DumpHeader d(this); + (args.visit(d), ...); + end_header(); + } + void begin_data(int step) { + ofs << "#" << step << "\n"; + } + template void data(int step, Args ...args) { + begin_data(step); + Dump d(this); + (args.visit(d), ...); + } + DumpHeader dump_header() { return DumpHeader(this); } + Dump dump() { return Dump(this); } }; -template -Signal random_signal(std::mt19937 &gen) { - std::uniform_int_distribution dist; - std::array words; - for(auto &w : words) - w = dist(gen); - return Signal::from_array(words); +template Signal random_signal(std::mt19937 &gen) +{ + std::uniform_int_distribution dist; + std::array words; + for (auto &w : words) + w = dist(gen); + return Signal::from_array(words); } -struct Reset { - template - void operator()(const char *, Signal &signal) { - signal = 0; - } -}; - struct Randomize { - std::mt19937 &gen; - Randomize(std::mt19937 &gen) : gen(gen) {} + std::mt19937 &gen; + Randomize(std::mt19937 &gen) : gen(gen) {} - template - void operator()(const char *, Signal &signal) { - signal = random_signal(gen); - } + template void operator()(const char *, Signal &signal) { signal = random_signal(gen); } }; int main(int argc, char **argv) { - if (argc != 4) { - std::cerr << "Usage: " << argv[0] << " \n"; - return 1; - } + if (argc != 4) { + std::cerr << "Usage: " << argv[0] << " \n"; + return 1; + } - const std::string functional_vcd_filename = argv[1]; - const int steps = atoi(argv[2]); - const uint32_t seed = atoi(argv[3]); + const std::string functional_vcd_filename = argv[1]; + const int steps = atoi(argv[2]); + const uint32_t seed = atoi(argv[3]); - constexpr int number_timescale = 1; - const std::string units_timescale = "us"; - gold::Inputs inputs; - gold::Outputs outputs; - gold::State state; - gold::State next_state; + gold::Inputs inputs; + gold::Outputs outputs; + gold::State state; + gold::State next_state; - std::ofstream vcd_file(functional_vcd_filename); + std::ofstream vcd_file(functional_vcd_filename); + VcdFile vcd(vcd_file); + vcd.header(inputs, outputs, state); - vcd_file << "$timescale " << number_timescale << " " << units_timescale << " $end\n"; - vcd_file << "$scope module gold $end\n"; - { - DumpHeader d(vcd_file); - inputs.visit(d); - outputs.visit(d); - state.visit(d); - } - vcd_file << "$enddefinitions $end\n$dumpvars\n"; - std::mt19937 gen(seed); + std::mt19937 gen(seed); - inputs.visit(Reset()); + gold::initialize(state); - for (int step = 0; step < steps; ++step) { - vcd_file << "#" << step << "\n"; - gold::eval(inputs, outputs, state, next_state); - { - Dump d(vcd_file); - inputs.visit(d); - outputs.visit(d); - state.visit(d); - } + for (int step = 0; step < steps; ++step) { + inputs.visit(Randomize(gen)); - state = next_state; - inputs.visit(Randomize(gen)); - } + gold::eval(inputs, outputs, state, next_state); + vcd.data(step, inputs, outputs, state); - vcd_file.close(); + state = next_state; + } - return 0; + return 0; }