add support for initializing registers and memories to the functional backend

This commit is contained in:
Emily Schmidt 2024-07-24 17:37:17 +01:00
parent bdb59ffc8e
commit 99effb6789
10 changed files with 418 additions and 282 deletions

View File

@ -72,8 +72,8 @@ struct CxxStruct {
std::string name; std::string name;
dict<IdString, CxxType> types; dict<IdString, CxxType> types;
CxxScope<IdString> scope; CxxScope<IdString> scope;
CxxStruct(std::string name) CxxStruct(std::string name) : name(name)
: name(name) { {
scope.reserve("fn"); scope.reserve("fn");
scope.reserve("visit"); scope.reserve("visit");
} }
@ -98,6 +98,19 @@ struct CxxStruct {
} }
}; };
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<class NodePrinter> struct CxxPrintVisitor : public FunctionalIR::AbstractVisitor<void> { template<class NodePrinter> struct CxxPrintVisitor : public FunctionalIR::AbstractVisitor<void> {
using Node = FunctionalIR::Node; using Node = FunctionalIR::Node;
CxxWriter &f; CxxWriter &f;
@ -136,20 +149,7 @@ template<class NodePrinter> struct CxxPrintVisitor : public FunctionalIR::Abstra
void logical_shift_right(Node, Node a, Node b) override { print("{} >> {}", a, b); } 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 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 mux(Node, Node a, Node b, Node s) override { print("{2}.any() ? {1} : {0}", a, b, s); }
void constant(Node, RTLIL::Const value) override { void constant(Node, RTLIL::Const const & value) override { print("{}", cxx_const(value)); }
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 input(Node, IdString name) override { print("input.{}", input_struct[name]); } void input(Node, IdString name) override { print("input.{}", input_struct[name]); }
void state(Node, IdString name) override { print("current_state.{}", state_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); } void memory_read(Node, Node mem, Node addr) override { print("{}.read({})", mem, addr); }
@ -184,8 +184,24 @@ struct CxxModule {
output_struct.print(f); output_struct.print(f);
state_struct.print(f); state_struct.print(f);
f.print("\tstatic void eval(Inputs const &, Outputs &, State const &, State &);\n"); f.print("\tstatic void eval(Inputs const &, Outputs &, State const &, State &);\n");
f.print("\tstatic void initialize(State &);\n");
f.print("}};\n\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) { void write_eval_def(CxxWriter &f) {
f.print("void {0}::eval({0}::Inputs const &input, {0}::Outputs &output, {0}::State const &current_state, {0}::State &next_state)\n{{\n", module_name); f.print("void {0}::eval({0}::Inputs const &input, {0}::Outputs &output, {0}::State const &current_state, {0}::State &next_state)\n{{\n", module_name);
CxxScope<int> locals; CxxScope<int> locals;
@ -204,7 +220,7 @@ struct CxxModule {
f.print("\tnext_state.{} = {};\n", state_struct[name], node_name(ir.get_state_next_node(name))); f.print("\tnext_state.{} = {};\n", state_struct[name], node_name(ir.get_state_next_node(name)));
for (auto [name, sort] : ir.outputs()) for (auto [name, sort] : ir.outputs())
f.print("\toutput.{} = {};\n", output_struct[name], node_name(ir.get_output_node(name))); 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_header(f);
mod.write_struct_def(f); mod.write_struct_def(f);
mod.write_eval_def(f); mod.write_eval_def(f);
mod.write_initial_def(f);
} }
void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override

View File

@ -410,6 +410,9 @@ public:
ret._contents[addr.template as_numeric<size_t>()] = data; ret._contents[addr.template as_numeric<size_t>()] = data;
return ret; return ret;
} }
// mutating methods for initializing a state
void fill(Signal<d> data) { _contents.fill(data); }
Signal<d> &operator[](Signal<a> addr) { return _contents[addr.template as_numeric<size_t>()]; }
}; };
#endif #endif

View File

