mirror of https://github.com/YosysHQ/yosys.git
Docs: updating to current 'master'
Pulling for #4133 and removing related TODO.
This commit is contained in:
commit
65bb0d3059
|
@ -8,7 +8,7 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
- { id: macos-11, name: 'Big Sur' }
|
||||
- { id: macos-13, name: 'Ventura' }
|
||||
cpp_std:
|
||||
- 'c++11'
|
||||
- 'c++17'
|
||||
|
|
18
CHANGELOG
18
CHANGELOG
|
@ -2,12 +2,26 @@
|
|||
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
|
||||
--------------------------
|
||||
* New commands and options
|
||||
* New commands and options
|
||||
- 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 option "-cmp2softlogic" to synth_lattice.
|
||||
|
|
5
Makefile
5
Makefile
|
@ -141,7 +141,7 @@ LDLIBS += -lrt
|
|||
endif
|
||||
endif
|
||||
|
||||
YOSYS_VER := 0.36+13
|
||||
YOSYS_VER := 0.37+21
|
||||
|
||||
# Note: We arrange for .gitcommit to contain the (short) commit hash in
|
||||
# tarballs generated with git-archive(1) using .gitattributes. The git repo
|
||||
|
@ -157,7 +157,7 @@ endif
|
|||
OBJS = kernel/version_$(GIT_REV).o
|
||||
|
||||
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
|
||||
#
|
||||
|
@ -888,6 +888,7 @@ endif
|
|||
+cd tests/verilog && bash run-test.sh
|
||||
+cd tests/xprop && bash run-test.sh $(SEEDOPT)
|
||||
+cd tests/fmt && bash run-test.sh
|
||||
+cd tests/cxxrtl && bash run-test.sh
|
||||
@echo ""
|
||||
@echo " Passed \"make test\"."
|
||||
@echo ""
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
|
|
|
@ -628,6 +628,20 @@ std::string escape_cxx_string(const std::string &input)
|
|||
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>
|
||||
std::string get_hdl_name(T *object)
|
||||
{
|
||||
|
@ -1058,9 +1072,45 @@ struct CxxrtlWorker {
|
|||
dump_sigspec_rhs(cell->getPort(ID::EN));
|
||||
f << " == value<1>{1u}) {\n";
|
||||
inc_indent();
|
||||
f << indent << print_output;
|
||||
fmt.emit_cxxrtl(f, [this](const RTLIL::SigSpec &sig) { dump_sigspec_rhs(sig); });
|
||||
f << ";\n";
|
||||
dict<std::string, RTLIL::SigSpec> fmt_args;
|
||||
f << indent << "struct : public lazy_fmt {\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();
|
||||
f << indent << "}\n";
|
||||
}
|
||||
|
@ -1277,20 +1327,29 @@ struct CxxrtlWorker {
|
|||
log_assert(!for_debug);
|
||||
|
||||
// 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 = ";
|
||||
dump_sigspec_rhs(cell->getPort(ID::EN));
|
||||
f << ".concat(";
|
||||
dump_sigspec_rhs(cell->getPort(ID::ARGS));
|
||||
f << ").val();\n";
|
||||
if (!cell->getParam(ID::TRG_ENABLE).as_bool()) { // async $print cell
|
||||
f << indent << "auto " << mangle(cell) << "_curr = ";
|
||||
dump_sigspec_rhs(cell->getPort(ID::EN));
|
||||
f << ".concat(";
|
||||
dump_sigspec_rhs(cell->getPort(ID::ARGS));
|
||||
f << ").val();\n";
|
||||
|
||||
f << indent << "if (" << mangle(cell) << " != " << mangle(cell) << "_curr) {\n";
|
||||
inc_indent();
|
||||
dump_print(cell);
|
||||
f << indent << mangle(cell) << " = " << mangle(cell) << "_curr;\n";
|
||||
dec_indent();
|
||||
f << indent << "}\n";
|
||||
f << indent << "if (" << mangle(cell) << " != " << mangle(cell) << "_curr) {\n";
|
||||
inc_indent();
|
||||
dump_print(cell);
|
||||
f << indent << mangle(cell) << " = " << mangle(cell) << "_curr;\n";
|
||||
dec_indent();
|
||||
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
|
||||
} else if (is_ff_cell(cell->type)) {
|
||||
log_assert(!for_debug);
|
||||
|
@ -1471,11 +1530,11 @@ struct CxxrtlWorker {
|
|||
};
|
||||
if (buffered_inputs) {
|
||||
// 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";
|
||||
assign_from_outputs(/*cell_converged=*/false);
|
||||
} else {
|
||||
f << indent << "if (" << mangle(cell) << access << "eval()) {\n";
|
||||
f << indent << "if (" << mangle(cell) << access << "eval(performer)) {\n";
|
||||
inc_indent();
|
||||
assign_from_outputs(/*cell_converged=*/true);
|
||||
dec_indent();
|
||||
|
@ -1988,6 +2047,11 @@ struct CxxrtlWorker {
|
|||
}
|
||||
}
|
||||
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))
|
||||
continue;
|
||||
f << indent << mangle(cell);
|
||||
|
@ -2101,19 +2165,19 @@ struct CxxrtlWorker {
|
|||
if (wire_type.type == WireType::MEMBER && edge_wires[wire])
|
||||
f << indent << "prev_" << mangle(wire) << " = " << mangle(wire) << ";\n";
|
||||
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))) {
|
||||
for (auto &mem : mod_memories[module]) {
|
||||
if (!writable_memories.count({module, mem.memid}))
|
||||
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()) {
|
||||
if (is_internal_cell(cell->type))
|
||||
continue;
|
||||
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";
|
||||
|
@ -2132,7 +2196,7 @@ struct CxxrtlWorker {
|
|||
if (!metadata_item.first.isPublic())
|
||||
continue;
|
||||
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;
|
||||
}
|
||||
f << indent << "{ " << escape_cxx_string(metadata_item.first.str().substr(1)) << ", ";
|
||||
|
@ -2353,20 +2417,27 @@ struct CxxrtlWorker {
|
|||
dump_reset_method(module);
|
||||
f << indent << "}\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);
|
||||
f << indent << "}\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);
|
||||
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) {
|
||||
f << "\n";
|
||||
f << indent << "void debug_info(debug_items &items, std::string path = \"\") override {\n";
|
||||
dump_debug_info_method(module);
|
||||
f << indent << "}\n";
|
||||
f << "\n";
|
||||
}
|
||||
f << "\n";
|
||||
f << indent << "static std::unique_ptr<" << mangle(module);
|
||||
f << template_params(module, /*is_decl=*/false) << "> ";
|
||||
f << "create(std::string name, metadata_map parameters, metadata_map attributes);\n";
|
||||
|
@ -2410,11 +2481,11 @@ struct CxxrtlWorker {
|
|||
f << "\n";
|
||||
bool has_cells = false;
|
||||
for (auto cell : module->cells()) {
|
||||
if (cell->type == ID($print) && !cell->getParam(ID::TRG_ENABLE).as_bool()) {
|
||||
// comb $print cell -- store the last EN/ARGS values to know when they change.
|
||||
dump_attrs(cell);
|
||||
// Certain $print cells have additional state, which requires storage.
|
||||
if (cell->type == ID($print) && !cell->getParam(ID::TRG_ENABLE).as_bool())
|
||||
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))
|
||||
continue;
|
||||
dump_attrs(cell);
|
||||
|
@ -2443,8 +2514,18 @@ struct CxxrtlWorker {
|
|||
f << indent << "};\n";
|
||||
f << "\n";
|
||||
f << indent << "void reset() override;\n";
|
||||
f << indent << "bool eval() override;\n";
|
||||
f << indent << "bool commit() override;\n";
|
||||
f << "\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_eval) {
|
||||
f << "\n";
|
||||
|
@ -2473,27 +2554,23 @@ struct CxxrtlWorker {
|
|||
dump_reset_method(module);
|
||||
f << indent << "}\n";
|
||||
f << "\n";
|
||||
f << indent << "bool " << mangle(module) << "::eval() {\n";
|
||||
f << indent << "bool " << mangle(module) << "::eval(performer *performer) {\n";
|
||||
dump_eval_method(module);
|
||||
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_eval) {
|
||||
f << "\n";
|
||||
f << indent << "void " << mangle(module) << "::debug_eval() {\n";
|
||||
dump_debug_eval_method(module);
|
||||
f << indent << "}\n";
|
||||
f << "\n";
|
||||
}
|
||||
f << "\n";
|
||||
f << indent << "CXXRTL_EXTREMELY_COLD\n";
|
||||
f << indent << "void " << mangle(module) << "::debug_info(debug_items &items, std::string path) {\n";
|
||||
dump_debug_info_method(module);
|
||||
f << indent << "}\n";
|
||||
f << "\n";
|
||||
}
|
||||
f << "\n";
|
||||
}
|
||||
|
||||
void dump_design(RTLIL::Design *design)
|
||||
|
@ -2501,7 +2578,6 @@ struct CxxrtlWorker {
|
|||
RTLIL::Module *top_module = nullptr;
|
||||
std::vector<RTLIL::Module*> modules;
|
||||
TopoSort<RTLIL::Module*> topo_design;
|
||||
bool has_prints = false;
|
||||
for (auto module : design->modules()) {
|
||||
if (!design->selected_module(module))
|
||||
continue;
|
||||
|
@ -2514,8 +2590,6 @@ struct CxxrtlWorker {
|
|||
|
||||
topo_design.node(module);
|
||||
for (auto cell : module->cells()) {
|
||||
if (cell->type == ID($print))
|
||||
has_prints = true;
|
||||
if (is_internal_cell(cell->type) || is_cxxrtl_blackbox_cell(cell))
|
||||
continue;
|
||||
RTLIL::Module *cell_module = design->module(cell->type);
|
||||
|
@ -2571,11 +2645,9 @@ struct CxxrtlWorker {
|
|||
}
|
||||
|
||||
if (split_intf)
|
||||
f << "#include \"" << intf_filename << "\"\n";
|
||||
f << "#include \"" << basename(intf_filename) << "\"\n";
|
||||
else
|
||||
f << "#include <cxxrtl/cxxrtl.h>\n";
|
||||
if (has_prints)
|
||||
f << "#include <iostream>\n";
|
||||
f << "\n";
|
||||
f << "#if defined(CXXRTL_INCLUDE_CAPI_IMPL) || \\\n";
|
||||
f << " defined(CXXRTL_INCLUDE_VCD_CAPI_IMPL)\n";
|
||||
|
@ -2938,8 +3010,9 @@ struct CxxrtlWorker {
|
|||
for (auto node : node_order)
|
||||
if (live_nodes[node]) {
|
||||
if (node->type == FlowGraph::Node::Type::CELL_EVAL &&
|
||||
node->cell->type == ID($print) &&
|
||||
node->cell->getParam(ID::TRG_ENABLE).as_bool())
|
||||
node->cell->type == ID($print) &&
|
||||
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);
|
||||
else
|
||||
schedule[module].push_back(*node);
|
||||
|
@ -3252,7 +3325,9 @@ struct CxxrtlBackend : public Backend {
|
|||
log(" value<8> p_i_data;\n");
|
||||
log(" wire<8> p_o_data;\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("\n");
|
||||
log(" static std::unique_ptr<bb_p_debug>\n");
|
||||
|
@ -3265,11 +3340,11 @@ struct CxxrtlBackend : public Backend {
|
|||
log(" namespace cxxrtl_design {\n");
|
||||
log("\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(" fprintf(stderr, \"debug: %%02x\\n\", p_i_data.data[0]);\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");
|
||||
|
@ -3370,7 +3445,7 @@ struct CxxrtlBackend : public Backend {
|
|||
log(" -print-output <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(" \"std::cout\" is used.\n");
|
||||
log(" \"std::cout\" is used. explicitly provided performer overrides this.\n");
|
||||
log("\n");
|
||||
log(" -nohierarchy\n");
|
||||
log(" use design hierarchy as-is. in most designs, a top module should be\n");
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
@ -38,6 +39,7 @@
|
|||
#include <memory>
|
||||
#include <functional>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
// `cxxrtl::debug_item` has to inherit from `cxxrtl_object` to satisfy strict aliasing requirements.
|
||||
#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
|
||||
// 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.
|
||||
template<class IntegerT>
|
||||
template<class IntegerT, typename std::enable_if<!std::is_signed<IntegerT>::value, int>::type = 0>
|
||||
CXXRTL_ALWAYS_INLINE
|
||||
IntegerT get() const {
|
||||
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;
|
||||
}
|
||||
|
||||
template<class IntegerT>
|
||||
template<class IntegerT, typename std::enable_if<std::is_signed<IntegerT>::value, int>::type = 0>
|
||||
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,
|
||||
"set<T>() requires T to be an unsigned integral type");
|
||||
static_assert(std::numeric_limits<IntegerT>::digits >= Bits,
|
||||
"set<T>() requires the value to be at least as wide as T is");
|
||||
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.
|
||||
|
@ -419,6 +438,7 @@ struct value : public expr_base<value<Bits>> {
|
|||
carry = (shift_bits == 0) ? 0
|
||||
: data[n] >> (chunk::bits - shift_bits);
|
||||
}
|
||||
result.data[result.chunks - 1] &= result.msb_mask;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -429,12 +449,12 @@ struct value : public expr_base<value<Bits>> {
|
|||
// Detect shifts definitely large than Bits early.
|
||||
for (size_t n = 1; n < amount.chunks; n++)
|
||||
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.
|
||||
size_t shift_chunks = amount.data[0] / chunk::bits;
|
||||
size_t shift_bits = amount.data[0] % chunk::bits;
|
||||
if (shift_chunks >= chunks)
|
||||
return {};
|
||||
return (Signed && is_neg()) ? value<Bits>().bit_not() : value<Bits>();
|
||||
value<Bits> result;
|
||||
chunk::type carry = 0;
|
||||
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);
|
||||
}
|
||||
if (Signed && is_neg()) {
|
||||
size_t top_chunk_idx = (Bits - shift_bits) / chunk::bits;
|
||||
size_t top_chunk_bits = (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 = amount.data[0] > Bits ? 0 : (Bits - amount.data[0]) % chunk::bits;
|
||||
for (size_t n = top_chunk_idx + 1; n < chunks; n++)
|
||||
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[result.chunks - 1] &= result.msb_mask;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -473,6 +494,7 @@ struct value : public expr_base<value<Bits>> {
|
|||
carry = (shift_bits == 0) ? 0
|
||||
: data[result.chunks + shift_chunks - 1 - n] << (chunk::bits - shift_bits);
|
||||
}
|
||||
result.data[result.chunks - 1] &= result.msb_mask;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -509,7 +531,8 @@ struct value : public expr_base<value<Bits>> {
|
|||
for (size_t n = 0; n < chunks; n++) {
|
||||
chunk::type x = data[chunks - 1 - n];
|
||||
// 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 (x != 0) {
|
||||
for (; x != 0; count--)
|
||||
|
@ -543,7 +566,7 @@ struct value : public expr_base<value<Bits>> {
|
|||
}
|
||||
|
||||
value<Bits> neg() const {
|
||||
return value<Bits> { 0u }.sub(*this);
|
||||
return value<Bits>().sub(*this);
|
||||
}
|
||||
|
||||
bool ucmp(const value<Bits> &other) const {
|
||||
|
@ -741,102 +764,6 @@ std::ostream &operator<<(std::ostream &os, const value<Bits> &val) {
|
|||
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>
|
||||
struct wire {
|
||||
static constexpr size_t bits = Bits;
|
||||
|
@ -871,8 +798,13 @@ struct wire {
|
|||
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) {
|
||||
observer.on_update(curr.chunks, curr.data, next.data);
|
||||
curr = next;
|
||||
return true;
|
||||
}
|
||||
|
@ -946,12 +878,17 @@ struct memory {
|
|||
write { index, val, mask, priority });
|
||||
}
|
||||
|
||||
bool commit() {
|
||||
// See the note for `wire::commit()`.
|
||||
template<class ObserverT>
|
||||
bool commit(ObserverT &observer) {
|
||||
bool changed = false;
|
||||
for (const write &entry : write_queue) {
|
||||
value<Width> elem = data[entry.index];
|
||||
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;
|
||||
}
|
||||
write_queue.clear();
|
||||
|
@ -1008,6 +945,174 @@ struct metadata {
|
|||
|
||||
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.
|
||||
struct debug_alias {};
|
||||
|
||||
|
@ -1250,17 +1355,14 @@ struct module {
|
|||
|
||||
virtual void reset() = 0;
|
||||
|
||||
virtual bool eval() = 0;
|
||||
virtual bool commit() = 0;
|
||||
virtual bool eval(performer *performer = nullptr) = 0;
|
||||
virtual bool commit() = 0; // commit observer isn't available since it avoids virtual calls
|
||||
|
||||
unsigned int steps = 0;
|
||||
|
||||
size_t step() {
|
||||
++steps;
|
||||
size_t step(performer *performer = nullptr) {
|
||||
size_t deltas = 0;
|
||||
bool converged = false;
|
||||
do {
|
||||
converged = eval();
|
||||
converged = eval(performer);
|
||||
deltas++;
|
||||
} while (commit() && !converged);
|
||||
return deltas;
|
||||
|
|
|
@ -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 ×tamp) {
|
||||
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 ×tamp) {
|
||||
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 ×tamp) {
|
||||
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 ×tamp) {
|
||||
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 ¤t_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 ×tamp) {
|
||||
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
|
|
@ -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
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
|
||||
cd ../../
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
../../yosys -p 'synth -top test; write_simplec -verbose -i8 test00_uut.c' test00_uut.v
|
||||
clang -o test00_tb test00_tb.c
|
||||
|
|
|
@ -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)
|
||||
mem_init_values.append((sig, overlap_bits.replace("x", "?")))
|
||||
|
||||
exprs = []
|
||||
all_sigs = []
|
||||
|
||||
for i, k in enumerate(steps):
|
||||
step_values = WitnessValues()
|
||||
|
||||
|
@ -1337,8 +1340,15 @@ def write_yw_trace(steps, index, allregs=False, filename=None):
|
|||
else:
|
||||
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:
|
||||
value = smt.bv2bin(smt.get(smt.witness_net_expr(topmod, f"s{k}", sig)))
|
||||
value = smt.bv2bin(next(bvs))
|
||||
step_values[sig["sig"]] = value
|
||||
yw.step(step_values)
|
||||
|
||||
|
|
|
@ -194,9 +194,31 @@ class Incremental:
|
|||
|
||||
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):
|
||||
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]
|
||||
subexpr = expr[2]
|
||||
|
||||
|
@ -226,6 +248,7 @@ class Incremental:
|
|||
"or": expr_andor,
|
||||
"=": expr_eq,
|
||||
"yw": expr_yw,
|
||||
"smtlib": expr_smtlib,
|
||||
"!": expr_label,
|
||||
}
|
||||
|
||||
|
@ -251,7 +274,8 @@ class Incremental:
|
|||
)
|
||||
):
|
||||
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
|
||||
raise InteractiveError(f"unknown expression {json.dumps(expr[0])}")
|
||||
|
@ -287,6 +311,14 @@ class Incremental:
|
|||
def cmd_check(self, cmd):
|
||||
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):
|
||||
allregs = (cmd is None) or bool(cmd.get("allreges", False))
|
||||
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)}
|
||||
|
||||
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] = {
|
||||
map_steps.get(i, i): [smtexpr for cexfile, smtexpr in constraint_list]
|
||||
for i, constraint_list in constraints.items()
|
||||
}
|
||||
|
||||
return dict(last_step=last_step)
|
||||
|
||||
def cmd_ping(self, cmd):
|
||||
return cmd
|
||||
|
||||
|
@ -344,6 +380,7 @@ class Incremental:
|
|||
"push": cmd_push,
|
||||
"pop": cmd_pop,
|
||||
"check": cmd_check,
|
||||
"smtlib": cmd_smtlib,
|
||||
"design_hierwitness": cmd_design_hierwitness,
|
||||
"write_yw_trace": cmd_write_yw_trace,
|
||||
"read_yw_trace": cmd_read_yw_trace,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
|
|
|
@ -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("output", type=click.File("w"))
|
||||
@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
|
||||
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}")
|
||||
|
@ -211,16 +212,23 @@ def aiw2yw(input, mapfile, output, skip_x):
|
|||
if not re.match(r'[0]*$', ffline):
|
||||
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:
|
||||
outyw.add_clock(clock["path"], clock["offset"], clock["edge"])
|
||||
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():
|
||||
outyw.add_sig(path, offset, init_only=id in aiger_map.init_inputs)
|
||||
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)
|
||||
|
||||
missing = set()
|
||||
seen = set()
|
||||
|
||||
buffered_steps = []
|
||||
|
||||
skip = "x?" if skip_x else "?"
|
||||
|
||||
t = -1
|
||||
while True:
|
||||
inline = next(input, None)
|
||||
if inline is None:
|
||||
|
@ -232,17 +240,20 @@ def aiw2yw(input, mapfile, output, skip_x):
|
|||
if inline.startswith("#"):
|
||||
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")
|
||||
|
||||
if len(inline) != aiger_map.input_count:
|
||||
raise click.ClickException(
|
||||
f"{input_name}: {mapfile.name}: number of inputs does not match, "
|
||||
f"{len(inline)} in witness, {aiger_map.input_count} in map file")
|
||||
f"{input_name}: {mapfile.name}: number of inputs does not match, "
|
||||
f"{len(inline)} in witness, {aiger_map.input_count} in map file"
|
||||
)
|
||||
|
||||
values = WitnessValues()
|
||||
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
|
||||
|
||||
try:
|
||||
|
@ -250,11 +261,29 @@ def aiw2yw(input, mapfile, output, skip_x):
|
|||
except IndexError:
|
||||
bit = None
|
||||
if bit is None:
|
||||
missing.insert(i)
|
||||
missing.add(i)
|
||||
elif present_only:
|
||||
seen.add(i)
|
||||
|
||||
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()
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
|
|
|
@ -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)
|
||||
return;
|
||||
|
@ -392,13 +392,13 @@ void dump_attributes(std::ostream &f, std::string indent, dict<RTLIL::IdString,
|
|||
f << stringf(" 1 ");
|
||||
else
|
||||
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)
|
||||
{
|
||||
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 (wire->port_input && !wire->port_output)
|
||||
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());
|
||||
dump_sigspec(f, cell->getPort(ID::Y));
|
||||
f << stringf(" = %s ", op.c_str());
|
||||
dump_attributes(f, "", cell->attributes, ' ');
|
||||
dump_attributes(f, "", cell->attributes, " ");
|
||||
dump_cell_expr_port(f, cell, "A", true);
|
||||
f << stringf(";\n");
|
||||
}
|
||||
|
@ -1001,7 +1001,7 @@ void dump_cell_expr_binop(std::ostream &f, std::string indent, RTLIL::Cell *cell
|
|||
f << stringf(" = ");
|
||||
dump_cell_expr_port(f, cell, "A", true);
|
||||
f << stringf(" %s ", op.c_str());
|
||||
dump_attributes(f, "", cell->attributes, ' ');
|
||||
dump_attributes(f, "", cell->attributes, " ");
|
||||
dump_cell_expr_port(f, cell, "B", true);
|
||||
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));
|
||||
f << stringf(" = ");
|
||||
f << stringf("~");
|
||||
dump_attributes(f, "", cell->attributes, ' ');
|
||||
dump_attributes(f, "", cell->attributes, " ");
|
||||
dump_cell_expr_port(f, cell, "A", false);
|
||||
f << stringf(";\n");
|
||||
return true;
|
||||
|
@ -1068,7 +1068,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell)
|
|||
f << stringf("|");
|
||||
if (cell->type.in(ID($_XOR_), ID($_XNOR_)))
|
||||
f << stringf("^");
|
||||
dump_attributes(f, "", cell->attributes, ' ');
|
||||
dump_attributes(f, "", cell->attributes, " ");
|
||||
f << stringf(" ");
|
||||
if (cell->type.in(ID($_ANDNOT_), ID($_ORNOT_)))
|
||||
f << stringf("~(");
|
||||
|
@ -1085,7 +1085,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell)
|
|||
f << stringf(" = ");
|
||||
dump_cell_expr_port(f, cell, "S", false);
|
||||
f << stringf(" ? ");
|
||||
dump_attributes(f, "", cell->attributes, ' ');
|
||||
dump_attributes(f, "", cell->attributes, " ");
|
||||
dump_cell_expr_port(f, cell, "B", false);
|
||||
f << stringf(" : ");
|
||||
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(" = !(");
|
||||
dump_cell_expr_port(f, cell, "S", false);
|
||||
f << stringf(" ? ");
|
||||
dump_attributes(f, "", cell->attributes, ' ');
|
||||
dump_attributes(f, "", cell->attributes, " ");
|
||||
dump_cell_expr_port(f, cell, "B", false);
|
||||
f << stringf(" : ");
|
||||
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_) ? " & " : " | ");
|
||||
dump_cell_expr_port(f, cell, "B", false);
|
||||
f << stringf(cell->type == ID($_AOI3_) ? ") |" : ") &");
|
||||
dump_attributes(f, "", cell->attributes, ' ');
|
||||
dump_attributes(f, "", cell->attributes, " ");
|
||||
f << stringf(" ");
|
||||
dump_cell_expr_port(f, cell, "C", false);
|
||||
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_) ? " & " : " | ");
|
||||
dump_cell_expr_port(f, cell, "B", false);
|
||||
f << stringf(cell->type == ID($_AOI4_) ? ") |" : ") &");
|
||||
dump_attributes(f, "", cell->attributes, ' ');
|
||||
dump_attributes(f, "", cell->attributes, " ");
|
||||
f << stringf(" (");
|
||||
dump_cell_expr_port(f, cell, "C", false);
|
||||
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());
|
||||
dump_sigspec(f, cell->getPort(ID::Y));
|
||||
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());
|
||||
return true;
|
||||
} 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());
|
||||
dump_cell_expr_port(f, cell, "A", true);
|
||||
f << stringf(" %% ");
|
||||
dump_attributes(f, "", cell->attributes, ' ');
|
||||
dump_attributes(f, "", cell->attributes, " ");
|
||||
dump_cell_expr_port(f, cell, "B", true);
|
||||
f << stringf(";\n");
|
||||
|
||||
|
@ -1330,7 +1330,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell)
|
|||
f << stringf(" = ");
|
||||
dump_sigspec(f, cell->getPort(ID::S));
|
||||
f << stringf(" ? ");
|
||||
dump_attributes(f, "", cell->attributes, ' ');
|
||||
dump_attributes(f, "", cell->attributes, " ");
|
||||
dump_sigspec(f, cell->getPort(ID::B));
|
||||
f << stringf(" : ");
|
||||
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(" = ");
|
||||
dump_const(f, cell->parameters.at(ID::LUT));
|
||||
f << stringf(" >> ");
|
||||
dump_attributes(f, "", cell->attributes, ' ');
|
||||
dump_attributes(f, "", cell->attributes, " ");
|
||||
dump_sigspec(f, cell->getPort(ID::A));
|
||||
f << stringf(";\n");
|
||||
return true;
|
||||
|
@ -1830,7 +1830,8 @@ void dump_cell(std::ostream &f, std::string indent, RTLIL::Cell *cell)
|
|||
if (it != cell->parameters.begin())
|
||||
f << stringf(",");
|
||||
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("\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)
|
||||
{
|
||||
f << stringf("%s" "always @(", indent.c_str());
|
||||
for (int i = 0; i < trg.size(); i++) {
|
||||
if (i != 0)
|
||||
f << " or ";
|
||||
if (polarity[i])
|
||||
f << "posedge ";
|
||||
else
|
||||
f << "negedge ";
|
||||
dump_sigspec(f, trg[i]);
|
||||
if (trg.size() == 0) {
|
||||
f << stringf("%s" "initial begin\n", indent.c_str());
|
||||
} else {
|
||||
f << stringf("%s" "always @(", indent.c_str());
|
||||
for (int i = 0; i < trg.size(); i++) {
|
||||
if (i != 0)
|
||||
f << " or ";
|
||||
if (polarity[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) {
|
||||
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());
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (sw->signal.size() == 0) {
|
||||
|
@ -1983,17 +2038,18 @@ void dump_proc_switch(std::ostream &f, std::string indent, RTLIL::SwitchRule *sw
|
|||
return;
|
||||
}
|
||||
|
||||
if (dump_proc_switch_ifelse(f, indent, sw))
|
||||
return;
|
||||
|
||||
dump_attributes(f, indent, sw->attributes);
|
||||
f << stringf("%s" "casez (", indent.c_str());
|
||||
dump_sigspec(f, sw->signal);
|
||||
f << stringf(")\n");
|
||||
|
||||
bool got_default = false;
|
||||
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 (got_default)
|
||||
continue;
|
||||
f << stringf("%s default", indent.c_str());
|
||||
got_default = true;
|
||||
} else {
|
||||
|
@ -2006,6 +2062,14 @@ void dump_proc_switch(std::ostream &f, std::string indent, RTLIL::SwitchRule *sw
|
|||
}
|
||||
f << stringf(":\n");
|
||||
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()) {
|
||||
|
@ -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());
|
||||
bool keep_running = true;
|
||||
int cnt = 0;
|
||||
|
|
|
@ -127,8 +127,6 @@ Our ``addr_gen`` circuit now looks like this:
|
|||
|
||||
``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
|
||||
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``)
|
||||
|
|
|
@ -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` $logic_and :verilog:`Y = A * B` $mul
|
||||
: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` $pow ``N/A`` $modfoor
|
||||
======================= ============= ======================= =========
|
||||
|
@ -664,6 +664,8 @@ Ports:
|
|||
|
||||
``\TRG``
|
||||
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``
|
||||
Enable signal for the whole cell.
|
||||
|
|
|
@ -45,7 +45,7 @@ namespace AST {
|
|||
|
||||
// instantiate global variables (private API)
|
||||
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;
|
||||
AstNode *current_ast, *current_ast_mod;
|
||||
std::map<std::string, AstNode*> current_scope;
|
||||
|
@ -850,6 +850,25 @@ AstNode *AstNode::mkconst_str(const std::string &str)
|
|||
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
|
||||
{
|
||||
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'
|
||||
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)
|
||||
{
|
||||
current_ast = ast;
|
||||
current_ast_mod = nullptr;
|
||||
flag_nodisplay = nodisplay;
|
||||
flag_dump_ast1 = dump_ast1;
|
||||
flag_dump_ast2 = dump_ast2;
|
||||
flag_no_dump_ptr = no_dump_ptr;
|
||||
|
|
|
@ -287,7 +287,7 @@ namespace AST
|
|||
bool is_simple_const_expr();
|
||||
|
||||
// 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;
|
||||
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::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
|
||||
RTLIL::Const bitsAsConst(int width, bool is_signed);
|
||||
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
|
||||
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);
|
||||
|
||||
// parametric modules are supported directly by the AST library
|
||||
|
@ -429,7 +432,7 @@ namespace AST
|
|||
namespace AST_INTERNAL
|
||||
{
|
||||
// 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 AST::AstNode *current_ast, *current_ast_mod;
|
||||
extern std::map<std::string, AST::AstNode*> current_scope;
|
||||
|
|
|
@ -718,7 +718,7 @@ struct AST_INTERNAL::ProcessGenerator
|
|||
}
|
||||
}
|
||||
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::PRIORITY] = --last_print_priority;
|
||||
cell->setPort(ID::TRG, triggers);
|
||||
|
|
|
@ -35,12 +35,25 @@
|
|||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
// For std::gcd in C++17
|
||||
// #include <numeric>
|
||||
|
||||
YOSYS_NAMESPACE_BEGIN
|
||||
|
||||
using namespace AST;
|
||||
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)
|
||||
{
|
||||
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
|
||||
|
||||
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;
|
||||
for (size_t index = first_arg_at; index < children.size(); 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.sig = node_arg->bitsAsConst();
|
||||
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 {
|
||||
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);
|
||||
is_signed = enum_item->children[1]->is_signed;
|
||||
} else {
|
||||
log_error("enum_item children size==%lu, expected 1 or 2 for %s (%s)\n",
|
||||
enum_item->children.size(),
|
||||
log_error("enum_item children size==%zu, expected 1 or 2 for %s (%s)\n",
|
||||
(size_t) enum_item->children.size(),
|
||||
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)
|
||||
{
|
||||
// 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) {
|
||||
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) {
|
||||
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);
|
||||
if (str.substr(0, 8) == "$display")
|
||||
fmt.append_string("\n");
|
||||
log("%s", fmt.render().c_str());
|
||||
delete_children();
|
||||
str = std::string();
|
||||
} else {
|
||||
// when $display()/$write() functions are used in an always block, simplify the expressions and
|
||||
// convert them to a special cell later in genrtlil
|
||||
// simplify the expressions and convert them to a special cell later in genrtlil
|
||||
for (auto node : children)
|
||||
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;
|
||||
}
|
||||
|
||||
delete_children();
|
||||
str = std::string();
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// check if a plausible struct member sss.mmmm
|
||||
std::string sname;
|
||||
if (name_has_dot(str, sname)) {
|
||||
if (current_scope.count(str) > 0) {
|
||||
auto item_node = current_scope[str];
|
||||
if (item_node->type == AST_STRUCT_ITEM || item_node->type == AST_STRUCT || item_node->type == AST_UNION) {
|
||||
if (!str.empty() && str[0] == '\\' && current_scope.count(str)) {
|
||||
auto item_node = current_scope[str];
|
||||
if (item_node->type == AST_STRUCT_ITEM || item_node->type == AST_STRUCT || item_node->type == AST_UNION) {
|
||||
// Traverse any hierarchical path until the full name for the referenced struct/union is found.
|
||||
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
|
||||
auto range = make_struct_member_range(this, item_node);
|
||||
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)
|
||||
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]);
|
||||
if (member_node) {
|
||||
// Clamp chunk to range of member within struct/union.
|
||||
log_assert(!source_offset && !children[0]->id2ast->range_swapped);
|
||||
source_width = member_node->range_left - member_node->range_right + 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);
|
||||
}
|
||||
}
|
||||
int wire_width = member_node ?
|
||||
member_node->range_left - member_node->range_right + 1 :
|
||||
children[0]->id2ast->range_left - children[0]->id2ast->range_right + 1;
|
||||
int wire_offset = children[0]->id2ast->range_right;
|
||||
int result_width = 1;
|
||||
|
||||
AstNode *shift_expr = NULL;
|
||||
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
|
||||
shift_expr = range->children[0]->clone();
|
||||
|
||||
bool use_case_method = false;
|
||||
|
||||
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;
|
||||
}
|
||||
bool use_case_method = children[0]->id2ast->get_bool_attribute(ID::nowrshmsk);
|
||||
|
||||
if (!use_case_method && current_always->detect_latch(children[0]->str))
|
||||
use_case_method = true;
|
||||
|
||||
if (use_case_method)
|
||||
{
|
||||
if (use_case_method) {
|
||||
// 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;
|
||||
newNode = new AstNode(AST_CASE, shift_expr);
|
||||
for (int i = 0; i < source_width; i += stride) {
|
||||
int start_bit = source_offset + i;
|
||||
int end_bit = std::min(start_bit+result_width,source_width) - 1;
|
||||
AstNode *cond = new AstNode(AST_COND, mkconst_int(start_bit, true));
|
||||
for (int i = 1 - result_width; i < wire_width; i++) {
|
||||
// Out of range indexes are handled in genrtlil.cc
|
||||
int start_bit = wire_offset + i;
|
||||
int end_bit = start_bit + result_width - 1;
|
||||
// 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();
|
||||
lvalue->delete_children();
|
||||
if (member_node)
|
||||
lvalue->set_attribute(ID::wiretype, member_node->clone());
|
||||
lvalue->children.push_back(new AstNode(AST_RANGE,
|
||||
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())));
|
||||
newNode->children.push_back(cond);
|
||||
cond->children.push_back(new AstNode(AST_BLOCK, new AstNode(type, lvalue, rvalue->clone())));
|
||||
caseNode->children.push_back(cond);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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);
|
||||
} else {
|
||||
// mask and shift operations
|
||||
// dst = (dst & ~(width'1 << lsb)) | unsigned'(width'(src)) << lsb)
|
||||
|
||||
AstNode *lvalue = children[0]->clone();
|
||||
lvalue->delete_children();
|
||||
if (member_node)
|
||||
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();
|
||||
if (type == AST_ASSIGN_LE)
|
||||
old_data->lookahead = true;
|
||||
|
||||
AstNode *s = new AstNode(AST_ASSIGN_EQ, ref_sel->clone(), shift_expr);
|
||||
newNode->children.push_back(s);
|
||||
int shift_width_hint;
|
||||
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
|
||||
shamt = new AstNode(AST_CAST_SIZE, mkconst_int(shamt_width_hint + 1, true), shamt);
|
||||
shamt = new AstNode(AST_TO_SIGNED, shamt);
|
||||
// Temporary register holding the result of the bit- or part-select position expression.
|
||||
AstNode *pos = mktemp_logic("$bitselwrite$pos$", current_ast_mod, true, shift_width_hint - 1, 0, shift_sign_hint);
|
||||
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
|
||||
int start_bit = source_offset;
|
||||
shamt = new AstNode(AST_SUB, shamt, mkconst_int(start_bit, true));
|
||||
if (wire_offset != 0)
|
||||
shift_val = new AstNode(AST_SUB, shift_val, mkconst_int(wire_offset, true));
|
||||
|
||||
// reflect the shift amount if the dimension is 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
|
||||
shamt = new AstNode(AST_NEG, shamt);
|
||||
shift_val = new AstNode(AST_NEG, shift_val);
|
||||
|
||||
AstNode *t;
|
||||
|
||||
t = mkconst_bits(std::vector<RTLIL::State>(result_width, State::S1), false);
|
||||
t = new AstNode(AST_SHIFT, t, shamt->clone());
|
||||
t = new AstNode(AST_ASSIGN_EQ, ref_mask->clone(), t);
|
||||
newNode->children.push_back(t);
|
||||
|
||||
t = new AstNode(AST_BIT_AND, mkconst_bits(std::vector<RTLIL::State>(result_width, State::S1), false), children[1]->clone());
|
||||
t = new AstNode(AST_SHIFT, t, shamt);
|
||||
t = new AstNode(AST_ASSIGN_EQ, ref_data->clone(), t);
|
||||
newNode->children.push_back(t);
|
||||
|
||||
t = new AstNode(AST_BIT_AND, old_data, new AstNode(AST_BIT_NOT, ref_mask));
|
||||
t = new AstNode(AST_BIT_OR, t, ref_data);
|
||||
t = new AstNode(type, lvalue, t);
|
||||
newNode->children.push_back(t);
|
||||
// dst = (dst & ~(width'1 << lsb)) | unsigned'(width'(src)) << lsb)
|
||||
did_something = true;
|
||||
AstNode *bitmask = mkconst_bits(std::vector<RTLIL::State>(result_width, State::S1), false);
|
||||
newNode->children.push_back(
|
||||
new AstNode(type,
|
||||
lvalue,
|
||||
new AstNode(AST_BIT_OR,
|
||||
new AstNode(AST_BIT_AND,
|
||||
old_data,
|
||||
new AstNode(AST_BIT_NOT,
|
||||
new AstNode(AST_SHIFT,
|
||||
bitmask,
|
||||
shift_val->clone()))),
|
||||
new AstNode(AST_SHIFT,
|
||||
new AstNode(AST_TO_UNSIGNED,
|
||||
new AstNode(AST_CAST_SIZE,
|
||||
mkconst_int(result_width, true),
|
||||
children[1]->clone())),
|
||||
shift_val))));
|
||||
|
||||
newNode->fixup_hierarchy_flags(true);
|
||||
}
|
||||
|
@ -4681,6 +4736,8 @@ void AstNode::expand_genblock(const std::string &prefix)
|
|||
switch (child->type) {
|
||||
case AST_WIRE:
|
||||
case AST_MEMORY:
|
||||
case AST_STRUCT:
|
||||
case AST_UNION:
|
||||
case AST_PARAMETER:
|
||||
case AST_LOCALPARAM:
|
||||
case AST_FUNCTION:
|
||||
|
|
|
@ -2110,7 +2110,7 @@ VerificClocking::VerificClocking(VerificImporter *importer, Net *net, bool sva_a
|
|||
if (sva_at_only)
|
||||
do {
|
||||
Instance *inst_mux = net->Driver();
|
||||
if (inst_mux->Type() != PRIM_MUX)
|
||||
if (inst_mux == nullptr || inst_mux->Type() != PRIM_MUX)
|
||||
break;
|
||||
|
||||
bool pwr1 = inst_mux->GetInput1()->IsPwr();
|
||||
|
|
|
@ -100,6 +100,10 @@ struct VerilogFrontend : public Frontend {
|
|||
log(" -assert-assumes\n");
|
||||
log(" treat all assume() statements like assert() statements\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(" alias for -dump_ast1 -dump_ast2 -dump_vlog1 -dump_vlog2 -yydebug\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
|
||||
{
|
||||
bool flag_nodisplay = false;
|
||||
bool flag_dump_ast1 = false;
|
||||
bool flag_dump_ast2 = false;
|
||||
bool flag_no_dump_ptr = false;
|
||||
|
@ -308,6 +313,10 @@ struct VerilogFrontend : public Frontend {
|
|||
assert_assumes_mode = true;
|
||||
continue;
|
||||
}
|
||||
if (arg == "-nodisplay") {
|
||||
flag_nodisplay = true;
|
||||
continue;
|
||||
}
|
||||
if (arg == "-debug") {
|
||||
flag_dump_ast1 = true;
|
||||
flag_dump_ast2 = true;
|
||||
|
@ -510,7 +519,7 @@ struct VerilogFrontend : public Frontend {
|
|||
if (flag_nodpi)
|
||||
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);
|
||||
|
||||
|
||||
|
|
144
kernel/fmt.cc
144
kernel/fmt.cc
|
@ -110,9 +110,9 @@ void Fmt::parse_rtlil(const RTLIL::Cell *cell) {
|
|||
} else if (fmt[i] == 'c') {
|
||||
part.type = FmtPart::CHARACTER;
|
||||
} else if (fmt[i] == 't') {
|
||||
part.type = FmtPart::TIME;
|
||||
part.type = FmtPart::VLOG_TIME;
|
||||
} else if (fmt[i] == 'r') {
|
||||
part.type = FmtPart::TIME;
|
||||
part.type = FmtPart::VLOG_TIME;
|
||||
part.realtime = true;
|
||||
} else {
|
||||
log_assert(false && "Unexpected character in format substitution");
|
||||
|
@ -172,7 +172,7 @@ void Fmt::emit_rtlil(RTLIL::Cell *cell) const {
|
|||
}
|
||||
break;
|
||||
|
||||
case FmtPart::TIME:
|
||||
case FmtPart::VLOG_TIME:
|
||||
log_assert(part.sig.size() == 0);
|
||||
YS_FALLTHROUGH
|
||||
case FmtPart::CHARACTER:
|
||||
|
@ -205,7 +205,7 @@ void Fmt::emit_rtlil(RTLIL::Cell *cell) const {
|
|||
fmt += part.signed_ ? 's' : 'u';
|
||||
} else if (part.type == FmtPart::CHARACTER) {
|
||||
fmt += 'c';
|
||||
} else if (part.type == FmtPart::TIME) {
|
||||
} else if (part.type == FmtPart::VLOG_TIME) {
|
||||
if (part.realtime)
|
||||
fmt += 'r';
|
||||
else
|
||||
|
@ -328,7 +328,7 @@ void Fmt::parse_verilog(const std::vector<VerilogFmtArg> &args, bool sformat_lik
|
|||
|
||||
case VerilogFmtArg::TIME: {
|
||||
FmtPart part = {};
|
||||
part.type = FmtPart::TIME;
|
||||
part.type = FmtPart::VLOG_TIME;
|
||||
part.realtime = arg->realtime;
|
||||
part.padding = ' ';
|
||||
part.width = 20;
|
||||
|
@ -419,7 +419,7 @@ void Fmt::parse_verilog(const std::vector<VerilogFmtArg> &args, bool sformat_lik
|
|||
part.padding = ' ';
|
||||
} else if (fmt[i] == 't' || fmt[i] == 'T') {
|
||||
if (arg->type == VerilogFmtArg::TIME) {
|
||||
part.type = FmtPart::TIME;
|
||||
part.type = FmtPart::VLOG_TIME;
|
||||
part.realtime = arg->realtime;
|
||||
if (!has_width && !has_leading_zero)
|
||||
part.width = 20;
|
||||
|
@ -541,7 +541,7 @@ std::vector<VerilogFmtArg> Fmt::emit_verilog() const
|
|||
break;
|
||||
}
|
||||
|
||||
case FmtPart::TIME: {
|
||||
case FmtPart::VLOG_TIME: {
|
||||
VerilogFmtArg arg;
|
||||
arg.type = VerilogFmtArg::TIME;
|
||||
if (part.realtime)
|
||||
|
@ -569,82 +569,60 @@ std::vector<VerilogFmtArg> Fmt::emit_verilog() const
|
|||
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) {
|
||||
switch (part.type) {
|
||||
case FmtPart::STRING:
|
||||
f << " << \"";
|
||||
for (char c : part.str) {
|
||||
switch (c) {
|
||||
case '\\':
|
||||
YS_FALLTHROUGH
|
||||
case '"':
|
||||
f << '\\' << c;
|
||||
break;
|
||||
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();
|
||||
std::string output = "\"";
|
||||
for (auto c : input) {
|
||||
if (::isprint(c)) {
|
||||
if (c == '\\')
|
||||
output.push_back('\\');
|
||||
output.push_back(c);
|
||||
} else {
|
||||
char l = c & 0xf, h = (c >> 4) & 0xf;
|
||||
output.append("\\x");
|
||||
output.push_back((h < 10 ? '0' + h : 'a' + h - 10));
|
||||
output.push_back((l < 10 ? '0' + l : 'a' + l - 10));
|
||||
}
|
||||
}
|
||||
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
|
||||
|
@ -658,8 +636,8 @@ std::string Fmt::render() const
|
|||
break;
|
||||
|
||||
case FmtPart::INTEGER:
|
||||
case FmtPart::TIME:
|
||||
case FmtPart::CHARACTER: {
|
||||
case FmtPart::CHARACTER:
|
||||
case FmtPart::VLOG_TIME: {
|
||||
std::string buf;
|
||||
if (part.type == FmtPart::INTEGER) {
|
||||
RTLIL::Const value = part.sig.as_const();
|
||||
|
@ -742,7 +720,7 @@ std::string Fmt::render() const
|
|||
} else log_abort();
|
||||
} else if (part.type == FmtPart::CHARACTER) {
|
||||
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.
|
||||
buf = "0";
|
||||
}
|
||||
|
|
11
kernel/fmt.h
11
kernel/fmt.h
|
@ -50,12 +50,13 @@ struct VerilogFmtArg {
|
|||
|
||||
// RTLIL format part, such as the substitutions in:
|
||||
// "foo {4:> 4du} bar {2:<01hs}"
|
||||
// Must be kept in sync with `struct fmt_part` in backends/cxxrtl/runtime/cxxrtl/cxxrtl.h!
|
||||
struct FmtPart {
|
||||
enum {
|
||||
STRING = 0,
|
||||
INTEGER = 1,
|
||||
CHARACTER = 2,
|
||||
TIME = 3,
|
||||
VLOG_TIME = 3,
|
||||
} type;
|
||||
|
||||
// STRING type
|
||||
|
@ -64,20 +65,20 @@ struct FmtPart {
|
|||
// INTEGER/CHARACTER types
|
||||
RTLIL::SigSpec sig;
|
||||
|
||||
// INTEGER/CHARACTER/TIME types
|
||||
// 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;
|
||||
|
||||
// TIME type
|
||||
// VLOG_TIME type
|
||||
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);
|
||||
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;
|
||||
|
||||
|
|
|
@ -420,7 +420,7 @@ public:
|
|||
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); }
|
||||
};
|
||||
|
||||
pool()
|
||||
constexpr pool()
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -1062,6 +1062,10 @@ public:
|
|||
const K *operator->() const { return &container[index]; }
|
||||
};
|
||||
|
||||
constexpr idict()
|
||||
{
|
||||
}
|
||||
|
||||
int operator()(const K &key)
|
||||
{
|
||||
int hash = database.do_hash(key);
|
||||
|
@ -1132,6 +1136,10 @@ class mfp
|
|||
public:
|
||||
typedef typename idict<K, 0, OPS>::const_iterator const_iterator;
|
||||
|
||||
constexpr mfp()
|
||||
{
|
||||
}
|
||||
|
||||
int operator()(const K &key) const
|
||||
{
|
||||
int i = database(key);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
bad=
|
||||
|
||||
|
|
|
@ -539,7 +539,7 @@ struct ShowWorker
|
|||
std::string proc_src = RTLIL::unescape_id(proc->name);
|
||||
if (proc->attributes.count(ID::src) > 0)
|
||||
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())
|
||||
|
|
|
@ -60,7 +60,7 @@ struct EquivSimpleWorker
|
|||
for (auto &conn : cell->connections())
|
||||
if (yosys_celltypes.cell_input(cell->type, conn.first))
|
||||
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))
|
||||
next_seed.insert(bit);
|
||||
} else
|
||||
|
@ -133,11 +133,9 @@ struct EquivSimpleWorker
|
|||
|
||||
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);
|
||||
next_seed_a.clear();
|
||||
|
||||
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);
|
||||
next_seed_b.clear();
|
||||
|
||||
pool<Cell*> short_cells_cone_a, short_cells_cone_b;
|
||||
pool<SigBit> short_bits_cone_a, short_bits_cone_b;
|
||||
|
@ -145,10 +143,12 @@ struct EquivSimpleWorker
|
|||
|
||||
if (short_cones)
|
||||
{
|
||||
next_seed_a.clear();
|
||||
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);
|
||||
next_seed_a.swap(seed_a);
|
||||
|
||||
next_seed_b.clear();
|
||||
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);
|
||||
next_seed_b.swap(seed_b);
|
||||
|
@ -364,7 +364,7 @@ struct EquivSimplePass : public Pass {
|
|||
unproven_cells_counter, GetSize(unproven_equiv_cells), log_id(module));
|
||||
|
||||
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;
|
||||
for (auto &conn : cell->connections())
|
||||
if (yosys_celltypes.cell_output(cell->type, conn.first))
|
||||
|
|
|
@ -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.
|
||||
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) {
|
||||
if (!port.clk_enable) {
|
||||
// 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.
|
||||
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++) {
|
||||
auto &port = mem.rd_ports[pidx];
|
||||
MemConfigs new_cfgs;
|
||||
|
@ -900,7 +900,7 @@ void MemMapping::assign_rd_ports() {
|
|||
|
||||
// Validate transparency restrictions, determine where to add soft transparency logic.
|
||||
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()) {
|
||||
MemConfigs new_cfgs;
|
||||
for (auto &cfg: cfgs) {
|
||||
|
|
|
@ -24,6 +24,10 @@
|
|||
USING_YOSYS_NAMESPACE
|
||||
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 {
|
||||
IdString cell_type;
|
||||
// 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 {
|
||||
OptLutPass() : Pass("opt_lut", "optimize LUT cells") { }
|
||||
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 same constant.\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(" only perform the first N combines, then stop. useful for debugging.\n");
|
||||
log("\n");
|
||||
|
@ -555,28 +553,28 @@ struct OptLutPass : public Pass {
|
|||
size_t 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;
|
||||
split(tokens, args[++argidx], ':');
|
||||
if (tokens.size() < 2)
|
||||
log_cmd_error("The -dlogic option requires at least one connection.\n");
|
||||
dlogic_t entry;
|
||||
entry.cell_type = "\\" + tokens[0];
|
||||
for (auto it = tokens.begin() + 1; it != tokens.end(); ++it) {
|
||||
std::vector<std::string> conn_tokens;
|
||||
split(conn_tokens, *it, '=');
|
||||
if (conn_tokens.size() != 2)
|
||||
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());
|
||||
entry.lut_input_port[lut_input] = logic_port;
|
||||
}
|
||||
dlogic.push_back(entry);
|
||||
std::string tech = args[++argidx];
|
||||
if (tech != "ice40")
|
||||
log_cmd_error("Unsupported -tech argument: %s\n", tech.c_str());
|
||||
|
||||
dlogic = {{
|
||||
ID(SB_CARRY),
|
||||
dict<int, IdString>{
|
||||
std::make_pair(1, ID(I0)),
|
||||
std::make_pair(2, ID(I1)),
|
||||
std::make_pair(3, ID(CI))
|
||||
}
|
||||
}, {
|
||||
ID(SB_CARRY),
|
||||
dict<int, IdString>{
|
||||
std::make_pair(3, ID(CO))
|
||||
}
|
||||
}};
|
||||
continue;
|
||||
}
|
||||
if (args[argidx] == "-limit" && argidx + 1 < args.size())
|
||||
{
|
||||
if (args[argidx] == "-limit" && argidx + 1 < args.size()) {
|
||||
limit = atoi(args[++argidx].c_str());
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <queue>
|
||||
#include <cinttypes>
|
||||
|
||||
USING_YOSYS_NAMESPACE
|
||||
|
||||
|
@ -623,7 +624,7 @@ struct RecoverNamesWorker {
|
|||
if (pop == 1 || pop == (8*sizeof(equiv_cls_t) - 1))
|
||||
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<InvBit> &gate_bits = cls2bits.at(cls.first).second;
|
||||
if (gold_bits.empty() || gate_bits.empty())
|
||||
|
|
|
@ -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
|
||||
{
|
||||
bool debug = false;
|
||||
|
@ -110,6 +121,7 @@ struct SimShared
|
|||
int next_output_id = 0;
|
||||
int step = 0;
|
||||
std::vector<TriggeredAssertion> triggered_assertions;
|
||||
std::vector<DisplayOutput> display_output;
|
||||
bool serious_asserts = false;
|
||||
bool initstate = true;
|
||||
};
|
||||
|
@ -173,6 +185,7 @@ struct SimInstance
|
|||
|
||||
struct print_state_t
|
||||
{
|
||||
bool initial_done;
|
||||
Const past_trg;
|
||||
Const past_en;
|
||||
Const past_args;
|
||||
|
@ -338,6 +351,7 @@ struct SimInstance
|
|||
print.past_trg = Const(State::Sx, cell->getPort(ID::TRG).size());
|
||||
print.past_args = Const(State::Sx, cell->getPort(ID::ARGS).size());
|
||||
print.past_en = State::Sx;
|
||||
print.initial_done = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -840,13 +854,14 @@ struct SimInstance
|
|||
bool triggered = false;
|
||||
|
||||
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 args = get_state(cell->getPort(ID::ARGS));
|
||||
|
||||
if (!en.as_bool())
|
||||
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);
|
||||
for (int i = 0; i < trg.size(); i++) {
|
||||
bool pol = trg_pol[i] == State::S1;
|
||||
|
@ -856,7 +871,12 @@ struct SimInstance
|
|||
if (!pol && curr == State::S0 && past == State::S1)
|
||||
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 {
|
||||
// always @(*) $print
|
||||
if (args != print.past_args || en != print.past_en)
|
||||
triggered = true;
|
||||
}
|
||||
|
@ -870,12 +890,14 @@ struct SimInstance
|
|||
|
||||
std::string rendered = print.fmt.render();
|
||||
log("%s", rendered.c_str());
|
||||
shared->display_output.emplace_back(shared->step, this, cell, rendered);
|
||||
}
|
||||
|
||||
update_print:
|
||||
print.past_trg = trg;
|
||||
print.past_en = en;
|
||||
print.past_args = args;
|
||||
print.initial_done = true;
|
||||
}
|
||||
|
||||
if (check_assertions)
|
||||
|
@ -2055,6 +2077,20 @@ struct SimWorker : SimShared
|
|||
json.end_object();
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
|
@ -131,7 +131,7 @@ if (PORT_A_WIDTH < 9) begin
|
|||
.CE(PORT_A_CLK_EN),
|
||||
.WRE(WRE),
|
||||
.RESET(RST),
|
||||
.OCE(1'b0),
|
||||
.OCE(1'b1),
|
||||
.AD(AD),
|
||||
.DI(DI),
|
||||
.DO(DO),
|
||||
|
@ -157,7 +157,7 @@ end else begin
|
|||
.CE(PORT_A_CLK_EN),
|
||||
.WRE(WRE),
|
||||
.RESET(RST),
|
||||
.OCE(1'b0),
|
||||
.OCE(1'b1),
|
||||
.AD(AD),
|
||||
.DI(DI),
|
||||
.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_B_RD_DATA = `x8_rd_data(DOB);
|
||||
|
||||
DP #(
|
||||
DPB #(
|
||||
`INIT(init_slice_x8)
|
||||
.READ_MODE0(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),
|
||||
.BIT_WIDTH_0(`x8_width(PORT_A_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),
|
||||
) _TECHMAP_REPLACE_ (
|
||||
.BLKSEL(3'b000),
|
||||
.BLKSELA(3'b000),
|
||||
.BLKSELB(3'b000),
|
||||
|
||||
.CLKA(PORT_A_CLK),
|
||||
.CEA(PORT_A_CLK_EN),
|
||||
.WREA(WREA),
|
||||
.RESETA(RSTA),
|
||||
.OCEA(1'b0),
|
||||
.OCEA(1'b1),
|
||||
.ADA(ADA),
|
||||
.DIA(DIA),
|
||||
.DOA(DOA),
|
||||
|
@ -250,7 +252,7 @@ if (PORT_A_WIDTH < 9 || PORT_B_WIDTH < 9) begin
|
|||
.CEB(PORT_B_CLK_EN),
|
||||
.WREB(WREB),
|
||||
.RESETB(RSTB),
|
||||
.OCEB(1'b0),
|
||||
.OCEB(1'b1),
|
||||
.ADB(ADB),
|
||||
.DIB(DIB),
|
||||
.DOB(DOB),
|
||||
|
@ -266,7 +268,7 @@ end else begin
|
|||
assign PORT_A_RD_DATA = DOA;
|
||||
assign PORT_B_RD_DATA = DOB;
|
||||
|
||||
DPX9 #(
|
||||
DPX9B #(
|
||||
`INIT(init_slice_x9)
|
||||
.READ_MODE0(1'b0),
|
||||
.READ_MODE1(1'b0),
|
||||
|
@ -274,16 +276,18 @@ end else begin
|
|||
.WRITE_MODE1(PORT_B_OPTION_WRITE_MODE),
|
||||
.BIT_WIDTH_0(PORT_A_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),
|
||||
) _TECHMAP_REPLACE_ (
|
||||
.BLKSEL(3'b000),
|
||||
.BLKSELA(3'b000),
|
||||
.BLKSELB(3'b000),
|
||||
|
||||
.CLKA(PORT_A_CLK),
|
||||
.CEA(PORT_A_CLK_EN),
|
||||
.WREA(WREA),
|
||||
.RESETA(RSTA),
|
||||
.OCEA(1'b0),
|
||||
.OCEA(1'b1),
|
||||
.ADA(ADA),
|
||||
.DIA(DIA),
|
||||
.DOA(DOA),
|
||||
|
@ -292,7 +296,7 @@ end else begin
|
|||
.CEB(PORT_B_CLK_EN),
|
||||
.WREB(WREB),
|
||||
.RESETB(RSTB),
|
||||
.OCEB(1'b0),
|
||||
.OCEB(1'b1),
|
||||
.ADB(ADB),
|
||||
.DIB(DIB),
|
||||
.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);
|
||||
|
||||
SDP #(
|
||||
SDPB #(
|
||||
`INIT(init_slice_x8)
|
||||
.READ_MODE(1'b0),
|
||||
.BIT_WIDTH_0(`x8_width(PORT_W_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),
|
||||
) _TECHMAP_REPLACE_ (
|
||||
.BLKSEL(3'b000),
|
||||
.BLKSELA(3'b000),
|
||||
.BLKSELB(3'b000),
|
||||
|
||||
.CLKA(PORT_W_CLK),
|
||||
.CEA(PORT_W_CLK_EN),
|
||||
.WREA(WRE),
|
||||
.RESETA(1'b0),
|
||||
.ADA(ADW),
|
||||
.DI(DI),
|
||||
|
||||
.CLKB(PORT_R_CLK),
|
||||
.CEB(PORT_R_CLK_EN),
|
||||
.WREB(1'b0),
|
||||
.RESETB(RST),
|
||||
.OCE(1'b0),
|
||||
.OCE(1'b1),
|
||||
.ADB(PORT_R_ADDR),
|
||||
.DO(DO),
|
||||
);
|
||||
|
@ -377,28 +381,28 @@ end else begin
|
|||
|
||||
assign PORT_R_RD_DATA = DO;
|
||||
|
||||
SDPX9 #(
|
||||
SDPX9B #(
|
||||
`INIT(init_slice_x9)
|
||||
.READ_MODE(1'b0),
|
||||
.BIT_WIDTH_0(PORT_W_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),
|
||||
) _TECHMAP_REPLACE_ (
|
||||
.BLKSEL(3'b000),
|
||||
.BLKSELA(3'b000),
|
||||
.BLKSELB(3'b000),
|
||||
|
||||
.CLKA(PORT_W_CLK),
|
||||
.CEA(PORT_W_CLK_EN),
|
||||
.WREA(WRE),
|
||||
.RESETA(1'b0),
|
||||
.ADA(ADW),
|
||||
.DI(DI),
|
||||
|
||||
.CLKB(PORT_R_CLK),
|
||||
.CEB(PORT_R_CLK_EN),
|
||||
.WREB(1'b0),
|
||||
.RESETB(RST),
|
||||
.OCE(1'b0),
|
||||
.OCE(1'b1),
|
||||
.ADB(PORT_R_ADDR),
|
||||
.DO(DO),
|
||||
);
|
||||
|
|
|
@ -130,7 +130,6 @@ struct SynthGowinPass : public ScriptPass
|
|||
}
|
||||
if (args[argidx] == "-json" && argidx+1 < args.size()) {
|
||||
json_file = args[++argidx];
|
||||
nobram = true;
|
||||
continue;
|
||||
}
|
||||
if (args[argidx] == "-run" && argidx+1 < args.size()) {
|
||||
|
|
|
@ -432,7 +432,7 @@ struct SynthIce40Pass : public ScriptPass
|
|||
run("ice40_wrapcarry -unwrap");
|
||||
run("techmap -map +/ice40/ff_map.v");
|
||||
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"))
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
|
||||
for iter in {1..100}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
for CLKPOL in 0 1; do
|
||||
for ENABLE_EN in 0 1; do
|
||||
|
|
|
@ -32,6 +32,10 @@ ram block $__PDPW8KC_ {
|
|||
cost 64;
|
||||
init no_undef;
|
||||
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;
|
||||
clken;
|
||||
option "RESETMODE" "SYNC" {
|
||||
|
|
|
@ -38,8 +38,20 @@ endfunction
|
|||
|
||||
wire [8:0] DOA;
|
||||
wire [8:0] DOB;
|
||||
wire [8:0] DIA = PORT_A_WR_DATA;
|
||||
wire [8:0] DIB = PORT_B_WR_DATA;
|
||||
wire [8:0] DIA;
|
||||
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_B_RD_DATA = DOB;
|
||||
|
|
|
@ -31,9 +31,6 @@ PRIVATE_NAMESPACE_BEGIN
|
|||
|
||||
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
|
||||
typedef dict<RTLIL::IdString, RTLIL::Const> MergeableGroupKeyType;
|
||||
|
||||
|
@ -42,6 +39,8 @@ struct QlBramMergeWorker {
|
|||
|
||||
QlBramMergeWorker(RTLIL::Module* module) : module(module)
|
||||
{
|
||||
const RTLIL::IdString split_cell_type = ID($__QLF_TDP36K);
|
||||
|
||||
for (RTLIL::Cell* cell : module->selected_cells())
|
||||
{
|
||||
if(cell->type != split_cell_type) continue;
|
||||
|
@ -125,6 +124,7 @@ struct QlBramMergeWorker {
|
|||
|
||||
void merge_brams(RTLIL::Cell* bram1, RTLIL::Cell* bram2)
|
||||
{
|
||||
const RTLIL::IdString merged_cell_type = ID($__QLF_TDP36K_MERGED);
|
||||
|
||||
// Create the new cell
|
||||
RTLIL::Cell* merged = module->addCell(NEW_ID, merged_cell_type);
|
||||
|
|
|
@ -30,10 +30,6 @@ PRIVATE_NAMESPACE_BEGIN
|
|||
// ============================================================================
|
||||
|
||||
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;
|
||||
|
||||
// ..........................................
|
||||
|
@ -67,6 +63,11 @@ struct QlDspIORegs : public Pass {
|
|||
|
||||
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);
|
||||
|
||||
for (auto cell : module->cells()) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
// 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
|
||||
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.
|
||||
SigMap sigmap;
|
||||
|
||||
|
@ -106,6 +74,38 @@ struct QlDspSimdPass : public Pass {
|
|||
{
|
||||
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
|
||||
extra_args(a_Args, 1, a_Design);
|
||||
|
||||
|
@ -126,7 +126,7 @@ struct QlDspSimdPass : public Pass {
|
|||
continue;
|
||||
|
||||
// Add to a group
|
||||
const auto key = getDspConfig(cell);
|
||||
const auto key = getDspConfig(cell, m_DspCfgPorts);
|
||||
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.
|
||||
DspConfig getDspConfig(RTLIL::Cell *a_Cell)
|
||||
DspConfig getDspConfig(RTLIL::Cell *a_Cell, const std::vector<std::pair<IdString, IdString>> &dspCfgPorts)
|
||||
{
|
||||
DspConfig config;
|
||||
|
||||
for (const auto &it : m_DspCfgPorts) {
|
||||
for (const auto &it : dspCfgPorts) {
|
||||
auto port = it.first;
|
||||
|
||||
// Port unconnected
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
unisims=/opt/Xilinx/Vivado/2014.4/data/verilog/src/unisims
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
if [ -z $ISE_DIR ]; then
|
||||
ISE_DIR=/opt/Xilinx/ISE/14.7
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
if [ -z $ISE_DIR ]; then
|
||||
ISE_DIR=/opt/Xilinx/ISE/14.7
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
if [ -z $VIVADO_DIR ]; then
|
||||
VIVADO_DIR=/opt/Xilinx/Vivado/2019.1
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
OPTIND=1
|
||||
seed="" # default to no seed specified
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
for x in *.ys; do
|
||||
echo "Running $x.."
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
../../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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# 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'
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
cxxrtl-test-*
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
{
|
||||
}
|
|
@ -2,8 +2,13 @@
|
|||
|
||||
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;
|
||||
uut.p_clk.set(!uut.p_clk);
|
||||
uut.step();
|
||||
uut.step(&performer);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# run this test many times:
|
||||
# time bash -c 'for ((i=0; i<100; i++)); do echo "-- $i --"; bash run-test.sh || exit 1; done'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
OPTIND=1
|
||||
seed="" # default to no seed specified
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
for x in *.lib; do
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
for x in *.v; do
|
||||
echo "Running $x.."
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
|
||||
OPTIND=1
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -eu
|
||||
source ../gen-tests-makefile.sh
|
||||
run_tests --yosys-scripts
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# run this test many times:
|
||||
# time bash -c 'for ((i=0; i<100; i++)); do echo "-- $i --"; bash run-test.sh || exit 1; done'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
for x in *.ys; do
|
||||
echo "Running $x.."
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
OPTIND=1
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
for x in *.ys; do
|
||||
echo "Running $x.."
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
for x in *.ys; do
|
||||
echo "Running $x.."
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# run this test many times:
|
||||
# time bash -c 'for ((i=0; i<100; i++)); do echo "-- $i --"; bash run-test.sh || exit 1; done'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
OPTIND=1
|
||||
seed="" # default to no seed specified
|
||||
|
|
|
@ -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
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
OPTIND=1
|
||||
seed="" # default to no seed specified
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cat > $1.tpl <<EOT
|
||||
%module main
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Run a simple test with a .ys file
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
|
||||
TESTNAME=$1
|
||||
|
|
|
@ -45,12 +45,12 @@ module top;
|
|||
localparam W = 10;
|
||||
typedef T U;
|
||||
typedef logic [W-1:0] V;
|
||||
struct packed {
|
||||
typedef struct packed {
|
||||
logic [W-1:0] x; // width 10
|
||||
U y; // width 5
|
||||
V z; // width 10
|
||||
} shadow;
|
||||
// This currently only works as long as long as shadow is not typedef'ed
|
||||
} shadow_t;
|
||||
shadow_t shadow;
|
||||
always @(*) assert($bits(shadow.x) == 10);
|
||||
always @(*) assert($bits(shadow.y) == 5);
|
||||
always @(*) assert($bits(shadow.z) == 10);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
../../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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
trap 'echo "ERROR in chparam.sh" >&2; exit 1' ERR
|
||||
|
||||
|
|
|
@ -69,6 +69,24 @@ 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
|
||||
|
||||
### 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) ###
|
||||
design -reset
|
||||
read_verilog ./dynamic_part_select/reset_test.v
|
||||
|
|
|
@ -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
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
fail() {
|
||||
echo "$1" >&2
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -ex
|
||||
../../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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
trap 'echo "ERROR in sv_implicit_ports.sh" >&2; exit 1' ERR
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
trap 'echo "ERROR in svalways.sh" >&2; exit 1' ERR
|
||||
|
||||
|
|
|
@ -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
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
run() {
|
||||
alt=$1
|
||||
|
@ -15,7 +15,7 @@ run() {
|
|||
-p "read_verilog dynamic_range_lhs.v" \
|
||||
-p "proc" \
|
||||
-p "equiv_make gold gate equiv" \
|
||||
-p "equiv_simple" \
|
||||
-p "equiv_simple -undef" \
|
||||
-p "equiv_status -assert"
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
module gate(
|
||||
output reg [`LEFT:`RIGHT] out_u, out_s,
|
||||
(* nowrshmsk = `ALT *)
|
||||
output reg [`LEFT:`RIGHT] out_u, out_s,
|
||||
input wire data,
|
||||
input wire [1:0] sel1, sel2
|
||||
);
|
||||
always @* begin
|
||||
out_u = 0;
|
||||
out_s = 0;
|
||||
out_u = 'x;
|
||||
out_s = 'x;
|
||||
case (`SPAN)
|
||||
1: begin
|
||||
out_u[sel1*sel2] = data;
|
||||
|
@ -43,8 +43,8 @@ task set;
|
|||
out_s[b] = data;
|
||||
endtask
|
||||
always @* begin
|
||||
out_u = 0;
|
||||
out_s = 0;
|
||||
out_u = 'x;
|
||||
out_s = 'x;
|
||||
case (sel1*sel2)
|
||||
2'b00: set(0, 0);
|
||||
2'b01: set(1, 1);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
source common.sh
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue