Added "test_cell -const"
[yosys.git] / passes / tests / test_cell.cc
index 4034f120e78f928bdb875188253ffd3ae8cda5f4..310c3bdfeef1a707ba968f2a2fa577eff32fbe34 100644 (file)
  */
 
 #include "kernel/yosys.h"
+#include "kernel/satgen.h"
+#include "kernel/consteval.h"
+#include "kernel/macc.h"
 #include <algorithm>
 
+static uint32_t xorshift32_state = 123456789;
+
 static uint32_t xorshift32(uint32_t limit) {
-       static uint32_t x = 123456789;
-       x ^= x << 13;
-       x ^= x >> 17;
-       x ^= x << 5;
-       return x % limit;
+       xorshift32_state ^= xorshift32_state << 13;
+       xorshift32_state ^= xorshift32_state >> 17;
+       xorshift32_state ^= xorshift32_state << 5;
+       return xorshift32_state % limit;
 }
 
-static void create_gold_module(RTLIL::Design *design, std::string cell_type, std::string cell_type_flags)
+static void create_gold_module(RTLIL::Design *design, RTLIL::IdString cell_type, std::string cell_type_flags, bool constmode)
 {
        RTLIL::Module *module = design->addModule("\\gold");
        RTLIL::Cell *cell = module->addCell("\\UUT", cell_type);
+       RTLIL::Wire *wire;
+
+       if (cell_type == "$macc")
+       {
+               Macc macc;
+               int width = 1 + xorshift32(8);
+               int depth = 1 + xorshift32(6);
+               int mulbits_a = 0, mulbits_b = 0;
+
+               RTLIL::Wire *wire_a = module->addWire("\\A");
+               wire_a->width = 0;
+               wire_a->port_input = true;
+
+               for (int i = 0; i < depth; i++)
+               {
+                       int size_a = xorshift32(width) + 1;
+                       int size_b = depth > 4 ? 0 : xorshift32(width) + 1;
+
+                       if (mulbits_a + size_a*size_b <= 96 && mulbits_b + size_a + size_b <= 16 && xorshift32(2) == 1) {
+                               mulbits_a += size_a * size_b;
+                               mulbits_b += size_a + size_b;
+                       } else
+                               size_b = 0;
+
+                       Macc::port_t this_port;
+
+                       wire_a->width += size_a;
+                       this_port.in_a = RTLIL::SigSpec(wire_a, wire_a->width - size_a, size_a);
+
+                       wire_a->width += size_b;
+                       this_port.in_b = RTLIL::SigSpec(wire_a, wire_a->width - size_b, size_b);
+
+                       this_port.is_signed = xorshift32(2) == 1;
+                       this_port.do_subtract = xorshift32(2) == 1;
+                       macc.ports.push_back(this_port);
+               }
+
+               wire = module->addWire("\\B");
+               wire->width = xorshift32(mulbits_a ? xorshift32(4)+1 : xorshift32(16)+1);
+               wire->port_input = true;
+               macc.bit_ports = wire;
+
+               wire = module->addWire("\\Y");
+               wire->width = width;
+               wire->port_output = true;
+               cell->setPort("\\Y", wire);
+
+               macc.to_cell(cell);
+       }
+
+       if (cell_type == "$lut")
+       {
+               int width = 1 + xorshift32(6);
+
+               wire = module->addWire("\\A");
+               wire->width = width;
+               wire->port_input = true;
+               cell->setPort("\\A", wire);
+
+               wire = module->addWire("\\Y");
+               wire->port_output = true;
+               cell->setPort("\\Y", wire);
+
+               RTLIL::SigSpec config;
+               for (int i = 0; i < (1 << width); i++)
+                       config.append(xorshift32(2) ? RTLIL::S1 : RTLIL::S0);
+
+               cell->setParam("\\LUT", config.as_const());
+       }
 
        if (cell_type_flags.find('A') != std::string::npos) {
-               RTLIL::Wire *wire = module->addWire("\\A");
+               wire = module->addWire("\\A");
                wire->width = 1 + xorshift32(8);
                wire->port_input = true;
-               cell->set("\\A", wire);
+               cell->setPort("\\A", wire);
        }
 
        if (cell_type_flags.find('B') != std::string::npos) {
-               RTLIL::Wire *wire = module->addWire("\\B");
+               wire = module->addWire("\\B");
                if (cell_type_flags.find('h') != std::string::npos)
                        wire->width = 1 + xorshift32(6);
                else
                        wire->width = 1 + xorshift32(8);
                wire->port_input = true;
-               cell->set("\\B", wire);
+               cell->setPort("\\B", wire);
        }
 
        if (cell_type_flags.find('S') != std::string::npos && xorshift32(2)) {
@@ -66,10 +139,66 @@ static void create_gold_module(RTLIL::Design *design, std::string cell_type, std
        }
 
        if (cell_type_flags.find('Y') != std::string::npos) {
-               RTLIL::Wire *wire = module->addWire("\\Y");
+               wire = module->addWire("\\Y");
                wire->width = 1 + xorshift32(8);
                wire->port_output = true;
-               cell->set("\\Y", wire);
+               cell->setPort("\\Y", wire);
+       }
+
+       if (cell_type == "$alu")
+       {
+               wire = module->addWire("\\CI");
+               wire->port_input = true;
+               cell->setPort("\\CI", wire);
+
+               wire = module->addWire("\\BI");
+               wire->port_input = true;
+               cell->setPort("\\BI", wire);
+
+               wire = module->addWire("\\X");
+               wire->width = SIZE(cell->getPort("\\Y"));
+               wire->port_output = true;
+               cell->setPort("\\X", wire);
+
+               wire = module->addWire("\\CO");
+               wire->width = SIZE(cell->getPort("\\Y"));
+               wire->port_output = true;
+               cell->setPort("\\CO", wire);
+       }
+
+       if (constmode)
+       {
+               auto conn_list = cell->connections();
+               for (auto &conn : conn_list)
+               {
+                       RTLIL::SigSpec sig = conn.second;
+
+                       if (SIZE(sig) == 0 || sig[0].wire == nullptr || sig[0].wire->port_output)
+                               continue;
+
+                       int n, m;
+                       switch (xorshift32(5))
+                       {
+                       case 0:
+                               n = xorshift32(SIZE(sig) + 1);
+                               for (int i = 0; i < n; i++)
+                                       sig[i] = xorshift32(2) == 1 ? RTLIL::S1 : RTLIL::S0;
+                               break;
+                       case 1:
+                               n = xorshift32(SIZE(sig) + 1);
+                               for (int i = n; i < SIZE(sig); i++)
+                                       sig[i] = xorshift32(2) == 1 ? RTLIL::S1 : RTLIL::S0;
+                               break;
+                       case 2:
+                               n = xorshift32(SIZE(sig));
+                               m = xorshift32(SIZE(sig));
+                               for (int i = std::min(n, m); i < std::max(n, m); i++)
+                                       sig[i] = xorshift32(2) == 1 ? RTLIL::S1 : RTLIL::S0;
+                               break;
+                       }
+
+                       cell->setPort(conn.first, sig);
+               }
        }
 
        module->fixup_ports();
@@ -77,6 +206,239 @@ static void create_gold_module(RTLIL::Design *design, std::string cell_type, std
        cell->check();
 }
 
+static void run_eval_test(RTLIL::Design *design, bool verbose, bool nosat, std::string uut_name, std::ofstream &vlog_file)
+{
+       log("Eval testing:%c", verbose ? '\n' : ' ');
+
+       RTLIL::Module *gold_mod = design->module("\\gold");
+       RTLIL::Module *gate_mod = design->module("\\gate");
+       ConstEval gold_ce(gold_mod), gate_ce(gate_mod);
+
+       ezDefaultSAT ez1, ez2;
+       SigMap sigmap(gold_mod);
+       SatGen satgen1(&ez1, &sigmap);
+       SatGen satgen2(&ez2, &sigmap);
+       satgen2.model_undef = true;
+
+       if (!nosat)
+               for (auto cell : gold_mod->cells()) {
+                       satgen1.importCell(cell);
+                       satgen2.importCell(cell);
+               }
+
+       if (vlog_file.is_open())
+       {
+               vlog_file << stringf("\nmodule %s;\n", uut_name.c_str());
+
+               for (auto port : gold_mod->ports) {
+                       RTLIL::Wire *wire = gold_mod->wire(port);
+                       if (wire->port_input)
+                               vlog_file << stringf("  reg [%d:0] %s;\n", SIZE(wire)-1, log_id(wire));
+                       else
+                               vlog_file << stringf("  wire [%d:0] %s_expr, %s_noexpr;\n", SIZE(wire)-1, log_id(wire), log_id(wire));
+               }
+
+               vlog_file << stringf("  %s_expr uut_expr(", uut_name.c_str());
+               for (int i = 0; i < SIZE(gold_mod->ports); i++)
+                       vlog_file << stringf("%s.%s(%s%s)", i ? ", " : "", log_id(gold_mod->ports[i]), log_id(gold_mod->ports[i]),
+                                       gold_mod->wire(gold_mod->ports[i])->port_input ? "" : "_expr");
+               vlog_file << stringf(");\n");
+
+               vlog_file << stringf("  %s_expr uut_noexpr(", uut_name.c_str());
+               for (int i = 0; i < SIZE(gold_mod->ports); i++)
+                       vlog_file << stringf("%s.%s(%s%s)", i ? ", " : "", log_id(gold_mod->ports[i]), log_id(gold_mod->ports[i]),
+                                       gold_mod->wire(gold_mod->ports[i])->port_input ? "" : "_noexpr");
+               vlog_file << stringf(");\n");
+
+               vlog_file << stringf("  task run;\n");
+               vlog_file << stringf("    begin\n");
+               vlog_file << stringf("      $display(\"%s\");\n", uut_name.c_str());
+       }
+
+       for (int i = 0; i < 64; i++)
+       {
+               log(verbose ? "\n" : ".");
+               gold_ce.clear();
+               gate_ce.clear();
+
+               RTLIL::SigSpec in_sig, in_val;
+               RTLIL::SigSpec out_sig, out_val;
+               std::string vlog_pattern_info;
+
+               for (auto port : gold_mod->ports)
+               {
+                       RTLIL::Wire *gold_wire = gold_mod->wire(port);
+                       RTLIL::Wire *gate_wire = gate_mod->wire(port);
+
+                       log_assert(gold_wire != nullptr);
+                       log_assert(gate_wire != nullptr);
+                       log_assert(gold_wire->port_input == gate_wire->port_input);
+                       log_assert(SIZE(gold_wire) == SIZE(gate_wire));
+
+                       if (!gold_wire->port_input)
+                               continue;
+
+                       RTLIL::Const in_value;
+                       for (int i = 0; i < SIZE(gold_wire); i++)
+                               in_value.bits.push_back(xorshift32(2) ? RTLIL::S1 : RTLIL::S0);
+
+                       if (xorshift32(4) == 0) {
+                               int inv_chance = 1 + xorshift32(8);
+                               for (int i = 0; i < SIZE(gold_wire); i++)
+                                       if (xorshift32(inv_chance) == 0)
+                                               in_value.bits[i] = RTLIL::Sx;
+                       }
+
+                       if (verbose)
+                               log("%s: %s\n", log_id(gold_wire), log_signal(in_value));
+
+                       in_sig.append(gold_wire);
+                       in_val.append(in_value);
+
+                       gold_ce.set(gold_wire, in_value);
+                       gate_ce.set(gate_wire, in_value);
+
+                       if (vlog_file.is_open() && SIZE(in_value) > 0) {
+                               vlog_file << stringf("      %s = 'b%s;\n", log_id(gold_wire), in_value.as_string().c_str());
+                               if (!vlog_pattern_info.empty())
+                                       vlog_pattern_info += " ";
+                               vlog_pattern_info += stringf("%s=%s", log_id(gold_wire), log_signal(in_value));
+                       }
+               }
+
+               if (vlog_file.is_open())
+                       vlog_file << stringf("      #1;\n");
+
+               for (auto port : gold_mod->ports)
+               {
+                       RTLIL::Wire *gold_wire = gold_mod->wire(port);
+                       RTLIL::Wire *gate_wire = gate_mod->wire(port);
+
+                       log_assert(gold_wire != nullptr);
+                       log_assert(gate_wire != nullptr);
+                       log_assert(gold_wire->port_output == gate_wire->port_output);
+                       log_assert(SIZE(gold_wire) == SIZE(gate_wire));
+
+                       if (!gold_wire->port_output)
+                               continue;
+
+                       RTLIL::SigSpec gold_outval(gold_wire);
+                       RTLIL::SigSpec gate_outval(gate_wire);
+
+                       if (!gold_ce.eval(gold_outval))
+                               log_error("Failed to eval %s in gold module.\n", log_id(gold_wire));
+
+                       if (!gate_ce.eval(gate_outval))
+                               log_error("Failed to eval %s in gate module.\n", log_id(gate_wire));
+
+                       bool gold_gate_mismatch = false;
+                       for (int i = 0; i < SIZE(gold_wire); i++) {
+                               if (gold_outval[i] == RTLIL::Sx)
+                                       continue;
+                               if (gold_outval[i] == gate_outval[i])
+                                       continue;
+                               gold_gate_mismatch = true;
+                               break;
+                       }
+
+                       if (gold_gate_mismatch)
+                               log_error("Mismatch in output %s: gold:%s != gate:%s\n", log_id(gate_wire), log_signal(gold_outval), log_signal(gate_outval));
+
+                       if (verbose)
+                               log("%s: %s\n", log_id(gold_wire), log_signal(gold_outval));
+
+                       out_sig.append(gold_wire);
+                       out_val.append(gold_outval);
+
+                       if (vlog_file.is_open()) {
+                               vlog_file << stringf("      $display(\"[%s] %s expected: %%b, expr: %%b, noexpr: %%b\", %d'b%s, %s_expr, %s_noexpr);\n",
+                                               vlog_pattern_info.c_str(), log_id(gold_wire), SIZE(gold_outval), gold_outval.as_string().c_str(), log_id(gold_wire), log_id(gold_wire));
+                               vlog_file << stringf("      if (%s_expr !== %d'b%s) begin $display(\"ERROR\"); $finish; end\n", log_id(gold_wire), SIZE(gold_outval), gold_outval.as_string().c_str());
+                               vlog_file << stringf("      if (%s_noexpr !== %d'b%s) begin $display(\"ERROR\"); $finish; end\n", log_id(gold_wire), SIZE(gold_outval), gold_outval.as_string().c_str());
+                       }
+               }
+
+               if (verbose)
+                       log("EVAL:  %s\n", out_val.as_string().c_str());
+
+               if (!nosat)
+               {
+                       std::vector<int> sat1_in_sig = satgen1.importSigSpec(in_sig);
+                       std::vector<int> sat1_in_val = satgen1.importSigSpec(in_val);
+
+                       std::vector<int> sat1_model = satgen1.importSigSpec(out_sig);
+                       std::vector<bool> sat1_model_value;
+
+                       if (!ez1.solve(sat1_model, sat1_model_value, ez1.vec_eq(sat1_in_sig, sat1_in_val)))
+                               log_error("Evaluating sat model 1 (no undef modeling) failed!\n");
+
+                       if (verbose) {
+                               log("SAT 1: ");
+                               for (int i = SIZE(out_sig)-1; i >= 0; i--)
+                                       log("%c", sat1_model_value.at(i) ? '1' : '0');
+                               log("\n");
+                       }
+
+                       for (int i = 0; i < SIZE(out_sig); i++) {
+                               if (out_val[i] != RTLIL::S0 && out_val[i] != RTLIL::S1)
+                                       continue;
+                               if (out_val[i] == RTLIL::S0 && sat1_model_value.at(i) == false)
+                                       continue;
+                               if (out_val[i] == RTLIL::S1 && sat1_model_value.at(i) == true)
+                                       continue;
+                               log_error("Mismatch in sat model 1 (no undef modeling) output!\n");
+                       }
+
+                       std::vector<int> sat2_in_def_sig = satgen2.importDefSigSpec(in_sig);
+                       std::vector<int> sat2_in_def_val = satgen2.importDefSigSpec(in_val);
+
+                       std::vector<int> sat2_in_undef_sig = satgen2.importUndefSigSpec(in_sig);
+                       std::vector<int> sat2_in_undef_val = satgen2.importUndefSigSpec(in_val);
+
+                       std::vector<int> sat2_model_def_sig = satgen2.importDefSigSpec(out_sig);
+                       std::vector<int> sat2_model_undef_sig = satgen2.importUndefSigSpec(out_sig);
+
+                       std::vector<int> sat2_model;
+                       sat2_model.insert(sat2_model.end(), sat2_model_def_sig.begin(), sat2_model_def_sig.end());
+                       sat2_model.insert(sat2_model.end(), sat2_model_undef_sig.begin(), sat2_model_undef_sig.end());
+
+                       std::vector<bool> sat2_model_value;
+
+                       if (!ez2.solve(sat2_model, sat2_model_value, ez2.vec_eq(sat2_in_def_sig, sat2_in_def_val), ez2.vec_eq(sat2_in_undef_sig, sat2_in_undef_val)))
+                               log_error("Evaluating sat model 2 (undef modeling) failed!\n");
+
+                       if (verbose) {
+                               log("SAT 2: ");
+                               for (int i = SIZE(out_sig)-1; i >= 0; i--)
+                                       log("%c", sat2_model_value.at(SIZE(out_sig) + i) ? 'x' : sat2_model_value.at(i) ? '1' : '0');
+                               log("\n");
+                       }
+
+                       for (int i = 0; i < SIZE(out_sig); i++) {
+                               if (sat2_model_value.at(SIZE(out_sig) + i)) {
+                                       if (out_val[i] != RTLIL::S0 && out_val[i] != RTLIL::S1)
+                                               continue;
+                               } else {
+                                       if (out_val[i] == RTLIL::S0 && sat2_model_value.at(i) == false)
+                                               continue;
+                                       if (out_val[i] == RTLIL::S1 && sat2_model_value.at(i) == true)
+                                               continue;
+                               }
+                               log_error("Mismatch in sat model 2 (undef modeling) output!\n");
+                       }
+               }
+       }
+
+       if (vlog_file.is_open()) {
+               vlog_file << stringf("    end\n");
+               vlog_file << stringf("  endtask\n");
+               vlog_file << stringf("endmodule\n");
+       }
+
+       if (!verbose)
+               log(" ok.\n");
+}
+
 struct TestCellPass : public Pass {
        TestCellPass() : Pass("test_cell", "automatically test the implementation of a cell type") { }
        virtual void help()
@@ -94,18 +456,44 @@ struct TestCellPass : public Pass {
                log("    -n {integer}\n");
                log("        create this number of cell instances and test them (default = 100).\n");
                log("\n");
+               log("    -s {positive_integer}\n");
+               log("        use this value as rng seed value (default = unix time).\n");
+               log("\n");
                log("    -f {ilang_file}\n");
                log("        don't generate circuits. instead load the specified ilang file.\n");
                log("\n");
                log("    -map {filename}\n");
                log("        pass this option to techmap.\n");
                log("\n");
+               log("    -simplib\n");
+               log("        use \"techmap -map +/simlib.v -max_iter 2 -autoproc\"\n");
+               log("\n");
+               log("    -script {script_file}\n");
+               log("        instead of calling \"techmap\", call \"script {script_file}\".\n");
+               log("\n");
+               log("    -const\n");
+               log("        set some input bits to random constant values\n");
+               log("\n");
+               log("    -nosat\n");
+               log("        do not check SAT model or run SAT equivalence checking\n");
+               log("\n");
+               log("    -v\n");
+               log("        print additional debug information to the console\n");
+               log("\n");
+               log("    -vlog {filename}\n");
+               log("        create a verilog test bench to test simlib and write_verilog\n");
+               log("\n");
        }
        virtual void execute(std::vector<std::string> args, RTLIL::Design*)
        {
                int num_iter = 100;
                std::string techmap_cmd = "techmap -assert";
                std::string ilang_file;
+               xorshift32_state = 0;
+               std::ofstream vlog_file;
+               bool verbose = false;
+               bool constmode = false;
+               bool nosat = false;
 
                int argidx;
                for (argidx = 1; argidx < SIZE(args); argidx++)
@@ -114,6 +502,10 @@ struct TestCellPass : public Pass {
                                num_iter = atoi(args[++argidx].c_str());
                                continue;
                        }
+                       if (args[argidx] == "-s" && argidx+1 < SIZE(args)) {
+                               xorshift32_state = atoi(args[++argidx].c_str());
+                               continue;
+                       }
                        if (args[argidx] == "-map" && argidx+1 < SIZE(args)) {
                                techmap_cmd += " -map " + args[++argidx];
                                continue;
@@ -123,15 +515,45 @@ struct TestCellPass : public Pass {
                                num_iter = 1;
                                continue;
                        }
+                       if (args[argidx] == "-script" && argidx+1 < SIZE(args)) {
+                               techmap_cmd = "script " + args[++argidx];
+                               continue;
+                       }
+                       if (args[argidx] == "-simlib") {
+                               techmap_cmd = "techmap -map +/simlib.v -max_iter 2 -autoproc";
+                               continue;
+                       }
+                       if (args[argidx] == "-const") {
+                               constmode = true;
+                               continue;
+                       }
+                       if (args[argidx] == "-nosat") {
+                               nosat = true;
+                               continue;
+                       }
+                       if (args[argidx] == "-v") {
+                               verbose = true;
+                               continue;
+                       }
+                       if (args[argidx] == "-vlog" && argidx+1 < SIZE(args)) {
+                               vlog_file.open(args[++argidx], std::ios_base::trunc);
+                               if (!vlog_file.is_open())
+                                       log_cmd_error("Failed to open output file `%s'.\n", args[argidx].c_str());
+                               continue;
+                       }
                        break;
                }
 
+               if (xorshift32_state == 0) {
+                       xorshift32_state = time(NULL) & 0x7fffffff;
+                       log("Rng seed value: %d\n", int(xorshift32_state));
+               }
+
                std::map<std::string, std::string> cell_types;
                std::vector<std::string> selected_cell_types;
 
                cell_types["$not"] = "ASY";
                cell_types["$pos"] = "ASY";
-               cell_types["$bu0"] = "ASY";
                cell_types["$neg"] = "ASY";
 
                cell_types["$and"]  = "ABSY";
@@ -176,10 +598,12 @@ struct TestCellPass : public Pass {
                // cell_types["$pmux"] = "A";
                // cell_types["$slice"] = "A";
                // cell_types["$concat"] = "A";
-               // cell_types["$safe_pmux"] = "A";
-               // cell_types["$lut"] = "A";
                // cell_types["$assert"] = "A";
 
+               cell_types["$lut"] = "*";
+               cell_types["$alu"] = "ABSY";
+               cell_types["$macc"] = "*";
+
                for (; argidx < SIZE(args); argidx++)
                {
                        if (args[argidx].rfind("-", 0) == 0)
@@ -220,6 +644,8 @@ struct TestCellPass : public Pass {
                if (selected_cell_types.empty())
                        log_cmd_error("No cell type to test specified.\n");
 
+               std::vector<std::string> uut_names;
+
                for (auto cell_type : selected_cell_types)
                        for (int i = 0; i < num_iter; i++)
                        {
@@ -227,12 +653,37 @@ struct TestCellPass : public Pass {
                                if (cell_type == "ilang")
                                        Frontend::frontend_call(design, NULL, std::string(), "ilang " + ilang_file);
                                else
-                                       create_gold_module(design, cell_type, cell_types.at(cell_type));
-                               Pass::call(design, stringf("copy gold gate; %s gate; opt gate", techmap_cmd.c_str()));
-                               Pass::call(design, "miter -equiv -flatten -make_outputs -ignore_gold_x gold gate miter; dump gold");
-                               Pass::call(design, "sat -verify -enable_undef -prove trigger 0 -show-inputs -show-outputs miter");
+                                       create_gold_module(design, cell_type, cell_types.at(cell_type), constmode);
+                               Pass::call(design, stringf("copy gold gate; cd gate; %s; cd ..; opt -fast gate", techmap_cmd.c_str()));
+                               if (!nosat)
+                                       Pass::call(design, "miter -equiv -flatten -make_outputs -ignore_gold_x gold gate miter");
+                               if (verbose)
+                                       Pass::call(design, "dump gate");
+                               Pass::call(design, "dump gold");
+                               if (!nosat)
+                                       Pass::call(design, "sat -verify -enable_undef -prove trigger 0 -show-inputs -show-outputs miter");
+                               std::string uut_name = stringf("uut_%s_%d", cell_type.substr(1).c_str(), i);
+                               if (vlog_file.is_open()) {
+                                       Pass::call(design, stringf("copy gold %s_expr; select %s_expr", uut_name.c_str(), uut_name.c_str()));
+                                       Backend::backend_call(design, &vlog_file, "<test_cell -vlog>", "verilog -selected");
+                                       Pass::call(design, stringf("copy gold %s_noexpr; select %s_noexpr", uut_name.c_str(), uut_name.c_str()));
+                                       Backend::backend_call(design, &vlog_file, "<test_cell -vlog>", "verilog -selected -noexpr");
+                                       uut_names.push_back(uut_name);
+                               }
+                               run_eval_test(design, verbose, nosat, uut_name, vlog_file);
                                delete design;
                        }
+
+               if (vlog_file.is_open()) {
+                       vlog_file << "\nmodule testbench;\n";
+                       for (auto &uut : uut_names)
+                               vlog_file << stringf("  %s %s ();\n", uut.c_str(), uut.c_str());
+                       vlog_file << "  initial begin\n";
+                       for (auto &uut : uut_names)
+                               vlog_file << "    " << uut << ".run;\n";
+                       vlog_file << "  end\n";
+                       vlog_file << "endmodule\n";
+               }
        }
 } TestCellPass;