Docs: updating to current 'master'

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

View File

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

View File

@ -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.

View File

@ -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 ""

View File

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

View File

@ -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");

View File

@ -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;

View File

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

View File

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

View File

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

View File

@ -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

View File

@ -1327,6 +1327,9 @@ def write_yw_trace(steps, index, allregs=False, filename=None):
sig = yw.add_sig(word_path, overlap_start, overlap_end - overlap_start, True)
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)

View File

@ -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,

View File

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

View File

@ -183,7 +183,8 @@ This requires a Yosys witness AIGER map file as generated by 'write_aiger -ywmap
@click.argument("mapfile", type=click.File("r"))
@click.argument("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()

View File

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

View File

@ -376,7 +376,7 @@ void dump_sigspec(std::ostream &f, const RTLIL::SigSpec &sig)
}
}
void dump_attributes(std::ostream &f, std::string indent, dict<RTLIL::IdString, RTLIL::Const> &attributes, char term = '\n', bool modattr = false, bool regattr = false, bool as_comment = false)
void dump_attributes(std::ostream &f, std::string indent, dict<RTLIL::IdString, RTLIL::Const> &attributes, std::string term = "\n", bool modattr = false, bool regattr = false, bool as_comment = false)
{
if (noattr)
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;

View File

@ -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``)

View File

@ -122,7 +122,7 @@ All binary RTL cells have two input ports ``\A`` and ``\B`` and one output port
:verilog:`Y = A >>> B` $sshr :verilog:`Y = A - B` $sub
:verilog:`Y = A && B` $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.

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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:

View File

@ -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();

View File

@ -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);

View File

@ -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";
}

View File

@ -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;

View File

@ -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);

View File

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

View File

@ -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())

View File

@ -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))

View File

@ -690,7 +690,7 @@ bool apply_clock(MemConfig &cfg, const PortVariant &def, SigBit clk, bool clk_po
// Perform write port assignment, validating clock options as we go.
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) {

View File

@ -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;
}

View File

@ -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())

View File

@ -88,6 +88,17 @@ struct TriggeredAssertion {
{ }
};
struct DisplayOutput {
int step;
SimInstance *instance;
Cell *cell;
std::string output;
DisplayOutput(int step, SimInstance *instance, Cell *cell, std::string output) :
step(step), instance(instance), cell(cell), output(output)
{ }
};
struct SimShared
{
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();
}

View File

@ -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),
);

View File

@ -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()) {

View File

@ -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"))

View File

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

View File

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

View File

@ -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

View File

@ -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

View File

@ -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" {

View File

@ -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;

View File

@ -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);

View File

@ -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()) {

View File

@ -60,43 +60,11 @@ struct QlDspSimdPass : public Pass {
// ..........................................
// DSP control and config ports to consider and how to map them to ports
// of the target DSP cell
const std::vector<std::pair<IdString, IdString>> m_DspCfgPorts = {
std::make_pair(ID(clock_i), ID(clk)),
std::make_pair(ID(reset_i), ID(reset)),
std::make_pair(ID(feedback_i), ID(feedback)),
std::make_pair(ID(load_acc_i), ID(load_acc)),
std::make_pair(ID(unsigned_a_i), ID(unsigned_a)),
std::make_pair(ID(unsigned_b_i), ID(unsigned_b)),
std::make_pair(ID(subtract_i), ID(subtract)),
std::make_pair(ID(output_select_i), ID(output_select)),
std::make_pair(ID(saturate_enable_i), ID(saturate_enable)),
std::make_pair(ID(shift_right_i), ID(shift_right)),
std::make_pair(ID(round_i), ID(round)),
std::make_pair(ID(register_inputs_i), ID(register_inputs))
};
const int m_ModeBitsSize = 80;
// 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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

View File

@ -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'

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

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

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

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

View File

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

View File

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

View File

@ -2,8 +2,13 @@
int main()
{
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;
}

View File

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

View File

@ -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'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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'

View File

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

View File

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

View File

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

View File

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

View File

@ -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'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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);

View File

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

View File

@ -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

View File

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

View File

@ -69,6 +69,24 @@ design -copy-from gate -as gate gate
miter -equiv -make_assert -make_outcmp -flatten gold gate equiv
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

View File

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

View File

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

View File

@ -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

View File

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

View File

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

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

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

View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
run() {
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"
}

View File

@ -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);

View File

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

View File

@ -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