/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2014 Clifford Wolf * Copyright (C) 2014 Johann Glaser * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include "kernel/yosys.h" #include "kernel/satgen.h" #include "kernel/consteval.h" #include static uint32_t xorshift32_state = 123456789; static uint32_t xorshift32(uint32_t 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, RTLIL::IdString cell_type, std::string cell_type_flags) { RTLIL::Module *module = design->addModule("\\gold"); RTLIL::Cell *cell = module->addCell("\\UUT", cell_type); RTLIL::Wire *wire; 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) { wire = module->addWire("\\A"); wire->width = 1 + xorshift32(8); wire->port_input = true; cell->setPort("\\A", wire); } if (cell_type_flags.find('B') != std::string::npos) { 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->setPort("\\B", wire); } if (cell_type_flags.find('S') != std::string::npos && xorshift32(2)) { if (cell_type_flags.find('A') != std::string::npos) cell->parameters["\\A_SIGNED"] = true; if (cell_type_flags.find('B') != std::string::npos) cell->parameters["\\B_SIGNED"] = true; } if (cell_type_flags.find('s') != std::string::npos) { if (cell_type_flags.find('A') != std::string::npos && xorshift32(2)) cell->parameters["\\A_SIGNED"] = true; if (cell_type_flags.find('B') != std::string::npos && xorshift32(2)) cell->parameters["\\B_SIGNED"] = true; } if (cell_type_flags.find('Y') != std::string::npos) { wire = module->addWire("\\Y"); wire->width = 1 + xorshift32(8); wire->port_output = true; 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); } module->fixup_ports(); cell->fixup_parameters(); cell->check(); } static void run_eval_test(RTLIL::Design *design, bool verbose, 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; 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()) { 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()); std::vector sat1_in_sig = satgen1.importSigSpec(in_sig); std::vector sat1_in_val = satgen1.importSigSpec(in_val); std::vector sat1_model = satgen1.importSigSpec(out_sig); std::vector 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 sat2_in_def_sig = satgen2.importDefSigSpec(in_sig); std::vector sat2_in_def_val = satgen2.importDefSigSpec(in_val); std::vector sat2_in_undef_sig = satgen2.importUndefSigSpec(in_sig); std::vector sat2_in_undef_val = satgen2.importUndefSigSpec(in_val); std::vector sat2_model_def_sig = satgen2.importDefSigSpec(out_sig); std::vector sat2_model_undef_sig = satgen2.importUndefSigSpec(out_sig); std::vector 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 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() { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); log(" test_cell [options] {cell-types}\n"); log("\n"); log("Tests the internal implementation of the given cell type (for example '$mux')\n"); log("by comparing SAT solver, EVAL and TECHMAP implementations of the cell types..\n"); log("\n"); log("Run with 'all' instead of a cell type to run the test on all supported\n"); log("cell types.\n"); log("\n"); 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(" -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 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; int argidx; for (argidx = 1; argidx < SIZE(args); argidx++) { if (args[argidx] == "-n" && argidx+1 < SIZE(args)) { 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; } if (args[argidx] == "-f" && argidx+1 < SIZE(args)) { ilang_file = args[++argidx]; num_iter = 1; continue; } if (args[argidx] == "-simlib") { techmap_cmd = "techmap -map +/simlib.v -max_iter 2 -autoproc"; 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); std::map cell_types; std::vector selected_cell_types; cell_types["$not"] = "ASY"; cell_types["$pos"] = "ASY"; cell_types["$neg"] = "ASY"; cell_types["$and"] = "ABSY"; cell_types["$or"] = "ABSY"; cell_types["$xor"] = "ABSY"; cell_types["$xnor"] = "ABSY"; cell_types["$reduce_and"] = "ASY"; cell_types["$reduce_or"] = "ASY"; cell_types["$reduce_xor"] = "ASY"; cell_types["$reduce_xnor"] = "ASY"; cell_types["$reduce_bool"] = "ASY"; cell_types["$shl"] = "ABshY"; cell_types["$shr"] = "ABshY"; cell_types["$sshl"] = "ABshY"; cell_types["$sshr"] = "ABshY"; cell_types["$shift"] = "ABshY"; cell_types["$shiftx"] = "ABshY"; cell_types["$lt"] = "ABSY"; cell_types["$le"] = "ABSY"; cell_types["$eq"] = "ABSY"; cell_types["$ne"] = "ABSY"; // cell_types["$eqx"] = "ABSY"; // cell_types["$nex"] = "ABSY"; cell_types["$ge"] = "ABSY"; cell_types["$gt"] = "ABSY"; cell_types["$add"] = "ABSY"; cell_types["$sub"] = "ABSY"; cell_types["$mul"] = "ABSY"; cell_types["$div"] = "ABSY"; cell_types["$mod"] = "ABSY"; // cell_types["$pow"] = "ABsY"; cell_types["$logic_not"] = "ASY"; cell_types["$logic_and"] = "ABSY"; cell_types["$logic_or"] = "ABSY"; // cell_types["$mux"] = "A"; // cell_types["$pmux"] = "A"; // cell_types["$slice"] = "A"; // cell_types["$concat"] = "A"; // cell_types["$assert"] = "A"; cell_types["$lut"] = "*"; cell_types["$alu"] = "ABSY"; for (; argidx < SIZE(args); argidx++) { if (args[argidx].rfind("-", 0) == 0) log_cmd_error("Unexpected option: %s\n", args[argidx].c_str()); if (args[argidx] == "all") { for (auto &it : cell_types) if (std::count(selected_cell_types.begin(), selected_cell_types.end(), it.first) == 0) selected_cell_types.push_back(it.first); continue; } if (cell_types.count(args[argidx]) == 0) { std::string cell_type_list; int charcount = 100; for (auto &it : cell_types) { if (charcount > 60) { cell_type_list += "\n" + it.first; charcount = 0; } else cell_type_list += " " + it.first; charcount += SIZE(it.first); } log_cmd_error("The cell type `%s' is currently not supported. Try one of these:%s\n", args[argidx].c_str(), cell_type_list.c_str()); } if (std::count(selected_cell_types.begin(), selected_cell_types.end(), args[argidx]) == 0) selected_cell_types.push_back(args[argidx]); } if (!ilang_file.empty()) { if (!selected_cell_types.empty()) log_cmd_error("Do not specify any cell types when using -f.\n"); selected_cell_types.push_back("ilang"); } if (selected_cell_types.empty()) log_cmd_error("No cell type to test specified.\n"); std::vector uut_names; for (auto cell_type : selected_cell_types) for (int i = 0; i < num_iter; i++) { RTLIL::Design *design = new RTLIL::Design; 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"); if (verbose) Pass::call(design, "dump gate"); Pass::call(design, "dump gold"); 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, "", "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, "", "verilog -selected -noexpr"); uut_names.push_back(uut_name); } run_eval_test(design, verbose, 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;