@ -109,6 +109,13 @@ public:
} }
}; };
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<SExpr> { struct SmtPrintVisitor : public FunctionalIR::AbstractVisitor<SExpr> {
using Node = FunctionalIR::Node; using Node = FunctionalIR::Node;
std::function<SExpr(Node)> n; std::function<SExpr(Node)> n;
@ -117,13 +124,6 @@ struct SmtPrintVisitor : public FunctionalIR::AbstractVisitor<SExpr> {
SmtPrintVisitor(SmtStruct &input_struct, SmtStruct &state_struct) : input_struct(input_struct), state_struct(state_struct) {} 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) { SExpr from_bool(SExpr &&arg) {
return list("ite", std::move(arg), "#b1", "#b0"); return list("ite", std::move(arg), "#b1", "#b0");
} }
@ -149,8 +149,8 @@ struct SmtPrintVisitor : public FunctionalIR::AbstractVisitor<SExpr> {
SExpr bitwise_xor(Node, Node a, Node b) override { return list("bvxor", n(a), n(b)); } 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 bitwise_not(Node, Node a) override { return list("bvnot", n(a)); }
SExpr unary_minus(Node, Node a) override { return list("bvneg", 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_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), literal(RTLIL::Const(State::S0, 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 { SExpr reduce_xor(Node, Node a) override {
vector<SExpr> s { "bvxor" }; vector<SExpr> s { "bvxor" };
for(int i = 0; i < a.width(); i++) for(int i = 0; i < a.width(); i++)
@ -174,7 +174,7 @@ struct SmtPrintVisitor : public FunctionalIR::AbstractVisitor<SExpr> {
SExpr logical_shift_right(Node, Node a, Node b) override { return list("bvlshr", n(a), extend(n(b), b.width(), a.width())); } 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 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 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_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)); } 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) , output_struct(scope.unique_name(module->name.str() + "_Outputs"), scope)
, state_struct(scope.unique_name(module->name.str() + "_State"), scope) , state_struct(scope.unique_name(module->name.str() + "_State"), scope)
{ {
scope.reserve(name + "-initial");
for (const auto &input : ir.inputs()) for (const auto &input : ir.inputs())
input_struct.insert(input.first, input.second); input_struct.insert(input.first, input.second);
for (const auto &output : ir.outputs()) for (const auto &output : ir.outputs())
@ -207,18 +208,8 @@ struct SmtModule {
state_struct.insert(state.first, state.second); state_struct.insert(state.first, state.second);
} }
void write(std::ostream &out) void write_eval(SExprWriter &w)
{ {
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"))))));
w.push(); w.push();
w.open(list("define-fun", name, w.open(list("define-fun", name,
list(list("inputs", input_struct.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)); }); state_struct.write_value(w, [&](IdString name) { return node_to_sexpr(ir.get_state_next_node(name)); });
w.pop(); 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<<sort.addr_width(); i++) {
auto addr = smt_const(RTLIL::Const(i, sort.addr_width()));
w << list("assert", list("=", list("select", state_struct.access(initial, name), addr), smt_const(contents[i])));
}
}
}
}
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"))))));
write_eval(w);
write_initial(w);
}
}; };
struct FunctionalSmtBackend : public Backend { struct FunctionalSmtBackend : public Backend {

View File

@ -19,10 +19,100 @@
#include "kernel/yosys.h" #include "kernel/yosys.h"
#include "kernel/functionalir.h" #include "kernel/functionalir.h"
#include <random>
USING_YOSYS_NAMESPACE USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN PRIVATE_NAMESPACE_BEGIN
struct MemContentsTest {
int addr_width, data_width;
MemContents state;
using addr_t = MemContents::addr_t;
std::map<addr_t, RTLIL::Const> 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<<addr_width); addr++) {
auto it = reference.find(addr);
if(it != reference.end()) {
if(state.count_range(addr, addr + 1) != 1) goto error;
if(it->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<<addr_width); addr++) {
if(addr % 8 == 0) printf("%.8x ", addr);
auto it = reference.find(addr);
bool ref_def = it != reference.end();
RTLIL::Const ref_value = ref_def ? it->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<typename Rnd> void run(Rnd &rnd, int n) {
std::uniform_int_distribution<addr_t> addr_dist(0, (1<<addr_width) - 1);
std::poisson_distribution<addr_t> length_dist(10);
std::uniform_int_distribution<uint64_t> data_dist(0, ((uint64_t)1<<data_width) - 1);
while(n-- > 0) {
addr_t low = addr_dist(rnd);
//addr_t length = std::min((1<<addr_width) - low, length_dist(rnd));
//addr_t high = low + length - 1;
addr_t high = addr_dist(rnd);
if(low > 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 struct FunctionalTestGeneric : public Pass
{ {
FunctionalTestGeneric() : Pass("test_generic", "test the generic compute graph") {} FunctionalTestGeneric() : Pass("test_generic", "test the generic compute graph") {}
@ -40,6 +130,14 @@ struct FunctionalTestGeneric : public Pass
size_t argidx = 1; size_t argidx = 1;
extra_args(args, argidx, design); 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()) { for (auto module : design->selected_modules()) {
log("Dumping module `%s'.\n", module->name.c_str()); log("Dumping module `%s'.\n", module->name.c_str());
auto fir = FunctionalIR::from_module(module); auto fir = FunctionalIR::from_module(module);
@ -50,6 +148,7 @@ struct FunctionalTestGeneric : public Pass
for(auto [name, sort] : fir.state()) for(auto [name, sort] : fir.state())
std::cout << RTLIL::unescape_id(name) << " = " << RTLIL::unescape_id(fir.get_state_next_node(name).name()) << "\n"; std::cout << RTLIL::unescape_id(name) << " = " << RTLIL::unescape_id(fir.get_state_next_node(name).name()) << "\n";
} }
*/
} }
} FunctionalCxxBackend; } FunctionalCxxBackend;

View File

@ -19,6 +19,8 @@
#include "kernel/functionalir.h" #include "kernel/functionalir.h"
#include <optional> #include <optional>
#include "ff.h"
#include "ffinit.h"
YOSYS_NAMESPACE_BEGIN YOSYS_NAMESPACE_BEGIN
@ -70,7 +72,7 @@ struct PrintVisitor : FunctionalIR::DefaultVisitor<std::string> {
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 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 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 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 input(Node, IdString name) override { return "input(" + name.str() + ")"; }
std::string state(Node, IdString name) override { return "state(" + name.str() + ")"; } std::string state(Node, IdString name) override { return "state(" + name.str() + ")"; }
std::string default_handler(Node self) override { std::string default_handler(Node self) override {
@ -407,6 +409,8 @@ class FunctionalIRConstruction {
CellSimplifier simplifier; CellSimplifier simplifier;
vector<Mem> memories_vector; vector<Mem> memories_vector;
dict<Cell*, Mem*> memories; dict<Cell*, Mem*> memories;
SigMap sig_map; // TODO: this is only for FfInitVals, remove this once FfInitVals supports DriverMap
FfInitVals ff_initvals;
Node enqueue(DriveSpec const &spec) Node enqueue(DriveSpec const &spec)
{ {
@ -438,8 +442,11 @@ class FunctionalIRConstruction {
return it->second; return it->second;
} }
public: public:
FunctionalIRConstruction(FunctionalIR::Factory &f) : factory(f), simplifier(f) {} FunctionalIRConstruction(Module *module, FunctionalIR::Factory &f)
void add_module(Module *module) : factory(f)
, simplifier(f)
, sig_map(module)
, ff_initvals(&sig_map, module)
{ {
driver_map.add(module); driver_map.add(module);
for (auto cell : module->cells()) { for (auto cell : module->cells()) {
@ -447,9 +454,12 @@ public:
queue.emplace_back(cell); queue.emplace_back(cell);
} }
for (auto wire : module->wires()) { for (auto wire : module->wires()) {
if (wire->port_input)
factory.add_input(wire->name, wire->width);
if (wire->port_output) { if (wire->port_output) {
Node node = enqueue(DriveChunk(DriveChunkWire(wire, 0, wire->width))); factory.add_output(wire->name, wire->width);
factory.declare_output(node, 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); memories_vector = Mem::get_all_memories(module);
@ -487,9 +497,9 @@ public:
// - Since wr port j can only have priority over wr port i if j > i, if we do writes in // - 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. // ascending index order the result will obey the priorty relation.
vector<Node> read_results; vector<Node> read_results;
int addr_width = ceil_log2(mem->size); factory.add_state(mem->cell->name, FunctionalIR::Sort(ceil_log2(mem->size), mem->width));
int data_width = mem->width; factory.set_initial_state(mem->cell->name, MemContents(mem));
Node node = factory.state_memory(mem->cell->name, addr_width, data_width); Node node = factory.get_current_state(mem->cell->name);
for (size_t i = 0; i < mem->wr_ports.size(); i++) { for (size_t i = 0; i < mem->wr_ports.size(); i++) {
const auto &wr = mem->wr_ports[i]; const auto &wr = mem->wr_ports[i];
if (wr.clk_enable) if (wr.clk_enable)
@ -513,7 +523,7 @@ public:
Node addr = enqueue(driver_map(DriveSpec(rd.addr))); Node addr = enqueue(driver_map(DriveSpec(rd.addr)));
read_results.push_back(factory.memory_read(node, 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); return concatenate_read_results(mem, read_results);
} }
void process_cell(Cell *cell) void process_cell(Cell *cell)
@ -527,6 +537,17 @@ public:
} }
Node node = handle_memory(mem); Node node = handle_memory(mem);
factory.update_pending(cell_outputs.at({cell, ID(RD_DATA)}), node); 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 { } else {
dict<IdString, Node> connections; dict<IdString, Node> connections;
IdString output_name; // for the single output case IdString output_name; // for the single output case
@ -572,7 +593,7 @@ public:
DriveChunkWire wire_chunk = chunk.wire(); DriveChunkWire wire_chunk = chunk.wire();
if (wire_chunk.is_whole()) { if (wire_chunk.is_whole()) {
if (wire_chunk.wire->port_input) { 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.suggest_name(node, wire_chunk.wire->name);
factory.update_pending(pending, node); factory.update_pending(pending, node);
} else { } else {
@ -590,24 +611,8 @@ public:
DriveChunkPort port_chunk = chunk.port(); DriveChunkPort port_chunk = chunk.port();
if (port_chunk.is_whole()) { if (port_chunk.is_whole()) {
if (driver_map.celltypes.cell_output(port_chunk.cell->type, port_chunk.port)) { 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); Node node = enqueue_cell(port_chunk.cell, port_chunk.port);
factory.update_pending(pending, node); factory.update_pending(pending, node);
}
} else { } else {
DriveSpec driver = driver_map(DriveSpec(port_chunk)); DriveSpec driver = driver_map(DriveSpec(port_chunk));
factory.update_pending(pending, enqueue(driver)); factory.update_pending(pending, enqueue(driver));
@ -641,8 +646,7 @@ public:
FunctionalIR FunctionalIR::from_module(Module *module) { FunctionalIR FunctionalIR::from_module(Module *module) {
FunctionalIR ir; FunctionalIR ir;
auto factory = ir.factory(); auto factory = ir.factory();
FunctionalIRConstruction ctor(factory); FunctionalIRConstruction ctor(module, factory);
ctor.add_module(module);
ctor.process_queue(); ctor.process_queue();
ir.topological_sort(); ir.topological_sort();
ir.forward_buf(); ir.forward_buf();

View File

@ -189,24 +189,11 @@ private:
// the bool is true for next state values // the bool is true for next state values
using Graph = ComputeGraph<NodeData, Attr, IdString, std::pair<IdString, bool>>; using Graph = ComputeGraph<NodeData, Attr, IdString, std::pair<IdString, bool>>;
Graph _graph; Graph _graph;
dict<IdString, Sort> _inputs; dict<IdString, Sort> _input_sorts;
dict<IdString, Sort> _outputs; dict<IdString, Sort> _output_sorts;
dict<IdString, Sort> _state; dict<IdString, Sort> _state_sorts;
void add_input(IdString name, Sort sort) { dict<IdString, RTLIL::Const> _initial_state_signal;
auto [it, found] = _inputs.emplace(name, std::move(sort)); dict<IdString, MemContents> _initial_state_memory;
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);
}
public: public:
class Factory; class Factory;
// Node is an immutable reference to a FunctionalIR node // Node is an immutable reference to a FunctionalIR node
@ -306,7 +293,7 @@ public:
virtual T logical_shift_right(Node self, Node a, Node b) = 0; 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 arithmetic_shift_right(Node self, Node a, Node b) = 0;
virtual T mux(Node self, Node a, Node b, Node s) = 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 input(Node self, IdString name) = 0;
virtual T state(Node self, IdString name) = 0; virtual T state(Node self, IdString name) = 0;
virtual T memory_read(Node self, Node mem, Node addr) = 0; virtual T memory_read(Node self, Node mem, Node addr) = 0;
@ -343,7 +330,7 @@ public:
T logical_shift_right(Node self, Node, Node) override { return default_handler(self); } 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 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 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 input(Node self, IdString) override { return default_handler(self); }
T state(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); } T memory_read(Node self, Node, Node) override { return default_handler(self); }
@ -453,29 +440,40 @@ public:
log_assert(node.sort() == value.sort()); log_assert(node.sort() == value.sort());
mutate(node).append_arg(value._ref); mutate(node).append_arg(value._ref);
} }
Node input(IdString name, int width) { void add_input(IdString name, int width) {
_ir.add_input(name, Sort(width)); auto [it, inserted] = _ir._input_sorts.emplace(name, Sort(width));
return add(NodeData(Fn::input, name), Sort(width), {}); if (!inserted) log_error("input `%s` was re-defined", name.c_str());
} }
Node state(IdString name, int width) { void add_output(IdString name, int width) {
_ir.add_state(name, Sort(width)); auto [it, inserted] = _ir._output_sorts.emplace(name, Sort(width));
return add(NodeData(Fn::state, name), Sort(width), {}); if (!inserted) log_error("output `%s` was re-defined", name.c_str());
} }
Node state_memory(IdString name, int addr_width, int data_width) { void add_state(IdString name, Sort sort) {
_ir.add_state(name, Sort(addr_width, data_width)); auto [it, inserted] = _ir._state_sorts.emplace(name, sort);
return add(NodeData(Fn::state, name), Sort(addr_width, data_width), {}); if (!inserted) log_error("state `%s` was re-defined", name.c_str());
} }
void declare_output(Node node, IdString name, int width) { Node get_input(IdString name) {
_ir.add_output(name, Sort(width)); return add(NodeData(Fn::input, name), Sort(_ir._input_sorts.at(name)), {});
mutate(node).assign_key({name, false});
} }
void declare_state(Node node, IdString name, int width) { Node get_current_state(IdString name) {
_ir.add_state(name, Sort(width)); return add(NodeData(Fn::state, name), Sort(_ir._state_sorts.at(name)), {});
mutate(node).assign_key({name, true});
} }
void declare_state_memory(Node node, IdString name, int addr_width, int data_width) { void set_output(IdString output, Node value) {
_ir.add_state(name, Sort(addr_width, data_width)); log_assert(_ir._output_sorts.at(output) == value.sort());
mutate(node).assign_key({name, true}); 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) { void suggest_name(Node node, IdString name) {
mutate(node).sparse_attr() = name; mutate(node).sparse_attr() = name;
@ -487,9 +485,11 @@ public:
Node operator[](int i) { return Node(_graph[i]); } Node operator[](int i) { return Node(_graph[i]); }
void topological_sort(); void topological_sort();
void forward_buf(); void forward_buf();
dict<IdString, Sort> inputs() const { return _inputs; } dict<IdString, Sort> inputs() const { return _input_sorts; }
dict<IdString, Sort> outputs() const { return _outputs; } dict<IdString, Sort> outputs() const { return _output_sorts; }
dict<IdString, Sort> state() const { return _state; } dict<IdString, Sort> 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_output_node(IdString name) { return Node(_graph({name, false})); }
Node get_state_next_node(IdString name) { return Node(_graph({name, true})); } Node get_state_next_node(IdString name) { return Node(_graph({name, true})); }
class Iterator { class Iterator {

View File

@ -153,15 +153,17 @@ class FFCell(BaseCell):
from test_functional import yosys_synth from test_functional import yosys_synth
verilog_file = path.parent / 'verilog.v' verilog_file = path.parent / 'verilog.v'
with open(verilog_file, 'w') as f: with open(verilog_file, 'w') as f:
f.write(""" width = parameters['WIDTH']
f.write(f"""
module gold( module gold(
input wire clk, input wire clk,
input wire [{0}:0] D, input wire [{width-1}:0] D,
output reg [{0}:0] Q output reg [{width-1}:0] Q
); );
initial Q = {width}'b{("101" * width)[:width]};
always @(posedge clk) always @(posedge clk)
Q <= D; Q <= D;
endmodule""".format(parameters['WIDTH'] - 1)) endmodule""")
yosys_synth(verilog_file, path) yosys_synth(verilog_file, path)
class MemCell(BaseCell): class MemCell(BaseCell):
@ -180,6 +182,10 @@ module gold(
output reg [{0}:0] RD output reg [{0}:0] RD
); );
reg [{0}:0] mem[0:{2}]; reg [{0}:0] mem[0:{2}];
integer i;
initial
for(i = 0; i <= {2}; i = i + 1)
mem[i] = 9192 * (i + 1);
always @(*) always @(*)
RD = mem[RA]; RD = mem[RA];
always @(posedge clk) always @(posedge clk)
@ -211,8 +217,11 @@ module gold(
output reg [{0}:0] RD1, output reg [{0}:0] RD1,
output reg [{0}:0] RD2 output reg [{0}:0] RD2
); );
(*keep*)
reg [{0}:0] mem[0:{2}]; reg [{0}:0] mem[0:{2}];
integer i;
initial
for(i = 0; i <= {2}; i = i + 1)
mem[i] = 9192 * (i + 1);
always @(*) always @(*)
RD1 = mem[RA1]; RD1 = mem[RA1];
always @(*) always @(*)

View File

@ -81,25 +81,6 @@ def simulate_smt_with_smtio(smt_file_path, vcd_path, smt_io, num_steps, rnd):
parser.finish() parser.finish()
assert smt_io.check_sat() == 'sat' 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): def set_step(inputs, step):
# This function assumes 'inputs' is a dictionary like {"A": 5, "B": 4} # 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. # and 'input_values' is a dictionary like {"A": 5, "B": 13} specifying the concrete values for each input.
@ -118,7 +99,7 @@ def simulate_smt_with_smtio(smt_file_path, vcd_path, smt_io, num_steps, rnd):
f"(define-const test_state_step_n{step+1} gold_State (second test_results_step_n{step}))\n", 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 in range(num_steps):
for step_command in set_step(inputs, step): for step_command in set_step(inputs, step):
smt_commands.append(step_command) smt_commands.append(step_command)

View File

@ -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 # 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 = ""): def yosys_sim(rtlil_file, vcd_reference_file, vcd_out_file, preprocessing = ""):
try: 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: except:
# if yosys sim fails it's probably because of a simulation mismatch # if yosys sim fails it's probably because of a simulation mismatch
# since yosys sim aborts on simulation mismatch to generate vcd output # since yosys sim aborts on simulation mismatch to generate vcd output
# we have to re-run with a different set of flags # 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 # 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', 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) capture_output=True, check=False)
raise raise

View File

@ -7,7 +7,11 @@
#include "my_module_functional_cxx.cc" #include "my_module_functional_cxx.cc"
std::string vcd_name_mangle(std::string name) { class VcdFile {
std::ofstream &ofs;
std::string code_alloc = "!";
std::unordered_map<std::string, std::string> codes;
std::string name_mangle(std::string name) {
std::string ret = name; std::string ret = name;
bool escape = ret.empty() || !isalpha(ret[0]) && ret[0] != '_'; bool escape = ret.empty() || !isalpha(ret[0]) && ret[0] != '_';
for(size_t i = 0; i < ret.size(); i++) { for(size_t i = 0; i < ret.size(); i++) {
@ -19,78 +23,89 @@ std::string vcd_name_mangle(std::string name) {
return "\\" + ret; return "\\" + ret;
else else
return ret; return ret;
} }
std::unordered_map<std::string, std::string> codes; std::string allocate_code() {
std::string ret = code_alloc;
struct DumpHeader { for (size_t i = 0; i < code_alloc.size(); i++)
std::ofstream &ofs; if (code_alloc[i]++ == '~')
std::string code = "!"; code_alloc[i] = '!';
DumpHeader(std::ofstream &ofs) : ofs(ofs) {}
void increment_code() {
for(size_t i = 0; i < code.size(); i++)
if(code[i]++ == '~')
code[i] = '!';
else else
return; return ret;
code.push_back('!'); code_alloc.push_back('!');
return ret;
} }
template <size_t n> public:
void operator()(const char *name, Signal<n> value) { VcdFile(std::ofstream &ofs) : ofs(ofs) {}
ofs << "$var wire " << n << " " << code << " " << vcd_name_mangle(name) << " $end\n"; struct DumpHeader {
codes[name] = code; VcdFile *file;
increment_code(); explicit DumpHeader(VcdFile *file) : file(file) {}
template <size_t n> void operator()(const char *name, Signal<n> 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 <size_t n, size_t m> template <size_t n, size_t m> void operator()(const char *name, Memory<n, m> value) {}
void operator()(const char *name, Memory<n, m> value) { };
} struct Dump {
}; VcdFile *file;
explicit Dump(VcdFile *file) : file(file) {}
struct Dump { template <size_t n> void operator()(const char *name, Signal<n> value)
std::ofstream &ofs; {
Dump(std::ofstream &ofs) : ofs(ofs) {}
template <size_t n>
void operator()(const char *name, Signal<n> value) {
// Bit
if (n == 1) { if (n == 1) {
ofs << (value[0] ? '1' : '0'); file->ofs << (value[0] ? '1' : '0');
ofs << codes[name] << "\n"; file->ofs << file->codes.at(name) << "\n";
return; } else {
} file->ofs << "b";
// vector (multi-bit) signals
ofs << "b";
for (size_t i = n; i-- > 0;) for (size_t i = n; i-- > 0;)
ofs << (value[i] ? '1' : '0'); file->ofs << (value[i] ? '1' : '0');
ofs << " " << codes[name] << "\n"; file->ofs << " " << file->codes.at(name) << "\n";
} }
template <size_t n, size_t m>
void operator()(const char *name, Memory<n, m> value) {
} }
template <size_t n, size_t m> void operator()(const char *name, Memory<n, m> 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<typename... Args> void header(Args ...args) {
begin_header();
DumpHeader d(this);
(args.visit(d), ...);
end_header();
}
void begin_data(int step) {
ofs << "#" << step << "\n";
}
template<typename... Args> 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<size_t n> template <size_t n> Signal<n> random_signal(std::mt19937 &gen)
Signal<n> random_signal(std::mt19937 &gen) { {
std::uniform_int_distribution<uint32_t> dist; std::uniform_int_distribution<uint32_t> dist;
std::array<uint32_t, (n+31)/32> words; std::array<uint32_t, (n + 31) / 32> words;
for(auto &w : words) for (auto &w : words)
w = dist(gen); w = dist(gen);
return Signal<n>::from_array(words); return Signal<n>::from_array(words);
} }
struct Reset {
template <size_t n>
void operator()(const char *, Signal<n> &signal) {
signal = 0;
}
};
struct Randomize { struct Randomize {
std::mt19937 &gen; std::mt19937 &gen;
Randomize(std::mt19937 &gen) : gen(gen) {} Randomize(std::mt19937 &gen) : gen(gen) {}
template <size_t n> template <size_t n> void operator()(const char *, Signal<n> &signal) { signal = random_signal<n>(gen); }
void operator()(const char *, Signal<n> &signal) {
signal = random_signal<n>(gen);
}
}; };
int main(int argc, char **argv) int main(int argc, char **argv)
@ -104,43 +119,27 @@ int main(int argc, char **argv)
const int steps = atoi(argv[2]); const int steps = atoi(argv[2]);
const uint32_t seed = atoi(argv[3]); const uint32_t seed = atoi(argv[3]);
constexpr int number_timescale = 1;
const std::string units_timescale = "us";
gold::Inputs inputs; gold::Inputs inputs;
gold::Outputs outputs; gold::Outputs outputs;
gold::State state; gold::State state;
gold::State next_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) { for (int step = 0; step < steps; ++step) {
vcd_file << "#" << step << "\n"; inputs.visit(Randomize(gen));
gold::eval(inputs, outputs, state, next_state); gold::eval(inputs, outputs, state, next_state);
{ vcd.data(step, inputs, outputs, state);
Dump d(vcd_file);
inputs.visit(d);
outputs.visit(d);
state.visit(d);
}
state = next_state; state = next_state;
inputs.visit(Randomize(gen));
} }
vcd_file.close();
return 0; return 0;
} }