3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2025-04-13 04:28:18 +00:00

Docs: updating to current 'master'

Pulling for #4133 and removing related TODO.
This commit is contained in:
Krystine Sherwin 2024-01-22 11:18:07 +13:00
commit 65bb0d3059
No known key found for this signature in database
103 changed files with 2513 additions and 646 deletions

View file

@ -8,7 +8,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: os:
- { id: macos-11, name: 'Big Sur' } - { id: macos-13, name: 'Ventura' }
cpp_std: cpp_std:
- 'c++11' - 'c++11'
- 'c++17' - 'c++17'

View file

@ -2,12 +2,26 @@
List of major changes and improvements between releases List of major changes and improvements between releases
======================================================= =======================================================
Yosys 0.36 .. Yosys 0.37-dev Yosys 0.37 .. Yosys 0.38-dev
-------------------------- --------------------------
Yosys 0.36 .. Yosys 0.37
--------------------------
* New commands and options
- Added option "-nodisplay" to read_verilog.
* SystemVerilog
- Correct hierarchical path names for structs and unions.
* Various
- Print hierarchy for failed assertions in "sim" pass.
- Add "--present-only" option to "yosys-witness" to omit unused signals.
- Implement a generic record/replay interface for CXXRTL.
- Improved readability of emitted code with "write_verilog".
Yosys 0.35 .. Yosys 0.36 Yosys 0.35 .. Yosys 0.36
-------------------------- --------------------------
* New commands and options * New commands and options
- Added option "--" to pass arguments down to tcl when using -c option. - Added option "--" to pass arguments down to tcl when using -c option.
- Added ability on MacOS and Windows to pass options after arguments on cli. - Added ability on MacOS and Windows to pass options after arguments on cli.
- Added option "-cmp2softlogic" to synth_lattice. - Added option "-cmp2softlogic" to synth_lattice.

View file

@ -141,7 +141,7 @@ LDLIBS += -lrt
endif endif
endif endif
YOSYS_VER := 0.36+13 YOSYS_VER := 0.37+21
# Note: We arrange for .gitcommit to contain the (short) commit hash in # Note: We arrange for .gitcommit to contain the (short) commit hash in
# tarballs generated with git-archive(1) using .gitattributes. The git repo # tarballs generated with git-archive(1) using .gitattributes. The git repo
@ -157,7 +157,7 @@ endif
OBJS = kernel/version_$(GIT_REV).o OBJS = kernel/version_$(GIT_REV).o
bumpversion: bumpversion:
sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline 8f07a0d.. | wc -l`/;" Makefile sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline a5c7f69.. | wc -l`/;" Makefile
# set 'ABCREV = default' to use abc/ as it is # set 'ABCREV = default' to use abc/ as it is
# #
@ -888,6 +888,7 @@ endif
+cd tests/verilog && bash run-test.sh +cd tests/verilog && bash run-test.sh
+cd tests/xprop && bash run-test.sh $(SEEDOPT) +cd tests/xprop && bash run-test.sh $(SEEDOPT)
+cd tests/fmt && bash run-test.sh +cd tests/fmt && bash run-test.sh
+cd tests/cxxrtl && bash run-test.sh
@echo "" @echo ""
@echo " Passed \"make test\"." @echo " Passed \"make test\"."
@echo "" @echo ""

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -ex set -ex

View file

@ -628,6 +628,20 @@ std::string escape_cxx_string(const std::string &input)
return output; return output;
} }
std::string basename(const std::string &filepath)
{
#ifdef _WIN32
const std::string dir_seps = "\\/";
#else
const std::string dir_seps = "/";
#endif
size_t sep_pos = filepath.find_last_of(dir_seps);
if (sep_pos != std::string::npos)
return filepath.substr(sep_pos + 1);
else
return filepath;
}
template<class T> template<class T>
std::string get_hdl_name(T *object) std::string get_hdl_name(T *object)
{ {
@ -1058,9 +1072,45 @@ struct CxxrtlWorker {
dump_sigspec_rhs(cell->getPort(ID::EN)); dump_sigspec_rhs(cell->getPort(ID::EN));
f << " == value<1>{1u}) {\n"; f << " == value<1>{1u}) {\n";
inc_indent(); inc_indent();
f << indent << print_output; dict<std::string, RTLIL::SigSpec> fmt_args;
fmt.emit_cxxrtl(f, [this](const RTLIL::SigSpec &sig) { dump_sigspec_rhs(sig); }); f << indent << "struct : public lazy_fmt {\n";
f << ";\n"; inc_indent();
f << indent << "std::string operator() () const override {\n";
inc_indent();
fmt.emit_cxxrtl(f, indent, [&](const RTLIL::SigSpec &sig) {
if (sig.size() == 0)
f << "value<0>()";
else {
std::string arg_name = "arg" + std::to_string(fmt_args.size());
fmt_args[arg_name] = sig;
f << arg_name;
}
}, "performer");
dec_indent();
f << indent << "}\n";
f << indent << "struct performer *performer;\n";
for (auto arg : fmt_args)
f << indent << "value<" << arg.second.size() << "> " << arg.first << ";\n";
dec_indent();
f << indent << "} formatter;\n";
f << indent << "formatter.performer = performer;\n";
for (auto arg : fmt_args) {
f << indent << "formatter." << arg.first << " = ";
dump_sigspec_rhs(arg.second);
f << ";\n";
}
f << indent << "if (performer) {\n";
inc_indent();
f << indent << "static const metadata_map attributes = ";
dump_metadata_map(cell->attributes);
f << ";\n";
f << indent << "performer->on_print(formatter, attributes);\n";
dec_indent();
f << indent << "} else {\n";
inc_indent();
f << indent << print_output << " << formatter();\n";
dec_indent();
f << indent << "}\n";
dec_indent(); dec_indent();
f << indent << "}\n"; f << indent << "}\n";
} }
@ -1277,20 +1327,29 @@ struct CxxrtlWorker {
log_assert(!for_debug); log_assert(!for_debug);
// Sync $print cells are grouped into PRINT_SYNC nodes in the FlowGraph. // Sync $print cells are grouped into PRINT_SYNC nodes in the FlowGraph.
log_assert(!cell->getParam(ID::TRG_ENABLE).as_bool()); log_assert(!cell->getParam(ID::TRG_ENABLE).as_bool() || (cell->getParam(ID::TRG_ENABLE).as_bool() && cell->getParam(ID::TRG_WIDTH).as_int() == 0));
f << indent << "auto " << mangle(cell) << "_curr = "; if (!cell->getParam(ID::TRG_ENABLE).as_bool()) { // async $print cell
dump_sigspec_rhs(cell->getPort(ID::EN)); f << indent << "auto " << mangle(cell) << "_curr = ";
f << ".concat("; dump_sigspec_rhs(cell->getPort(ID::EN));
dump_sigspec_rhs(cell->getPort(ID::ARGS)); f << ".concat(";
f << ").val();\n"; dump_sigspec_rhs(cell->getPort(ID::ARGS));
f << ").val();\n";
f << indent << "if (" << mangle(cell) << " != " << mangle(cell) << "_curr) {\n"; f << indent << "if (" << mangle(cell) << " != " << mangle(cell) << "_curr) {\n";
inc_indent(); inc_indent();
dump_print(cell); dump_print(cell);
f << indent << mangle(cell) << " = " << mangle(cell) << "_curr;\n"; f << indent << mangle(cell) << " = " << mangle(cell) << "_curr;\n";
dec_indent(); dec_indent();
f << indent << "}\n"; f << indent << "}\n";
} else { // initial $print cell
f << indent << "if (!" << mangle(cell) << ") {\n";
inc_indent();
dump_print(cell);
f << indent << mangle(cell) << " = value<1>{1u};\n";
dec_indent();
f << indent << "}\n";
}
// Flip-flops // Flip-flops
} else if (is_ff_cell(cell->type)) { } else if (is_ff_cell(cell->type)) {
log_assert(!for_debug); log_assert(!for_debug);
@ -1471,11 +1530,11 @@ struct CxxrtlWorker {
}; };
if (buffered_inputs) { if (buffered_inputs) {
// If we have any buffered inputs, there's no chance of converging immediately. // If we have any buffered inputs, there's no chance of converging immediately.
f << indent << mangle(cell) << access << "eval();\n"; f << indent << mangle(cell) << access << "eval(performer);\n";
f << indent << "converged = false;\n"; f << indent << "converged = false;\n";
assign_from_outputs(/*cell_converged=*/false); assign_from_outputs(/*cell_converged=*/false);
} else { } else {
f << indent << "if (" << mangle(cell) << access << "eval()) {\n"; f << indent << "if (" << mangle(cell) << access << "eval(performer)) {\n";
inc_indent(); inc_indent();
assign_from_outputs(/*cell_converged=*/true); assign_from_outputs(/*cell_converged=*/true);
dec_indent(); dec_indent();
@ -1988,6 +2047,11 @@ struct CxxrtlWorker {
} }
} }
for (auto cell : module->cells()) { for (auto cell : module->cells()) {
// Certain $print cells have additional state, which must be reset as well.
if (cell->type == ID($print) && !cell->getParam(ID::TRG_ENABLE).as_bool())
f << indent << mangle(cell) << " = value<" << (1 + cell->getParam(ID::ARGS_WIDTH).as_int()) << ">();\n";
if (cell->type == ID($print) && cell->getParam(ID::TRG_ENABLE).as_bool() && cell->getParam(ID::TRG_WIDTH).as_int() == 0)
f << indent << mangle(cell) << " = value<1>();\n";
if (is_internal_cell(cell->type)) if (is_internal_cell(cell->type))
continue; continue;
f << indent << mangle(cell); f << indent << mangle(cell);
@ -2101,19 +2165,19 @@ struct CxxrtlWorker {
if (wire_type.type == WireType::MEMBER && edge_wires[wire]) if (wire_type.type == WireType::MEMBER && edge_wires[wire])
f << indent << "prev_" << mangle(wire) << " = " << mangle(wire) << ";\n"; f << indent << "prev_" << mangle(wire) << " = " << mangle(wire) << ";\n";
if (wire_type.is_buffered()) if (wire_type.is_buffered())
f << indent << "if (" << mangle(wire) << ".commit()) changed = true;\n"; f << indent << "if (" << mangle(wire) << ".commit(observer)) changed = true;\n";
} }
if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) { if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) {
for (auto &mem : mod_memories[module]) { for (auto &mem : mod_memories[module]) {
if (!writable_memories.count({module, mem.memid})) if (!writable_memories.count({module, mem.memid}))
continue; continue;
f << indent << "if (" << mangle(&mem) << ".commit()) changed = true;\n"; f << indent << "if (" << mangle(&mem) << ".commit(observer)) changed = true;\n";
} }
for (auto cell : module->cells()) { for (auto cell : module->cells()) {
if (is_internal_cell(cell->type)) if (is_internal_cell(cell->type))
continue; continue;
const char *access = is_cxxrtl_blackbox_cell(cell) ? "->" : "."; const char *access = is_cxxrtl_blackbox_cell(cell) ? "->" : ".";
f << indent << "if (" << mangle(cell) << access << "commit()) changed = true;\n"; f << indent << "if (" << mangle(cell) << access << "commit(observer)) changed = true;\n";
} }
} }
f << indent << "return changed;\n"; f << indent << "return changed;\n";
@ -2132,7 +2196,7 @@ struct CxxrtlWorker {
if (!metadata_item.first.isPublic()) if (!metadata_item.first.isPublic())
continue; continue;
if (metadata_item.second.size() > 64 && (metadata_item.second.flags & RTLIL::CONST_FLAG_STRING) == 0) { if (metadata_item.second.size() > 64 && (metadata_item.second.flags & RTLIL::CONST_FLAG_STRING) == 0) {
f << indent << "/* attribute " << metadata_item.first.str().substr(1) << " is over 64 bits wide */"; f << indent << "/* attribute " << metadata_item.first.str().substr(1) << " is over 64 bits wide */\n";
continue; continue;
} }
f << indent << "{ " << escape_cxx_string(metadata_item.first.str().substr(1)) << ", "; f << indent << "{ " << escape_cxx_string(metadata_item.first.str().substr(1)) << ", ";
@ -2353,20 +2417,27 @@ struct CxxrtlWorker {
dump_reset_method(module); dump_reset_method(module);
f << indent << "}\n"; f << indent << "}\n";
f << "\n"; f << "\n";
f << indent << "bool eval() override {\n"; // No default argument, to prevent unintentional `return bb_foo::eval();` calls that drop performer.
f << indent << "bool eval(performer *performer) override {\n";
dump_eval_method(module); dump_eval_method(module);
f << indent << "}\n"; f << indent << "}\n";
f << "\n"; f << "\n";
f << indent << "bool commit() override {\n"; f << indent << "template<class ObserverT>\n";
f << indent << "bool commit(ObserverT &observer) {\n";
dump_commit_method(module); dump_commit_method(module);
f << indent << "}\n"; f << indent << "}\n";
f << "\n"; f << "\n";
f << indent << "bool commit() override {\n";
f << indent << indent << "observer observer;\n";
f << indent << indent << "return commit<>(observer);\n";
f << indent << "}\n";
if (debug_info) { if (debug_info) {
f << "\n";
f << indent << "void debug_info(debug_items &items, std::string path = \"\") override {\n"; f << indent << "void debug_info(debug_items &items, std::string path = \"\") override {\n";
dump_debug_info_method(module); dump_debug_info_method(module);
f << indent << "}\n"; f << indent << "}\n";
f << "\n";
} }
f << "\n";
f << indent << "static std::unique_ptr<" << mangle(module); f << indent << "static std::unique_ptr<" << mangle(module);
f << template_params(module, /*is_decl=*/false) << "> "; f << template_params(module, /*is_decl=*/false) << "> ";
f << "create(std::string name, metadata_map parameters, metadata_map attributes);\n"; f << "create(std::string name, metadata_map parameters, metadata_map attributes);\n";
@ -2410,11 +2481,11 @@ struct CxxrtlWorker {
f << "\n"; f << "\n";
bool has_cells = false; bool has_cells = false;
for (auto cell : module->cells()) { for (auto cell : module->cells()) {
if (cell->type == ID($print) && !cell->getParam(ID::TRG_ENABLE).as_bool()) { // Certain $print cells have additional state, which requires storage.
// comb $print cell -- store the last EN/ARGS values to know when they change. if (cell->type == ID($print) && !cell->getParam(ID::TRG_ENABLE).as_bool())
dump_attrs(cell);
f << indent << "value<" << (1 + cell->getParam(ID::ARGS_WIDTH).as_int()) << "> " << mangle(cell) << ";\n"; f << indent << "value<" << (1 + cell->getParam(ID::ARGS_WIDTH).as_int()) << "> " << mangle(cell) << ";\n";
} if (cell->type == ID($print) && cell->getParam(ID::TRG_ENABLE).as_bool() && cell->getParam(ID::TRG_WIDTH).as_int() == 0)
f << indent << "value<1> " << mangle(cell) << ";\n";
if (is_internal_cell(cell->type)) if (is_internal_cell(cell->type))
continue; continue;
dump_attrs(cell); dump_attrs(cell);
@ -2443,8 +2514,18 @@ struct CxxrtlWorker {
f << indent << "};\n"; f << indent << "};\n";
f << "\n"; f << "\n";
f << indent << "void reset() override;\n"; f << indent << "void reset() override;\n";
f << indent << "bool eval() override;\n"; f << "\n";
f << indent << "bool commit() override;\n"; f << indent << "bool eval(performer *performer = nullptr) override;\n";
f << "\n";
f << indent << "template<class ObserverT>\n";
f << indent << "bool commit(ObserverT &observer) {\n";
dump_commit_method(module);
f << indent << "}\n";
f << "\n";
f << indent << "bool commit() override {\n";
f << indent << indent << "observer observer;\n";
f << indent << indent << "return commit<>(observer);\n";
f << indent << "}\n";
if (debug_info) { if (debug_info) {
if (debug_eval) { if (debug_eval) {
f << "\n"; f << "\n";
@ -2473,27 +2554,23 @@ struct CxxrtlWorker {
dump_reset_method(module); dump_reset_method(module);
f << indent << "}\n"; f << indent << "}\n";
f << "\n"; f << "\n";
f << indent << "bool " << mangle(module) << "::eval() {\n"; f << indent << "bool " << mangle(module) << "::eval(performer *performer) {\n";
dump_eval_method(module); dump_eval_method(module);
f << indent << "}\n"; f << indent << "}\n";
f << "\n";
f << indent << "bool " << mangle(module) << "::commit() {\n";
dump_commit_method(module);
f << indent << "}\n";
f << "\n";
if (debug_info) { if (debug_info) {
if (debug_eval) { if (debug_eval) {
f << "\n";
f << indent << "void " << mangle(module) << "::debug_eval() {\n"; f << indent << "void " << mangle(module) << "::debug_eval() {\n";
dump_debug_eval_method(module); dump_debug_eval_method(module);
f << indent << "}\n"; f << indent << "}\n";
f << "\n";
} }
f << "\n";
f << indent << "CXXRTL_EXTREMELY_COLD\n"; f << indent << "CXXRTL_EXTREMELY_COLD\n";
f << indent << "void " << mangle(module) << "::debug_info(debug_items &items, std::string path) {\n"; f << indent << "void " << mangle(module) << "::debug_info(debug_items &items, std::string path) {\n";
dump_debug_info_method(module); dump_debug_info_method(module);
f << indent << "}\n"; f << indent << "}\n";
f << "\n";
} }
f << "\n";
} }
void dump_design(RTLIL::Design *design) void dump_design(RTLIL::Design *design)
@ -2501,7 +2578,6 @@ struct CxxrtlWorker {
RTLIL::Module *top_module = nullptr; RTLIL::Module *top_module = nullptr;
std::vector<RTLIL::Module*> modules; std::vector<RTLIL::Module*> modules;
TopoSort<RTLIL::Module*> topo_design; TopoSort<RTLIL::Module*> topo_design;
bool has_prints = false;
for (auto module : design->modules()) { for (auto module : design->modules()) {
if (!design->selected_module(module)) if (!design->selected_module(module))
continue; continue;
@ -2514,8 +2590,6 @@ struct CxxrtlWorker {
topo_design.node(module); topo_design.node(module);
for (auto cell : module->cells()) { for (auto cell : module->cells()) {
if (cell->type == ID($print))
has_prints = true;
if (is_internal_cell(cell->type) || is_cxxrtl_blackbox_cell(cell)) if (is_internal_cell(cell->type) || is_cxxrtl_blackbox_cell(cell))
continue; continue;
RTLIL::Module *cell_module = design->module(cell->type); RTLIL::Module *cell_module = design->module(cell->type);
@ -2571,11 +2645,9 @@ struct CxxrtlWorker {
} }
if (split_intf) if (split_intf)
f << "#include \"" << intf_filename << "\"\n"; f << "#include \"" << basename(intf_filename) << "\"\n";
else else
f << "#include <cxxrtl/cxxrtl.h>\n"; f << "#include <cxxrtl/cxxrtl.h>\n";
if (has_prints)
f << "#include <iostream>\n";
f << "\n"; f << "\n";
f << "#if defined(CXXRTL_INCLUDE_CAPI_IMPL) || \\\n"; f << "#if defined(CXXRTL_INCLUDE_CAPI_IMPL) || \\\n";
f << " defined(CXXRTL_INCLUDE_VCD_CAPI_IMPL)\n"; f << " defined(CXXRTL_INCLUDE_VCD_CAPI_IMPL)\n";
@ -2938,8 +3010,9 @@ struct CxxrtlWorker {
for (auto node : node_order) for (auto node : node_order)
if (live_nodes[node]) { if (live_nodes[node]) {
if (node->type == FlowGraph::Node::Type::CELL_EVAL && if (node->type == FlowGraph::Node::Type::CELL_EVAL &&
node->cell->type == ID($print) && node->cell->type == ID($print) &&
node->cell->getParam(ID::TRG_ENABLE).as_bool()) node->cell->getParam(ID::TRG_ENABLE).as_bool() &&
node->cell->getParam(ID::TRG_WIDTH).as_int() != 0)
sync_print_cells[make_pair(node->cell->getPort(ID::TRG), node->cell->getParam(ID::TRG_POLARITY))].push_back(node->cell); sync_print_cells[make_pair(node->cell->getPort(ID::TRG), node->cell->getParam(ID::TRG_POLARITY))].push_back(node->cell);
else else
schedule[module].push_back(*node); schedule[module].push_back(*node);
@ -3252,7 +3325,9 @@ struct CxxrtlBackend : public Backend {
log(" value<8> p_i_data;\n"); log(" value<8> p_i_data;\n");
log(" wire<8> p_o_data;\n"); log(" wire<8> p_o_data;\n");
log("\n"); log("\n");
log(" bool eval() override;\n"); log(" bool eval(performer *performer) override;\n");
log(" template<class ObserverT>\n");
log(" bool commit(ObserverT &observer);\n");
log(" bool commit() override;\n"); log(" bool commit() override;\n");
log("\n"); log("\n");
log(" static std::unique_ptr<bb_p_debug>\n"); log(" static std::unique_ptr<bb_p_debug>\n");
@ -3265,11 +3340,11 @@ struct CxxrtlBackend : public Backend {
log(" namespace cxxrtl_design {\n"); log(" namespace cxxrtl_design {\n");
log("\n"); log("\n");
log(" struct stderr_debug : public bb_p_debug {\n"); log(" struct stderr_debug : public bb_p_debug {\n");
log(" bool eval() override {\n"); log(" bool eval(performer *performer) override {\n");
log(" if (posedge_p_clk() && p_en)\n"); log(" if (posedge_p_clk() && p_en)\n");
log(" fprintf(stderr, \"debug: %%02x\\n\", p_i_data.data[0]);\n"); log(" fprintf(stderr, \"debug: %%02x\\n\", p_i_data.data[0]);\n");
log(" p_o_data.next = p_i_data;\n"); log(" p_o_data.next = p_i_data;\n");
log(" return bb_p_debug::eval();\n"); log(" return bb_p_debug::eval(performer);\n");
log(" }\n"); log(" }\n");
log(" };\n"); log(" };\n");
log("\n"); log("\n");
@ -3370,7 +3445,7 @@ struct CxxrtlBackend : public Backend {
log(" -print-output <stream>\n"); log(" -print-output <stream>\n");
log(" $print cells in the generated code direct their output to <stream>.\n"); log(" $print cells in the generated code direct their output to <stream>.\n");
log(" must be one of \"std::cout\", \"std::cerr\". if not specified,\n"); log(" must be one of \"std::cout\", \"std::cerr\". if not specified,\n");
log(" \"std::cout\" is used.\n"); log(" \"std::cout\" is used. explicitly provided performer overrides this.\n");
log("\n"); log("\n");
log(" -nohierarchy\n"); log(" -nohierarchy\n");
log(" use design hierarchy as-is. in most designs, a top module should be\n"); log(" use design hierarchy as-is. in most designs, a top module should be\n");

View file

@ -28,6 +28,7 @@
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <cstring>
#include <cassert> #include <cassert>
#include <limits> #include <limits>
#include <type_traits> #include <type_traits>
@ -38,6 +39,7 @@
#include <memory> #include <memory>
#include <functional> #include <functional>
#include <sstream> #include <sstream>
#include <iostream>
// `cxxrtl::debug_item` has to inherit from `cxxrtl_object` to satisfy strict aliasing requirements. // `cxxrtl::debug_item` has to inherit from `cxxrtl_object` to satisfy strict aliasing requirements.
#include <cxxrtl/capi/cxxrtl_capi.h> #include <cxxrtl/capi/cxxrtl_capi.h>
@ -145,7 +147,7 @@ struct value : public expr_base<value<Bits>> {
// These functions ensure that a conversion is never out of range, and should be always used, if at all // These functions ensure that a conversion is never out of range, and should be always used, if at all
// possible, instead of direct manipulation of the `data` member. For very large types, .slice() and // possible, instead of direct manipulation of the `data` member. For very large types, .slice() and
// .concat() can be used to split them into more manageable parts. // .concat() can be used to split them into more manageable parts.
template<class IntegerT> template<class IntegerT, typename std::enable_if<!std::is_signed<IntegerT>::value, int>::type = 0>
CXXRTL_ALWAYS_INLINE CXXRTL_ALWAYS_INLINE
IntegerT get() const { IntegerT get() const {
static_assert(std::numeric_limits<IntegerT>::is_integer && !std::numeric_limits<IntegerT>::is_signed, static_assert(std::numeric_limits<IntegerT>::is_integer && !std::numeric_limits<IntegerT>::is_signed,
@ -158,15 +160,32 @@ struct value : public expr_base<value<Bits>> {
return result; return result;
} }
template<class IntegerT> template<class IntegerT, typename std::enable_if<std::is_signed<IntegerT>::value, int>::type = 0>
CXXRTL_ALWAYS_INLINE CXXRTL_ALWAYS_INLINE
void set(IntegerT other) { IntegerT get() const {
auto unsigned_result = get<typename std::make_unsigned<IntegerT>::type>();
IntegerT result;
memcpy(&result, &unsigned_result, sizeof(IntegerT));
return result;
}
template<class IntegerT, typename std::enable_if<!std::is_signed<IntegerT>::value, int>::type = 0>
CXXRTL_ALWAYS_INLINE
void set(IntegerT value) {
static_assert(std::numeric_limits<IntegerT>::is_integer && !std::numeric_limits<IntegerT>::is_signed, static_assert(std::numeric_limits<IntegerT>::is_integer && !std::numeric_limits<IntegerT>::is_signed,
"set<T>() requires T to be an unsigned integral type"); "set<T>() requires T to be an unsigned integral type");
static_assert(std::numeric_limits<IntegerT>::digits >= Bits, static_assert(std::numeric_limits<IntegerT>::digits >= Bits,
"set<T>() requires the value to be at least as wide as T is"); "set<T>() requires the value to be at least as wide as T is");
for (size_t n = 0; n < chunks; n++) for (size_t n = 0; n < chunks; n++)
data[n] = (other >> (n * chunk::bits)) & chunk::mask; data[n] = (value >> (n * chunk::bits)) & chunk::mask;
}
template<class IntegerT, typename std::enable_if<std::is_signed<IntegerT>::value, int>::type = 0>
CXXRTL_ALWAYS_INLINE
void set(IntegerT value) {
typename std::make_unsigned<IntegerT>::type unsigned_value;
memcpy(&unsigned_value, &value, sizeof(IntegerT));
set(unsigned_value);
} }
// Operations with compile-time parameters. // Operations with compile-time parameters.
@ -419,6 +438,7 @@ struct value : public expr_base<value<Bits>> {
carry = (shift_bits == 0) ? 0 carry = (shift_bits == 0) ? 0
: data[n] >> (chunk::bits - shift_bits); : data[n] >> (chunk::bits - shift_bits);
} }
result.data[result.chunks - 1] &= result.msb_mask;
return result; return result;
} }
@ -429,12 +449,12 @@ struct value : public expr_base<value<Bits>> {
// Detect shifts definitely large than Bits early. // Detect shifts definitely large than Bits early.
for (size_t n = 1; n < amount.chunks; n++) for (size_t n = 1; n < amount.chunks; n++)
if (amount.data[n] != 0) if (amount.data[n] != 0)
return {}; return (Signed && is_neg()) ? value<Bits>().bit_not() : value<Bits>();
// Past this point we can use the least significant chunk as the shift size. // Past this point we can use the least significant chunk as the shift size.
size_t shift_chunks = amount.data[0] / chunk::bits; size_t shift_chunks = amount.data[0] / chunk::bits;
size_t shift_bits = amount.data[0] % chunk::bits; size_t shift_bits = amount.data[0] % chunk::bits;
if (shift_chunks >= chunks) if (shift_chunks >= chunks)
return {}; return (Signed && is_neg()) ? value<Bits>().bit_not() : value<Bits>();
value<Bits> result; value<Bits> result;
chunk::type carry = 0; chunk::type carry = 0;
for (size_t n = 0; n < chunks - shift_chunks; n++) { for (size_t n = 0; n < chunks - shift_chunks; n++) {
@ -443,12 +463,13 @@ struct value : public expr_base<value<Bits>> {
: data[chunks - 1 - n] << (chunk::bits - shift_bits); : data[chunks - 1 - n] << (chunk::bits - shift_bits);
} }
if (Signed && is_neg()) { if (Signed && is_neg()) {
size_t top_chunk_idx = (Bits - shift_bits) / chunk::bits; size_t top_chunk_idx = amount.data[0] > Bits ? 0 : (Bits - amount.data[0]) / chunk::bits;
size_t top_chunk_bits = (Bits - shift_bits) % chunk::bits; size_t top_chunk_bits = amount.data[0] > Bits ? 0 : (Bits - amount.data[0]) % chunk::bits;
for (size_t n = top_chunk_idx + 1; n < chunks; n++) for (size_t n = top_chunk_idx + 1; n < chunks; n++)
result.data[n] = chunk::mask; result.data[n] = chunk::mask;
if (shift_bits != 0) if (amount.data[0] != 0)
result.data[top_chunk_idx] |= chunk::mask << top_chunk_bits; result.data[top_chunk_idx] |= chunk::mask << top_chunk_bits;
result.data[result.chunks - 1] &= result.msb_mask;
} }
return result; return result;
} }
@ -473,6 +494,7 @@ struct value : public expr_base<value<Bits>> {
carry = (shift_bits == 0) ? 0 carry = (shift_bits == 0) ? 0
: data[result.chunks + shift_chunks - 1 - n] << (chunk::bits - shift_bits); : data[result.chunks + shift_chunks - 1 - n] << (chunk::bits - shift_bits);
} }
result.data[result.chunks - 1] &= result.msb_mask;
return result; return result;
} }
@ -509,7 +531,8 @@ struct value : public expr_base<value<Bits>> {
for (size_t n = 0; n < chunks; n++) { for (size_t n = 0; n < chunks; n++) {
chunk::type x = data[chunks - 1 - n]; chunk::type x = data[chunks - 1 - n];
// First add to `count` as if the chunk is zero // First add to `count` as if the chunk is zero
count += (n == 0 ? Bits % chunk::bits : chunk::bits); constexpr size_t msb_chunk_bits = Bits % chunk::bits != 0 ? Bits % chunk::bits : chunk::bits;
count += (n == 0 ? msb_chunk_bits : chunk::bits);
// If the chunk isn't zero, correct the `count` value and return // If the chunk isn't zero, correct the `count` value and return
if (x != 0) { if (x != 0) {
for (; x != 0; count--) for (; x != 0; count--)
@ -543,7 +566,7 @@ struct value : public expr_base<value<Bits>> {
} }
value<Bits> neg() const { value<Bits> neg() const {
return value<Bits> { 0u }.sub(*this); return value<Bits>().sub(*this);
} }
bool ucmp(const value<Bits> &other) const { bool ucmp(const value<Bits> &other) const {
@ -741,102 +764,6 @@ std::ostream &operator<<(std::ostream &os, const value<Bits> &val) {
return os; return os;
} }
template<size_t Bits>
struct value_formatted {
const value<Bits> &val;
bool character;
bool justify_left;
char padding;
int width;
int base;
bool signed_;
bool plus;
value_formatted(const value<Bits> &val, bool character, bool justify_left, char padding, int width, int base, bool signed_, bool plus) :
val(val), character(character), justify_left(justify_left), padding(padding), width(width), base(base), signed_(signed_), plus(plus) {}
value_formatted(const value_formatted<Bits> &) = delete;
value_formatted<Bits> &operator=(const value_formatted<Bits> &rhs) = delete;
};
template<size_t Bits>
std::ostream &operator<<(std::ostream &os, const value_formatted<Bits> &vf)
{
value<Bits> val = vf.val;
std::string buf;
// We might want to replace some of these bit() calls with direct
// chunk access if it turns out to be slow enough to matter.
if (!vf.character) {
size_t width = Bits;
if (vf.base != 10) {
width = 0;
for (size_t index = 0; index < Bits; index++)
if (val.bit(index))
width = index + 1;
}
if (vf.base == 2) {
for (size_t i = width; i > 0; i--)
buf += (val.bit(i - 1) ? '1' : '0');
} else if (vf.base == 8 || vf.base == 16) {
size_t step = (vf.base == 16) ? 4 : 3;
for (size_t index = 0; index < width; index += step) {
uint8_t value = val.bit(index) | (val.bit(index + 1) << 1) | (val.bit(index + 2) << 2);
if (step == 4)
value |= val.bit(index + 3) << 3;
buf += "0123456789abcdef"[value];
}
std::reverse(buf.begin(), buf.end());
} else if (vf.base == 10) {
bool negative = vf.signed_ && val.is_neg();
if (negative)
val = val.neg();
if (val.is_zero())
buf += '0';
while (!val.is_zero()) {
value<Bits> quotient, remainder;
if (Bits >= 4)
std::tie(quotient, remainder) = val.udivmod(value<Bits>{10u});
else
std::tie(quotient, remainder) = std::make_pair(value<Bits>{0u}, val);
buf += '0' + remainder.template trunc<(Bits > 4 ? 4 : Bits)>().val().template get<uint8_t>();
val = quotient;
}
if (negative || vf.plus)
buf += negative ? '-' : '+';
std::reverse(buf.begin(), buf.end());
} else assert(false);
} else {
buf.reserve(Bits/8);
for (int i = 0; i < Bits; i += 8) {
char ch = 0;
for (int j = 0; j < 8 && i + j < int(Bits); j++)
if (val.bit(i + j))
ch |= 1 << j;
if (ch != 0)
buf.append({ch});
}
std::reverse(buf.begin(), buf.end());
}
assert(vf.width == 0 || vf.padding != '\0');
if (!vf.justify_left && buf.size() < vf.width) {
size_t pad_width = vf.width - buf.size();
if (vf.padding == '0' && (buf.front() == '+' || buf.front() == '-')) {
os << buf.front();
buf.erase(0, 1);
}
os << std::string(pad_width, vf.padding);
}
os << buf;
if (vf.justify_left && buf.size() < vf.width)
os << std::string(vf.width - buf.size(), vf.padding);
return os;
}
template<size_t Bits> template<size_t Bits>
struct wire { struct wire {
static constexpr size_t bits = Bits; static constexpr size_t bits = Bits;
@ -871,8 +798,13 @@ struct wire {
next.template set<IntegerT>(other); next.template set<IntegerT>(other);
} }
bool commit() { // This method intentionally takes a mandatory argument (to make it more difficult to misuse in
// black box implementations, leading to missed observer events). It is generic over its argument
// to allow the `on_update` method to be non-virtual.
template<class ObserverT>
bool commit(ObserverT &observer) {
if (curr != next) { if (curr != next) {
observer.on_update(curr.chunks, curr.data, next.data);
curr = next; curr = next;
return true; return true;
} }
@ -946,12 +878,17 @@ struct memory {
write { index, val, mask, priority }); write { index, val, mask, priority });
} }
bool commit() { // See the note for `wire::commit()`.
template<class ObserverT>
bool commit(ObserverT &observer) {
bool changed = false; bool changed = false;
for (const write &entry : write_queue) { for (const write &entry : write_queue) {
value<Width> elem = data[entry.index]; value<Width> elem = data[entry.index];
elem = elem.update(entry.val, entry.mask); elem = elem.update(entry.val, entry.mask);
changed |= (data[entry.index] != elem); if (data[entry.index] != elem) {
observer.on_update(value<Width>::chunks, data[0].data, elem.data, entry.index);
changed |= true;
}
data[entry.index] = elem; data[entry.index] = elem;
} }
write_queue.clear(); write_queue.clear();
@ -1008,6 +945,174 @@ struct metadata {
typedef std::map<std::string, metadata> metadata_map; typedef std::map<std::string, metadata> metadata_map;
struct performer;
// An object that allows formatting a string lazily.
struct lazy_fmt {
virtual std::string operator() () const = 0;
};
// An object that can be passed to a `eval()` method in order to act on side effects.
struct performer {
// Called by generated formatting code to evaluate a Verilog `$time` expression.
virtual int64_t vlog_time() const { return 0; }
// Called by generated formatting code to evaluate a Verilog `$realtime` expression.
virtual double vlog_realtime() const { return vlog_time(); }
// Called when a `$print` cell is triggered.
virtual void on_print(const lazy_fmt &formatter, const metadata_map &attributes) {
std::cout << formatter();
}
};
// An object that can be passed to a `commit()` method in order to produce a replay log of every state change in
// the simulation. Unlike `performer`, `observer` does not use virtual calls as their overhead is unacceptable, and
// a comparatively heavyweight template-based solution is justified.
struct observer {
// Called when the `commit()` method for a wire is about to update the `chunks` chunks at `base` with `chunks` chunks
// at `value` that have a different bit pattern. It is guaranteed that `chunks` is equal to the wire chunk count and
// `base` points to the first chunk.
void on_update(size_t chunks, const chunk_t *base, const chunk_t *value) {}
// Called when the `commit()` method for a memory is about to update the `chunks` chunks at `&base[chunks * index]`
// with `chunks` chunks at `value` that have a different bit pattern. It is guaranteed that `chunks` is equal to
// the memory element chunk count and `base` points to the first chunk of the first element of the memory.
void on_update(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) {}
};
// Must be kept in sync with `struct FmtPart` in kernel/fmt.h!
// Default member initializers would make this a non-aggregate-type in C++11, so they are commented out.
struct fmt_part {
enum {
STRING = 0,
INTEGER = 1,
CHARACTER = 2,
VLOG_TIME = 3,
} type;
// STRING type
std::string str;
// INTEGER/CHARACTER types
// + value<Bits> val;
// INTEGER/CHARACTER/VLOG_TIME types
enum {
RIGHT = 0,
LEFT = 1,
} justify; // = RIGHT;
char padding; // = '\0';
size_t width; // = 0;
// INTEGER type
unsigned base; // = 10;
bool signed_; // = false;
bool plus; // = false;
// VLOG_TIME type
bool realtime; // = false;
// + int64_t itime;
// + double ftime;
// Format the part as a string.
//
// The values of `vlog_time` and `vlog_realtime` are used for Verilog `$time` and `$realtime`, correspondingly.
template<size_t Bits>
std::string render(value<Bits> val, performer *performer = nullptr)
{
// We might want to replace some of these bit() calls with direct
// chunk access if it turns out to be slow enough to matter.
std::string buf;
switch (type) {
case STRING:
return str;
case CHARACTER: {
buf.reserve(Bits/8);
for (int i = 0; i < Bits; i += 8) {
char ch = 0;
for (int j = 0; j < 8 && i + j < int(Bits); j++)
if (val.bit(i + j))
ch |= 1 << j;
if (ch != 0)
buf.append({ch});
}
std::reverse(buf.begin(), buf.end());
break;
}
case INTEGER: {
size_t width = Bits;
if (base != 10) {
width = 0;
for (size_t index = 0; index < Bits; index++)
if (val.bit(index))
width = index + 1;
}
if (base == 2) {
for (size_t i = width; i > 0; i--)
buf += (val.bit(i - 1) ? '1' : '0');
} else if (base == 8 || base == 16) {
size_t step = (base == 16) ? 4 : 3;
for (size_t index = 0; index < width; index += step) {
uint8_t value = val.bit(index) | (val.bit(index + 1) << 1) | (val.bit(index + 2) << 2);
if (step == 4)
value |= val.bit(index + 3) << 3;
buf += "0123456789abcdef"[value];
}
std::reverse(buf.begin(), buf.end());
} else if (base == 10) {
bool negative = signed_ && val.is_neg();
if (negative)
val = val.neg();
if (val.is_zero())
buf += '0';
value<(Bits > 4 ? Bits : 4)> xval = val.template zext<(Bits > 4 ? Bits : 4)>();
while (!xval.is_zero()) {
value<(Bits > 4 ? Bits : 4)> quotient, remainder;
if (Bits >= 4)
std::tie(quotient, remainder) = xval.udivmod(value<(Bits > 4 ? Bits : 4)>{10u});
else
std::tie(quotient, remainder) = std::make_pair(value<(Bits > 4 ? Bits : 4)>{0u}, xval);
buf += '0' + remainder.template trunc<4>().template get<uint8_t>();
xval = quotient;
}
if (negative || plus)
buf += negative ? '-' : '+';
std::reverse(buf.begin(), buf.end());
} else assert(false && "Unsupported base for fmt_part");
break;
}
case VLOG_TIME: {
if (performer) {
buf = realtime ? std::to_string(performer->vlog_realtime()) : std::to_string(performer->vlog_time());
} else {
buf = realtime ? std::to_string(0.0) : std::to_string(0);
}
break;
}
}
std::string str;
assert(width == 0 || padding != '\0');
if (justify == RIGHT && buf.size() < width) {
size_t pad_width = width - buf.size();
if (padding == '0' && (buf.front() == '+' || buf.front() == '-')) {
str += buf.front();
buf.erase(0, 1);
}
str += std::string(pad_width, padding);
}
str += buf;
if (justify == LEFT && buf.size() < width)
str += std::string(width - buf.size(), padding);
return str;
}
};
// Tag class to disambiguate values/wires and their aliases. // Tag class to disambiguate values/wires and their aliases.
struct debug_alias {}; struct debug_alias {};
@ -1250,17 +1355,14 @@ struct module {
virtual void reset() = 0; virtual void reset() = 0;
virtual bool eval() = 0; virtual bool eval(performer *performer = nullptr) = 0;
virtual bool commit() = 0; virtual bool commit() = 0; // commit observer isn't available since it avoids virtual calls
unsigned int steps = 0; size_t step(performer *performer = nullptr) {
size_t step() {
++steps;
size_t deltas = 0; size_t deltas = 0;
bool converged = false; bool converged = false;
do { do {
converged = eval(); converged = eval(performer);
deltas++; deltas++;
} while (commit() && !converged); } while (commit() && !converged);
return deltas; return deltas;

View file

@ -0,0 +1,783 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2023 Catherine <whitequark@whitequark.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#ifndef CXXRTL_REPLAY_H
#define CXXRTL_REPLAY_H
#if !defined(WIN32)
#include <unistd.h>
#define O_BINARY 0
#else
#include <io.h>
#endif
#include <fcntl.h>
#include <cstring>
#include <cstdio>
#include <atomic>
#include <unordered_map>
#include <cxxrtl/cxxrtl.h>
#include <cxxrtl/cxxrtl_time.h>
// Theory of operation
// ===================
//
// Log format
// ----------
//
// The replay log is a simple data format based on a sequence of 32-bit words. The following BNF-like grammar describes
// enough detail to understand the overall structure of the log data and be able to read hex dumps. For a greater
// degree of detail see the source code. The format is considered fully internal to CXXRTL and is subject to change
// without notice.
//
// <file> ::= <file-header> <definitions> <sample>+
// <file-header> ::= 0x52585843 0x00004c54
// <definitions> ::= <packet-define>* <packet-end>
// <sample> ::= <packet-sample> <packet-change>* <packet-end>
// <packet-define> ::= 0xc0000000 ...
// <packet-sample> ::= 0xc0000001 ...
// <packet-change> ::= 0x0??????? <chunk>+ | 0x1??????? <index> <chunk>+ | 0x2??????? | 0x3???????
// <chunk>, <index> ::= 0x????????
// <packet-end> ::= 0xFFFFFFFF
//
// The replay log contains sample data, however, it does not cover the entire design. Rather, it only contains sample
// data for the subset of debug items containing _design state_: inputs and registers/latches. This keeps its size to
// a minimum, and recording speed to a maximum. The player samples any missing data by setting the design state items
// to the same values they had during recording, and re-evaluating the design.
//
// Limits
// ------
//
// The log may contain:
//
// * Up to 2**28-1 debug items containing design state.
// * Up to 2**32 chunks per debug item.
// * Up to 2**32 rows per memory.
// * Up to 2**32 samples.
//
// Of these limits, the last two are most likely to be eventually exceeded by practical recordings. However, other
// performance considerations will likely limit the size of such practical recordings first, so the log data format
// will undergo a breaking change at that point.
//
// Operations
// ----------
//
// As suggested by the name "replay log", this format is designed for recording (writing) once and playing (reading)
// many times afterwards, such that reading the format can be done linearly and quickly. The log format is designed to
// support three primary read operations:
//
// 1. Initialization
// 2. Rewinding (to time T)
// 3. Replaying (for N samples)
//
// During initialization, the player establishes the mapping between debug item names and their 28-bit identifiers in
// the log. It is done once.
//
// During rewinding, the player begins reading at the latest non-incremental sample that still lies before the requested
// sample time. It continues reading incremental samples after that point until it reaches the requested sample time.
// This process is very cheap as the design is not evaluated; it is essentially a (convoluted) memory copy operation.
//
// During replaying, the player evaluates the design at the current time, which causes all debug items to assume
// the values they had before recording. This process is expensive. Once done, the player advances to the next state
// by reading the next (complete or incremental) sample, as above. Since a range of samples is replayed, this process
// is repeated several times in a row.
//
// In principle, when replaying, the player could only read the state of the inputs and the time delta and use a normal
// eval/commit loop to progress the simulation, which is fully deterministic so its calculated design state should be
// exactly the same as the recorded design state. In practice, it is both faster and more reliable (in presence of e.g.
// user-defined black boxes) to read the recorded values instead of calculating them.
//
// Note: The operations described above are conceptual and do not correspond exactly to methods on `cxxrtl::player`.
// The `cxxrtl::player::replay()` method does not evaluate the design. This is so that delta cycles could be ignored
// if they are not of interest while replaying.
namespace cxxrtl {
// A spool stores CXXRTL design state changes in a file.
class spool {
public:
// Unique pointer to a specific sample within a replay log. (Timestamps are not unique.)
typedef uint32_t pointer_t;
// Numeric identifier assigned to a debug item within a replay log. Range limited to [1, MAXIMUM_IDENT].
typedef uint32_t ident_t;
static constexpr uint16_t VERSION = 0x0400;
static constexpr uint64_t HEADER_MAGIC = 0x00004c5452585843;
static constexpr uint64_t VERSION_MASK = 0xffff000000000000;
static constexpr uint32_t PACKET_DEFINE = 0xc0000000;
static constexpr uint32_t PACKET_SAMPLE = 0xc0000001;
enum sample_flag : uint32_t {
EMPTY = 0,
INCREMENTAL = 1,
};
static constexpr uint32_t MAXIMUM_IDENT = 0x0fffffff;
static constexpr uint32_t CHANGE_MASK = 0x30000000;
static constexpr uint32_t PACKET_CHANGE = 0x00000000/* | ident */;
static constexpr uint32_t PACKET_CHANGEI = 0x10000000/* | ident */;
static constexpr uint32_t PACKET_CHANGEL = 0x20000000/* | ident */;
static constexpr uint32_t PACKET_CHANGEH = 0x30000000/* | ident */;
static constexpr uint32_t PACKET_END = 0xffffffff;
// Writing spools.
class writer {
int fd;
size_t position;
std::vector<uint32_t> buffer;
// These functions aren't overloaded because of implicit numeric conversions.
void emit_word(uint32_t word) {
if (position + 1 == buffer.size())
flush();
buffer[position++] = word;
}
void emit_dword(uint64_t dword) {
emit_word(dword >> 0);
emit_word(dword >> 32);
}
void emit_ident(ident_t ident) {
assert(ident <= MAXIMUM_IDENT);
emit_word(ident);
}
void emit_size(size_t size) {
assert(size <= std::numeric_limits<uint32_t>::max());
emit_word(size);
}
// Same implementation as `emit_size()`, different declared intent.
void emit_index(size_t index) {
assert(index <= std::numeric_limits<uint32_t>::max());
emit_word(index);
}
void emit_string(std::string str) {
// Align to a word boundary, and add at least one terminating \0.
str.resize(str.size() + (sizeof(uint32_t) - (str.size() + sizeof(uint32_t)) % sizeof(uint32_t)));
for (size_t index = 0; index < str.size(); index += sizeof(uint32_t)) {
uint32_t word;
memcpy(&word, &str[index], sizeof(uint32_t));
emit_word(word);
}
}
void emit_time(const time &timestamp) {
const value<time::bits> &raw_timestamp(timestamp);
emit_word(raw_timestamp.data[0]);
emit_word(raw_timestamp.data[1]);
emit_word(raw_timestamp.data[2]);
}
public:
// Creates a writer, and transfers ownership of `fd`, which must be open for appending.
//
// The buffer size is currently fixed to a "reasonably large" size, determined empirically by measuring writer
// performance on a representative design; large but not so large it would e.g. cause address space exhaustion
// on 32-bit platforms.
writer(spool &spool) : fd(spool.take_write()), position(0), buffer(32 * 1024 * 1024) {
assert(fd != -1);
#if !defined(WIN32)
int result = ftruncate(fd, 0);
#else
int result = _chsize_s(fd, 0);
#endif
assert(result == 0);
}
writer(writer &&moved) : fd(moved.fd), position(moved.position), buffer(moved.buffer) {
moved.fd = -1;
moved.position = 0;
}
writer(const writer &) = delete;
writer &operator=(const writer &) = delete;
// Both write() calls and fwrite() calls are too expensive to perform implicitly. The API consumer must determine
// the optimal time to flush the writer and do that explicitly for best performance.
void flush() {
assert(fd != -1);
size_t data_size = position * sizeof(uint32_t);
size_t data_written = write(fd, buffer.data(), data_size);
assert(data_size == data_written);
position = 0;
}
~writer() {
if (fd != -1) {
flush();
close(fd);
}
}
void write_magic() {
// `CXXRTL` followed by version in binary. This header will read backwards on big-endian machines, which allows
// detection of this case, both visually and programmatically.
emit_dword(((uint64_t)VERSION << 48) | HEADER_MAGIC);
}
void write_define(ident_t ident, const std::string &name, size_t part_index, size_t chunks, size_t depth) {
emit_word(PACKET_DEFINE);
emit_ident(ident);
emit_string(name);
emit_index(part_index);
emit_size(chunks);
emit_size(depth);
}
void write_sample(bool incremental, pointer_t pointer, const time &timestamp) {
uint32_t flags = (incremental ? sample_flag::INCREMENTAL : 0);
emit_word(PACKET_SAMPLE);
emit_word(flags);
emit_word(pointer);
emit_time(timestamp);
}
void write_change(ident_t ident, size_t chunks, const chunk_t *data) {
assert(ident <= MAXIMUM_IDENT);
if (chunks == 1 && *data == 0) {
emit_word(PACKET_CHANGEL | ident);
} else if (chunks == 1 && *data == 1) {
emit_word(PACKET_CHANGEH | ident);
} else {
emit_word(PACKET_CHANGE | ident);
for (size_t offset = 0; offset < chunks; offset++)
emit_word(data[offset]);
}
}
void write_change(ident_t ident, size_t chunks, const chunk_t *data, size_t index) {
assert(ident <= MAXIMUM_IDENT);
emit_word(PACKET_CHANGEI | ident);
emit_index(index);
for (size_t offset = 0; offset < chunks; offset++)
emit_word(data[offset]);
}
void write_end() {
emit_word(PACKET_END);
}
};
// Reading spools.
class reader {
FILE *f;
uint32_t absorb_word() {
// If we're at end of file, `fread` will not write to `word`, and `PACKET_END` will be returned.
uint32_t word = PACKET_END;
fread(&word, sizeof(word), 1, f);
return word;
}
uint64_t absorb_dword() {
uint32_t lo = absorb_word();
uint32_t hi = absorb_word();
return ((uint64_t)hi << 32) | lo;
}
ident_t absorb_ident() {
ident_t ident = absorb_word();
assert(ident <= MAXIMUM_IDENT);
return ident;
}
size_t absorb_size() {
return absorb_word();
}
size_t absorb_index() {
return absorb_word();
}
std::string absorb_string() {
std::string str;
do {
size_t end = str.size();
str.resize(end + 4);
uint32_t word = absorb_word();
memcpy(&str[end], &word, sizeof(uint32_t));
} while (str.back() != '\0');
// Strings have no embedded zeroes besides the terminating one(s).
return str.substr(0, str.find('\0'));
}
time absorb_time() {
value<time::bits> raw_timestamp;
raw_timestamp.data[0] = absorb_word();
raw_timestamp.data[1] = absorb_word();
raw_timestamp.data[2] = absorb_word();
return time(raw_timestamp);
}
public:
typedef uint64_t pos_t;
// Creates a reader, and transfers ownership of `fd`, which must be open for reading.
reader(spool &spool) : f(fdopen(spool.take_read(), "r")) {
assert(f != nullptr);
}
reader(reader &&moved) : f(moved.f) {
moved.f = nullptr;
}
reader(const reader &) = delete;
reader &operator=(const reader &) = delete;
~reader() {
if (f != nullptr)
fclose(f);
}
pos_t position() {
return ftell(f);
}
void rewind(pos_t position) {
fseek(f, position, SEEK_SET);
}
void read_magic() {
uint64_t magic = absorb_dword();
assert((magic & ~VERSION_MASK) == HEADER_MAGIC);
assert((magic >> 48) == VERSION);
}
bool read_define(ident_t &ident, std::string &name, size_t &part_index, size_t &chunks, size_t &depth) {
uint32_t header = absorb_word();
if (header == PACKET_END)
return false;
assert(header == PACKET_DEFINE);
ident = absorb_ident();
name = absorb_string();
part_index = absorb_index();
chunks = absorb_size();
depth = absorb_size();
return true;
}
bool read_sample(bool &incremental, pointer_t &pointer, time &timestamp) {
uint32_t header = absorb_word();
if (header == PACKET_END)
return false;
assert(header == PACKET_SAMPLE);
uint32_t flags = absorb_word();
incremental = (flags & sample_flag::INCREMENTAL);
pointer = absorb_word();
timestamp = absorb_time();
return true;
}
bool read_change_header(uint32_t &header, ident_t &ident) {
header = absorb_word();
if (header == PACKET_END)
return false;
assert((header & ~(CHANGE_MASK | MAXIMUM_IDENT)) == 0);
ident = header & MAXIMUM_IDENT;
return true;
}
void read_change_data(uint32_t header, size_t chunks, size_t depth, chunk_t *data) {
uint32_t index = 0;
switch (header & CHANGE_MASK) {
case PACKET_CHANGEL:
*data = 0;
return;
case PACKET_CHANGEH:
*data = 1;
return;
case PACKET_CHANGE:
break;
case PACKET_CHANGEI:
index = absorb_word();
assert(index < depth);
break;
default:
assert(false && "Unrecognized change packet");
}
for (size_t offset = 0; offset < chunks; offset++)
data[chunks * index + offset] = absorb_word();
}
};
// Opening spools. For certain uses of the record/replay mechanism, two distinct open files (two open files, i.e.
// two distinct file pointers, and not just file descriptors, which share the file pointer if duplicated) are used,
// for a reader and writer thread. This class manages the lifetime of the descriptors for these files. When only
// one of them is used, the other is closed harmlessly when the spool is destroyed.
private:
std::atomic<int> writefd;
std::atomic<int> readfd;
public:
spool(const std::string &filename)
: writefd(open(filename.c_str(), O_CREAT|O_BINARY|O_WRONLY|O_APPEND, 0644)),
readfd(open(filename.c_str(), O_BINARY|O_RDONLY)) {
assert(writefd.load() != -1 && readfd.load() != -1);
}
spool(spool &&moved) : writefd(moved.writefd.exchange(-1)), readfd(moved.readfd.exchange(-1)) {}
spool(const spool &) = delete;
spool &operator=(const spool &) = delete;
~spool() {
if (int fd = writefd.exchange(-1))
close(fd);
if (int fd = readfd.exchange(-1))
close(fd);
}
// Atomically acquire a write file descriptor for the spool. Can be called once, and will return -1 the next time
// it is called. Thread-safe.
int take_write() {
return writefd.exchange(-1);
}
// Atomically acquire a read file descriptor for the spool. Can be called once, and will return -1 the next time
// it is called. Thread-safe.
int take_read() {
return readfd.exchange(-1);
}
};
// A CXXRTL recorder samples design state, producing complete or incremental updates, and writes them to a spool.
class recorder {
struct variable {
spool::ident_t ident; /* <= spool::MAXIMUM_IDENT */
size_t chunks;
size_t depth; /* == 1 for wires */
chunk_t *curr;
bool memory;
};
spool::writer writer;
std::vector<variable> variables;
std::vector<size_t> inputs; // values of inputs must be recorded explicitly, as their changes are not observed
std::unordered_map<const chunk_t*, spool::ident_t> ident_lookup;
bool streaming = false; // whether variable definitions have been written
spool::pointer_t pointer = 0;
time timestamp;
public:
template<typename ...Args>
recorder(Args &&...args) : writer(std::forward<Args>(args)...) {}
void start(module &module) {
debug_items items;
module.debug_info(items);
start(items);
}
void start(const debug_items &items) {
assert(!streaming);
writer.write_magic();
for (auto item : items.table)
for (size_t part_index = 0; part_index < item.second.size(); part_index++) {
auto &part = item.second[part_index];
if ((part.flags & debug_item::INPUT) || (part.flags & debug_item::DRIVEN_SYNC) ||
(part.type == debug_item::MEMORY)) {
variable var;
var.ident = variables.size() + 1;
var.chunks = (part.width + sizeof(chunk_t) * 8 - 1) / (sizeof(chunk_t) * 8);
var.depth = part.depth;
var.curr = part.curr;
var.memory = (part.type == debug_item::MEMORY);
ident_lookup[var.curr] = var.ident;
assert(variables.size() < spool::MAXIMUM_IDENT);
if (part.flags & debug_item::INPUT)
inputs.push_back(variables.size());
variables.push_back(var);
writer.write_define(var.ident, item.first, part_index, var.chunks, var.depth);
}
}
writer.write_end();
streaming = true;
}
const time &latest_time() {
return timestamp;
}
const time &advance_time(const time &delta) {
assert(!delta.is_negative());
timestamp += delta;
return timestamp;
}
void record_complete() {
assert(streaming);
writer.write_sample(/*incremental=*/false, pointer++, timestamp);
for (auto var : variables) {
assert(var.ident != 0);
if (!var.memory)
writer.write_change(var.ident, var.chunks, var.curr);
else
for (size_t index = 0; index < var.depth; index++)
writer.write_change(var.ident, var.chunks, &var.curr[var.chunks * index], index);
}
writer.write_end();
}
// This function is generic over ModuleT to encourage observer callbacks to be inlined into the commit function.
template<class ModuleT>
bool record_incremental(ModuleT &module) {
assert(streaming);
struct {
std::unordered_map<const chunk_t*, spool::ident_t> *ident_lookup;
spool::writer *writer;
CXXRTL_ALWAYS_INLINE
void on_update(size_t chunks, const chunk_t *base, const chunk_t *value) {
writer->write_change(ident_lookup->at(base), chunks, value);
}
CXXRTL_ALWAYS_INLINE
void on_update(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) {
writer->write_change(ident_lookup->at(base), chunks, value, index);
}
} record_observer = { &ident_lookup, &writer };
writer.write_sample(/*incremental=*/true, pointer++, timestamp);
for (auto input_index : inputs) {
variable &var = variables.at(input_index);
assert(!var.memory);
writer.write_change(var.ident, var.chunks, var.curr);
}
bool changed = module.commit(record_observer);
writer.write_end();
return changed;
}
void flush() {
writer.flush();
}
};
// A CXXRTL player reads samples from a spool, and changes the design state accordingly. To start reading samples,
// a spool must have been initialized: the recorder must have been started and an initial complete sample must have
// been written.
class player {
struct variable {
size_t chunks;
size_t depth; /* == 1 for wires */
chunk_t *curr;
};
spool::reader reader;
std::unordered_map<spool::ident_t, variable> variables;
bool streaming = false; // whether variable definitions have been read
bool initialized = false; // whether a sample has ever been read
spool::pointer_t pointer = 0;
time timestamp;
std::map<spool::pointer_t, spool::reader::pos_t, std::greater<spool::pointer_t>> index_by_pointer;
std::map<time, spool::reader::pos_t, std::greater<time>> index_by_timestamp;
bool peek_sample(spool::pointer_t &pointer, time &timestamp) {
bool incremental;
auto position = reader.position();
bool success = reader.read_sample(incremental, pointer, timestamp);
reader.rewind(position);
return success;
}
public:
template<typename ...Args>
player(Args &&...args) : reader(std::forward<Args>(args)...) {}
void start(module &module) {
debug_items items;
module.debug_info(items);
start(items);
}
void start(const debug_items &items) {
assert(!streaming);
reader.read_magic();
while (true) {
spool::ident_t ident;
std::string name;
size_t part_index;
size_t chunks;
size_t depth;
if (!reader.read_define(ident, name, part_index, chunks, depth))
break;
assert(variables.count(ident) == 0);
assert(items.count(name) != 0);
assert(part_index < items.count(name));
const debug_item &part = items.parts_at(name).at(part_index);
assert(chunks == (part.width + sizeof(chunk_t) * 8 - 1) / (sizeof(chunk_t) * 8));
assert(depth == part.depth);
variable &var = variables[ident];
var.chunks = chunks;
var.depth = depth;
var.curr = part.curr;
}
assert(variables.size() > 0);
streaming = true;
// Establish the initial state of the design.
initialized = replay();
assert(initialized);
}
// Returns the pointer of the current sample.
spool::pointer_t current_pointer() {
assert(initialized);
return pointer;
}
// Returns the time of the current sample.
const time &current_time() {
assert(initialized);
return timestamp;
}
// Returns `true` if there is a next sample to read, and sets `pointer` to its pointer if there is.
bool get_next_pointer(spool::pointer_t &pointer) {
assert(streaming);
time timestamp;
return peek_sample(pointer, timestamp);
}
// Returns `true` if there is a next sample to read, and sets `timestamp` to its time if there is.
bool get_next_time(time &timestamp) {
assert(streaming);
uint32_t pointer;
return peek_sample(pointer, timestamp);
}
// If this function returns `true`, then `current_pointer() == at_pointer`, and the module contains values that
// correspond to this pointer in the replay log. To obtain a valid pointer, call `current_pointer()`; while pointers
// are monotonically increasing for each consecutive sample, using arithmetic operations to create a new pointer is
// not allowed.
bool rewind_to(spool::pointer_t at_pointer) {
assert(initialized);
// The pointers in the replay log start from one that is greater than `at_pointer`. In this case the pointer will
// never be reached.
assert(index_by_pointer.size() > 0);
if (at_pointer < index_by_pointer.rbegin()->first)
return false;
// Find the last complete sample whose pointer is less than or equal to `at_pointer`. Note that the comparison
// function used here is `std::greater`, inverting the direction of `lower_bound`.
auto position_it = index_by_pointer.lower_bound(at_pointer);
assert(position_it != index_by_pointer.end());
reader.rewind(position_it->second);
// Replay samples until eventually arriving to `at_pointer` or encountering end of file.
while(replay()) {
if (pointer == at_pointer)
return true;
}
return false;
}
// If this function returns `true`, then `current_time() <= at_or_before_timestamp`, and the module contains values
// that correspond to `current_time()` in the replay log. If `current_time() == at_or_before_timestamp` and there
// are several consecutive samples with the same time, the module contains values that correspond to the first of
// these samples.
bool rewind_to_or_before(const time &at_or_before_timestamp) {
assert(initialized);
// The timestamps in the replay log start from one that is greater than `at_or_before_timestamp`. In this case
// the timestamp will never be reached. Otherwise, this function will always succeed.
assert(index_by_timestamp.size() > 0);
if (at_or_before_timestamp < index_by_timestamp.rbegin()->first)
return false;
// Find the last complete sample whose timestamp is less than or equal to `at_or_before_timestamp`. Note that
// the comparison function used here is `std::greater`, inverting the direction of `lower_bound`.
auto position_it = index_by_timestamp.lower_bound(at_or_before_timestamp);
assert(position_it != index_by_timestamp.end());
reader.rewind(position_it->second);
// Replay samples until eventually arriving to or past `at_or_before_timestamp` or encountering end of file.
while (replay()) {
if (timestamp == at_or_before_timestamp)
break;
time next_timestamp;
if (!get_next_time(next_timestamp))
break;
if (next_timestamp > at_or_before_timestamp)
break;
}
return true;
}
// If this function returns `true`, then `current_pointer()` and `current_time()` are updated for the next sample
// and the module now contains values that correspond to that sample. If it returns `false`, there was no next sample
// to read.
bool replay() {
assert(streaming);
bool incremental;
auto position = reader.position();
if (!reader.read_sample(incremental, pointer, timestamp))
return false;
// The very first sample that is read must be a complete sample. This is required for the rewind functions to work.
assert(initialized || !incremental);
// It is possible (though not very useful) to have several complete samples with the same timestamp in a row.
// Ensure that we associate the timestamp with the position of the first such complete sample. (This condition
// works because the player never jumps over a sample.)
if (!incremental && !index_by_pointer.count(pointer)) {
assert(!index_by_timestamp.count(timestamp));
index_by_pointer[pointer] = position;
index_by_timestamp[timestamp] = position;
}
uint32_t header;
spool::ident_t ident;
variable var;
while (reader.read_change_header(header, ident)) {
variable &var = variables.at(ident);
reader.read_change_data(header, var.chunks, var.depth, var.curr);
}
return true;
}
};
}
#endif

View file

@ -0,0 +1,231 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2023 Catherine <whitequark@whitequark.org>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#ifndef CXXRTL_TIME_H
#define CXXRTL_TIME_H
#include <cinttypes>
#include <string>
#include <cxxrtl/cxxrtl.h>
namespace cxxrtl {
// A timestamp or a difference in time, stored as a 96-bit number of femtoseconds (10e-15 s). The range and resolution
// of this format can represent any VCD timestamp within approx. ±1255321.2 years, without the need for a timescale.
class time {
public:
static constexpr size_t bits = 96; // 3 chunks
private:
static constexpr size_t resolution_digits = 15;
static_assert(sizeof(chunk_t) == 4, "a chunk is expected to be 32-bit");
static constexpr value<bits> resolution = value<bits> {
chunk_t(1000000000000000ull & 0xffffffffull), chunk_t(1000000000000000ull >> 32), 0u
};
// Signed number of femtoseconds from the beginning of time.
value<bits> raw;
public:
constexpr time() {}
explicit constexpr time(const value<bits> &raw) : raw(raw) {}
explicit operator const value<bits> &() const { return raw; }
static constexpr time maximum() {
return time(value<bits> { 0xffffffffu, 0xffffffffu, 0x7fffffffu });
}
time(int64_t secs, int64_t femtos) {
value<64> secs_val;
secs_val.set(secs);
value<64> femtos_val;
femtos_val.set(femtos);
raw = secs_val.sext<bits>().mul<bits>(resolution).add(femtos_val.sext<bits>());
}
bool is_zero() const {
return raw.is_zero();
}
// Extracts the sign of the value.
bool is_negative() const {
return raw.is_neg();
}
// Extracts the number of whole seconds. Negative if the value is negative.
int64_t secs() const {
return raw.sdivmod(resolution).first.trunc<64>().get<int64_t>();
}
// Extracts the number of femtoseconds in the fractional second. Negative if the value is negative.
int64_t femtos() const {
return raw.sdivmod(resolution).second.trunc<64>().get<int64_t>();
}
bool operator==(const time &other) const {
return raw == other.raw;
}
bool operator!=(const time &other) const {
return raw != other.raw;
}
bool operator>(const time &other) const {
return other.raw.scmp(raw);
}
bool operator>=(const time &other) const {
return !raw.scmp(other.raw);
}
bool operator<(const time &other) const {
return raw.scmp(other.raw);
}
bool operator<=(const time &other) const {
return !other.raw.scmp(raw);
}
time operator+(const time &other) const {
return time(raw.add(other.raw));
}
time &operator+=(const time &other) {
*this = *this + other;
return *this;
}
time operator-() const {
return time(raw.neg());
}
time operator-(const time &other) const {
return *this + (-other);
}
time &operator-=(const time &other) {
*this = *this - other;
return *this;
}
operator std::string() const {
char buf[48]; // x=2**95; len(f"-{x/1_000_000_000_000_000}.{x^1_000_000_000_000_000}") == 48
int64_t secs = this->secs();
int64_t femtos = this->femtos();
snprintf(buf, sizeof(buf), "%s%" PRIi64 ".%015" PRIi64,
is_negative() ? "-" : "", secs >= 0 ? secs : -secs, femtos >= 0 ? femtos : -femtos);
return buf;
}
#if __cplusplus >= 201603L
[[nodiscard("ignoring parse errors")]]
#endif
bool parse(const std::string &str) {
enum {
parse_sign_opt,
parse_integral,
parse_fractional,
} state = parse_sign_opt;
bool negative = false;
int64_t integral = 0;
int64_t fractional = 0;
size_t frac_digits = 0;
for (auto chr : str) {
switch (state) {
case parse_sign_opt:
state = parse_integral;
if (chr == '+' || chr == '-') {
negative = (chr == '-');
break;
}
/* fallthrough */
case parse_integral:
if (chr >= '0' && chr <= '9') {
integral *= 10;
integral += chr - '0';
} else if (chr == '.') {
state = parse_fractional;
} else {
return false;
}
break;
case parse_fractional:
if (chr >= '0' && chr <= '9' && frac_digits < resolution_digits) {
fractional *= 10;
fractional += chr - '0';
frac_digits++;
} else {
return false;
}
break;
}
}
if (frac_digits == 0)
return false;
while (frac_digits++ < resolution_digits)
fractional *= 10;
*this = negative ? -time { integral, fractional} : time { integral, fractional };
return true;
}
};
// Out-of-line definition required until C++17.
constexpr value<time::bits> time::resolution;
std::ostream &operator<<(std::ostream &os, const time &val) {
os << (std::string)val;
return os;
}
// These literals are (confusingly) compatible with the ones from `std::chrono`: the `std::chrono` literals do not
// have an underscore (e.g. 1ms) and the `cxxrtl::time` literals do (e.g. 1_ms). This syntactic difference is
// a requirement of the C++ standard. Despite being compatible the literals should not be mixed in the same namespace.
namespace time_literals {
time operator""_s(unsigned long long seconds) {
return time { (int64_t)seconds, 0 };
}
time operator""_ms(unsigned long long milliseconds) {
return time { 0, (int64_t)milliseconds * 1000000000000 };
}
time operator""_us(unsigned long long microseconds) {
return time { 0, (int64_t)microseconds * 1000000000 };
}
time operator""_ns(unsigned long long nanoseconds) {
return time { 0, (int64_t)nanoseconds * 1000000 };
}
time operator""_ps(unsigned long long picoseconds) {
return time { 0, (int64_t)picoseconds * 1000 };
}
time operator""_fs(unsigned long long femtoseconds) {
return time { 0, (int64_t)femtoseconds };
}
};
};
#endif

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -ex set -ex
cd ../../ cd ../../

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -ex set -ex
../../yosys -p 'synth -top test; write_simplec -verbose -i8 test00_uut.c' test00_uut.v ../../yosys -p 'synth -top test; write_simplec -verbose -i8 test00_uut.c' test00_uut.v
clang -o test00_tb test00_tb.c clang -o test00_tb test00_tb.c

View file

@ -1327,6 +1327,9 @@ def write_yw_trace(steps, index, allregs=False, filename=None):
sig = yw.add_sig(word_path, overlap_start, overlap_end - overlap_start, True) sig = yw.add_sig(word_path, overlap_start, overlap_end - overlap_start, True)
mem_init_values.append((sig, overlap_bits.replace("x", "?"))) mem_init_values.append((sig, overlap_bits.replace("x", "?")))
exprs = []
all_sigs = []
for i, k in enumerate(steps): for i, k in enumerate(steps):
step_values = WitnessValues() step_values = WitnessValues()
@ -1337,8 +1340,15 @@ def write_yw_trace(steps, index, allregs=False, filename=None):
else: else:
sigs = seqs sigs = seqs
exprs.extend(smt.witness_net_expr(topmod, f"s{k}", sig) for sig in sigs)
all_sigs.append(sigs)
bvs = iter(smt.get_list(exprs))
for sigs in all_sigs:
for sig in sigs: for sig in sigs:
value = smt.bv2bin(smt.get(smt.witness_net_expr(topmod, f"s{k}", sig))) value = smt.bv2bin(next(bvs))
step_values[sig["sig"]] = value step_values[sig["sig"]] = value
yw.step(step_values) yw.step(step_values)

View file

@ -194,9 +194,31 @@ class Incremental:
return "Bool" return "Bool"
def expr_smtlib(self, expr, smt_out):
self.expr_arg_len(expr, 2)
smtlib_expr = expr[1]
sort = expr[2]
if not isinstance(smtlib_expr, str):
raise InteractiveError(
"raw SMT-LIB expression has to be a string, "
f"got {json.dumps(smtlib_expr)}"
)
if not isinstance(sort, str):
raise InteractiveError(
f"raw SMT-LIB sort has to be a string, got {json.dumps(sort)}"
)
smt_out.append(smtlib_expr)
return sort
def expr_label(self, expr, smt_out): def expr_label(self, expr, smt_out):
if len(expr) != 3: if len(expr) != 3:
raise InteractiveError(f'expected ["!", label, sub_expr], got {expr!r}') raise InteractiveError(
f'expected ["!", label, sub_expr], got {json.dumps(expr)}'
)
label = expr[1] label = expr[1]
subexpr = expr[2] subexpr = expr[2]
@ -226,6 +248,7 @@ class Incremental:
"or": expr_andor, "or": expr_andor,
"=": expr_eq, "=": expr_eq,
"yw": expr_yw, "yw": expr_yw,
"smtlib": expr_smtlib,
"!": expr_label, "!": expr_label,
} }
@ -251,7 +274,8 @@ class Incremental:
) )
): ):
raise InteractiveError( raise InteractiveError(
f"required sort {json.dumps(required_sort)} found sort {json.dumps(sort)}" f"required sort {json.dumps(required_sort)} "
f"found sort {json.dumps(sort)}"
) )
return sort return sort
raise InteractiveError(f"unknown expression {json.dumps(expr[0])}") raise InteractiveError(f"unknown expression {json.dumps(expr[0])}")
@ -287,6 +311,14 @@ class Incremental:
def cmd_check(self, cmd): def cmd_check(self, cmd):
return smtbmc.smt_check_sat() return smtbmc.smt_check_sat()
def cmd_smtlib(self, cmd):
command = cmd.get("command")
if not isinstance(command, str):
raise InteractiveError(
f"raw SMT-LIB command must be a string, found {json.dumps(command)}"
)
smtbmc.smt.write(command)
def cmd_design_hierwitness(self, cmd=None): def cmd_design_hierwitness(self, cmd=None):
allregs = (cmd is None) or bool(cmd.get("allreges", False)) allregs = (cmd is None) or bool(cmd.get("allreges", False))
if self._cached_hierwitness[allregs] is not None: if self._cached_hierwitness[allregs] is not None:
@ -326,13 +358,17 @@ class Incremental:
map_steps = {i: int(j) for i, j in enumerate(steps)} map_steps = {i: int(j) for i, j in enumerate(steps)}
smtbmc.ywfile_constraints(path, constraints, map_steps=map_steps, skip_x=skip_x) last_step = smtbmc.ywfile_constraints(
path, constraints, map_steps=map_steps, skip_x=skip_x
)
self._yw_constraints[name] = { self._yw_constraints[name] = {
map_steps.get(i, i): [smtexpr for cexfile, smtexpr in constraint_list] map_steps.get(i, i): [smtexpr for cexfile, smtexpr in constraint_list]
for i, constraint_list in constraints.items() for i, constraint_list in constraints.items()
} }
return dict(last_step=last_step)
def cmd_ping(self, cmd): def cmd_ping(self, cmd):
return cmd return cmd
@ -344,6 +380,7 @@ class Incremental:
"push": cmd_push, "push": cmd_push,
"pop": cmd_pop, "pop": cmd_pop,
"check": cmd_check, "check": cmd_check,
"smtlib": cmd_smtlib,
"design_hierwitness": cmd_design_hierwitness, "design_hierwitness": cmd_design_hierwitness,
"write_yw_trace": cmd_write_yw_trace, "write_yw_trace": cmd_write_yw_trace,
"read_yw_trace": cmd_read_yw_trace, "read_yw_trace": cmd_read_yw_trace,

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -ex set -ex

View file

@ -183,7 +183,8 @@ This requires a Yosys witness AIGER map file as generated by 'write_aiger -ywmap
@click.argument("mapfile", type=click.File("r")) @click.argument("mapfile", type=click.File("r"))
@click.argument("output", type=click.File("w")) @click.argument("output", type=click.File("w"))
@click.option("--skip-x", help="Leave input x bits unassigned.", is_flag=True) @click.option("--skip-x", help="Leave input x bits unassigned.", is_flag=True)
def aiw2yw(input, mapfile, output, skip_x): @click.option("--present-only", help="Only include bits present in at least one time step.", is_flag=True)
def aiw2yw(input, mapfile, output, skip_x, present_only):
input_name = input.name input_name = input.name
click.echo(f"Converting AIGER witness trace {input_name!r} to Yosys witness trace {output.name!r}...") click.echo(f"Converting AIGER witness trace {input_name!r} to Yosys witness trace {output.name!r}...")
click.echo(f"Using Yosys witness AIGER map file {mapfile.name!r}") click.echo(f"Using Yosys witness AIGER map file {mapfile.name!r}")
@ -211,16 +212,23 @@ def aiw2yw(input, mapfile, output, skip_x):
if not re.match(r'[0]*$', ffline): if not re.match(r'[0]*$', ffline):
raise click.ClickException(f"{input_name}: non-default initial state not supported") raise click.ClickException(f"{input_name}: non-default initial state not supported")
outyw = WriteWitness(output, 'yosys-witness aiw2yw') if not present_only:
outyw = WriteWitness(output, "yosys-witness aiw2yw")
for clock in aiger_map.clocks: for clock in aiger_map.clocks:
outyw.add_clock(clock["path"], clock["offset"], clock["edge"]) outyw.add_clock(clock["path"], clock["offset"], clock["edge"])
for (path, offset), id in aiger_map.sigmap.bit_to_id.items(): for (path, offset), id in aiger_map.sigmap.bit_to_id.items():
outyw.add_sig(path, offset, init_only=id in aiger_map.init_inputs) outyw.add_sig(path, offset, init_only=id in aiger_map.init_inputs)
missing = set() missing = set()
seen = set()
buffered_steps = []
skip = "x?" if skip_x else "?"
t = -1
while True: while True:
inline = next(input, None) inline = next(input, None)
if inline is None: if inline is None:
@ -232,17 +240,20 @@ def aiw2yw(input, mapfile, output, skip_x):
if inline.startswith("#"): if inline.startswith("#"):
continue continue
if not re.match(r'[01x]*$', ffline): t += 1
if not re.match(r"[01x]*$", inline):
raise click.ClickException(f"{input_name}: unexpected data in AIGER witness file") raise click.ClickException(f"{input_name}: unexpected data in AIGER witness file")
if len(inline) != aiger_map.input_count: if len(inline) != aiger_map.input_count:
raise click.ClickException( raise click.ClickException(
f"{input_name}: {mapfile.name}: number of inputs does not match, " f"{input_name}: {mapfile.name}: number of inputs does not match, "
f"{len(inline)} in witness, {aiger_map.input_count} in map file") f"{len(inline)} in witness, {aiger_map.input_count} in map file"
)
values = WitnessValues() values = WitnessValues()
for i, v in enumerate(inline): for i, v in enumerate(inline):
if outyw.t > 0 and i in aiger_map.init_inputs: if v in skip or (t > 0 and i in aiger_map.init_inputs):
continue continue
try: try:
@ -250,11 +261,29 @@ def aiw2yw(input, mapfile, output, skip_x):
except IndexError: except IndexError:
bit = None bit = None
if bit is None: if bit is None:
missing.insert(i) missing.add(i)
elif present_only:
seen.add(i)
values[bit] = v values[bit] = v
outyw.step(values, skip_x=skip_x) if present_only:
buffered_steps.append(values)
else:
outyw.step(values)
if present_only:
outyw = WriteWitness(output, "yosys-witness aiw2yw")
for clock in aiger_map.clocks:
outyw.add_clock(clock["path"], clock["offset"], clock["edge"])
for (path, offset), id in aiger_map.sigmap.bit_to_id.items():
if id in seen:
outyw.add_sig(path, offset, init_only=id in aiger_map.init_inputs)
for values in buffered_steps:
outyw.step(values)
outyw.end_trace() outyw.end_trace()

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -ex set -ex

View file

@ -376,7 +376,7 @@ void dump_sigspec(std::ostream &f, const RTLIL::SigSpec &sig)
} }
} }
void dump_attributes(std::ostream &f, std::string indent, dict<RTLIL::IdString, RTLIL::Const> &attributes, char term = '\n', bool modattr = false, bool regattr = false, bool as_comment = false) void dump_attributes(std::ostream &f, std::string indent, dict<RTLIL::IdString, RTLIL::Const> &attributes, std::string term = "\n", bool modattr = false, bool regattr = false, bool as_comment = false)
{ {
if (noattr) if (noattr)
return; return;
@ -392,13 +392,13 @@ void dump_attributes(std::ostream &f, std::string indent, dict<RTLIL::IdString,
f << stringf(" 1 "); f << stringf(" 1 ");
else else
dump_const(f, it->second, -1, 0, false, as_comment); dump_const(f, it->second, -1, 0, false, as_comment);
f << stringf(" %s%c", as_comment ? "*/" : "*)", term); f << stringf(" %s%s", as_comment ? "*/" : "*)", term.c_str());
} }
} }
void dump_wire(std::ostream &f, std::string indent, RTLIL::Wire *wire) void dump_wire(std::ostream &f, std::string indent, RTLIL::Wire *wire)
{ {
dump_attributes(f, indent, wire->attributes, '\n', /*modattr=*/false, /*regattr=*/reg_wires.count(wire->name)); dump_attributes(f, indent, wire->attributes, "\n", /*modattr=*/false, /*regattr=*/reg_wires.count(wire->name));
#if 0 #if 0
if (wire->port_input && !wire->port_output) if (wire->port_input && !wire->port_output)
f << stringf("%s" "input %s", indent.c_str(), reg_wires.count(wire->name) ? "reg " : ""); f << stringf("%s" "input %s", indent.c_str(), reg_wires.count(wire->name) ? "reg " : "");
@ -989,7 +989,7 @@ void dump_cell_expr_uniop(std::ostream &f, std::string indent, RTLIL::Cell *cell
f << stringf("%s" "assign ", indent.c_str()); f << stringf("%s" "assign ", indent.c_str());
dump_sigspec(f, cell->getPort(ID::Y)); dump_sigspec(f, cell->getPort(ID::Y));
f << stringf(" = %s ", op.c_str()); f << stringf(" = %s ", op.c_str());
dump_attributes(f, "", cell->attributes, ' '); dump_attributes(f, "", cell->attributes, " ");
dump_cell_expr_port(f, cell, "A", true); dump_cell_expr_port(f, cell, "A", true);
f << stringf(";\n"); f << stringf(";\n");
} }
@ -1001,7 +1001,7 @@ void dump_cell_expr_binop(std::ostream &f, std::string indent, RTLIL::Cell *cell
f << stringf(" = "); f << stringf(" = ");
dump_cell_expr_port(f, cell, "A", true); dump_cell_expr_port(f, cell, "A", true);
f << stringf(" %s ", op.c_str()); f << stringf(" %s ", op.c_str());
dump_attributes(f, "", cell->attributes, ' '); dump_attributes(f, "", cell->attributes, " ");
dump_cell_expr_port(f, cell, "B", true); dump_cell_expr_port(f, cell, "B", true);
f << stringf(";\n"); f << stringf(";\n");
} }
@ -1048,7 +1048,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell)
dump_sigspec(f, cell->getPort(ID::Y)); dump_sigspec(f, cell->getPort(ID::Y));
f << stringf(" = "); f << stringf(" = ");
f << stringf("~"); f << stringf("~");
dump_attributes(f, "", cell->attributes, ' '); dump_attributes(f, "", cell->attributes, " ");
dump_cell_expr_port(f, cell, "A", false); dump_cell_expr_port(f, cell, "A", false);
f << stringf(";\n"); f << stringf(";\n");
return true; return true;
@ -1068,7 +1068,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell)
f << stringf("|"); f << stringf("|");
if (cell->type.in(ID($_XOR_), ID($_XNOR_))) if (cell->type.in(ID($_XOR_), ID($_XNOR_)))
f << stringf("^"); f << stringf("^");
dump_attributes(f, "", cell->attributes, ' '); dump_attributes(f, "", cell->attributes, " ");
f << stringf(" "); f << stringf(" ");
if (cell->type.in(ID($_ANDNOT_), ID($_ORNOT_))) if (cell->type.in(ID($_ANDNOT_), ID($_ORNOT_)))
f << stringf("~("); f << stringf("~(");
@ -1085,7 +1085,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell)
f << stringf(" = "); f << stringf(" = ");
dump_cell_expr_port(f, cell, "S", false); dump_cell_expr_port(f, cell, "S", false);
f << stringf(" ? "); f << stringf(" ? ");
dump_attributes(f, "", cell->attributes, ' '); dump_attributes(f, "", cell->attributes, " ");
dump_cell_expr_port(f, cell, "B", false); dump_cell_expr_port(f, cell, "B", false);
f << stringf(" : "); f << stringf(" : ");
dump_cell_expr_port(f, cell, "A", false); dump_cell_expr_port(f, cell, "A", false);
@ -1099,7 +1099,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell)
f << stringf(" = !("); f << stringf(" = !(");
dump_cell_expr_port(f, cell, "S", false); dump_cell_expr_port(f, cell, "S", false);
f << stringf(" ? "); f << stringf(" ? ");
dump_attributes(f, "", cell->attributes, ' '); dump_attributes(f, "", cell->attributes, " ");
dump_cell_expr_port(f, cell, "B", false); dump_cell_expr_port(f, cell, "B", false);
f << stringf(" : "); f << stringf(" : ");
dump_cell_expr_port(f, cell, "A", false); dump_cell_expr_port(f, cell, "A", false);
@ -1115,7 +1115,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell)
f << stringf(cell->type == ID($_AOI3_) ? " & " : " | "); f << stringf(cell->type == ID($_AOI3_) ? " & " : " | ");
dump_cell_expr_port(f, cell, "B", false); dump_cell_expr_port(f, cell, "B", false);
f << stringf(cell->type == ID($_AOI3_) ? ") |" : ") &"); f << stringf(cell->type == ID($_AOI3_) ? ") |" : ") &");
dump_attributes(f, "", cell->attributes, ' '); dump_attributes(f, "", cell->attributes, " ");
f << stringf(" "); f << stringf(" ");
dump_cell_expr_port(f, cell, "C", false); dump_cell_expr_port(f, cell, "C", false);
f << stringf(");\n"); f << stringf(");\n");
@ -1130,7 +1130,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell)
f << stringf(cell->type == ID($_AOI4_) ? " & " : " | "); f << stringf(cell->type == ID($_AOI4_) ? " & " : " | ");
dump_cell_expr_port(f, cell, "B", false); dump_cell_expr_port(f, cell, "B", false);
f << stringf(cell->type == ID($_AOI4_) ? ") |" : ") &"); f << stringf(cell->type == ID($_AOI4_) ? ") |" : ") &");
dump_attributes(f, "", cell->attributes, ' '); dump_attributes(f, "", cell->attributes, " ");
f << stringf(" ("); f << stringf(" (");
dump_cell_expr_port(f, cell, "C", false); dump_cell_expr_port(f, cell, "C", false);
f << stringf(cell->type == ID($_AOI4_) ? " & " : " | "); f << stringf(cell->type == ID($_AOI4_) ? " & " : " | ");
@ -1232,7 +1232,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell)
f << stringf("%s" "assign ", indent.c_str()); f << stringf("%s" "assign ", indent.c_str());
dump_sigspec(f, cell->getPort(ID::Y)); dump_sigspec(f, cell->getPort(ID::Y));
f << stringf(" = $signed(%s) / ", buf_num.c_str()); f << stringf(" = $signed(%s) / ", buf_num.c_str());
dump_attributes(f, "", cell->attributes, ' '); dump_attributes(f, "", cell->attributes, " ");
f << stringf("$signed(%s);\n", buf_b.c_str()); f << stringf("$signed(%s);\n", buf_b.c_str());
return true; return true;
} else { } else {
@ -1255,7 +1255,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell)
f << stringf("%s" "wire [%d:0] %s = ", indent.c_str(), GetSize(cell->getPort(ID::A))-1, temp_id.c_str()); f << stringf("%s" "wire [%d:0] %s = ", indent.c_str(), GetSize(cell->getPort(ID::A))-1, temp_id.c_str());
dump_cell_expr_port(f, cell, "A", true); dump_cell_expr_port(f, cell, "A", true);
f << stringf(" %% "); f << stringf(" %% ");
dump_attributes(f, "", cell->attributes, ' '); dump_attributes(f, "", cell->attributes, " ");
dump_cell_expr_port(f, cell, "B", true); dump_cell_expr_port(f, cell, "B", true);
f << stringf(";\n"); f << stringf(";\n");
@ -1330,7 +1330,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell)
f << stringf(" = "); f << stringf(" = ");
dump_sigspec(f, cell->getPort(ID::S)); dump_sigspec(f, cell->getPort(ID::S));
f << stringf(" ? "); f << stringf(" ? ");
dump_attributes(f, "", cell->attributes, ' '); dump_attributes(f, "", cell->attributes, " ");
dump_sigspec(f, cell->getPort(ID::B)); dump_sigspec(f, cell->getPort(ID::B));
f << stringf(" : "); f << stringf(" : ");
dump_sigspec(f, cell->getPort(ID::A)); dump_sigspec(f, cell->getPort(ID::A));
@ -1439,7 +1439,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell)
f << stringf(" = "); f << stringf(" = ");
dump_const(f, cell->parameters.at(ID::LUT)); dump_const(f, cell->parameters.at(ID::LUT));
f << stringf(" >> "); f << stringf(" >> ");
dump_attributes(f, "", cell->attributes, ' '); dump_attributes(f, "", cell->attributes, " ");
dump_sigspec(f, cell->getPort(ID::A)); dump_sigspec(f, cell->getPort(ID::A));
f << stringf(";\n"); f << stringf(";\n");
return true; return true;
@ -1830,7 +1830,8 @@ void dump_cell(std::ostream &f, std::string indent, RTLIL::Cell *cell)
if (it != cell->parameters.begin()) if (it != cell->parameters.begin())
f << stringf(","); f << stringf(",");
f << stringf("\n%s .%s(", indent.c_str(), id(it->first).c_str()); f << stringf("\n%s .%s(", indent.c_str(), id(it->first).c_str());
dump_const(f, it->second); if (it->second.size() > 0)
dump_const(f, it->second);
f << stringf(")"); f << stringf(")");
} }
f << stringf("\n%s" ")", indent.c_str()); f << stringf("\n%s" ")", indent.c_str());
@ -1895,17 +1896,21 @@ void dump_cell(std::ostream &f, std::string indent, RTLIL::Cell *cell)
void dump_sync_print(std::ostream &f, std::string indent, const RTLIL::SigSpec &trg, const RTLIL::Const &polarity, std::vector<const RTLIL::Cell*> &cells) void dump_sync_print(std::ostream &f, std::string indent, const RTLIL::SigSpec &trg, const RTLIL::Const &polarity, std::vector<const RTLIL::Cell*> &cells)
{ {
f << stringf("%s" "always @(", indent.c_str()); if (trg.size() == 0) {
for (int i = 0; i < trg.size(); i++) { f << stringf("%s" "initial begin\n", indent.c_str());
if (i != 0) } else {
f << " or "; f << stringf("%s" "always @(", indent.c_str());
if (polarity[i]) for (int i = 0; i < trg.size(); i++) {
f << "posedge "; if (i != 0)
else f << " or ";
f << "negedge "; if (polarity[i])
dump_sigspec(f, trg[i]); f << "posedge ";
else
f << "negedge ";
dump_sigspec(f, trg[i]);
}
f << ") begin\n";
} }
f << ") begin\n";
std::sort(cells.begin(), cells.end(), [](const RTLIL::Cell *a, const RTLIL::Cell *b) { std::sort(cells.begin(), cells.end(), [](const RTLIL::Cell *a, const RTLIL::Cell *b) {
return a->getParam(ID::PRIORITY).as_int() > b->getParam(ID::PRIORITY).as_int(); return a->getParam(ID::PRIORITY).as_int() > b->getParam(ID::PRIORITY).as_int();
@ -1971,6 +1976,56 @@ void dump_case_body(std::ostream &f, std::string indent, RTLIL::CaseRule *cs, bo
f << stringf("%s" "end\n", indent.c_str()); f << stringf("%s" "end\n", indent.c_str());
} }
bool dump_proc_switch_ifelse(std::ostream &f, std::string indent, RTLIL::SwitchRule *sw)
{
for (auto it = sw->cases.begin(); it != sw->cases.end(); ++it) {
if ((*it)->compare.size() == 0) {
break;
} else if ((*it)->compare.size() == 1) {
int case_index = it - sw->cases.begin();
SigSpec compare = (*it)->compare.at(0);
if (case_index >= compare.size())
return false;
if (compare[case_index] != State::S1)
return false;
for (int bit_index = 0; bit_index < compare.size(); bit_index++)
if (bit_index != case_index && compare[bit_index] != State::Sa)
return false;
} else {
return false;
}
}
f << indent;
auto sig_it = sw->signal.begin();
for (auto it = sw->cases.begin(); it != sw->cases.end(); ++it, ++sig_it) {
bool had_newline = true;
if (it != sw->cases.begin()) {
if ((*it)->compare.empty()) {
f << indent << "else\n";
had_newline = true;
} else {
f << indent << "else ";
had_newline = false;
}
}
if (!(*it)->compare.empty()) {
if (!(*it)->attributes.empty()) {
if (!had_newline)
f << "\n" << indent;
dump_attributes(f, "", (*it)->attributes, "\n" + indent);
}
f << stringf("if (");
dump_sigspec(f, *sig_it);
f << stringf(")\n");
}
dump_case_body(f, indent, *it);
if ((*it)->compare.empty())
break;
}
return true;
}
void dump_proc_switch(std::ostream &f, std::string indent, RTLIL::SwitchRule *sw) void dump_proc_switch(std::ostream &f, std::string indent, RTLIL::SwitchRule *sw)
{ {
if (sw->signal.size() == 0) { if (sw->signal.size() == 0) {
@ -1983,17 +2038,18 @@ void dump_proc_switch(std::ostream &f, std::string indent, RTLIL::SwitchRule *sw
return; return;
} }
if (dump_proc_switch_ifelse(f, indent, sw))
return;
dump_attributes(f, indent, sw->attributes); dump_attributes(f, indent, sw->attributes);
f << stringf("%s" "casez (", indent.c_str()); f << stringf("%s" "casez (", indent.c_str());
dump_sigspec(f, sw->signal); dump_sigspec(f, sw->signal);
f << stringf(")\n"); f << stringf(")\n");
bool got_default = false;
for (auto it = sw->cases.begin(); it != sw->cases.end(); ++it) { for (auto it = sw->cases.begin(); it != sw->cases.end(); ++it) {
dump_attributes(f, indent + " ", (*it)->attributes, '\n', /*modattr=*/false, /*regattr=*/false, /*as_comment=*/true); bool got_default = false;
dump_attributes(f, indent + " ", (*it)->attributes, "\n", /*modattr=*/false, /*regattr=*/false, /*as_comment=*/true);
if ((*it)->compare.size() == 0) { if ((*it)->compare.size() == 0) {
if (got_default)
continue;
f << stringf("%s default", indent.c_str()); f << stringf("%s default", indent.c_str());
got_default = true; got_default = true;
} else { } else {
@ -2006,6 +2062,14 @@ void dump_proc_switch(std::ostream &f, std::string indent, RTLIL::SwitchRule *sw
} }
f << stringf(":\n"); f << stringf(":\n");
dump_case_body(f, indent + " ", *it); dump_case_body(f, indent + " ", *it);
if (got_default) {
// If we followed up the default with more cases the Verilog
// semantics would be to match those *before* the default, but
// the RTLIL semantics are to match those *after* the default
// (so they can never be selected). Exit now.
break;
}
} }
if (sw->cases.empty()) { if (sw->cases.empty()) {
@ -2167,7 +2231,7 @@ void dump_module(std::ostream &f, std::string indent, RTLIL::Module *module)
} }
} }
dump_attributes(f, indent, module->attributes, '\n', /*modattr=*/true); dump_attributes(f, indent, module->attributes, "\n", /*modattr=*/true);
f << stringf("%s" "module %s(", indent.c_str(), id(module->name, false).c_str()); f << stringf("%s" "module %s(", indent.c_str(), id(module->name, false).c_str());
bool keep_running = true; bool keep_running = true;
int cnt = 0; int cnt = 0;

View file

@ -127,8 +127,6 @@ Our ``addr_gen`` circuit now looks like this:
``addr_gen`` module after :cmd:ref:`hierarchy` ``addr_gen`` module after :cmd:ref:`hierarchy`
.. TODO:: pending https://github.com/YosysHQ/yosys/pull/4133
Simple operations like ``addr + 1`` and ``addr == MAX_DATA-1`` can be extracted Simple operations like ``addr + 1`` and ``addr == MAX_DATA-1`` can be extracted
from our ``always @`` block in :ref:`addr_gen-v`. This gives us the highlighted from our ``always @`` block in :ref:`addr_gen-v`. This gives us the highlighted
``$add`` and ``$eq`` cells we see. But control logic (like the ``if .. else``) ``$add`` and ``$eq`` cells we see. But control logic (like the ``if .. else``)

View file

@ -122,7 +122,7 @@ All binary RTL cells have two input ports ``\A`` and ``\B`` and one output port
:verilog:`Y = A >>> B` $sshr :verilog:`Y = A - B` $sub :verilog:`Y = A >>> B` $sshr :verilog:`Y = A - B` $sub
:verilog:`Y = A && B` $logic_and :verilog:`Y = A * B` $mul :verilog:`Y = A && B` $logic_and :verilog:`Y = A * B` $mul
:verilog:`Y = A || B` $logic_or :verilog:`Y = A / B` $div :verilog:`Y = A || B` $logic_or :verilog:`Y = A / B` $div
:verilog:`Y = A === B` $eqx :verilog:`Y = A % B` $mod :verilog:`Y = A === B` $eqx :verilog:`Y = A % B` $mod
:verilog:`Y = A !== B` $nex ``N/A`` $divfloor :verilog:`Y = A !== B` $nex ``N/A`` $divfloor
:verilog:`Y = A ** B` $pow ``N/A`` $modfoor :verilog:`Y = A ** B` $pow ``N/A`` $modfoor
======================= ============= ======================= ========= ======================= ============= ======================= =========
@ -664,6 +664,8 @@ Ports:
``\TRG`` ``\TRG``
The signals that control when this ``$print`` cell is triggered. The signals that control when this ``$print`` cell is triggered.
If the width of this port is zero and ``\TRG_ENABLE`` is true, the cell is
triggered during initial evaluation (time zero) only.
``\EN`` ``\EN``
Enable signal for the whole cell. Enable signal for the whole cell.

View file

@ -45,7 +45,7 @@ namespace AST {
// instantiate global variables (private API) // instantiate global variables (private API)
namespace AST_INTERNAL { namespace AST_INTERNAL {
bool flag_dump_ast1, flag_dump_ast2, flag_no_dump_ptr, flag_dump_vlog1, flag_dump_vlog2, flag_dump_rtlil, flag_nolatches, flag_nomeminit; bool flag_nodisplay, flag_dump_ast1, flag_dump_ast2, flag_no_dump_ptr, flag_dump_vlog1, flag_dump_vlog2, flag_dump_rtlil, flag_nolatches, flag_nomeminit;
bool flag_nomem2reg, flag_mem2reg, flag_noblackbox, flag_lib, flag_nowb, flag_noopt, flag_icells, flag_pwires, flag_autowire; bool flag_nomem2reg, flag_mem2reg, flag_noblackbox, flag_lib, flag_nowb, flag_noopt, flag_icells, flag_pwires, flag_autowire;
AstNode *current_ast, *current_ast_mod; AstNode *current_ast, *current_ast_mod;
std::map<std::string, AstNode*> current_scope; std::map<std::string, AstNode*> current_scope;
@ -850,6 +850,25 @@ AstNode *AstNode::mkconst_str(const std::string &str)
return node; return node;
} }
// create a temporary register
AstNode *AstNode::mktemp_logic(const std::string &name, AstNode *mod, bool nosync, int range_left, int range_right, bool is_signed)
{
AstNode *wire = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(range_left, true), mkconst_int(range_right, true)));
wire->str = stringf("%s%s:%d$%d", name.c_str(), RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++);
if (nosync)
wire->set_attribute(ID::nosync, AstNode::mkconst_int(1, false));
wire->is_signed = is_signed;
wire->is_logic = true;
mod->children.push_back(wire);
while (wire->simplify(true, 1, -1, false)) { }
AstNode *ident = new AstNode(AST_IDENTIFIER);
ident->str = wire->str;
ident->id2ast = wire;
return ident;
}
bool AstNode::bits_only_01() const bool AstNode::bits_only_01() const
{ {
for (auto bit : bits) for (auto bit : bits)
@ -1301,11 +1320,12 @@ static void rename_in_package_stmts(AstNode *pkg)
} }
// create AstModule instances for all modules in the AST tree and add them to 'design' // create AstModule instances for all modules in the AST tree and add them to 'design'
void AST::process(RTLIL::Design *design, AstNode *ast, bool dump_ast1, bool dump_ast2, bool no_dump_ptr, bool dump_vlog1, bool dump_vlog2, bool dump_rtlil, void AST::process(RTLIL::Design *design, AstNode *ast, bool nodisplay, bool dump_ast1, bool dump_ast2, bool no_dump_ptr, bool dump_vlog1, bool dump_vlog2, bool dump_rtlil,
bool nolatches, bool nomeminit, bool nomem2reg, bool mem2reg, bool noblackbox, bool lib, bool nowb, bool noopt, bool icells, bool pwires, bool nooverwrite, bool overwrite, bool defer, bool autowire) bool nolatches, bool nomeminit, bool nomem2reg, bool mem2reg, bool noblackbox, bool lib, bool nowb, bool noopt, bool icells, bool pwires, bool nooverwrite, bool overwrite, bool defer, bool autowire)
{ {
current_ast = ast; current_ast = ast;
current_ast_mod = nullptr; current_ast_mod = nullptr;
flag_nodisplay = nodisplay;
flag_dump_ast1 = dump_ast1; flag_dump_ast1 = dump_ast1;
flag_dump_ast2 = dump_ast2; flag_dump_ast2 = dump_ast2;
flag_no_dump_ptr = no_dump_ptr; flag_no_dump_ptr = no_dump_ptr;

View file

@ -287,7 +287,7 @@ namespace AST
bool is_simple_const_expr(); bool is_simple_const_expr();
// helper for parsing format strings // helper for parsing format strings
Fmt processFormat(int stage, bool sformat_like, int default_base = 10, size_t first_arg_at = 0); Fmt processFormat(int stage, bool sformat_like, int default_base = 10, size_t first_arg_at = 0, bool may_fail = false);
bool is_recursive_function() const; bool is_recursive_function() const;
std::pair<AstNode*, AstNode*> get_tern_choice(); std::pair<AstNode*, AstNode*> get_tern_choice();
@ -321,6 +321,9 @@ namespace AST
static AstNode *mkconst_str(const std::vector<RTLIL::State> &v); static AstNode *mkconst_str(const std::vector<RTLIL::State> &v);
static AstNode *mkconst_str(const std::string &str); static AstNode *mkconst_str(const std::string &str);
// helper function to create an AST node for a temporary register
AstNode *mktemp_logic(const std::string &name, AstNode *mod, bool nosync, int range_left, int range_right, bool is_signed);
// helper function for creating sign-extended const objects // helper function for creating sign-extended const objects
RTLIL::Const bitsAsConst(int width, bool is_signed); RTLIL::Const bitsAsConst(int width, bool is_signed);
RTLIL::Const bitsAsConst(int width = -1); RTLIL::Const bitsAsConst(int width = -1);
@ -373,7 +376,7 @@ namespace AST
}; };
// process an AST tree (ast must point to an AST_DESIGN node) and generate RTLIL code // process an AST tree (ast must point to an AST_DESIGN node) and generate RTLIL code
void process(RTLIL::Design *design, AstNode *ast, bool dump_ast1, bool dump_ast2, bool no_dump_ptr, bool dump_vlog1, bool dump_vlog2, bool dump_rtlil, bool nolatches, bool nomeminit, void process(RTLIL::Design *design, AstNode *ast, bool nodisplay, bool dump_ast1, bool dump_ast2, bool no_dump_ptr, bool dump_vlog1, bool dump_vlog2, bool dump_rtlil, bool nolatches, bool nomeminit,
bool nomem2reg, bool mem2reg, bool noblackbox, bool lib, bool nowb, bool noopt, bool icells, bool pwires, bool nooverwrite, bool overwrite, bool defer, bool autowire); bool nomem2reg, bool mem2reg, bool noblackbox, bool lib, bool nowb, bool noopt, bool icells, bool pwires, bool nooverwrite, bool overwrite, bool defer, bool autowire);
// parametric modules are supported directly by the AST library // parametric modules are supported directly by the AST library
@ -429,7 +432,7 @@ namespace AST
namespace AST_INTERNAL namespace AST_INTERNAL
{ {
// internal state variables // internal state variables
extern bool flag_dump_ast1, flag_dump_ast2, flag_no_dump_ptr, flag_dump_rtlil, flag_nolatches, flag_nomeminit; extern bool flag_nodisplay, flag_dump_ast1, flag_dump_ast2, flag_no_dump_ptr, flag_dump_rtlil, flag_nolatches, flag_nomeminit;
extern bool flag_nomem2reg, flag_mem2reg, flag_lib, flag_noopt, flag_icells, flag_pwires, flag_autowire; extern bool flag_nomem2reg, flag_mem2reg, flag_lib, flag_noopt, flag_icells, flag_pwires, flag_autowire;
extern AST::AstNode *current_ast, *current_ast_mod; extern AST::AstNode *current_ast, *current_ast_mod;
extern std::map<std::string, AST::AstNode*> current_scope; extern std::map<std::string, AST::AstNode*> current_scope;

View file

@ -718,7 +718,7 @@ struct AST_INTERNAL::ProcessGenerator
} }
} }
cell->parameters[ID::TRG_WIDTH] = triggers.size(); cell->parameters[ID::TRG_WIDTH] = triggers.size();
cell->parameters[ID::TRG_ENABLE] = !triggers.empty(); cell->parameters[ID::TRG_ENABLE] = (always->type == AST_INITIAL) || !triggers.empty();
cell->parameters[ID::TRG_POLARITY] = polarity; cell->parameters[ID::TRG_POLARITY] = polarity;
cell->parameters[ID::PRIORITY] = --last_print_priority; cell->parameters[ID::PRIORITY] = --last_print_priority;
cell->setPort(ID::TRG, triggers); cell->setPort(ID::TRG, triggers);

View file

@ -35,12 +35,25 @@
#include <stdarg.h> #include <stdarg.h>
#include <stdlib.h> #include <stdlib.h>
#include <math.h> #include <math.h>
// For std::gcd in C++17
// #include <numeric>
YOSYS_NAMESPACE_BEGIN YOSYS_NAMESPACE_BEGIN
using namespace AST; using namespace AST;
using namespace AST_INTERNAL; using namespace AST_INTERNAL;
// gcd computed by Euclidian division.
// To be replaced by C++17 std::gcd
template<class I> I gcd(I a, I b) {
while (b != 0) {
I tmp = b;
b = a%b;
a = tmp;
}
return std::abs(a);
}
void AstNode::set_in_lvalue_flag(bool flag, bool no_descend) void AstNode::set_in_lvalue_flag(bool flag, bool no_descend)
{ {
if (flag != in_lvalue_from_above) { if (flag != in_lvalue_from_above) {
@ -132,7 +145,7 @@ void AstNode::fixup_hierarchy_flags(bool force_descend)
// Process a format string and arguments for $display, $write, $sprintf, etc // Process a format string and arguments for $display, $write, $sprintf, etc
Fmt AstNode::processFormat(int stage, bool sformat_like, int default_base, size_t first_arg_at) { Fmt AstNode::processFormat(int stage, bool sformat_like, int default_base, size_t first_arg_at, bool may_fail) {
std::vector<VerilogFmtArg> args; std::vector<VerilogFmtArg> args;
for (size_t index = first_arg_at; index < children.size(); index++) { for (size_t index = first_arg_at; index < children.size(); index++) {
AstNode *node_arg = children[index]; AstNode *node_arg = children[index];
@ -156,6 +169,9 @@ Fmt AstNode::processFormat(int stage, bool sformat_like, int default_base, size_
arg.type = VerilogFmtArg::INTEGER; arg.type = VerilogFmtArg::INTEGER;
arg.sig = node_arg->bitsAsConst(); arg.sig = node_arg->bitsAsConst();
arg.signed_ = node_arg->is_signed; arg.signed_ = node_arg->is_signed;
} else if (may_fail) {
log_file_info(filename, location.first_line, "Skipping system task `%s' with non-constant argument at position %zu.\n", str.c_str(), index + 1);
return Fmt();
} else { } else {
log_file_error(filename, location.first_line, "Failed to evaluate system task `%s' with non-constant argument at position %zu.\n", str.c_str(), index + 1); log_file_error(filename, location.first_line, "Failed to evaluate system task `%s' with non-constant argument at position %zu.\n", str.c_str(), index + 1);
} }
@ -204,8 +220,8 @@ void AstNode::annotateTypedEnums(AstNode *template_node)
log_assert(enum_item->children[1]->type == AST_RANGE); log_assert(enum_item->children[1]->type == AST_RANGE);
is_signed = enum_item->children[1]->is_signed; is_signed = enum_item->children[1]->is_signed;
} else { } else {
log_error("enum_item children size==%lu, expected 1 or 2 for %s (%s)\n", log_error("enum_item children size==%zu, expected 1 or 2 for %s (%s)\n",
enum_item->children.size(), (size_t) enum_item->children.size(),
enum_item->str.c_str(), enum_node->str.c_str() enum_item->str.c_str(), enum_node->str.c_str()
); );
} }
@ -226,17 +242,6 @@ void AstNode::annotateTypedEnums(AstNode *template_node)
} }
} }
static bool name_has_dot(const std::string &name, std::string &struct_name)
{
// check if plausible struct member name \sss.mmm
std::string::size_type pos;
if (name.substr(0, 1) == "\\" && (pos = name.find('.', 0)) != std::string::npos) {
struct_name = name.substr(0, pos);
return true;
}
return false;
}
static AstNode *make_range(int left, int right, bool is_signed = false) static AstNode *make_range(int left, int right, bool is_signed = false)
{ {
// generate a pre-validated range node for a fixed signal range. // generate a pre-validated range node for a fixed signal range.
@ -1053,30 +1058,31 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin
{ {
if (!current_always) { if (!current_always) {
log_file_warning(filename, location.first_line, "System task `%s' outside initial or always block is unsupported.\n", str.c_str()); log_file_warning(filename, location.first_line, "System task `%s' outside initial or always block is unsupported.\n", str.c_str());
} else if (current_always->type == AST_INITIAL) { delete_children();
int default_base = 10; str = std::string();
if (str.back() == 'b')
default_base = 2;
else if (str.back() == 'o')
default_base = 8;
else if (str.back() == 'h')
default_base = 16;
// when $display()/$write() functions are used in an initial block, print them during synthesis
Fmt fmt = processFormat(stage, /*sformat_like=*/false, default_base);
if (str.substr(0, 8) == "$display")
fmt.append_string("\n");
log("%s", fmt.render().c_str());
} else { } else {
// when $display()/$write() functions are used in an always block, simplify the expressions and // simplify the expressions and convert them to a special cell later in genrtlil
// convert them to a special cell later in genrtlil
for (auto node : children) for (auto node : children)
while (node->simplify(true, stage, -1, false)) {} while (node->simplify(true, stage, -1, false)) {}
if (current_always->type == AST_INITIAL && !flag_nodisplay && stage == 2) {
int default_base = 10;
if (str.back() == 'b')
default_base = 2;
else if (str.back() == 'o')
default_base = 8;
else if (str.back() == 'h')
default_base = 16;
// when $display()/$write() functions are used in an initial block, print them during synthesis
Fmt fmt = processFormat(stage, /*sformat_like=*/false, default_base, /*first_arg_at=*/0, /*may_fail=*/true);
if (str.substr(0, 8) == "$display")
fmt.append_string("\n");
log("%s", fmt.render().c_str());
}
return false; return false;
} }
delete_children();
str = std::string();
} }
// activate const folding if this is anything that must be evaluated statically (ranges, parameters, attributes, etc.) // activate const folding if this is anything that must be evaluated statically (ranges, parameters, attributes, etc.)
@ -2185,11 +2191,24 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin
if (type == AST_IDENTIFIER && !basic_prep) { if (type == AST_IDENTIFIER && !basic_prep) {
// check if a plausible struct member sss.mmmm // check if a plausible struct member sss.mmmm
std::string sname; if (!str.empty() && str[0] == '\\' && current_scope.count(str)) {
if (name_has_dot(str, sname)) { auto item_node = current_scope[str];
if (current_scope.count(str) > 0) { if (item_node->type == AST_STRUCT_ITEM || item_node->type == AST_STRUCT || item_node->type == AST_UNION) {
auto item_node = current_scope[str]; // Traverse any hierarchical path until the full name for the referenced struct/union is found.
if (item_node->type == AST_STRUCT_ITEM || item_node->type == AST_STRUCT || item_node->type == AST_UNION) { std::string sname;
bool found_sname = false;
for (std::string::size_type pos = 0; (pos = str.find('.', pos)) != std::string::npos; pos++) {
sname = str.substr(0, pos);
if (current_scope.count(sname)) {
auto stype = current_scope[sname]->type;
if (stype == AST_WIRE || stype == AST_PARAMETER || stype == AST_LOCALPARAM) {
found_sname = true;
break;
}
}
}
if (found_sname) {
// structure member, rewrite this node to reference the packed struct wire // structure member, rewrite this node to reference the packed struct wire
auto range = make_struct_member_range(this, item_node); auto range = make_struct_member_range(this, item_node);
newNode = new AstNode(AST_IDENTIFIER, range); newNode = new AstNode(AST_IDENTIFIER, range);
@ -2816,27 +2835,12 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin
if (!children[0]->id2ast->range_valid) if (!children[0]->id2ast->range_valid)
goto skip_dynamic_range_lvalue_expansion; goto skip_dynamic_range_lvalue_expansion;
int source_width = children[0]->id2ast->range_left - children[0]->id2ast->range_right + 1;
int source_offset = children[0]->id2ast->range_right;
int result_width = 1;
int stride = 1;
AST::AstNode *member_node = get_struct_member(children[0]); AST::AstNode *member_node = get_struct_member(children[0]);
if (member_node) { int wire_width = member_node ?
// Clamp chunk to range of member within struct/union. member_node->range_left - member_node->range_right + 1 :
log_assert(!source_offset && !children[0]->id2ast->range_swapped); children[0]->id2ast->range_left - children[0]->id2ast->range_right + 1;
source_width = member_node->range_left - member_node->range_right + 1; int wire_offset = children[0]->id2ast->range_right;
int result_width = 1;
// When the (* nowrshmsk *) attribute is set, a CASE block is generated below
// to select the indexed bit slice. When a multirange array is indexed, the
// start of each possible slice is separated by the bit stride of the last
// index dimension, and we can optimize the CASE block accordingly.
// The dimension of the original array expression is saved in the 'integer' field.
int dims = children[0]->integer;
stride = source_width;
for (int dim = 0; dim < dims; dim++) {
stride /= get_struct_range_width(member_node, dim);
}
}
AstNode *shift_expr = NULL; AstNode *shift_expr = NULL;
AstNode *range = children[0]->children[0]; AstNode *range = children[0]->children[0];
@ -2849,133 +2853,184 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin
else else
shift_expr = range->children[0]->clone(); shift_expr = range->children[0]->clone();
bool use_case_method = false; bool use_case_method = children[0]->id2ast->get_bool_attribute(ID::nowrshmsk);
if (children[0]->id2ast->attributes.count(ID::nowrshmsk)) {
AstNode *node = children[0]->id2ast->attributes.at(ID::nowrshmsk);
while (node->simplify(true, stage, -1, false)) { }
if (node->type != AST_CONSTANT)
input_error("Non-constant value for `nowrshmsk' attribute on `%s'!\n", children[0]->id2ast->str.c_str());
if (node->asAttrConst().as_bool())
use_case_method = true;
}
if (!use_case_method && current_always->detect_latch(children[0]->str)) if (!use_case_method && current_always->detect_latch(children[0]->str))
use_case_method = true; use_case_method = true;
if (use_case_method) if (use_case_method) {
{
// big case block // big case block
int stride = 1;
long long bitno_div = stride;
int case_width_hint;
bool case_sign_hint;
shift_expr->detectSignWidth(case_width_hint, case_sign_hint);
int max_width = case_width_hint;
if (member_node) { // Member in packed struct/union
// Clamp chunk to range of member within struct/union.
log_assert(!wire_offset && !children[0]->id2ast->range_swapped);
// When the (* nowrshmsk *) attribute is set, a CASE block is generated below
// to select the indexed bit slice. When a multirange array is indexed, the
// start of each possible slice is separated by the bit stride of the last
// index dimension, and we can optimize the CASE block accordingly.
// The dimension of the original array expression is saved in the 'integer' field.
int dims = children[0]->integer;
stride = wire_width;
for (int dim = 0; dim < dims; dim++) {
stride /= get_struct_range_width(member_node, dim);
}
bitno_div = stride;
} else {
// Extract (index)*(width) from non_opt_range pattern ((@selfsz@((index)*(width)))+(0)).
AstNode *lsb_expr =
shift_expr->type == AST_ADD && shift_expr->children[0]->type == AST_SELFSZ &&
shift_expr->children[1]->type == AST_CONSTANT && shift_expr->children[1]->integer == 0 ?
shift_expr->children[0]->children[0] :
shift_expr;
// Extract stride from indexing of two-dimensional packed arrays and
// variable slices on the form dst[i*stride +: width] = src.
if (lsb_expr->type == AST_MUL &&
(lsb_expr->children[0]->type == AST_CONSTANT ||
lsb_expr->children[1]->type == AST_CONSTANT))
{
int stride_ix = lsb_expr->children[1]->type == AST_CONSTANT;
stride = (int)lsb_expr->children[stride_ix]->integer;
bitno_div = stride != 0 ? stride : 1;
// Check whether i*stride can overflow.
int i_width;
bool i_sign;
lsb_expr->children[1 - stride_ix]->detectSignWidth(i_width, i_sign);
int stride_width;
bool stride_sign;
lsb_expr->children[stride_ix]->detectSignWidth(stride_width, stride_sign);
max_width = std::max(i_width, stride_width);
// Stride width calculated from actual stride value.
stride_width = std::ceil(std::log2(std::abs(stride)));
if (i_width + stride_width > max_width) {
// For (truncated) i*stride to be within the range of dst, the following must hold:
// i*stride ≡ bitno (mod shift_mod), i.e.
// i*stride = k*shift_mod + bitno
//
// The Diophantine equation on the form ax + by = c:
// stride*i - shift_mod*k = bitno
// has solutions iff c is a multiple of d = gcd(a, b), i.e.
// bitno mod gcd(stride, shift_mod) = 0
//
// long long is at least 64 bits in C++11
long long shift_mod = 1ll << (max_width - case_sign_hint);
// std::gcd requires C++17
// bitno_div = std::gcd(stride, shift_mod);
bitno_div = gcd((long long)stride, shift_mod);
}
}
}
// long long is at least 64 bits in C++11
long long max_offset = (1ll << (max_width - case_sign_hint)) - 1;
long long min_offset = case_sign_hint ? -(1ll << (max_width - 1)) : 0;
// A temporary register holds the result of the (possibly complex) rvalue expression,
// avoiding repetition in each AST_COND below.
int rvalue_width;
bool rvalue_sign;
children[1]->detectSignWidth(rvalue_width, rvalue_sign);
AstNode *rvalue = mktemp_logic("$bitselwrite$rvalue$", current_ast_mod, true, rvalue_width - 1, 0, rvalue_sign);
AstNode *caseNode = new AstNode(AST_CASE, shift_expr);
newNode = new AstNode(AST_BLOCK,
new AstNode(AST_ASSIGN_EQ, rvalue, children[1]->clone()),
caseNode);
did_something = true; did_something = true;
newNode = new AstNode(AST_CASE, shift_expr); for (int i = 1 - result_width; i < wire_width; i++) {
for (int i = 0; i < source_width; i += stride) { // Out of range indexes are handled in genrtlil.cc
int start_bit = source_offset + i; int start_bit = wire_offset + i;
int end_bit = std::min(start_bit+result_width,source_width) - 1; int end_bit = start_bit + result_width - 1;
AstNode *cond = new AstNode(AST_COND, mkconst_int(start_bit, true)); // Check whether the current index can be generated by shift_expr.
if (start_bit < min_offset || start_bit > max_offset)
continue;
if (start_bit%bitno_div != 0 || (stride == 0 && start_bit != 0))
continue;
AstNode *cond = new AstNode(AST_COND, mkconst_int(start_bit, case_sign_hint, max_width));
AstNode *lvalue = children[0]->clone(); AstNode *lvalue = children[0]->clone();
lvalue->delete_children(); lvalue->delete_children();
if (member_node) if (member_node)
lvalue->set_attribute(ID::wiretype, member_node->clone()); lvalue->set_attribute(ID::wiretype, member_node->clone());
lvalue->children.push_back(new AstNode(AST_RANGE, lvalue->children.push_back(new AstNode(AST_RANGE,
mkconst_int(end_bit, true), mkconst_int(start_bit, true))); mkconst_int(end_bit, true), mkconst_int(start_bit, true)));
cond->children.push_back(new AstNode(AST_BLOCK, new AstNode(type, lvalue, children[1]->clone()))); cond->children.push_back(new AstNode(AST_BLOCK, new AstNode(type, lvalue, rvalue->clone())));
newNode->children.push_back(cond); caseNode->children.push_back(cond);
} }
} } else {
else // mask and shift operations
{ // dst = (dst & ~(width'1 << lsb)) | unsigned'(width'(src)) << lsb)
// mask and shift operations, disabled for now
AstNode *wire_mask = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(source_width-1, true), mkconst_int(0, true)));
wire_mask->str = stringf("$bitselwrite$mask$%s:%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++);
wire_mask->set_attribute(ID::nosync, AstNode::mkconst_int(1, false));
wire_mask->is_logic = true;
while (wire_mask->simplify(true, 1, -1, false)) { }
current_ast_mod->children.push_back(wire_mask);
AstNode *wire_data = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(source_width-1, true), mkconst_int(0, true)));
wire_data->str = stringf("$bitselwrite$data$%s:%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++);
wire_data->set_attribute(ID::nosync, AstNode::mkconst_int(1, false));
wire_data->is_logic = true;
while (wire_data->simplify(true, 1, -1, false)) { }
current_ast_mod->children.push_back(wire_data);
int shamt_width_hint = -1;
bool shamt_sign_hint = true;
shift_expr->detectSignWidth(shamt_width_hint, shamt_sign_hint);
AstNode *wire_sel = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(shamt_width_hint-1, true), mkconst_int(0, true)));
wire_sel->str = stringf("$bitselwrite$sel$%s:%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++);
wire_sel->set_attribute(ID::nosync, AstNode::mkconst_int(1, false));
wire_sel->is_logic = true;
wire_sel->is_signed = shamt_sign_hint;
while (wire_sel->simplify(true, 1, -1, false)) { }
current_ast_mod->children.push_back(wire_sel);
did_something = true;
newNode = new AstNode(AST_BLOCK);
AstNode *lvalue = children[0]->clone(); AstNode *lvalue = children[0]->clone();
lvalue->delete_children(); lvalue->delete_children();
if (member_node) if (member_node)
lvalue->set_attribute(ID::wiretype, member_node->clone()); lvalue->set_attribute(ID::wiretype, member_node->clone());
AstNode *ref_mask = new AstNode(AST_IDENTIFIER);
ref_mask->str = wire_mask->str;
ref_mask->id2ast = wire_mask;
ref_mask->was_checked = true;
AstNode *ref_data = new AstNode(AST_IDENTIFIER);
ref_data->str = wire_data->str;
ref_data->id2ast = wire_data;
ref_data->was_checked = true;
AstNode *ref_sel = new AstNode(AST_IDENTIFIER);
ref_sel->str = wire_sel->str;
ref_sel->id2ast = wire_sel;
ref_sel->was_checked = true;
AstNode *old_data = lvalue->clone(); AstNode *old_data = lvalue->clone();
if (type == AST_ASSIGN_LE) if (type == AST_ASSIGN_LE)
old_data->lookahead = true; old_data->lookahead = true;
AstNode *s = new AstNode(AST_ASSIGN_EQ, ref_sel->clone(), shift_expr); int shift_width_hint;
newNode->children.push_back(s); bool shift_sign_hint;
shift_expr->detectSignWidth(shift_width_hint, shift_sign_hint);
AstNode *shamt = ref_sel; // All operations are carried out in a new block.
newNode = new AstNode(AST_BLOCK);
// convert to signed while preserving the sign and value // Temporary register holding the result of the bit- or part-select position expression.
shamt = new AstNode(AST_CAST_SIZE, mkconst_int(shamt_width_hint + 1, true), shamt); AstNode *pos = mktemp_logic("$bitselwrite$pos$", current_ast_mod, true, shift_width_hint - 1, 0, shift_sign_hint);
shamt = new AstNode(AST_TO_SIGNED, shamt); newNode->children.push_back(new AstNode(AST_ASSIGN_EQ, pos, shift_expr));
// Calculate lsb from position.
AstNode *shift_val = pos->clone();
// If the expression is signed, we must add an extra bit for possible negation of the most negative number.
// If the expression is unsigned, we must add an extra bit for sign.
shift_val = new AstNode(AST_CAST_SIZE, mkconst_int(shift_width_hint + 1, true), shift_val);
if (!shift_sign_hint)
shift_val = new AstNode(AST_TO_SIGNED, shift_val);
// offset the shift amount by the lower bound of the dimension // offset the shift amount by the lower bound of the dimension
int start_bit = source_offset; if (wire_offset != 0)
shamt = new AstNode(AST_SUB, shamt, mkconst_int(start_bit, true)); shift_val = new AstNode(AST_SUB, shift_val, mkconst_int(wire_offset, true));
// reflect the shift amount if the dimension is swapped // reflect the shift amount if the dimension is swapped
if (children[0]->id2ast->range_swapped) if (children[0]->id2ast->range_swapped)
shamt = new AstNode(AST_SUB, mkconst_int(source_width - result_width, true), shamt); shift_val = new AstNode(AST_SUB, mkconst_int(wire_width - result_width, true), shift_val);
// AST_SHIFT uses negative amounts for shifting left // AST_SHIFT uses negative amounts for shifting left
shamt = new AstNode(AST_NEG, shamt); shift_val = new AstNode(AST_NEG, shift_val);
AstNode *t; // dst = (dst & ~(width'1 << lsb)) | unsigned'(width'(src)) << lsb)
did_something = true;
t = mkconst_bits(std::vector<RTLIL::State>(result_width, State::S1), false); AstNode *bitmask = mkconst_bits(std::vector<RTLIL::State>(result_width, State::S1), false);
t = new AstNode(AST_SHIFT, t, shamt->clone()); newNode->children.push_back(
t = new AstNode(AST_ASSIGN_EQ, ref_mask->clone(), t); new AstNode(type,
newNode->children.push_back(t); lvalue,
new AstNode(AST_BIT_OR,
t = new AstNode(AST_BIT_AND, mkconst_bits(std::vector<RTLIL::State>(result_width, State::S1), false), children[1]->clone()); new AstNode(AST_BIT_AND,
t = new AstNode(AST_SHIFT, t, shamt); old_data,
t = new AstNode(AST_ASSIGN_EQ, ref_data->clone(), t); new AstNode(AST_BIT_NOT,
newNode->children.push_back(t); new AstNode(AST_SHIFT,
bitmask,
t = new AstNode(AST_BIT_AND, old_data, new AstNode(AST_BIT_NOT, ref_mask)); shift_val->clone()))),
t = new AstNode(AST_BIT_OR, t, ref_data); new AstNode(AST_SHIFT,
t = new AstNode(type, lvalue, t); new AstNode(AST_TO_UNSIGNED,
newNode->children.push_back(t); new AstNode(AST_CAST_SIZE,
mkconst_int(result_width, true),
children[1]->clone())),
shift_val))));
newNode->fixup_hierarchy_flags(true); newNode->fixup_hierarchy_flags(true);
} }
@ -4681,6 +4736,8 @@ void AstNode::expand_genblock(const std::string &prefix)
switch (child->type) { switch (child->type) {
case AST_WIRE: case AST_WIRE:
case AST_MEMORY: case AST_MEMORY:
case AST_STRUCT:
case AST_UNION:
case AST_PARAMETER: case AST_PARAMETER:
case AST_LOCALPARAM: case AST_LOCALPARAM:
case AST_FUNCTION: case AST_FUNCTION:

View file

@ -2110,7 +2110,7 @@ VerificClocking::VerificClocking(VerificImporter *importer, Net *net, bool sva_a
if (sva_at_only) if (sva_at_only)
do { do {
Instance *inst_mux = net->Driver(); Instance *inst_mux = net->Driver();
if (inst_mux->Type() != PRIM_MUX) if (inst_mux == nullptr || inst_mux->Type() != PRIM_MUX)
break; break;
bool pwr1 = inst_mux->GetInput1()->IsPwr(); bool pwr1 = inst_mux->GetInput1()->IsPwr();

View file

@ -100,6 +100,10 @@ struct VerilogFrontend : public Frontend {
log(" -assert-assumes\n"); log(" -assert-assumes\n");
log(" treat all assume() statements like assert() statements\n"); log(" treat all assume() statements like assert() statements\n");
log("\n"); log("\n");
log(" -nodisplay\n");
log(" suppress output from display system tasks ($display et. al).\n");
log(" This does not affect the output from a later 'sim' command.\n");
log("\n");
log(" -debug\n"); log(" -debug\n");
log(" alias for -dump_ast1 -dump_ast2 -dump_vlog1 -dump_vlog2 -yydebug\n"); log(" alias for -dump_ast1 -dump_ast2 -dump_vlog1 -dump_vlog2 -yydebug\n");
log("\n"); log("\n");
@ -235,6 +239,7 @@ struct VerilogFrontend : public Frontend {
} }
void execute(std::istream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override void execute(std::istream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override
{ {
bool flag_nodisplay = false;
bool flag_dump_ast1 = false; bool flag_dump_ast1 = false;
bool flag_dump_ast2 = false; bool flag_dump_ast2 = false;
bool flag_no_dump_ptr = false; bool flag_no_dump_ptr = false;
@ -308,6 +313,10 @@ struct VerilogFrontend : public Frontend {
assert_assumes_mode = true; assert_assumes_mode = true;
continue; continue;
} }
if (arg == "-nodisplay") {
flag_nodisplay = true;
continue;
}
if (arg == "-debug") { if (arg == "-debug") {
flag_dump_ast1 = true; flag_dump_ast1 = true;
flag_dump_ast2 = true; flag_dump_ast2 = true;
@ -510,7 +519,7 @@ struct VerilogFrontend : public Frontend {
if (flag_nodpi) if (flag_nodpi)
error_on_dpi_function(current_ast); error_on_dpi_function(current_ast);
AST::process(design, current_ast, flag_dump_ast1, flag_dump_ast2, flag_no_dump_ptr, flag_dump_vlog1, flag_dump_vlog2, flag_dump_rtlil, flag_nolatches, AST::process(design, current_ast, flag_nodisplay, flag_dump_ast1, flag_dump_ast2, flag_no_dump_ptr, flag_dump_vlog1, flag_dump_vlog2, flag_dump_rtlil, flag_nolatches,
flag_nomeminit, flag_nomem2reg, flag_mem2reg, flag_noblackbox, lib_mode, flag_nowb, flag_noopt, flag_icells, flag_pwires, flag_nooverwrite, flag_overwrite, flag_defer, default_nettype_wire); flag_nomeminit, flag_nomem2reg, flag_mem2reg, flag_noblackbox, lib_mode, flag_nowb, flag_noopt, flag_icells, flag_pwires, flag_nooverwrite, flag_overwrite, flag_defer, default_nettype_wire);

View file

@ -110,9 +110,9 @@ void Fmt::parse_rtlil(const RTLIL::Cell *cell) {
} else if (fmt[i] == 'c') { } else if (fmt[i] == 'c') {
part.type = FmtPart::CHARACTER; part.type = FmtPart::CHARACTER;
} else if (fmt[i] == 't') { } else if (fmt[i] == 't') {
part.type = FmtPart::TIME; part.type = FmtPart::VLOG_TIME;
} else if (fmt[i] == 'r') { } else if (fmt[i] == 'r') {
part.type = FmtPart::TIME; part.type = FmtPart::VLOG_TIME;
part.realtime = true; part.realtime = true;
} else { } else {
log_assert(false && "Unexpected character in format substitution"); log_assert(false && "Unexpected character in format substitution");
@ -172,7 +172,7 @@ void Fmt::emit_rtlil(RTLIL::Cell *cell) const {
} }
break; break;
case FmtPart::TIME: case FmtPart::VLOG_TIME:
log_assert(part.sig.size() == 0); log_assert(part.sig.size() == 0);
YS_FALLTHROUGH YS_FALLTHROUGH
case FmtPart::CHARACTER: case FmtPart::CHARACTER:
@ -205,7 +205,7 @@ void Fmt::emit_rtlil(RTLIL::Cell *cell) const {
fmt += part.signed_ ? 's' : 'u'; fmt += part.signed_ ? 's' : 'u';
} else if (part.type == FmtPart::CHARACTER) { } else if (part.type == FmtPart::CHARACTER) {
fmt += 'c'; fmt += 'c';
} else if (part.type == FmtPart::TIME) { } else if (part.type == FmtPart::VLOG_TIME) {
if (part.realtime) if (part.realtime)
fmt += 'r'; fmt += 'r';
else else
@ -328,7 +328,7 @@ void Fmt::parse_verilog(const std::vector<VerilogFmtArg> &args, bool sformat_lik
case VerilogFmtArg::TIME: { case VerilogFmtArg::TIME: {
FmtPart part = {}; FmtPart part = {};
part.type = FmtPart::TIME; part.type = FmtPart::VLOG_TIME;
part.realtime = arg->realtime; part.realtime = arg->realtime;
part.padding = ' '; part.padding = ' ';
part.width = 20; part.width = 20;
@ -419,7 +419,7 @@ void Fmt::parse_verilog(const std::vector<VerilogFmtArg> &args, bool sformat_lik
part.padding = ' '; part.padding = ' ';
} else if (fmt[i] == 't' || fmt[i] == 'T') { } else if (fmt[i] == 't' || fmt[i] == 'T') {
if (arg->type == VerilogFmtArg::TIME) { if (arg->type == VerilogFmtArg::TIME) {
part.type = FmtPart::TIME; part.type = FmtPart::VLOG_TIME;
part.realtime = arg->realtime; part.realtime = arg->realtime;
if (!has_width && !has_leading_zero) if (!has_width && !has_leading_zero)
part.width = 20; part.width = 20;
@ -541,7 +541,7 @@ std::vector<VerilogFmtArg> Fmt::emit_verilog() const
break; break;
} }
case FmtPart::TIME: { case FmtPart::VLOG_TIME: {
VerilogFmtArg arg; VerilogFmtArg arg;
arg.type = VerilogFmtArg::TIME; arg.type = VerilogFmtArg::TIME;
if (part.realtime) if (part.realtime)
@ -569,82 +569,60 @@ std::vector<VerilogFmtArg> Fmt::emit_verilog() const
return args; return args;
} }
void Fmt::emit_cxxrtl(std::ostream &f, std::function<void(const RTLIL::SigSpec &)> emit_sig) const std::string escape_cxx_string(const std::string &input)
{ {
for (auto &part : parts) { std::string output = "\"";
switch (part.type) { for (auto c : input) {
case FmtPart::STRING: if (::isprint(c)) {
f << " << \""; if (c == '\\')
for (char c : part.str) { output.push_back('\\');
switch (c) { output.push_back(c);
case '\\': } else {
YS_FALLTHROUGH char l = c & 0xf, h = (c >> 4) & 0xf;
case '"': output.append("\\x");
f << '\\' << c; output.push_back((h < 10 ? '0' + h : 'a' + h - 10));
break; output.push_back((l < 10 ? '0' + l : 'a' + l - 10));
case '\a':
f << "\\a";
break;
case '\b':
f << "\\b";
break;
case '\f':
f << "\\f";
break;
case '\n':
f << "\\n";
break;
case '\r':
f << "\\r";
break;
case '\t':
f << "\\t";
break;
case '\v':
f << "\\v";
break;
default:
f << c;
break;
}
}
f << '"';
break;
case FmtPart::INTEGER:
case FmtPart::CHARACTER: {
f << " << value_formatted<" << part.sig.size() << ">(";
emit_sig(part.sig);
f << ", " << (part.type == FmtPart::CHARACTER);
f << ", " << (part.justify == FmtPart::LEFT);
f << ", (char)" << (int)part.padding;
f << ", " << part.width;
f << ", " << part.base;
f << ", " << part.signed_;
f << ", " << part.plus;
f << ')';
break;
}
case FmtPart::TIME: {
// CXXRTL only records steps taken, so there's no difference between
// the values taken by $time and $realtime.
f << " << value_formatted<64>(";
f << "value<64>{steps}";
f << ", " << (part.type == FmtPart::CHARACTER);
f << ", " << (part.justify == FmtPart::LEFT);
f << ", (char)" << (int)part.padding;
f << ", " << part.width;
f << ", " << part.base;
f << ", " << part.signed_;
f << ", " << part.plus;
f << ')';
break;
}
default: log_abort();
} }
} }
output.push_back('"');
if (output.find('\0') != std::string::npos) {
output.insert(0, "std::string {");
output.append(stringf(", %zu}", input.size()));
}
return output;
}
void Fmt::emit_cxxrtl(std::ostream &os, std::string indent, std::function<void(const RTLIL::SigSpec &)> emit_sig, const std::string &context) const
{
os << indent << "std::string buf;\n";
for (auto &part : parts) {
os << indent << "buf += fmt_part { ";
os << "fmt_part::";
switch (part.type) {
case FmtPart::STRING: os << "STRING"; break;
case FmtPart::INTEGER: os << "INTEGER"; break;
case FmtPart::CHARACTER: os << "CHARACTER"; break;
case FmtPart::VLOG_TIME: os << "VLOG_TIME"; break;
}
os << ", ";
os << escape_cxx_string(part.str) << ", ";
os << "fmt_part::";
switch (part.justify) {
case FmtPart::LEFT: os << "LEFT"; break;
case FmtPart::RIGHT: os << "RIGHT"; break;
}
os << ", ";
os << "(char)" << (int)part.padding << ", ";
os << part.width << ", ";
os << part.base << ", ";
os << part.signed_ << ", ";
os << part.plus << ", ";
os << part.realtime;
os << " }.render(";
emit_sig(part.sig);
os << ", " << context << ");\n";
}
os << indent << "return buf;\n";
} }
std::string Fmt::render() const std::string Fmt::render() const
@ -658,8 +636,8 @@ std::string Fmt::render() const
break; break;
case FmtPart::INTEGER: case FmtPart::INTEGER:
case FmtPart::TIME: case FmtPart::CHARACTER:
case FmtPart::CHARACTER: { case FmtPart::VLOG_TIME: {
std::string buf; std::string buf;
if (part.type == FmtPart::INTEGER) { if (part.type == FmtPart::INTEGER) {
RTLIL::Const value = part.sig.as_const(); RTLIL::Const value = part.sig.as_const();
@ -742,7 +720,7 @@ std::string Fmt::render() const
} else log_abort(); } else log_abort();
} else if (part.type == FmtPart::CHARACTER) { } else if (part.type == FmtPart::CHARACTER) {
buf = part.sig.as_const().decode_string(); buf = part.sig.as_const().decode_string();
} else if (part.type == FmtPart::TIME) { } else if (part.type == FmtPart::VLOG_TIME) {
// We only render() during initial, so time is always zero. // We only render() during initial, so time is always zero.
buf = "0"; buf = "0";
} }

View file

@ -50,12 +50,13 @@ struct VerilogFmtArg {
// RTLIL format part, such as the substitutions in: // RTLIL format part, such as the substitutions in:
// "foo {4:> 4du} bar {2:<01hs}" // "foo {4:> 4du} bar {2:<01hs}"
// Must be kept in sync with `struct fmt_part` in backends/cxxrtl/runtime/cxxrtl/cxxrtl.h!
struct FmtPart { struct FmtPart {
enum { enum {
STRING = 0, STRING = 0,
INTEGER = 1, INTEGER = 1,
CHARACTER = 2, CHARACTER = 2,
TIME = 3, VLOG_TIME = 3,
} type; } type;
// STRING type // STRING type
@ -64,20 +65,20 @@ struct FmtPart {
// INTEGER/CHARACTER types // INTEGER/CHARACTER types
RTLIL::SigSpec sig; RTLIL::SigSpec sig;
// INTEGER/CHARACTER/TIME types // INTEGER/CHARACTER/VLOG_TIME types
enum { enum {
RIGHT = 0, RIGHT = 0,
LEFT = 1, LEFT = 1,
} justify = RIGHT; } justify = RIGHT;
char padding = '\0'; char padding = '\0';
size_t width = 0; size_t width = 0;
// INTEGER type // INTEGER type
unsigned base = 10; unsigned base = 10;
bool signed_ = false; bool signed_ = false;
bool plus = false; bool plus = false;
// TIME type // VLOG_TIME type
bool realtime = false; bool realtime = false;
}; };
@ -93,7 +94,7 @@ public:
void parse_verilog(const std::vector<VerilogFmtArg> &args, bool sformat_like, int default_base, RTLIL::IdString task_name, RTLIL::IdString module_name); void parse_verilog(const std::vector<VerilogFmtArg> &args, bool sformat_like, int default_base, RTLIL::IdString task_name, RTLIL::IdString module_name);
std::vector<VerilogFmtArg> emit_verilog() const; std::vector<VerilogFmtArg> emit_verilog() const;
void emit_cxxrtl(std::ostream &f, std::function<void(const RTLIL::SigSpec &)> emit_sig) const; void emit_cxxrtl(std::ostream &os, std::string indent, std::function<void(const RTLIL::SigSpec &)> emit_sig, const std::string &context) const;
std::string render() const; std::string render() const;

View file

@ -420,7 +420,7 @@ public:
operator const_iterator() const { return const_iterator(ptr, index); } operator const_iterator() const { return const_iterator(ptr, index); }
}; };
dict() constexpr dict()
{ {
} }
@ -855,7 +855,7 @@ public:
operator const_iterator() const { return const_iterator(ptr, index); } operator const_iterator() const { return const_iterator(ptr, index); }
}; };
pool() constexpr pool()
{ {
} }
@ -1062,6 +1062,10 @@ public:
const K *operator->() const { return &container[index]; } const K *operator->() const { return &container[index]; }
}; };
constexpr idict()
{
}
int operator()(const K &key) int operator()(const K &key)
{ {
int hash = database.do_hash(key); int hash = database.do_hash(key);
@ -1132,6 +1136,10 @@ class mfp
public: public:
typedef typename idict<K, 0, OPS>::const_iterator const_iterator; typedef typename idict<K, 0, OPS>::const_iterator const_iterator;
constexpr mfp()
{
}
int operator()(const K &key) const int operator()(const K &key) const
{ {
int i = database(key); int i = database(key);

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
bad= bad=

View file

@ -539,7 +539,7 @@ struct ShowWorker
std::string proc_src = RTLIL::unescape_id(proc->name); std::string proc_src = RTLIL::unescape_id(proc->name);
if (proc->attributes.count(ID::src) > 0) if (proc->attributes.count(ID::src) > 0)
proc_src = proc->attributes.at(ID::src).decode_string(); proc_src = proc->attributes.at(ID::src).decode_string();
fprintf(f, "p%d [shape=box, style=rounded, label=\"PROC %s\\n%s\"];\n", pidx, findLabel(proc->name.str()), proc_src.c_str()); fprintf(f, "p%d [shape=box, style=rounded, label=\"PROC %s\\n%s\", %s];\n", pidx, findLabel(proc->name.str()), proc_src.c_str(), findColor(proc->name).c_str());
} }
for (auto &conn : module->connections()) for (auto &conn : module->connections())

View file

@ -60,7 +60,7 @@ struct EquivSimpleWorker
for (auto &conn : cell->connections()) for (auto &conn : cell->connections())
if (yosys_celltypes.cell_input(cell->type, conn.first)) if (yosys_celltypes.cell_input(cell->type, conn.first))
for (auto bit : sigmap(conn.second)) { for (auto bit : sigmap(conn.second)) {
if (cell->type.in(ID($dff), ID($_DFF_P_), ID($_DFF_N_), ID($ff), ID($_FF_))) { if (RTLIL::builtin_ff_cell_types().count(cell->type)) {
if (!conn.first.in(ID::CLK, ID::C)) if (!conn.first.in(ID::CLK, ID::C))
next_seed.insert(bit); next_seed.insert(bit);
} else } else
@ -133,11 +133,9 @@ struct EquivSimpleWorker
for (auto bit_a : seed_a) for (auto bit_a : seed_a)
find_input_cone(next_seed_a, full_cells_cone_a, full_bits_cone_a, no_stop_cells, no_stop_bits, nullptr, bit_a); find_input_cone(next_seed_a, full_cells_cone_a, full_bits_cone_a, no_stop_cells, no_stop_bits, nullptr, bit_a);
next_seed_a.clear();
for (auto bit_b : seed_b) for (auto bit_b : seed_b)
find_input_cone(next_seed_b, full_cells_cone_b, full_bits_cone_b, no_stop_cells, no_stop_bits, nullptr, bit_b); find_input_cone(next_seed_b, full_cells_cone_b, full_bits_cone_b, no_stop_cells, no_stop_bits, nullptr, bit_b);
next_seed_b.clear();
pool<Cell*> short_cells_cone_a, short_cells_cone_b; pool<Cell*> short_cells_cone_a, short_cells_cone_b;
pool<SigBit> short_bits_cone_a, short_bits_cone_b; pool<SigBit> short_bits_cone_a, short_bits_cone_b;
@ -145,10 +143,12 @@ struct EquivSimpleWorker
if (short_cones) if (short_cones)
{ {
next_seed_a.clear();
for (auto bit_a : seed_a) for (auto bit_a : seed_a)
find_input_cone(next_seed_a, short_cells_cone_a, short_bits_cone_a, full_cells_cone_b, full_bits_cone_b, &input_bits, bit_a); find_input_cone(next_seed_a, short_cells_cone_a, short_bits_cone_a, full_cells_cone_b, full_bits_cone_b, &input_bits, bit_a);
next_seed_a.swap(seed_a); next_seed_a.swap(seed_a);
next_seed_b.clear();
for (auto bit_b : seed_b) for (auto bit_b : seed_b)
find_input_cone(next_seed_b, short_cells_cone_b, short_bits_cone_b, full_cells_cone_a, full_bits_cone_a, &input_bits, bit_b); find_input_cone(next_seed_b, short_cells_cone_b, short_bits_cone_b, full_cells_cone_a, full_bits_cone_a, &input_bits, bit_b);
next_seed_b.swap(seed_b); next_seed_b.swap(seed_b);
@ -364,7 +364,7 @@ struct EquivSimplePass : public Pass {
unproven_cells_counter, GetSize(unproven_equiv_cells), log_id(module)); unproven_cells_counter, GetSize(unproven_equiv_cells), log_id(module));
for (auto cell : module->cells()) { for (auto cell : module->cells()) {
if (!ct.cell_known(cell->type) && !cell->type.in(ID($dff), ID($_DFF_P_), ID($_DFF_N_), ID($ff), ID($_FF_))) if (!ct.cell_known(cell->type))
continue; continue;
for (auto &conn : cell->connections()) for (auto &conn : cell->connections())
if (yosys_celltypes.cell_output(cell->type, conn.first)) if (yosys_celltypes.cell_output(cell->type, conn.first))

View file

@ -690,7 +690,7 @@ bool apply_clock(MemConfig &cfg, const PortVariant &def, SigBit clk, bool clk_po
// Perform write port assignment, validating clock options as we go. // Perform write port assignment, validating clock options as we go.
void MemMapping::assign_wr_ports() { void MemMapping::assign_wr_ports() {
log_reject(stringf("Assigning write ports... (candidate configs: %lu)", cfgs.size())); log_reject(stringf("Assigning write ports... (candidate configs: %zu)", (size_t) cfgs.size()));
for (auto &port: mem.wr_ports) { for (auto &port: mem.wr_ports) {
if (!port.clk_enable) { if (!port.clk_enable) {
// Async write ports not supported. // Async write ports not supported.
@ -739,7 +739,7 @@ void MemMapping::assign_wr_ports() {
// Perform read port assignment, validating clock and rden options as we go. // Perform read port assignment, validating clock and rden options as we go.
void MemMapping::assign_rd_ports() { void MemMapping::assign_rd_ports() {
log_reject(stringf("Assigning read ports... (candidate configs: %lu)", cfgs.size())); log_reject(stringf("Assigning read ports... (candidate configs: %zu)", (size_t) cfgs.size()));
for (int pidx = 0; pidx < GetSize(mem.rd_ports); pidx++) { for (int pidx = 0; pidx < GetSize(mem.rd_ports); pidx++) {
auto &port = mem.rd_ports[pidx]; auto &port = mem.rd_ports[pidx];
MemConfigs new_cfgs; MemConfigs new_cfgs;
@ -900,7 +900,7 @@ void MemMapping::assign_rd_ports() {
// Validate transparency restrictions, determine where to add soft transparency logic. // Validate transparency restrictions, determine where to add soft transparency logic.
void MemMapping::handle_trans() { void MemMapping::handle_trans() {
log_reject(stringf("Handling transparency... (candidate configs: %lu)", cfgs.size())); log_reject(stringf("Handling transparency... (candidate configs: %zu)", (size_t) cfgs.size()));
if (mem.emulate_read_first_ok()) { if (mem.emulate_read_first_ok()) {
MemConfigs new_cfgs; MemConfigs new_cfgs;
for (auto &cfg: cfgs) { for (auto &cfg: cfgs) {

View file

@ -24,6 +24,10 @@
USING_YOSYS_NAMESPACE USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN PRIVATE_NAMESPACE_BEGIN
// Type represents the following constraint: Preserve connections to dedicated
// logic cell <cell_type> that has ports connected to LUT inputs. This includes
// the case where both LUT and dedicated logic input are connected to the same
// constant.
struct dlogic_t { struct dlogic_t {
IdString cell_type; IdString cell_type;
// LUT input idx -> hard cell's port name // LUT input idx -> hard cell's port name
@ -515,16 +519,6 @@ struct OptLutWorker
} }
}; };
static void split(std::vector<std::string> &tokens, const std::string &text, char sep)
{
size_t start = 0, end = 0;
while ((end = text.find(sep, start)) != std::string::npos) {
tokens.push_back(text.substr(start, end - start));
start = end + 1;
}
tokens.push_back(text.substr(start));
}
struct OptLutPass : public Pass { struct OptLutPass : public Pass {
OptLutPass() : Pass("opt_lut", "optimize LUT cells") { } OptLutPass() : Pass("opt_lut", "optimize LUT cells") { }
void help() override void help() override
@ -541,6 +535,10 @@ struct OptLutPass : public Pass {
log(" the case where both LUT and dedicated logic input are connected to\n"); log(" the case where both LUT and dedicated logic input are connected to\n");
log(" the same constant.\n"); log(" the same constant.\n");
log("\n"); log("\n");
log(" -tech ice40\n");
log(" treat the design as a LUT-mapped circuit for the iCE40 architecture\n");
log(" and preserve connections to SB_CARRY as appropriate\n");
log("\n");
log(" -limit N\n"); log(" -limit N\n");
log(" only perform the first N combines, then stop. useful for debugging.\n"); log(" only perform the first N combines, then stop. useful for debugging.\n");
log("\n"); log("\n");
@ -555,28 +553,28 @@ struct OptLutPass : public Pass {
size_t argidx; size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++) for (argidx = 1; argidx < args.size(); argidx++)
{ {
if (args[argidx] == "-dlogic" && argidx+1 < args.size()) if (args[argidx] == "-tech" && argidx+1 < args.size())
{ {
std::vector<std::string> tokens; std::string tech = args[++argidx];
split(tokens, args[++argidx], ':'); if (tech != "ice40")
if (tokens.size() < 2) log_cmd_error("Unsupported -tech argument: %s\n", tech.c_str());
log_cmd_error("The -dlogic option requires at least one connection.\n");
dlogic_t entry; dlogic = {{
entry.cell_type = "\\" + tokens[0]; ID(SB_CARRY),
for (auto it = tokens.begin() + 1; it != tokens.end(); ++it) { dict<int, IdString>{
std::vector<std::string> conn_tokens; std::make_pair(1, ID(I0)),
split(conn_tokens, *it, '='); std::make_pair(2, ID(I1)),
if (conn_tokens.size() != 2) std::make_pair(3, ID(CI))
log_cmd_error("Invalid format of -dlogic signal mapping.\n"); }
IdString logic_port = "\\" + conn_tokens[0]; }, {
int lut_input = atoi(conn_tokens[1].c_str()); ID(SB_CARRY),
entry.lut_input_port[lut_input] = logic_port; dict<int, IdString>{
} std::make_pair(3, ID(CO))
dlogic.push_back(entry); }
}};
continue; continue;
} }
if (args[argidx] == "-limit" && argidx + 1 < args.size()) if (args[argidx] == "-limit" && argidx + 1 < args.size()) {
{
limit = atoi(args[++argidx].c_str()); limit = atoi(args[++argidx].c_str());
continue; continue;
} }

View file

@ -26,6 +26,7 @@
#include <algorithm> #include <algorithm>
#include <queue> #include <queue>
#include <cinttypes>
USING_YOSYS_NAMESPACE USING_YOSYS_NAMESPACE
@ -623,7 +624,7 @@ struct RecoverNamesWorker {
if (pop == 1 || pop == (8*sizeof(equiv_cls_t) - 1)) if (pop == 1 || pop == (8*sizeof(equiv_cls_t) - 1))
continue; continue;
log_debug("equivalence class: %016lx\n", cls.first); log_debug("equivalence class: %016" PRIx64 "\n", cls.first);
const pool<IdBit> &gold_bits = cls2bits.at(cls.first).first; const pool<IdBit> &gold_bits = cls2bits.at(cls.first).first;
const pool<InvBit> &gate_bits = cls2bits.at(cls.first).second; const pool<InvBit> &gate_bits = cls2bits.at(cls.first).second;
if (gold_bits.empty() || gate_bits.empty()) if (gold_bits.empty() || gate_bits.empty())

View file

@ -88,6 +88,17 @@ struct TriggeredAssertion {
{ } { }
}; };
struct DisplayOutput {
int step;
SimInstance *instance;
Cell *cell;
std::string output;
DisplayOutput(int step, SimInstance *instance, Cell *cell, std::string output) :
step(step), instance(instance), cell(cell), output(output)
{ }
};
struct SimShared struct SimShared
{ {
bool debug = false; bool debug = false;
@ -110,6 +121,7 @@ struct SimShared
int next_output_id = 0; int next_output_id = 0;
int step = 0; int step = 0;
std::vector<TriggeredAssertion> triggered_assertions; std::vector<TriggeredAssertion> triggered_assertions;
std::vector<DisplayOutput> display_output;
bool serious_asserts = false; bool serious_asserts = false;
bool initstate = true; bool initstate = true;
}; };
@ -173,6 +185,7 @@ struct SimInstance
struct print_state_t struct print_state_t
{ {
bool initial_done;
Const past_trg; Const past_trg;
Const past_en; Const past_en;
Const past_args; Const past_args;
@ -338,6 +351,7 @@ struct SimInstance
print.past_trg = Const(State::Sx, cell->getPort(ID::TRG).size()); print.past_trg = Const(State::Sx, cell->getPort(ID::TRG).size());
print.past_args = Const(State::Sx, cell->getPort(ID::ARGS).size()); print.past_args = Const(State::Sx, cell->getPort(ID::ARGS).size());
print.past_en = State::Sx; print.past_en = State::Sx;
print.initial_done = false;
} }
} }
@ -840,13 +854,14 @@ struct SimInstance
bool triggered = false; bool triggered = false;
Const trg = get_state(cell->getPort(ID::TRG)); Const trg = get_state(cell->getPort(ID::TRG));
bool trg_en = cell->getParam(ID::TRG_ENABLE).as_bool();
Const en = get_state(cell->getPort(ID::EN)); Const en = get_state(cell->getPort(ID::EN));
Const args = get_state(cell->getPort(ID::ARGS)); Const args = get_state(cell->getPort(ID::ARGS));
if (!en.as_bool()) if (!en.as_bool())
goto update_print; goto update_print;
if (cell->getParam(ID::TRG_ENABLE).as_bool()) { if (trg.size() > 0 && trg_en) {
Const trg_pol = cell->getParam(ID::TRG_POLARITY); Const trg_pol = cell->getParam(ID::TRG_POLARITY);
for (int i = 0; i < trg.size(); i++) { for (int i = 0; i < trg.size(); i++) {
bool pol = trg_pol[i] == State::S1; bool pol = trg_pol[i] == State::S1;
@ -856,7 +871,12 @@ struct SimInstance
if (!pol && curr == State::S0 && past == State::S1) if (!pol && curr == State::S0 && past == State::S1)
triggered = true; triggered = true;
} }
} else if (trg_en) {
// initial $print (TRG width = 0, TRG_ENABLE = true)
if (!print.initial_done && en != print.past_en)
triggered = true;
} else { } else {
// always @(*) $print
if (args != print.past_args || en != print.past_en) if (args != print.past_args || en != print.past_en)
triggered = true; triggered = true;
} }
@ -870,12 +890,14 @@ struct SimInstance
std::string rendered = print.fmt.render(); std::string rendered = print.fmt.render();
log("%s", rendered.c_str()); log("%s", rendered.c_str());
shared->display_output.emplace_back(shared->step, this, cell, rendered);
} }
update_print: update_print:
print.past_trg = trg; print.past_trg = trg;
print.past_en = en; print.past_en = en;
print.past_args = args; print.past_args = args;
print.initial_done = true;
} }
if (check_assertions) if (check_assertions)
@ -2055,6 +2077,20 @@ struct SimWorker : SimShared
json.end_object(); json.end_object();
} }
json.end_array(); json.end_array();
json.name("display_output");
json.begin_array();
for (auto &output : display_output) {
json.begin_object();
json.entry("step", output.step);
json.entry("path", output.instance->witness_full_path(output.cell));
auto src = output.cell->get_string_attribute(ID::src);
if (!src.empty()) {
json.entry("src", src);
}
json.entry("output", output.output);
json.end_object();
}
json.end_array();
json.end_object(); json.end_object();
} }

View file

@ -131,7 +131,7 @@ if (PORT_A_WIDTH < 9) begin
.CE(PORT_A_CLK_EN), .CE(PORT_A_CLK_EN),
.WRE(WRE), .WRE(WRE),
.RESET(RST), .RESET(RST),
.OCE(1'b0), .OCE(1'b1),
.AD(AD), .AD(AD),
.DI(DI), .DI(DI),
.DO(DO), .DO(DO),
@ -157,7 +157,7 @@ end else begin
.CE(PORT_A_CLK_EN), .CE(PORT_A_CLK_EN),
.WRE(WRE), .WRE(WRE),
.RESET(RST), .RESET(RST),
.OCE(1'b0), .OCE(1'b1),
.AD(AD), .AD(AD),
.DI(DI), .DI(DI),
.DO(DO), .DO(DO),
@ -224,7 +224,7 @@ if (PORT_A_WIDTH < 9 || PORT_B_WIDTH < 9) begin
assign PORT_A_RD_DATA = `x8_rd_data(DOA); assign PORT_A_RD_DATA = `x8_rd_data(DOA);
assign PORT_B_RD_DATA = `x8_rd_data(DOB); assign PORT_B_RD_DATA = `x8_rd_data(DOB);
DP #( DPB #(
`INIT(init_slice_x8) `INIT(init_slice_x8)
.READ_MODE0(1'b0), .READ_MODE0(1'b0),
.READ_MODE1(1'b0), .READ_MODE1(1'b0),
@ -232,16 +232,18 @@ if (PORT_A_WIDTH < 9 || PORT_B_WIDTH < 9) begin
.WRITE_MODE1(PORT_B_OPTION_WRITE_MODE), .WRITE_MODE1(PORT_B_OPTION_WRITE_MODE),
.BIT_WIDTH_0(`x8_width(PORT_A_WIDTH)), .BIT_WIDTH_0(`x8_width(PORT_A_WIDTH)),
.BIT_WIDTH_1(`x8_width(PORT_B_WIDTH)), .BIT_WIDTH_1(`x8_width(PORT_B_WIDTH)),
.BLK_SEL(3'b000), .BLK_SEL_0(3'b000),
.BLK_SEL_1(3'b000),
.RESET_MODE(OPTION_RESET_MODE), .RESET_MODE(OPTION_RESET_MODE),
) _TECHMAP_REPLACE_ ( ) _TECHMAP_REPLACE_ (
.BLKSEL(3'b000), .BLKSELA(3'b000),
.BLKSELB(3'b000),
.CLKA(PORT_A_CLK), .CLKA(PORT_A_CLK),
.CEA(PORT_A_CLK_EN), .CEA(PORT_A_CLK_EN),
.WREA(WREA), .WREA(WREA),
.RESETA(RSTA), .RESETA(RSTA),
.OCEA(1'b0), .OCEA(1'b1),
.ADA(ADA), .ADA(ADA),
.DIA(DIA), .DIA(DIA),
.DOA(DOA), .DOA(DOA),
@ -250,7 +252,7 @@ if (PORT_A_WIDTH < 9 || PORT_B_WIDTH < 9) begin
.CEB(PORT_B_CLK_EN), .CEB(PORT_B_CLK_EN),
.WREB(WREB), .WREB(WREB),
.RESETB(RSTB), .RESETB(RSTB),
.OCEB(1'b0), .OCEB(1'b1),
.ADB(ADB), .ADB(ADB),
.DIB(DIB), .DIB(DIB),
.DOB(DOB), .DOB(DOB),
@ -266,7 +268,7 @@ end else begin
assign PORT_A_RD_DATA = DOA; assign PORT_A_RD_DATA = DOA;
assign PORT_B_RD_DATA = DOB; assign PORT_B_RD_DATA = DOB;
DPX9 #( DPX9B #(
`INIT(init_slice_x9) `INIT(init_slice_x9)
.READ_MODE0(1'b0), .READ_MODE0(1'b0),
.READ_MODE1(1'b0), .READ_MODE1(1'b0),
@ -274,16 +276,18 @@ end else begin
.WRITE_MODE1(PORT_B_OPTION_WRITE_MODE), .WRITE_MODE1(PORT_B_OPTION_WRITE_MODE),
.BIT_WIDTH_0(PORT_A_WIDTH), .BIT_WIDTH_0(PORT_A_WIDTH),
.BIT_WIDTH_1(PORT_B_WIDTH), .BIT_WIDTH_1(PORT_B_WIDTH),
.BLK_SEL(3'b000), .BLK_SEL_0(3'b000),
.BLK_SEL_1(3'b000),
.RESET_MODE(OPTION_RESET_MODE), .RESET_MODE(OPTION_RESET_MODE),
) _TECHMAP_REPLACE_ ( ) _TECHMAP_REPLACE_ (
.BLKSEL(3'b000), .BLKSELA(3'b000),
.BLKSELB(3'b000),
.CLKA(PORT_A_CLK), .CLKA(PORT_A_CLK),
.CEA(PORT_A_CLK_EN), .CEA(PORT_A_CLK_EN),
.WREA(WREA), .WREA(WREA),
.RESETA(RSTA), .RESETA(RSTA),
.OCEA(1'b0), .OCEA(1'b1),
.ADA(ADA), .ADA(ADA),
.DIA(DIA), .DIA(DIA),
.DOA(DOA), .DOA(DOA),
@ -292,7 +296,7 @@ end else begin
.CEB(PORT_B_CLK_EN), .CEB(PORT_B_CLK_EN),
.WREB(WREB), .WREB(WREB),
.RESETB(RSTB), .RESETB(RSTB),
.OCEB(1'b0), .OCEB(1'b1),
.ADB(ADB), .ADB(ADB),
.DIB(DIB), .DIB(DIB),
.DOB(DOB), .DOB(DOB),
@ -344,28 +348,28 @@ if (PORT_W_WIDTH < 9 || PORT_R_WIDTH < 9) begin
assign PORT_R_RD_DATA = `x8_rd_data(DO); assign PORT_R_RD_DATA = `x8_rd_data(DO);
SDP #( SDPB #(
`INIT(init_slice_x8) `INIT(init_slice_x8)
.READ_MODE(1'b0), .READ_MODE(1'b0),
.BIT_WIDTH_0(`x8_width(PORT_W_WIDTH)), .BIT_WIDTH_0(`x8_width(PORT_W_WIDTH)),
.BIT_WIDTH_1(`x8_width(PORT_R_WIDTH)), .BIT_WIDTH_1(`x8_width(PORT_R_WIDTH)),
.BLK_SEL(3'b000), .BLK_SEL_0(3'b000),
.BLK_SEL_1(3'b000),
.RESET_MODE(OPTION_RESET_MODE), .RESET_MODE(OPTION_RESET_MODE),
) _TECHMAP_REPLACE_ ( ) _TECHMAP_REPLACE_ (
.BLKSEL(3'b000), .BLKSELA(3'b000),
.BLKSELB(3'b000),
.CLKA(PORT_W_CLK), .CLKA(PORT_W_CLK),
.CEA(PORT_W_CLK_EN), .CEA(PORT_W_CLK_EN),
.WREA(WRE),
.RESETA(1'b0), .RESETA(1'b0),
.ADA(ADW), .ADA(ADW),
.DI(DI), .DI(DI),
.CLKB(PORT_R_CLK), .CLKB(PORT_R_CLK),
.CEB(PORT_R_CLK_EN), .CEB(PORT_R_CLK_EN),
.WREB(1'b0),
.RESETB(RST), .RESETB(RST),
.OCE(1'b0), .OCE(1'b1),
.ADB(PORT_R_ADDR), .ADB(PORT_R_ADDR),
.DO(DO), .DO(DO),
); );
@ -377,28 +381,28 @@ end else begin
assign PORT_R_RD_DATA = DO; assign PORT_R_RD_DATA = DO;
SDPX9 #( SDPX9B #(
`INIT(init_slice_x9) `INIT(init_slice_x9)
.READ_MODE(1'b0), .READ_MODE(1'b0),
.BIT_WIDTH_0(PORT_W_WIDTH), .BIT_WIDTH_0(PORT_W_WIDTH),
.BIT_WIDTH_1(PORT_R_WIDTH), .BIT_WIDTH_1(PORT_R_WIDTH),
.BLK_SEL(3'b000), .BLK_SEL_0(3'b000),
.BLK_SEL_1(3'b000),
.RESET_MODE(OPTION_RESET_MODE), .RESET_MODE(OPTION_RESET_MODE),
) _TECHMAP_REPLACE_ ( ) _TECHMAP_REPLACE_ (
.BLKSEL(3'b000), .BLKSELA(3'b000),
.BLKSELB(3'b000),
.CLKA(PORT_W_CLK), .CLKA(PORT_W_CLK),
.CEA(PORT_W_CLK_EN), .CEA(PORT_W_CLK_EN),
.WREA(WRE),
.RESETA(1'b0), .RESETA(1'b0),
.ADA(ADW), .ADA(ADW),
.DI(DI), .DI(DI),
.CLKB(PORT_R_CLK), .CLKB(PORT_R_CLK),
.CEB(PORT_R_CLK_EN), .CEB(PORT_R_CLK_EN),
.WREB(1'b0),
.RESETB(RST), .RESETB(RST),
.OCE(1'b0), .OCE(1'b1),
.ADB(PORT_R_ADDR), .ADB(PORT_R_ADDR),
.DO(DO), .DO(DO),
); );

View file

@ -130,7 +130,6 @@ struct SynthGowinPass : public ScriptPass
} }
if (args[argidx] == "-json" && argidx+1 < args.size()) { if (args[argidx] == "-json" && argidx+1 < args.size()) {
json_file = args[++argidx]; json_file = args[++argidx];
nobram = true;
continue; continue;
} }
if (args[argidx] == "-run" && argidx+1 < args.size()) { if (args[argidx] == "-run" && argidx+1 < args.size()) {

View file

@ -432,7 +432,7 @@ struct SynthIce40Pass : public ScriptPass
run("ice40_wrapcarry -unwrap"); run("ice40_wrapcarry -unwrap");
run("techmap -map +/ice40/ff_map.v"); run("techmap -map +/ice40/ff_map.v");
run("clean"); run("clean");
run("opt_lut -dlogic SB_CARRY:I0=1:I1=2:CI=3 -dlogic SB_CARRY:CO=3"); run("opt_lut -tech ice40");
} }
if (check_label("map_cells")) if (check_label("map_cells"))

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -ex set -ex

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -ex set -ex
for iter in {1..100} for iter in {1..100}

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -ex set -ex
sed 's/SB_MAC16/SB_MAC16_UUT/; /SB_MAC16_UUT/,/endmodule/ p; d;' < ../cells_sim.v > test_dsp_model_uut.v sed 's/SB_MAC16/SB_MAC16_UUT/; /SB_MAC16_UUT/,/endmodule/ p; d;' < ../cells_sim.v > test_dsp_model_uut.v
if [ ! -f "test_dsp_model_ref.v" ]; then if [ ! -f "test_dsp_model_ref.v" ]; then

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -ex set -ex
for CLKPOL in 0 1; do for CLKPOL in 0 1; do
for ENABLE_EN in 0 1; do for ENABLE_EN in 0 1; do

View file

@ -32,6 +32,10 @@ ram block $__PDPW8KC_ {
cost 64; cost 64;
init no_undef; init no_undef;
port sr "R" { port sr "R" {
# width 2 cannot be supported because of quirks
# of the primitive, and memlib requires us to
# remove width 1 as well
width 4 9 18;
clock posedge; clock posedge;
clken; clken;
option "RESETMODE" "SYNC" { option "RESETMODE" "SYNC" {

View file

@ -38,8 +38,20 @@ endfunction
wire [8:0] DOA; wire [8:0] DOA;
wire [8:0] DOB; wire [8:0] DOB;
wire [8:0] DIA = PORT_A_WR_DATA; wire [8:0] DIA;
wire [8:0] DIB = PORT_B_WR_DATA; wire [8:0] DIB;
case(PORT_A_WIDTH)
1: assign DIA = {7'bx, PORT_A_WR_DATA[0], 1'bx};
2: assign DIA = {3'bx, PORT_A_WR_DATA[1], 2'bx, PORT_A_WR_DATA[0], 2'bx};
default: assign DIA = PORT_A_WR_DATA;
endcase
case(PORT_B_WIDTH)
1: assign DIB = {7'bx, PORT_B_WR_DATA[0], 1'bx};
2: assign DIB = {3'bx, PORT_B_WR_DATA[1], 2'bx, PORT_B_WR_DATA[0], 2'bx};
default: assign DIB = PORT_B_WR_DATA;
endcase
assign PORT_A_RD_DATA = DOA; assign PORT_A_RD_DATA = DOA;
assign PORT_B_RD_DATA = DOB; assign PORT_B_RD_DATA = DOB;

View file

@ -31,9 +31,6 @@ PRIVATE_NAMESPACE_BEGIN
struct QlBramMergeWorker { struct QlBramMergeWorker {
const RTLIL::IdString split_cell_type = ID($__QLF_TDP36K);
const RTLIL::IdString merged_cell_type = ID($__QLF_TDP36K_MERGED);
// can be used to record parameter values that have to match on both sides // can be used to record parameter values that have to match on both sides
typedef dict<RTLIL::IdString, RTLIL::Const> MergeableGroupKeyType; typedef dict<RTLIL::IdString, RTLIL::Const> MergeableGroupKeyType;
@ -42,6 +39,8 @@ struct QlBramMergeWorker {
QlBramMergeWorker(RTLIL::Module* module) : module(module) QlBramMergeWorker(RTLIL::Module* module) : module(module)
{ {
const RTLIL::IdString split_cell_type = ID($__QLF_TDP36K);
for (RTLIL::Cell* cell : module->selected_cells()) for (RTLIL::Cell* cell : module->selected_cells())
{ {
if(cell->type != split_cell_type) continue; if(cell->type != split_cell_type) continue;
@ -125,6 +124,7 @@ struct QlBramMergeWorker {
void merge_brams(RTLIL::Cell* bram1, RTLIL::Cell* bram2) void merge_brams(RTLIL::Cell* bram1, RTLIL::Cell* bram2)
{ {
const RTLIL::IdString merged_cell_type = ID($__QLF_TDP36K_MERGED);
// Create the new cell // Create the new cell
RTLIL::Cell* merged = module->addCell(NEW_ID, merged_cell_type); RTLIL::Cell* merged = module->addCell(NEW_ID, merged_cell_type);

View file

@ -30,10 +30,6 @@ PRIVATE_NAMESPACE_BEGIN
// ============================================================================ // ============================================================================
struct QlDspIORegs : public Pass { struct QlDspIORegs : public Pass {
const std::vector<IdString> ports2del_mult = {ID(load_acc), ID(subtract), ID(acc_fir), ID(dly_b),
ID(saturate_enable), ID(shift_right), ID(round)};
const std::vector<IdString> ports2del_mult_acc = {ID(acc_fir), ID(dly_b)};
SigMap sigmap; SigMap sigmap;
// .......................................... // ..........................................
@ -67,6 +63,11 @@ struct QlDspIORegs : public Pass {
void ql_dsp_io_regs_pass(RTLIL::Module *module) void ql_dsp_io_regs_pass(RTLIL::Module *module)
{ {
static const std::vector<IdString> ports2del_mult = {ID(load_acc), ID(subtract), ID(acc_fir), ID(dly_b),
ID(saturate_enable), ID(shift_right), ID(round)};
static const std::vector<IdString> ports2del_mult_acc = {ID(acc_fir), ID(dly_b)};
sigmap.set(module); sigmap.set(module);
for (auto cell : module->cells()) { for (auto cell : module->cells()) {

View file

@ -60,43 +60,11 @@ struct QlDspSimdPass : public Pass {
// .......................................... // ..........................................
// DSP control and config ports to consider and how to map them to ports
// of the target DSP cell
const std::vector<std::pair<IdString, IdString>> m_DspCfgPorts = {
std::make_pair(ID(clock_i), ID(clk)),
std::make_pair(ID(reset_i), ID(reset)),
std::make_pair(ID(feedback_i), ID(feedback)),
std::make_pair(ID(load_acc_i), ID(load_acc)),
std::make_pair(ID(unsigned_a_i), ID(unsigned_a)),
std::make_pair(ID(unsigned_b_i), ID(unsigned_b)),
std::make_pair(ID(subtract_i), ID(subtract)),
std::make_pair(ID(output_select_i), ID(output_select)),
std::make_pair(ID(saturate_enable_i), ID(saturate_enable)),
std::make_pair(ID(shift_right_i), ID(shift_right)),
std::make_pair(ID(round_i), ID(round)),
std::make_pair(ID(register_inputs_i), ID(register_inputs))
};
const int m_ModeBitsSize = 80; const int m_ModeBitsSize = 80;
// DSP data ports and how to map them to ports of the target DSP cell
const std::vector<std::pair<IdString, IdString>> m_DspDataPorts = {
std::make_pair(ID(a_i), ID(a)),
std::make_pair(ID(b_i), ID(b)),
std::make_pair(ID(acc_fir_i), ID(acc_fir)),
std::make_pair(ID(z_o), ID(z)),
std::make_pair(ID(dly_b_o), ID(dly_b))
};
// DSP parameters // DSP parameters
const std::vector<std::string> m_DspParams = {"COEFF_3", "COEFF_2", "COEFF_1", "COEFF_0"}; const std::vector<std::string> m_DspParams = {"COEFF_3", "COEFF_2", "COEFF_1", "COEFF_0"};
// Source DSP cell type (SISD)
const IdString m_SisdDspType = ID(dsp_t1_10x9x32);
// Target DSP cell types for the SIMD mode
const IdString m_SimdDspType = ID(QL_DSP2);
/// Temporary SigBit to SigBit helper map. /// Temporary SigBit to SigBit helper map.
SigMap sigmap; SigMap sigmap;
@ -106,6 +74,38 @@ struct QlDspSimdPass : public Pass {
{ {
log_header(a_Design, "Executing QL_DSP_SIMD pass.\n"); log_header(a_Design, "Executing QL_DSP_SIMD pass.\n");
// DSP control and config ports to consider and how to map them to ports
// of the target DSP cell
static const std::vector<std::pair<IdString, IdString>> m_DspCfgPorts = {
std::make_pair(ID(clock_i), ID(clk)),
std::make_pair(ID(reset_i), ID(reset)),
std::make_pair(ID(feedback_i), ID(feedback)),
std::make_pair(ID(load_acc_i), ID(load_acc)),
std::make_pair(ID(unsigned_a_i), ID(unsigned_a)),
std::make_pair(ID(unsigned_b_i), ID(unsigned_b)),
std::make_pair(ID(subtract_i), ID(subtract)),
std::make_pair(ID(output_select_i), ID(output_select)),
std::make_pair(ID(saturate_enable_i), ID(saturate_enable)),
std::make_pair(ID(shift_right_i), ID(shift_right)),
std::make_pair(ID(round_i), ID(round)),
std::make_pair(ID(register_inputs_i), ID(register_inputs))
};
// DSP data ports and how to map them to ports of the target DSP cell
static const std::vector<std::pair<IdString, IdString>> m_DspDataPorts = {
std::make_pair(ID(a_i), ID(a)),
std::make_pair(ID(b_i), ID(b)),
std::make_pair(ID(acc_fir_i), ID(acc_fir)),
std::make_pair(ID(z_o), ID(z)),
std::make_pair(ID(dly_b_o), ID(dly_b))
};
// Source DSP cell type (SISD)
static const IdString m_SisdDspType = ID(dsp_t1_10x9x32);
// Target DSP cell types for the SIMD mode
static const IdString m_SimdDspType = ID(QL_DSP2);
// Parse args // Parse args
extra_args(a_Args, 1, a_Design); extra_args(a_Args, 1, a_Design);
@ -126,7 +126,7 @@ struct QlDspSimdPass : public Pass {
continue; continue;
// Add to a group // Add to a group
const auto key = getDspConfig(cell); const auto key = getDspConfig(cell, m_DspCfgPorts);
groups[key].push_back(cell); groups[key].push_back(cell);
} }
@ -255,11 +255,11 @@ struct QlDspSimdPass : public Pass {
} }
/// Given a DSP cell populates and returns a DspConfig struct for it. /// Given a DSP cell populates and returns a DspConfig struct for it.
DspConfig getDspConfig(RTLIL::Cell *a_Cell) DspConfig getDspConfig(RTLIL::Cell *a_Cell, const std::vector<std::pair<IdString, IdString>> &dspCfgPorts)
{ {
DspConfig config; DspConfig config;
for (const auto &it : m_DspCfgPorts) { for (const auto &it : dspCfgPorts) {
auto port = it.first; auto port = it.first;
// Port unconnected // Port unconnected

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -e set -e

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -ex set -ex
unisims=/opt/Xilinx/Vivado/2014.4/data/verilog/src/unisims unisims=/opt/Xilinx/Vivado/2014.4/data/verilog/src/unisims

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -ex set -ex
if [ -z $ISE_DIR ]; then if [ -z $ISE_DIR ]; then
ISE_DIR=/opt/Xilinx/ISE/14.7 ISE_DIR=/opt/Xilinx/ISE/14.7

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -ex set -ex
if [ -z $ISE_DIR ]; then if [ -z $ISE_DIR ]; then
ISE_DIR=/opt/Xilinx/ISE/14.7 ISE_DIR=/opt/Xilinx/ISE/14.7

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -ex set -ex
if [ -z $VIVADO_DIR ]; then if [ -z $VIVADO_DIR ]; then
VIVADO_DIR=/opt/Xilinx/Vivado/2019.1 VIVADO_DIR=/opt/Xilinx/Vivado/2019.1

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -e set -e

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -e set -e

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
OPTIND=1 OPTIND=1
seed="" # default to no seed specified seed="" # default to no seed specified

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -e set -e
for x in *.ys; do for x in *.ys; do
echo "Running $x.." echo "Running $x.."

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -e set -e
../../yosys -qq -f verilog -p "proc; opt; memory -nomap -bram temp/brams_${2}.txt; opt -fast -full" \ ../../yosys -qq -f verilog -p "proc; opt; memory -nomap -bram temp/brams_${2}.txt; opt -fast -full" \
-l temp/synth_${1}_${2}.log -o temp/synth_${1}_${2}.v temp/brams_${1}.v -l temp/synth_${1}_${2}.log -o temp/synth_${1}_${2}.v temp/brams_${1}.v

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
# run this test many times: # run this test many times:
# MAKE="make -j8" time bash -c 'for ((i=0; i<100; i++)); do echo "-- $i --"; bash run-test.sh || exit 1; done' # MAKE="make -j8" time bash -c 'for ((i=0; i<100; i++)); do echo "-- $i --"; bash run-test.sh || exit 1; done'

1
tests/cxxrtl/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
cxxrtl-test-*

13
tests/cxxrtl/run-test.sh Executable file
View file

@ -0,0 +1,13 @@
#!/bin/bash
set -ex
run_subtest () {
local subtest=$1; shift
${CC:-gcc} -std=c++11 -O2 -o cxxrtl-test-${subtest} -I../../backends/cxxrtl/runtime test_${subtest}.cc -lstdc++
./cxxrtl-test-${subtest}
}
run_subtest value
run_subtest value_fuzz

View file

@ -0,0 +1,52 @@
#include <cassert>
#include <cstdint>
#include "cxxrtl/cxxrtl.h"
int main()
{
{
// shl exceeding Bits should be masked
cxxrtl::value<6> a(1u);
cxxrtl::value<6> b(8u);
cxxrtl::value<6> c = a.shl(b);
assert(c.get<uint64_t>() == 0);
}
{
// sshr of unreasonably large size should sign extend correctly
cxxrtl::value<64> a(0u, 0x80000000u);
cxxrtl::value<64> b(0u, 1u);
cxxrtl::value<64> c = a.sshr(b);
assert(c.get<uint64_t>() == 0xffffffffffffffffu);
}
{
// sshr of exteeding Bits should sign extend correctly
cxxrtl::value<8> a(0x80u);
cxxrtl::value<8> b(10u);
cxxrtl::value<8> c = a.sshr(b);
assert(c.get<uint64_t>() == 0xffu);
}
{
// Sign extension should occur correctly
cxxrtl::value<64> a(0x23456789u, 0x8abcdef1u);
cxxrtl::value<8> b(32u);
cxxrtl::value<64> c = a.sshr(b);
assert(c.get<uint64_t>() == 0xffffffff8abcdef1u);
}
{
// ctlz should work with Bits that are a multiple of chunk size
cxxrtl::value<32> a(0x00040000u);
assert(a.ctlz() == 13);
}
{
// bmux clears top bits of result
cxxrtl::value<8> val(0x1fu);
cxxrtl::value<1> sel(0u);
assert(val.template bmux<4>(sel).get<uint64_t>() == 0xfu);
}
}

View file

@ -0,0 +1,251 @@
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <exception>
#include <limits>
#include <random>
#include <type_traits>
#include "cxxrtl/cxxrtl.h"
template<typename T>
T rand_int(T min = std::numeric_limits<T>::min(), T max = std::numeric_limits<T>::max())
{
static_assert(std::is_integral<T>::value, "T must be an integral type.");
static_assert(!std::is_same<T, signed char>::value && !std::is_same<T, unsigned char>::value,
"Using char with uniform_int_distribution is undefined behavior.");
static std::mt19937 generator = [] {
std::random_device rd;
std::mt19937 mt{rd()};
return mt;
}();
std::uniform_int_distribution<T> dist(min, max);
return dist(generator);
}
struct BinaryOperationBase
{
void tweak_input(uint64_t &a, uint64_t &b) {}
};
template<size_t Bits, typename Operation>
void test_binary_operation_for_bitsize(Operation &op)
{
constexpr int iteration_count = 10000000;
constexpr uint64_t mask = std::numeric_limits<uint64_t>::max() >> (64 - Bits);
using chunk_type = typename cxxrtl::value<Bits>::chunk::type;
constexpr size_t chunk_bits = cxxrtl::value<Bits>::chunk::bits;
for (int iteration = 0; iteration < iteration_count; iteration++) {
uint64_t ia = rand_int<uint64_t>() >> (64 - Bits);
uint64_t ib = rand_int<uint64_t>() >> (64 - Bits);
op.tweak_input(ia, ib);
cxxrtl::value<Bits> va, vb;
for (size_t i = 0; i * chunk_bits < Bits; i++) {
va.data[i] = (chunk_type)(ia >> (i * chunk_bits));
vb.data[i] = (chunk_type)(ib >> (i * chunk_bits));
}
uint64_t iresult = op.reference_impl(Bits, ia, ib) & mask;
cxxrtl::value<Bits> vresult = op.template testing_impl<Bits>(va, vb);
for (size_t i = 0; i * chunk_bits < Bits; i++) {
if ((chunk_type)(iresult >> (i * chunk_bits)) != vresult.data[i]) {
std::printf("Test failure:\n");
std::printf("Bits: %i\n", Bits);
std::printf("a: %016lx\n", ia);
std::printf("b: %016lx\n", ib);
std::printf("iresult: %016lx\n", iresult);
std::printf("vresult: %016lx\n", vresult.template get<uint64_t>());
std::terminate();
}
}
}
std::printf("Test passed @ Bits = %i.\n", Bits);
}
template<typename Operation>
void test_binary_operation(Operation &op)
{
// Test at a variety of bitwidths
test_binary_operation_for_bitsize<8>(op);
test_binary_operation_for_bitsize<32>(op);
test_binary_operation_for_bitsize<42>(op);
test_binary_operation_for_bitsize<63>(op);
test_binary_operation_for_bitsize<64>(op);
}
template<typename Operation>
struct UnaryOperationWrapper : BinaryOperationBase
{
Operation &op;
UnaryOperationWrapper(Operation &op) : op(op) {}
uint64_t reference_impl(size_t bits, uint64_t a, uint64_t b)
{
return op.reference_impl(bits, a);
}
template<size_t Bits>
cxxrtl::value<Bits> testing_impl(cxxrtl::value<Bits> a, cxxrtl::value<Bits> b)
{
return op.template testing_impl<Bits>(a);
}
};
template<typename Operation>
void test_unary_operation(Operation &op)
{
UnaryOperationWrapper<Operation> wrapped(op);
test_binary_operation(wrapped);
}
struct ShlTest : BinaryOperationBase
{
ShlTest()
{
std::printf("Randomized tests for value::shl:\n");
test_binary_operation(*this);
}
uint64_t reference_impl(size_t bits, uint64_t a, uint64_t b)
{
return b >= 64 ? 0 : a << b;
}
template<size_t Bits>
cxxrtl::value<Bits> testing_impl(cxxrtl::value<Bits> a, cxxrtl::value<Bits> b)
{
return a.shl(b);
}
void tweak_input(uint64_t &, uint64_t &b)
{
b &= 0x7f;
}
} shl;
struct ShrTest : BinaryOperationBase
{
ShrTest()
{
std::printf("Randomized tests for value::shr:\n");
test_binary_operation(*this);
}
uint64_t reference_impl(size_t bits, uint64_t a, uint64_t b)
{
return b >= 64 ? 0 : a >> b;
}
template<size_t Bits>
cxxrtl::value<Bits> testing_impl(cxxrtl::value<Bits> a, cxxrtl::value<Bits> b)
{
return a.shr(b);
}
void tweak_input(uint64_t &, uint64_t &b)
{
b &= 0x7f;
}
} shr;
struct SshrTest : BinaryOperationBase
{
SshrTest()
{
std::printf("Randomized tests for value::sshr:\n");
test_binary_operation(*this);
}
uint64_t reference_impl(size_t bits, uint64_t a, uint64_t b)
{
int64_t sa = (int64_t)(a << (64 - bits));
return sa >> (b >= bits ? 63 : (b + 64 - bits));
}
template<size_t Bits>
cxxrtl::value<Bits> testing_impl(cxxrtl::value<Bits> a, cxxrtl::value<Bits> b)
{
return a.sshr(b);
}
void tweak_input(uint64_t &, uint64_t &b)
{
b &= 0x7f;
}
} sshr;
struct AddTest : BinaryOperationBase
{
AddTest()
{
std::printf("Randomized tests for value::add:\n");
test_binary_operation(*this);
}
uint64_t reference_impl(size_t bits, uint64_t a, uint64_t b)
{
return a + b;
}
template<size_t Bits>
cxxrtl::value<Bits> testing_impl(cxxrtl::value<Bits> a, cxxrtl::value<Bits> b)
{
return a.add(b);
}
} add;
struct SubTest : BinaryOperationBase
{
SubTest()
{
std::printf("Randomized tests for value::sub:\n");
test_binary_operation(*this);
}
uint64_t reference_impl(size_t bits, uint64_t a, uint64_t b)
{
return a - b;
}
template<size_t Bits>
cxxrtl::value<Bits> testing_impl(cxxrtl::value<Bits> a, cxxrtl::value<Bits> b)
{
return a.sub(b);
}
} sub;
struct CtlzTest
{
CtlzTest()
{
std::printf("Randomized tests for value::ctlz:\n");
test_unary_operation(*this);
}
uint64_t reference_impl(size_t bits, uint64_t a)
{
if (a == 0)
return bits;
return __builtin_clzl(a) - (64 - bits);
}
template<size_t Bits>
cxxrtl::value<Bits> testing_impl(cxxrtl::value<Bits> a)
{
size_t result = a.ctlz();
return cxxrtl::value<Bits>((cxxrtl::chunk_t)result);
}
} ctlz;
int main()
{
}

View file

@ -2,8 +2,13 @@
int main() int main()
{ {
struct : public performer {
int64_t vlog_time() const override { return 1; }
void on_print(const lazy_fmt &output, const cxxrtl::metadata_map &) override { std::cerr << output(); }
} performer;
cxxrtl_design::p_always__full uut; cxxrtl_design::p_always__full uut;
uut.p_clk.set(!uut.p_clk); uut.p_clk.set(!uut.p_clk);
uut.step(); uut.step(&performer);
return 0; return 0;
} }

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -ex set -ex

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
# run this test many times: # run this test many times:
# time bash -c 'for ((i=0; i<100; i++)); do echo "-- $i --"; bash run-test.sh || exit 1; done' # time bash -c 'for ((i=0; i<100; i++)); do echo "-- $i --"; bash run-test.sh || exit 1; done'

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
OPTIND=1 OPTIND=1
seed="" # default to no seed specified seed="" # default to no seed specified

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -e set -e
for x in *.lib; do for x in *.lib; do

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -e set -e
for x in *.v; do for x in *.v; do
echo "Running $x.." echo "Running $x.."

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -e set -e

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -eu set -eu
OPTIND=1 OPTIND=1

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -e set -e

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -eu set -eu
source ../gen-tests-makefile.sh source ../gen-tests-makefile.sh
run_tests --yosys-scripts run_tests --yosys-scripts

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
# run this test many times: # run this test many times:
# time bash -c 'for ((i=0; i<100; i++)); do echo "-- $i --"; bash run-test.sh || exit 1; done' # time bash -c 'for ((i=0; i<100; i++)); do echo "-- $i --"; bash run-test.sh || exit 1; done'

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -e set -e
for x in *.ys; do for x in *.ys; do
echo "Running $x.." echo "Running $x.."

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -e set -e
OPTIND=1 OPTIND=1

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -e set -e
for x in *.ys; do for x in *.ys; do
echo "Running $x.." echo "Running $x.."

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -e set -e
for x in *.ys; do for x in *.ys; do
echo "Running $x.." echo "Running $x.."

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
# run this test many times: # run this test many times:
# time bash -c 'for ((i=0; i<100; i++)); do echo "-- $i --"; bash run-test.sh || exit 1; done' # time bash -c 'for ((i=0; i<100; i++)); do echo "-- $i --"; bash run-test.sh || exit 1; done'

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
OPTIND=1 OPTIND=1
seed="" # default to no seed specified seed="" # default to no seed specified

View file

@ -0,0 +1,20 @@
module test (
offset_i,
data_o,
data_ref_o
);
input wire [ 2:0] offset_i;
output reg [15:0] data_o;
output reg [15:0] data_ref_o;
always @(*) begin
// defaults
data_o = '0;
data_ref_o = '0;
// partial assigns
data_ref_o[offset_i+:4] = 4'b1111; // unsigned
data_o[offset_i+:4] = 1'sb1; // sign extension to 4'b1111
end
endmodule

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
OPTIND=1 OPTIND=1
seed="" # default to no seed specified seed="" # default to no seed specified

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
cat > $1.tpl <<EOT cat > $1.tpl <<EOT
%module main %module main

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -ex set -ex

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -ex set -ex

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
# Run a simple test with a .ys file # Run a simple test with a .ys file

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
TESTNAME=$1 TESTNAME=$1

View file

@ -45,12 +45,12 @@ module top;
localparam W = 10; localparam W = 10;
typedef T U; typedef T U;
typedef logic [W-1:0] V; typedef logic [W-1:0] V;
struct packed { typedef struct packed {
logic [W-1:0] x; // width 10 logic [W-1:0] x; // width 10
U y; // width 5 U y; // width 5
V z; // width 10 V z; // width 10
} shadow; } shadow_t;
// This currently only works as long as long as shadow is not typedef'ed shadow_t shadow;
always @(*) assert($bits(shadow.x) == 10); always @(*) assert($bits(shadow.x) == 10);
always @(*) assert($bits(shadow.y) == 5); always @(*) assert($bits(shadow.y) == 5);
always @(*) assert($bits(shadow.z) == 10); always @(*) assert($bits(shadow.z) == 10);

View file

@ -1,3 +1,3 @@
#!/bin/bash #!/usr/bin/env bash
exec ../tools/autotest.sh -G -j $@ -p 'proc; opt; memory -nomap; techmap -map ../mem_simple_4x1_map.v;; techmap; opt; abc;; stat' mem_simple_4x1_uut.v exec ../tools/autotest.sh -G -j $@ -p 'proc; opt; memory -nomap; techmap -map ../mem_simple_4x1_map.v;; techmap; opt; abc;; stat' mem_simple_4x1_uut.v

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -ex set -ex
../../yosys -q -o async_syn.v -r uut -p 'synth; rename uut syn' async.v ../../yosys -q -o async_syn.v -r uut -p 'synth; rename uut syn' async.v
../../yosys -q -o async_prp.v -r uut -p 'prep; rename uut prp' async.v ../../yosys -q -o async_prp.v -r uut -p 'prep; rename uut prp' async.v

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
trap 'echo "ERROR in chparam.sh" >&2; exit 1' ERR trap 'echo "ERROR in chparam.sh" >&2; exit 1' ERR

View file

@ -69,6 +69,24 @@ design -copy-from gate -as gate gate
miter -equiv -make_assert -make_outcmp -flatten gold gate equiv miter -equiv -make_assert -make_outcmp -flatten gold gate equiv
sat -prove-asserts -seq 10 -show-public -verify -set-init-zero equiv sat -prove-asserts -seq 10 -show-public -verify -set-init-zero equiv
### For-loop select, one dynamic input, (* nowrshmsk *)
design -reset
read_verilog ./dynamic_part_select/forloop_select_nowrshmsk.v
proc
rename -top gold
design -stash gold
read_verilog ./dynamic_part_select/forloop_select_gate.v
proc
rename -top gate
design -stash gate
design -copy-from gold -as gold gold
design -copy-from gate -as gate gate
miter -equiv -make_assert -make_outcmp -flatten gold gate equiv
sat -prove-asserts -seq 10 -show-public -verify -set-init-zero equiv
#### Double loop (part-select, reset) ### #### Double loop (part-select, reset) ###
design -reset design -reset
read_verilog ./dynamic_part_select/reset_test.v read_verilog ./dynamic_part_select/reset_test.v

View file

@ -0,0 +1,20 @@
`default_nettype none
module forloop_select #(parameter WIDTH=16, SELW=4, CTRLW=$clog2(WIDTH), DINW=2**SELW)
(input wire clk,
input wire [CTRLW-1:0] ctrl,
input wire [DINW-1:0] din,
input wire en,
(* nowrshmsk *)
output reg [WIDTH-1:0] dout);
reg [SELW:0] sel;
localparam SLICE = WIDTH/(SELW**2);
always @(posedge clk)
begin
if (en) begin
for (sel = 0; sel <= 4'hf; sel=sel+1'b1)
dout[(ctrl*sel)+:SLICE] <= din;
end
end
endmodule

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
fail() { fail() {
echo "$1" >&2 echo "$1" >&2

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -ex set -ex
../../yosys -q -p 'read_verilog -formal smtlib2_module.v; prep; write_smt2 smtlib2_module.smt2' ../../yosys -q -p 'read_verilog -formal smtlib2_module.v; prep; write_smt2 smtlib2_module.smt2'
sed 's/; SMT-LIBv2 description generated by Yosys .*/; SMT-LIBv2 description generated by Yosys $VERSION/;s/ *$//' smtlib2_module.smt2 > smtlib2_module-filtered.smt2 sed 's/; SMT-LIBv2 description generated by Yosys .*/; SMT-LIBv2 description generated by Yosys $VERSION/;s/ *$//' smtlib2_module.smt2 > smtlib2_module-filtered.smt2

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
trap 'echo "ERROR in sv_implicit_ports.sh" >&2; exit 1' ERR trap 'echo "ERROR in sv_implicit_ports.sh" >&2; exit 1' ERR

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
trap 'echo "ERROR in svalways.sh" >&2; exit 1' ERR trap 'echo "ERROR in svalways.sh" >&2; exit 1' ERR

10
tests/verific/clocking.ys Normal file
View file

@ -0,0 +1,10 @@
read -sv <<EOT
module test(input foo);
always @(*) assert(foo);
endmodule
EOT
verific -import test
prep
select -assert-count 1 t:$assert

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
run() { run() {
alt=$1 alt=$1
@ -15,7 +15,7 @@ run() {
-p "read_verilog dynamic_range_lhs.v" \ -p "read_verilog dynamic_range_lhs.v" \
-p "proc" \ -p "proc" \
-p "equiv_make gold gate equiv" \ -p "equiv_make gold gate equiv" \
-p "equiv_simple" \ -p "equiv_simple -undef" \
-p "equiv_status -assert" -p "equiv_status -assert"
} }

View file

@ -1,12 +1,12 @@
module gate( module gate(
output reg [`LEFT:`RIGHT] out_u, out_s,
(* nowrshmsk = `ALT *) (* nowrshmsk = `ALT *)
output reg [`LEFT:`RIGHT] out_u, out_s,
input wire data, input wire data,
input wire [1:0] sel1, sel2 input wire [1:0] sel1, sel2
); );
always @* begin always @* begin
out_u = 0; out_u = 'x;
out_s = 0; out_s = 'x;
case (`SPAN) case (`SPAN)
1: begin 1: begin
out_u[sel1*sel2] = data; out_u[sel1*sel2] = data;
@ -43,8 +43,8 @@ task set;
out_s[b] = data; out_s[b] = data;
endtask endtask
always @* begin always @* begin
out_u = 0; out_u = 'x;
out_s = 0; out_s = 'x;
case (sel1*sel2) case (sel1*sel2)
2'b00: set(0, 0); 2'b00: set(0, 0);
2'b01: set(1, 1); 2'b01: set(1, 1);

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -ex set -ex

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
set -e set -e
source common.sh source common.sh

Some files were not shown because too many files have changed in this diff Show more