diff --git a/Makefile b/Makefile index c56cb0a3f6e..7883cf2b9b8 100644 --- a/Makefile +++ b/Makefile @@ -652,12 +652,7 @@ $(eval $(call add_include_file,backends/cxxrtl/cxxrtl_vcd_capi.h)) OBJS += kernel/driver.o kernel/register.o kernel/rtlil.o kernel/log.o kernel/calc.o kernel/yosys.o OBJS += kernel/binding.o -ifeq ($(ENABLE_ABC),1) -ifneq ($(ABCEXTERNAL),) -kernel/yosys.o: CXXFLAGS += -DABCEXTERNAL='"$(ABCEXTERNAL)"' -endif -endif -OBJS += kernel/cellaigs.o kernel/celledges.o kernel/satgen.o kernel/qcsat.o kernel/mem.o kernel/ffmerge.o kernel/ff.o kernel/yw.o kernel/json.o +OBJS += kernel/cellaigs.o kernel/celledges.o kernel/satgen.o kernel/qcsat.o kernel/mem.o kernel/ffmerge.o kernel/ff.o kernel/yw.o kernel/json.o kernel/fmt.o ifeq ($(ENABLE_ZLIB),1) OBJS += kernel/fstdata.o endif @@ -669,6 +664,11 @@ endif kernel/log.o: CXXFLAGS += -DYOSYS_SRC='"$(YOSYS_SRC)"' kernel/yosys.o: CXXFLAGS += -DYOSYS_DATDIR='"$(DATDIR)"' -DYOSYS_PROGRAM_PREFIX='"$(PROGRAM_PREFIX)"' +ifeq ($(ENABLE_ABC),1) +ifneq ($(ABCEXTERNAL),) +kernel/yosys.o: CXXFLAGS += -DABCEXTERNAL='"$(ABCEXTERNAL)"' +endif +endif OBJS += libs/bigint/BigIntegerAlgorithms.o libs/bigint/BigInteger.o libs/bigint/BigIntegerUtils.o OBJS += libs/bigint/BigUnsigned.o libs/bigint/BigUnsignedInABase.o @@ -882,6 +882,7 @@ endif +cd tests/memfile && bash run-test.sh +cd tests/verilog && bash run-test.sh +cd tests/xprop && bash run-test.sh $(SEEDOPT) + +cd tests/fmt && bash run-test.sh @echo "" @echo " Passed \"make test\"." @echo "" diff --git a/backends/cxxrtl/cxxrtl.h b/backends/cxxrtl/cxxrtl.h index 5d0596f0d61..8f5e4035a34 100644 --- a/backends/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/cxxrtl.h @@ -518,6 +518,14 @@ struct value : public expr_base> { return count; } + size_t chunks_used() const { + for (size_t n = chunks; n > 0; n--) { + if (data[n - 1] != 0) + return n; + } + return 0; + } + template std::pair, bool /*CarryOut*/> alu(const value &other) const { value result; @@ -575,6 +583,84 @@ struct value : public expr_base> { result.data[result.chunks - 1] &= result.msb_mask; return result; } + + // parallel to BigUnsigned::divideWithRemainder; quotient is stored in q, + // *this is left with the remainder. See that function for commentary describing + // how/why this works. + void divideWithRemainder(const value &b, value &q) { + assert(this != &q); + + if (this == &b || &q == &b) { + value tmpB(b); + divideWithRemainder(tmpB, q); + return; + } + + q = value {0u}; + + size_t blen = b.chunks_used(); + if (blen == 0) { + return; + } + + size_t len = chunks_used(); + if (len < blen) { + return; + } + + size_t i, j, k; + size_t i2; + chunk_t temp; + bool borrowIn, borrowOut; + + size_t origLen = len; + len++; + chunk::type blk[len]; + std::copy(data, data + origLen, blk); + blk[origLen] = 0; + chunk::type subtractBuf[len]; + std::fill(subtractBuf, subtractBuf + len, 0); + + size_t qlen = origLen - blen + 1; + + i = qlen; + while (i > 0) { + i--; + i2 = chunk::bits; + while (i2 > 0) { + i2--; + for (j = 0, k = i, borrowIn = false; j <= blen; j++, k++) { + temp = blk[k] - getShiftedBlock(b, j, i2); + borrowOut = (temp > blk[k]); + if (borrowIn) { + borrowOut |= (temp == 0); + temp--; + } + subtractBuf[k] = temp; + borrowIn = borrowOut; + } + for (; k < origLen && borrowIn; k++) { + borrowIn = (blk[k] == 0); + subtractBuf[k] = blk[k] - 1; + } + if (!borrowIn) { + q.data[i] |= (chunk::type(1) << i2); + while (k > i) { + k--; + blk[k] = subtractBuf[k]; + } + } + } + } + + std::copy(blk, blk + origLen, data); + } + + static chunk::type getShiftedBlock(const value &num, size_t x, size_t y) { + chunk::type part1 = (x == 0 || y == 0) ? 0 : (num.data[x - 1] >> (chunk::bits - y)); + chunk::type part2 = (x == num.chunks) ? 0 : (num.data[x] << y); + return part1 | part2; + } }; // Expression template for a slice, usable as lvalue or rvalue, and composable with other expression templates here. @@ -707,6 +793,99 @@ std::ostream &operator<<(std::ostream &os, const value &val) { return os; } +template +struct value_formatted { + const value &val; + bool character; + bool justify_left; + char padding; + int width; + int base; + bool signed_; + bool plus; + + value_formatted(const value &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 &) = delete; + value_formatted &operator=(const value_formatted &rhs) = delete; +}; + +template +std::ostream &operator<<(std::ostream &os, const value_formatted &vf) +{ + value 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 quotient; + val.divideWithRemainder(value{10u}, quotient); + buf += '0' + val.template trunc<(Bits > 4 ? 4 : Bits)>().val().template get(); + 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 struct wire { static constexpr size_t bits = Bits; @@ -1091,7 +1270,10 @@ struct module { virtual bool eval() = 0; virtual bool commit() = 0; + unsigned int steps = 0; + size_t step() { + ++steps; size_t deltas = 0; bool converged = false; do { diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index 1b13985ab4e..3a3bc39bcd6 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -24,6 +24,7 @@ #include "kernel/celltypes.h" #include "kernel/mem.h" #include "kernel/log.h" +#include "kernel/fmt.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN @@ -217,7 +218,7 @@ bool is_internal_cell(RTLIL::IdString type) bool is_effectful_cell(RTLIL::IdString type) { - return type.isPublic(); + return type.isPublic() || type == ID($print); } bool is_cxxrtl_blackbox_cell(const RTLIL::Cell *cell) @@ -281,6 +282,7 @@ struct FlowGraph { CONNECT, CELL_SYNC, CELL_EVAL, + PRINT_SYNC, PROCESS_SYNC, PROCESS_CASE, MEM_RDPORT, @@ -290,6 +292,7 @@ struct FlowGraph { Type type; RTLIL::SigSig connect = {}; const RTLIL::Cell *cell = nullptr; + std::vector print_sync_cells; const RTLIL::Process *process = nullptr; const Mem *mem = nullptr; int portidx; @@ -477,6 +480,15 @@ struct FlowGraph { return node; } + Node *add_print_sync_node(std::vector cells) + { + Node *node = new Node; + node->type = Node::Type::PRINT_SYNC; + node->print_sync_cells = cells; + nodes.push_back(node); + return node; + } + // Processes void add_case_rule_defs_uses(Node *node, const RTLIL::CaseRule *case_) { @@ -681,6 +693,7 @@ struct CxxrtlWorker { bool split_intf = false; std::string intf_filename; std::string design_ns = "cxxrtl_design"; + std::string print_output = "std::cout"; std::ostream *impl_f = nullptr; std::ostream *intf_f = nullptr; @@ -1036,6 +1049,63 @@ struct CxxrtlWorker { f << ".val()"; } + void dump_print(const RTLIL::Cell *cell) + { + Fmt fmt = {}; + fmt.parse_rtlil(cell); + + f << indent << "if ("; + 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"; + dec_indent(); + f << indent << "}\n"; + } + + void dump_sync_print(std::vector &cells) + { + log_assert(!cells.empty()); + const auto &trg = cells[0]->getPort(ID::TRG); + const auto &trg_polarity = cells[0]->getParam(ID::TRG_POLARITY); + + f << indent << "if ("; + for (int i = 0; i < trg.size(); i++) { + RTLIL::SigBit trg_bit = trg[i]; + trg_bit = sigmaps[trg_bit.wire->module](trg_bit); + log_assert(trg_bit.wire); + + if (i != 0) + f << " || "; + + if (trg_polarity[i] == State::S1) + f << "posedge_"; + else + f << "negedge_"; + f << mangle(trg_bit); + } + f << ") {\n"; + inc_indent(); + 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(); + }); + for (auto cell : cells) { + log_assert(cell->getParam(ID::TRG_ENABLE).as_bool()); + log_assert(cell->getPort(ID::TRG) == trg); + log_assert(cell->getParam(ID::TRG_POLARITY) == trg_polarity); + + std::vector inlined_cells; + collect_cell_eval(cell, /*for_debug=*/false, inlined_cells); + dump_inlined_cells(inlined_cells); + dump_print(cell); + } + dec_indent(); + + f << indent << "}\n"; + } + void dump_inlined_cells(const std::vector &cells) { if (cells.empty()) { @@ -1202,6 +1272,25 @@ struct CxxrtlWorker { f << " = "; dump_cell_expr(cell, for_debug); f << ";\n"; + // $print cell + } else if (cell->type == ID($print)) { + 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()); + + 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"; // Flip-flops } else if (is_ff_cell(cell->type)) { log_assert(!for_debug); @@ -1946,6 +2035,9 @@ struct CxxrtlWorker { case FlowGraph::Node::Type::CELL_EVAL: dump_cell_eval(node.cell); break; + case FlowGraph::Node::Type::PRINT_SYNC: + dump_sync_print(node.print_sync_cells); + break; case FlowGraph::Node::Type::PROCESS_CASE: dump_process_case(node.process); break; @@ -2291,6 +2383,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); + f << indent << "value<" << (1 + cell->getParam(ID::ARGS_WIDTH).as_int()) << "> " << mangle(cell) << ";\n"; + } if (is_internal_cell(cell->type)) continue; dump_attrs(cell); @@ -2601,6 +2698,16 @@ struct CxxrtlWorker { register_edge_signal(sigmap, cell->getPort(ID::CLK), cell->parameters[ID::CLK_POLARITY].as_bool() ? RTLIL::STp : RTLIL::STn); } + + // $print cells may be triggered on posedge/negedge events. + if (cell->type == ID($print) && cell->getParam(ID::TRG_ENABLE).as_bool()) { + for (size_t i = 0; i < (size_t)cell->getParam(ID::TRG_WIDTH).as_int(); i++) { + RTLIL::SigBit trg = cell->getPort(ID::TRG).extract(i, 1); + if (is_valid_clock(trg)) + register_edge_signal(sigmap, trg, + cell->parameters[ID::TRG_POLARITY][i] == RTLIL::S1 ? RTLIL::STp : RTLIL::STn); + } + } } for (auto &mem : memories) { @@ -2736,6 +2843,8 @@ struct CxxrtlWorker { for (auto node : flow.nodes) { if (node->type == FlowGraph::Node::Type::CELL_EVAL && is_effectful_cell(node->cell->type)) worklist.insert(node); // node has effects + else if (node->type == FlowGraph::Node::Type::PRINT_SYNC) + worklist.insert(node); // node is sync $print else if (node->type == FlowGraph::Node::Type::MEM_WRPORTS) worklist.insert(node); // node is memory write else if (node->type == FlowGraph::Node::Type::PROCESS_SYNC && is_memwr_process(node->process)) @@ -2792,9 +2901,22 @@ struct CxxrtlWorker { } // Emit reachable nodes in eval(). + // Accumulate sync $print cells per trigger condition. + dict, std::vector> sync_print_cells; for (auto node : node_order) - if (live_nodes[node]) - schedule[module].push_back(*node); + 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()) + 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); + } + + for (auto &it : sync_print_cells) { + auto node = flow.add_print_sync_node(it.second); + schedule[module].push_back(*node); + } // For maximum performance, the state of the simulation (which is the same as the set of its double buffered // wires, since using a singly buffered wire for any kind of state introduces a race condition) should contain @@ -3213,6 +3335,11 @@ struct CxxrtlBackend : public Backend { log(" place the generated code into namespace . if not specified,\n"); log(" \"cxxrtl_design\" is used.\n"); log("\n"); + log(" -print-output \n"); + log(" $print cells in the generated code direct their output to .\n"); + log(" must be one of \"std::cout\", \"std::cerr\". if not specified,\n"); + log(" \"std::cout\" is used.\n"); + log("\n"); log(" -nohierarchy\n"); log(" use design hierarchy as-is. in most designs, a top module should be\n"); log(" present as it is exposed through the C API and has unbuffered outputs\n"); @@ -3351,6 +3478,14 @@ struct CxxrtlBackend : public Backend { worker.design_ns = args[++argidx]; continue; } + if (args[argidx] == "-print-output" && argidx+1 < args.size()) { + worker.print_output = args[++argidx]; + if (!(worker.print_output == "std::cout" || worker.print_output == "std::cerr")) { + log_cmd_error("Invalid output stream \"%s\".\n", worker.print_output.c_str()); + worker.print_output = "std::cout"; + } + continue; + } break; } extra_args(f, filename, args, argidx); diff --git a/backends/verilog/verilog_backend.cc b/backends/verilog/verilog_backend.cc index 5ff191a9c16..7099c18c349 100644 --- a/backends/verilog/verilog_backend.cc +++ b/backends/verilog/verilog_backend.cc @@ -27,6 +27,7 @@ #include "kernel/sigtools.h" #include "kernel/ff.h" #include "kernel/mem.h" +#include "kernel/fmt.h" #include #include #include @@ -1005,6 +1006,41 @@ void dump_cell_expr_binop(std::ostream &f, std::string indent, RTLIL::Cell *cell f << stringf(";\n"); } +void dump_cell_expr_print(std::ostream &f, std::string indent, const RTLIL::Cell *cell) +{ + Fmt fmt = {}; + fmt.parse_rtlil(cell); + std::vector args = fmt.emit_verilog(); + + f << stringf("%s" "$write(", indent.c_str()); + bool first = true; + for (auto &arg : args) { + if (first) { + first = false; + } else { + f << ", "; + } + switch (arg.type) { + case VerilogFmtArg::STRING: + dump_const(f, RTLIL::Const(arg.str)); + break; + case VerilogFmtArg::INTEGER: + f << (arg.signed_ ? "$signed(" : "$unsigned("); + dump_sigspec(f, arg.sig); + f << ")"; + break; + case VerilogFmtArg::TIME: + if (arg.realtime) + f << "$realtime"; + else + f << "$time"; + break; + default: log_abort(); + } + } + f << stringf(");\n"); +} + bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) { if (cell->type == ID($_NOT_)) { @@ -1753,6 +1789,22 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) return true; } + if (cell->type == ID($print)) + { + // Sync $print cells are accumulated and handled in dump_module. + if (cell->getParam(ID::TRG_ENABLE).as_bool()) + return true; + + f << stringf("%s" "always @*\n", indent.c_str()); + + f << stringf("%s" " if (", indent.c_str()); + dump_sigspec(f, cell->getPort(ID::EN)); + f << stringf(")\n"); + + dump_cell_expr_print(f, indent + " ", cell); + return true; + } + // FIXME: $fsm return false; @@ -1841,6 +1893,34 @@ 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 &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]); + } + 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(); + }); + for (auto cell : cells) { + f << stringf("%s" " if (", indent.c_str()); + dump_sigspec(f, cell->getPort(ID::EN)); + f << stringf(")\n"); + + dump_cell_expr_print(f, indent + " ", cell); + } + + f << stringf("%s" "end\n", indent.c_str()); +} + void dump_conn(std::ostream &f, std::string indent, const RTLIL::SigSpec &left, const RTLIL::SigSpec &right) { if (simple_lhs) { @@ -2022,6 +2102,8 @@ void dump_process(std::ostream &f, std::string indent, RTLIL::Process *proc, boo void dump_module(std::ostream &f, std::string indent, RTLIL::Module *module) { + std::map, std::vector> sync_print_cells; + reg_wires.clear(); reset_auto_counter(module); active_module = module; @@ -2052,6 +2134,11 @@ void dump_module(std::ostream &f, std::string indent, RTLIL::Module *module) std::set> reg_bits; for (auto cell : module->cells()) { + if (cell->type == ID($print) && cell->getParam(ID::TRG_ENABLE).as_bool()) { + sync_print_cells[make_pair(cell->getPort(ID::TRG), cell->getParam(ID::TRG_POLARITY))].push_back(cell); + continue; + } + if (!RTLIL::builtin_ff_cell_types().count(cell->type) || !cell->hasPort(ID::Q) || cell->type.in(ID($ff), ID($_FF_))) continue; @@ -2107,6 +2194,9 @@ void dump_module(std::ostream &f, std::string indent, RTLIL::Module *module) for (auto cell : module->cells()) dump_cell(f, indent + " ", cell); + for (auto &it : sync_print_cells) + dump_sync_print(f, indent + " ", it.first.first, it.first.second, it.second); + for (auto it = module->processes.begin(); it != module->processes.end(); ++it) dump_process(f, indent + " ", it->second); diff --git a/docs/source/CHAPTER_CellLib.rst b/docs/source/CHAPTER_CellLib.rst index c8904086879..494c0651c6a 100644 --- a/docs/source/CHAPTER_CellLib.rst +++ b/docs/source/CHAPTER_CellLib.rst @@ -627,6 +627,133 @@ Add information about ``$assert``, ``$assume``, ``$live``, ``$fair``, Add information about ``$ff`` and ``$_FF_`` cells. +Debugging cells +~~~~~~~~~~~~~~~ + +The ``$print`` cell is used to log the values of signals, akin to (and +translatable to) the ``$display`` and ``$write`` family of tasks in Verilog. It +has the following parameters: + +``\FORMAT`` + The internal format string. The syntax is described below. + +``\ARGS_WIDTH`` + The width (in bits) of the signal on the ``\ARGS`` port. + +``\TRG_ENABLE`` + True if triggered on specific signals defined in ``\TRG``; false if + triggered whenever ``\ARGS`` or ``\EN`` change and ``\EN`` is 1. + +If ``\TRG_ENABLE`` is true, the following parameters also apply: + +``\TRG_WIDTH`` + The number of bits in the ``\TRG`` port. + +``\TRG_POLARITY`` + For each bit in ``\TRG``, 1 if that signal is positive-edge triggered, 0 if + negative-edge triggered. + +``\PRIORITY`` + When multiple ``$print`` cells fire on the same trigger, they execute in + descending priority order. + +Ports: + +``\TRG`` + The signals that control when this ``$print`` cell is triggered. + +``\EN`` + Enable signal for the whole cell. + +``\ARGS`` + The values to be displayed, in format string order. + +Format string syntax +^^^^^^^^^^^^^^^^^^^^ + +The format string syntax resembles Python f-strings. Regular text is passed +through unchanged until a format specifier is reached, starting with a ``{``. + +Format specifiers have the following syntax. Unless noted, all items are +required: + +``{`` + Denotes the start of the format specifier. + +size + Signal size in bits; this many bits are consumed from the ``\ARGS`` port by + this specifier. + +``:`` + Separates the size from the remaining items. + +justify + ``>`` for right-justified, ``<`` for left-justified. + +padding + ``0`` for zero-padding, or a space for space-padding. + +width\ *?* + (optional) The number of characters wide to pad to. + +base + * ``b`` for base-2 integers (binary) + * ``o`` for base-8 integers (octal) + * ``d`` for base-10 integers (decimal) + * ``h`` for base-16 integers (hexadecimal) + * ``c`` for ASCII characters/strings + * ``t`` and ``r`` for simulation time (corresponding to :verilog:`$time` and :verilog:`$realtime`) + +For integers, this item may follow: + +``+``\ *?* + (optional, decimals only) Include a leading plus for non-negative numbers. + This can assist with symmetry with negatives in tabulated output. + +signedness + ``u`` for unsigned, ``s`` for signed. This distinction is only respected + when rendering decimals. + +ASCII characters/strings have no special options, but the signal size must be +divisible by 8. + +For simulation time, the signal size must be zero. + +Finally: + +``}`` + Denotes the end of the format specifier. + +Some example format specifiers: + ++ ``{8:>02hu}`` - 8-bit unsigned integer rendered as hexadecimal, + right-justified, zero-padded to 2 characters wide. ++ ``{32:< 15d+s}`` - 32-bit signed integer rendered as decimal, left-justified, + space-padded to 15 characters wide, positive values prefixed with ``+``. ++ ``{16:< 10hu}`` - 16-bit unsigned integer rendered as hexadecimal, + left-justified, space-padded to 10 characters wide. ++ ``{0:>010t}`` - simulation time, right-justified, zero-padded to 10 characters + wide. + +To include literal ``{`` and ``}`` characters in your format string, use ``{{`` +and ``}}`` respectively. + +It is an error for a format string to consume more or less bits from ``\ARGS`` +than the port width. + +Values are never truncated, regardless of the specified width. + +Note that further restrictions on allowable combinations of options may apply +depending on the backend used. + +For example, Verilog does not have a format specifier that allows zero-padding a +string (i.e. more than 1 ASCII character), though zero-padding a single +character is permitted. + +Thus, while the RTLIL format specifier ``{8:>02c}`` translates to ``%02c``, +``{16:>02c}`` cannot be represented in Verilog and will fail to emit. In this +case, ``{16:> 02c}`` must be used, which translates to ``%2s``. + .. _sec:celllib_gates: Gates diff --git a/frontends/ast/ast.h b/frontends/ast/ast.h index 8893d5e010c..e357579add2 100644 --- a/frontends/ast/ast.h +++ b/frontends/ast/ast.h @@ -30,6 +30,7 @@ #define AST_H #include "kernel/rtlil.h" +#include "kernel/fmt.h" #include #include @@ -277,7 +278,9 @@ namespace AST bool replace_variables(std::map &variables, AstNode *fcall, bool must_succeed); AstNode *eval_const_function(AstNode *fcall, bool must_succeed); bool is_simple_const_expr(); - std::string process_format_str(const std::string &sformat, int next_arg, int stage, int width_hint, bool sign_hint); + + // helper for parsing format strings + Fmt processFormat(int stage, bool sformat_like, int default_base = 10, size_t first_arg_at = 0); bool is_recursive_function() const; std::pair get_tern_choice(); diff --git a/frontends/ast/genrtlil.cc b/frontends/ast/genrtlil.cc index 3aa19b70687..81fb3189d94 100644 --- a/frontends/ast/genrtlil.cc +++ b/frontends/ast/genrtlil.cc @@ -315,7 +315,10 @@ struct AST_INTERNAL::ProcessGenerator // Buffer for generating the init action RTLIL::SigSpec init_lvalue, init_rvalue; - ProcessGenerator(AstNode *always, RTLIL::SigSpec initSyncSignalsArg = RTLIL::SigSpec()) : always(always), initSyncSignals(initSyncSignalsArg) + // The most recently assigned $print cell \PRIORITY. + int last_print_priority; + + ProcessGenerator(AstNode *always, RTLIL::SigSpec initSyncSignalsArg = RTLIL::SigSpec()) : always(always), initSyncSignals(initSyncSignalsArg), last_print_priority(0) { // rewrite lookahead references LookaheadRewriter la_rewriter(always); @@ -693,8 +696,86 @@ struct AST_INTERNAL::ProcessGenerator ast->input_error("Found parameter declaration in block without label!\n"); break; - case AST_NONE: case AST_TCALL: + if (ast->str == "$display" || ast->str == "$displayb" || ast->str == "$displayh" || ast->str == "$displayo" || + ast->str == "$write" || ast->str == "$writeb" || ast->str == "$writeh" || ast->str == "$writeo") { + std::stringstream sstr; + sstr << ast->str << "$" << ast->filename << ":" << ast->location.first_line << "$" << (autoidx++); + + RTLIL::Cell *cell = current_module->addCell(sstr.str(), ID($print)); + set_src_attr(cell, ast); + + RTLIL::SigSpec triggers; + RTLIL::Const polarity; + for (auto sync : proc->syncs) { + if (sync->type == RTLIL::STp) { + triggers.append(sync->signal); + polarity.bits.push_back(RTLIL::S1); + } else if (sync->type == RTLIL::STn) { + triggers.append(sync->signal); + polarity.bits.push_back(RTLIL::S0); + } + } + cell->parameters[ID::TRG_WIDTH] = triggers.size(); + cell->parameters[ID::TRG_ENABLE] = !triggers.empty(); + cell->parameters[ID::TRG_POLARITY] = polarity; + cell->parameters[ID::PRIORITY] = --last_print_priority; + cell->setPort(ID::TRG, triggers); + + Wire *wire = current_module->addWire(sstr.str() + "_EN", 1); + set_src_attr(wire, ast); + cell->setPort(ID::EN, wire); + + proc->root_case.actions.push_back(SigSig(wire, false)); + current_case->actions.push_back(SigSig(wire, true)); + + int default_base = 10; + if (ast->str.back() == 'b') + default_base = 2; + else if (ast->str.back() == 'o') + default_base = 8; + else if (ast->str.back() == 'h') + default_base = 16; + + std::vector args; + for (auto node : ast->children) { + int width; + bool is_signed; + node->detectSignWidth(width, is_signed, nullptr); + + VerilogFmtArg arg = {}; + arg.filename = node->filename; + arg.first_line = node->location.first_line; + if (node->type == AST_CONSTANT && node->is_string) { + arg.type = VerilogFmtArg::STRING; + arg.str = node->bitsAsConst().decode_string(); + // and in case this will be used as an argument... + arg.sig = node->bitsAsConst(); + arg.signed_ = false; + } else if (node->type == AST_IDENTIFIER && node->str == "$time") { + arg.type = VerilogFmtArg::TIME; + } else if (node->type == AST_IDENTIFIER && node->str == "$realtime") { + arg.type = VerilogFmtArg::TIME; + arg.realtime = true; + } else { + arg.type = VerilogFmtArg::INTEGER; + arg.sig = node->genRTLIL(); + arg.signed_ = is_signed; + } + args.push_back(arg); + } + + Fmt fmt = {}; + fmt.parse_verilog(args, /*sformat_like=*/false, default_base, /*task_name=*/ast->str, current_module->name); + if (ast->str.substr(0, 8) == "$display") + fmt.append_string("\n"); + fmt.emit_rtlil(cell); + } else if (!ast->str.empty()) { + log_file_error(ast->filename, ast->location.first_line, "Found unsupported invocation of system task `%s'!\n", ast->str.c_str()); + } + break; + + case AST_NONE: case AST_FOR: break; diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc index 64191cd7ebf..ee1be3781c8 100644 --- a/frontends/ast/simplify.cc +++ b/frontends/ast/simplify.cc @@ -43,141 +43,40 @@ using namespace AST_INTERNAL; // Process a format string and arguments for $display, $write, $sprintf, etc -std::string AstNode::process_format_str(const std::string &sformat, int next_arg, int stage, int width_hint, bool sign_hint) { - // Other arguments are placeholders. Process the string as we go through it - std::string sout; - for (size_t i = 0; i < sformat.length(); i++) - { - // format specifier - if (sformat[i] == '%') - { - // If there's no next character, that's a problem - if (i+1 >= sformat.length()) - input_error("System task `%s' called with `%%' at end of string.\n", str.c_str()); - - char cformat = sformat[++i]; - - // %% is special, does not need a matching argument - if (cformat == '%') - { - sout += '%'; - continue; - } - - bool got_len = false; - bool got_zlen = false; - int len_value = 0; - - while ('0' <= cformat && cformat <= '9') - { - if (!got_len && cformat == '0') - got_zlen = true; - - got_len = true; - len_value = 10*len_value + (cformat - '0'); - - cformat = sformat[++i]; - } - - // Simplify the argument - AstNode *node_arg = nullptr; - - // Everything from here on depends on the format specifier - switch (cformat) - { - case 's': - case 'S': - case 'd': - case 'D': - if (got_len && len_value != 0) - goto unsupported_format; - YS_FALLTHROUGH - case 'x': - case 'X': - if (next_arg >= GetSize(children)) - input_error("Missing argument for %%%c format specifier in system task `%s'.\n", - cformat, str.c_str()); - - node_arg = children[next_arg++]; - while (node_arg->simplify(true, false, stage, width_hint, sign_hint, false)) { } - if (node_arg->type != AST_CONSTANT) - input_error("Failed to evaluate system task `%s' with non-constant argument.\n", str.c_str()); - break; - - case 'm': - case 'M': - if (got_len) - goto unsupported_format; - break; - - case 'l': - case 'L': - if (got_len) - goto unsupported_format; - break; - - default: - unsupported_format: - input_error("System task `%s' called with invalid/unsupported format specifier.\n", str.c_str()); - break; - } - - switch (cformat) - { - case 's': - case 'S': - sout += node_arg->bitsAsConst().decode_string(); - break; - - case 'd': - case 'D': - sout += stringf("%d", node_arg->bitsAsConst().as_int()); - break; - - case 'x': - case 'X': - { - Const val = node_arg->bitsAsConst(); - - while (GetSize(val) % 4 != 0) - val.bits.push_back(State::S0); - - int len = GetSize(val) / 4; - for (int i = len; i < len_value; i++) - sout += got_zlen ? '0' : ' '; - - for (int i = len-1; i >= 0; i--) { - Const digit = val.extract(4*i, 4); - if (digit.is_fully_def()) - sout += stringf(cformat == 'x' ? "%x" : "%X", digit.as_int()); - else - sout += cformat == 'x' ? "x" : "X"; - } - } - break; - - case 'm': - case 'M': - sout += log_id(current_module->name); - break; - - case 'l': - case 'L': - sout += log_id(current_module->name); - break; - - default: - log_abort(); - } +Fmt AstNode::processFormat(int stage, bool sformat_like, int default_base, size_t first_arg_at) { + std::vector args; + for (size_t index = first_arg_at; index < children.size(); index++) { + AstNode *node_arg = children[index]; + while (node_arg->simplify(true, false, stage, -1, false, false)) { } + + VerilogFmtArg arg = {}; + arg.filename = filename; + arg.first_line = location.first_line; + if (node_arg->type == AST_CONSTANT && node_arg->is_string) { + arg.type = VerilogFmtArg::STRING; + arg.str = node_arg->bitsAsConst().decode_string(); + // and in case this will be used as an argument... + arg.sig = node_arg->bitsAsConst(); + arg.signed_ = false; + } else if (node_arg->type == AST_IDENTIFIER && node_arg->str == "$time") { + arg.type = VerilogFmtArg::TIME; + } else if (node_arg->type == AST_IDENTIFIER && node_arg->str == "$realtime") { + arg.type = VerilogFmtArg::TIME; + arg.realtime = true; + } else if (node_arg->type == AST_CONSTANT) { + arg.type = VerilogFmtArg::INTEGER; + arg.sig = node_arg->bitsAsConst(); + arg.signed_ = node_arg->is_signed; + } 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); } - - // not a format specifier - else - sout += sformat[i]; + args.push_back(arg); } - return sout; -} + Fmt fmt = {}; + fmt.parse_verilog(args, sformat_like, default_base, /*task_name=*/str, current_module->name); + return fmt; +} void AstNode::annotateTypedEnums(AstNode *template_node) { @@ -1057,33 +956,34 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin str = std::string(); } - if ((type == AST_TCALL) && (str == "$display" || str == "$write") && (!current_always || current_always->type != AST_INITIAL)) { - log_file_warning(filename, location.first_line, "System task `%s' outside initial block is unsupported.\n", str.c_str()); - delete_children(); - str = std::string(); - } - - // print messages if this a call to $display() or $write() - // This code implements only a small subset of Verilog-2005 $display() format specifiers, - // but should be good enough for most uses - if ((type == AST_TCALL) && ((str == "$display") || (str == "$write"))) + if ((type == AST_TCALL) && + (str == "$display" || str == "$displayb" || str == "$displayh" || str == "$displayo" || + str == "$write" || str == "$writeb" || str == "$writeh" || str == "$writeo")) { - int nargs = GetSize(children); - if (nargs < 1) - input_error("System task `%s' got %d arguments, expected >= 1.\n", - str.c_str(), int(children.size())); - - // First argument is the format string - AstNode *node_string = children[0]; - while (node_string->simplify(true, false, stage, width_hint, sign_hint, false)) { } - if (node_string->type != AST_CONSTANT) - input_error("Failed to evaluate system task `%s' with non-constant 1st argument.\n", str.c_str()); - std::string sformat = node_string->bitsAsConst().decode_string(); - std::string sout = process_format_str(sformat, 1, stage, width_hint, sign_hint); - // Finally, print the message (only include a \n for $display, not for $write) - log("%s", sout.c_str()); - if (str == "$display") - log("\n"); + 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()); + } else { + // when $display()/$write() functions are used in an always block, simplify the expressions and + // convert them to a special cell later in genrtlil + for (auto node : children) + while (node->simplify(true, false, stage, -1, false, false)) {} + return false; + } + delete_children(); str = std::string(); } @@ -3735,13 +3635,8 @@ skip_dynamic_range_lvalue_expansion:; } if (str == "\\$sformatf") { - AstNode *node_string = children[0]; - while (node_string->simplify(true, false, stage, width_hint, sign_hint, false)) { } - if (node_string->type != AST_CONSTANT) - input_error("Failed to evaluate system function `%s' with non-constant 1st argument.\n", str.c_str()); - std::string sformat = node_string->bitsAsConst().decode_string(); - std::string sout = process_format_str(sformat, 1, stage, width_hint, sign_hint); - newNode = AstNode::mkconst_str(sout); + Fmt fmt = processFormat(stage, /*sformat_like=*/true); + newNode = AstNode::mkconst_str(fmt.render()); goto apply_newNode; } diff --git a/frontends/verilog/verilog_lexer.l b/frontends/verilog/verilog_lexer.l index 24998666828..8a37343022f 100644 --- a/frontends/verilog/verilog_lexer.l +++ b/frontends/verilog/verilog_lexer.l @@ -386,7 +386,7 @@ and|nand|or|nor|xor|xnor|not|buf|bufif0|bufif1|notif0|notif1 { supply0 { return TOK_SUPPLY0; } supply1 { return TOK_SUPPLY1; } -"$"(display|write|strobe|monitor|time|stop|finish|dumpfile|dumpvars|dumpon|dumpoff|dumpall) { +"$"(display[bho]?|write[bho]?|strobe|monitor|time|realtime|stop|finish|dumpfile|dumpvars|dumpon|dumpoff|dumpall) { yylval->string = new std::string(yytext); return TOK_ID; } diff --git a/frontends/verilog/verilog_parser.y b/frontends/verilog/verilog_parser.y index 98bdbf9e5c5..1e82940bbbd 100644 --- a/frontends/verilog/verilog_parser.y +++ b/frontends/verilog/verilog_parser.y @@ -1830,7 +1830,7 @@ struct_member_type: { astbuf1 = new AstNode(AST_STRUCT_ITEM); } member_type_toke ; member_type_token: - member_type + member_type | hierarchical_type_id { addWiretypeNode($1, astbuf1); } @@ -2721,6 +2721,7 @@ behavioral_stmt: ast_stack.push_back(node); append_attr(node, $1); } opt_arg_list ';'{ + SET_AST_NODE_LOC(ast_stack.back(), @2, @5); ast_stack.pop_back(); } | attr TOK_MSG_TASKS { @@ -2731,6 +2732,7 @@ behavioral_stmt: ast_stack.push_back(node); append_attr(node, $1); } opt_arg_list ';'{ + SET_AST_NODE_LOC(ast_stack.back(), @2, @5); ast_stack.pop_back(); } | attr TOK_BEGIN { diff --git a/kernel/celltypes.h b/kernel/celltypes.h index 63e7408c142..4a0621a7386 100644 --- a/kernel/celltypes.h +++ b/kernel/celltypes.h @@ -101,6 +101,7 @@ struct CellTypes setup_type(ID($specify2), {ID::EN, ID::SRC, ID::DST}, pool(), true); setup_type(ID($specify3), {ID::EN, ID::SRC, ID::DST, ID::DAT}, pool(), true); setup_type(ID($specrule), {ID::EN_SRC, ID::EN_DST, ID::SRC, ID::DST}, pool(), true); + setup_type(ID($print), {ID::EN, ID::ARGS, ID::TRG}, pool()); } void setup_internals_eval() diff --git a/kernel/constids.inc b/kernel/constids.inc index 39211d0c753..08b0ecdc2b4 100644 --- a/kernel/constids.inc +++ b/kernel/constids.inc @@ -22,6 +22,8 @@ X(always_ff) X(always_latch) X(anyconst) X(anyseq) +X(ARGS) +X(ARGS_WIDTH) X(ARST) X(ARST_POLARITY) X(ARST_VALUE) @@ -86,6 +88,7 @@ X(equiv_merged) X(equiv_region) X(extract_order) X(F) +X(FORMAT) X(force_downto) X(force_upto) X(fsm_encoding) @@ -233,6 +236,10 @@ X(TRANS_NUM) X(TRANSPARENCY_MASK) X(TRANSPARENT) X(TRANS_TABLE) +X(TRG) +X(TRG_ENABLE) +X(TRG_POLARITY) +X(TRG_WIDTH) X(T_RISE_MAX) X(T_RISE_MIN) X(T_RISE_TYP) diff --git a/kernel/fmt.cc b/kernel/fmt.cc new file mode 100644 index 00000000000..69bdbb013b0 --- /dev/null +++ b/kernel/fmt.cc @@ -0,0 +1,758 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2020 whitequark + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "libs/bigint/BigUnsigned.hh" +#include "kernel/fmt.h" + +USING_YOSYS_NAMESPACE + +void Fmt::append_string(const std::string &str) { + FmtPart part = {}; + part.type = FmtPart::STRING; + part.str = str; + parts.push_back(part); +} + +void Fmt::parse_rtlil(const RTLIL::Cell *cell) { + std::string fmt = cell->getParam(ID(FORMAT)).decode_string(); + RTLIL::SigSpec args = cell->getPort(ID(ARGS)); + parts.clear(); + + FmtPart part; + for (size_t i = 0; i < fmt.size(); i++) { + if (fmt.substr(i, 2) == "}}") { + part.str += '}'; + ++i; + } else if (fmt.substr(i, 2) == "{{") { + part.str += '{'; + ++i; + } else if (fmt[i] == '}') + log_assert(false && "Unexpected '}' in format string"); + else if (fmt[i] == '{') { + if (!part.str.empty()) { + part.type = FmtPart::STRING; + parts.push_back(part); + part = {}; + } + + if (++i == fmt.size()) + log_assert(false && "Unexpected end in format substitution"); + + size_t arg_size = 0; + for (; i < fmt.size(); i++) { + if (fmt[i] >= '0' && fmt[i] <= '9') { + arg_size *= 10; + arg_size += fmt[i] - '0'; + } else if (fmt[i] == ':') { + ++i; + break; + } else { + log_assert(false && "Unexpected character in format substitution"); + } + } + if (i == fmt.size()) + log_assert(false && "Unexpected end in format substitution"); + + if ((size_t)args.size() < arg_size) + log_assert(false && "Format part overruns arguments"); + part.sig = args.extract(0, arg_size); + args.remove(0, arg_size); + + if (fmt[i] == '>') + part.justify = FmtPart::RIGHT; + else if (fmt[i] == '<') + part.justify = FmtPart::LEFT; + else + log_assert(false && "Unexpected justification in format substitution"); + if (++i == fmt.size()) + log_assert(false && "Unexpected end in format substitution"); + + if (fmt[i] == '0' || fmt[i] == ' ') + part.padding = fmt[i]; + else + log_assert(false && "Unexpected padding in format substitution"); + if (++i == fmt.size()) + log_assert(false && "Unexpected end in format substitution"); + + for (; i < fmt.size(); i++) { + if (fmt[i] >= '0' && fmt[i] <= '9') { + part.width *= 10; + part.width += fmt[i] - '0'; + continue; + } else if (fmt[i] == 'b') { + part.type = FmtPart::INTEGER; + part.base = 2; + } else if (fmt[i] == 'o') { + part.type = FmtPart::INTEGER; + part.base = 8; + } else if (fmt[i] == 'd') { + part.type = FmtPart::INTEGER; + part.base = 10; + } else if (fmt[i] == 'h') { + part.type = FmtPart::INTEGER; + part.base = 16; + } else if (fmt[i] == 'c') { + part.type = FmtPart::CHARACTER; + } else if (fmt[i] == 't') { + part.type = FmtPart::TIME; + } else if (fmt[i] == 'r') { + part.type = FmtPart::TIME; + part.realtime = true; + } else { + log_assert(false && "Unexpected character in format substitution"); + } + ++i; + break; + } + if (i == fmt.size()) + log_assert(false && "Unexpected end in format substitution"); + + if (part.type == FmtPart::INTEGER) { + if (fmt[i] == '+') { + part.plus = true; + if (++i == fmt.size()) + log_assert(false && "Unexpected end in format substitution"); + } + + if (fmt[i] == 'u') + part.signed_ = false; + else if (fmt[i] == 's') + part.signed_ = true; + else + log_assert(false && "Unexpected character in format substitution"); + if (++i == fmt.size()) + log_assert(false && "Unexpected end in format substitution"); + } + + if (fmt[i] != '}') + log_assert(false && "Expected '}' after format substitution"); + + parts.push_back(part); + part = {}; + } else { + part.str += fmt[i]; + } + } + if (!part.str.empty()) { + part.type = FmtPart::STRING; + parts.push_back(part); + } +} + +void Fmt::emit_rtlil(RTLIL::Cell *cell) const { + std::string fmt; + RTLIL::SigSpec args; + + for (auto &part : parts) { + switch (part.type) { + case FmtPart::STRING: + for (char c : part.str) { + if (c == '{') + fmt += "{{"; + else if (c == '}') + fmt += "}}"; + else + fmt += c; + } + break; + + case FmtPart::TIME: + log_assert(part.sig.size() == 0); + YS_FALLTHROUGH + case FmtPart::CHARACTER: + log_assert(part.sig.size() % 8 == 0); + YS_FALLTHROUGH + case FmtPart::INTEGER: + args.append(part.sig); + fmt += '{'; + fmt += std::to_string(part.sig.size()); + fmt += ':'; + if (part.justify == FmtPart::RIGHT) + fmt += '>'; + else if (part.justify == FmtPart::LEFT) + fmt += '<'; + else log_abort(); + log_assert(part.width == 0 || part.padding != '\0'); + fmt += part.padding != '\0' ? part.padding : ' '; + if (part.width > 0) + fmt += std::to_string(part.width); + if (part.type == FmtPart::INTEGER) { + switch (part.base) { + case 2: fmt += 'b'; break; + case 8: fmt += 'o'; break; + case 10: fmt += 'd'; break; + case 16: fmt += 'h'; break; + default: log_abort(); + } + if (part.plus) + fmt += '+'; + fmt += part.signed_ ? 's' : 'u'; + } else if (part.type == FmtPart::CHARACTER) { + fmt += 'c'; + } else if (part.type == FmtPart::TIME) { + if (part.realtime) + fmt += 'r'; + else + fmt += 't'; + } else log_abort(); + fmt += '}'; + break; + + default: log_abort(); + } + } + + cell->setParam(ID(FORMAT), fmt); + cell->setParam(ID(ARGS_WIDTH), args.size()); + cell->setPort(ID(ARGS), args); +} + +static size_t compute_required_decimal_places(size_t size, bool signed_) +{ + BigUnsigned max; + if (!signed_) + max.setBit(size, true); + else + max.setBit(size - 1, true); + size_t places = 0; + while (!max.isZero()) { + places++; + max /= 10; + } + if (signed_) + places++; + return places; +} + +static size_t compute_required_nondecimal_places(size_t size, unsigned base) +{ + log_assert(base != 10); + BigUnsigned max; + max.setBit(size - 1, true); + size_t places = 0; + while (!max.isZero()) { + places++; + max /= base; + } + return places; +} + +// Only called for integers, either when: +// +// (a) passed without a format string (e.g. "$display(a);"), or +// +// (b) the corresponding format specifier has no leading zero, e.g. "%b", +// "%20h", "%-10d". +// +// In these cases, for binary/octal/hex, we always zero-pad to the size of the +// signal; i.e. whether "%h" or "%10h" or "%-20h" is used, if the corresponding +// signal is 32'h1234, "00001234" will always be a substring of the output. +// +// For case (a), we have no specified width, so there is nothing more to do. +// +// For case (b), because we are only called with no leading zero on the +// specifier, any specified width beyond the signal size is therefore space +// padding, whatever the justification. +// +// For decimal, we do no zero-padding, instead space-padding to the size +// required for the signal's largest value. This is per other Verilog +// implementations, and intuitively makes sense as decimal representations lack +// a discrete mapping of digits to bit groups. Decimals may also show sign and +// must accommodate this, whereas other representations do not. +void Fmt::apply_verilog_automatic_sizing_and_add(FmtPart &part) +{ + if (part.base == 10) { + size_t places = compute_required_decimal_places(part.sig.size(), part.signed_); + part.padding = ' '; + part.width = std::max(part.width, places); + parts.push_back(part); + return; + } + + part.padding = '0'; + + size_t places = compute_required_nondecimal_places(part.sig.size(), part.base); + if (part.width < places) { + part.justify = FmtPart::RIGHT; + part.width = places; + parts.push_back(part); + } else if (part.width == places) { + parts.push_back(part); + } else if (part.width > places) { + auto gap = std::string(part.width - places, ' '); + part.width = places; + + if (part.justify == FmtPart::RIGHT) { + append_string(gap); + parts.push_back(part); + } else { + part.justify = FmtPart::RIGHT; + parts.push_back(part); + append_string(gap); + } + } +} + +void Fmt::parse_verilog(const std::vector &args, bool sformat_like, int default_base, RTLIL::IdString task_name, RTLIL::IdString module_name) +{ + parts.clear(); + + auto arg = args.begin(); + for (; arg != args.end(); ++arg) { + switch (arg->type) { + case VerilogFmtArg::INTEGER: { + FmtPart part = {}; + part.type = FmtPart::INTEGER; + part.sig = arg->sig; + part.base = default_base; + part.signed_ = arg->signed_; + apply_verilog_automatic_sizing_and_add(part); + break; + } + + case VerilogFmtArg::STRING: { + if (arg == args.begin() || !sformat_like) { + const auto fmtarg = arg; + const std::string &fmt = fmtarg->str; + FmtPart part = {}; + for (size_t i = 0; i < fmt.size(); i++) { + if (fmt[i] != '%') { + part.str += fmt[i]; + } else if (fmt.substr(i, 2) == "%%") { + i++; + part.str += '%'; + } else if (fmt.substr(i, 2) == "%l" || fmt.substr(i, 2) == "%L") { + i++; + part.str += module_name.str(); + } else if (fmt.substr(i, 2) == "%m" || fmt.substr(i, 2) == "%M") { + i++; + part.str += module_name.str(); + } else { + if (!part.str.empty()) { + part.type = FmtPart::STRING; + parts.push_back(part); + part = {}; + } + if (++i == fmt.size()) { + log_file_error(fmtarg->filename, fmtarg->first_line, "System task `%s' called with incomplete format specifier in argument %zu.\n", task_name.c_str(), fmtarg - args.begin() + 1); + } + + if (++arg == args.end()) { + log_file_error(fmtarg->filename, fmtarg->first_line, "System task `%s' called with fewer arguments than the format specifiers in argument %zu require.\n", task_name.c_str(), fmtarg - args.begin() + 1); + } + part.sig = arg->sig; + part.signed_ = arg->signed_; + + for (; i < fmt.size(); i++) { + if (fmt[i] == '-') { + // left justify; not in IEEE 1800-2017 or verilator but iverilog has it + part.justify = FmtPart::LEFT; + } else if (fmt[i] == '+') { + // always show sign; not in IEEE 1800-2017 or verilator but iverilog has it + part.plus = true; + } else break; + } + if (i == fmt.size()) { + log_file_error(fmtarg->filename, fmtarg->first_line, "System task `%s' called with incomplete format specifier in argument %zu.\n", task_name.c_str(), fmtarg - args.begin() + 1); + } + + bool has_leading_zero = false, has_width = false; + for (; i < fmt.size(); i++) { + if (fmt[i] >= '0' && fmt[i] <= '9') { + if (fmt[i] == '0' && !has_width) { + has_leading_zero = true; + } else { + has_width = true; + part.width *= 10; + part.width += fmt[i] - '0'; + } + continue; + } else if (fmt[i] == 'b' || fmt[i] == 'B') { + part.type = FmtPart::INTEGER; + part.base = 2; + } else if (fmt[i] == 'o' || fmt[i] == 'O') { + part.type = FmtPart::INTEGER; + part.base = 8; + } else if (fmt[i] == 'd' || fmt[i] == 'D') { + part.type = FmtPart::INTEGER; + part.base = 10; + } else if (fmt[i] == 'h' || fmt[i] == 'H' || + fmt[i] == 'x' || fmt[i] == 'X') { + // hex digits always printed in lowercase for %h%x as well as %H%X + part.type = FmtPart::INTEGER; + part.base = 16; + } else if (fmt[i] == 'c' || fmt[i] == 'C') { + part.type = FmtPart::CHARACTER; + part.sig.extend_u0(8); + // %10c and %010c not fully defined in IEEE 1800-2017 and do different things in iverilog + } else if (fmt[i] == 's' || fmt[i] == 'S') { + part.type = FmtPart::CHARACTER; + if ((part.sig.size() % 8) != 0) + part.sig.extend_u0((part.sig.size() + 7) / 8 * 8); + // %10s and %010s not fully defined in IEEE 1800-2017 and do the same thing in iverilog + part.padding = ' '; + } else if (fmt[i] == 't' || fmt[i] == 'T') { + if (arg->type == VerilogFmtArg::TIME) { + part.type = FmtPart::TIME; + part.realtime = arg->realtime; + if (!has_width && !has_leading_zero) + part.width = 20; + } else { + log_file_error(fmtarg->filename, fmtarg->first_line, "System task `%s' called with format character `%c' in argument %zu, but the argument is not $time or $realtime.\n", task_name.c_str(), fmt[i], fmtarg - args.begin() + 1); + } + } else { + log_file_error(fmtarg->filename, fmtarg->first_line, "System task `%s' called with unrecognized format character `%c' in argument %zu.\n", task_name.c_str(), fmt[i], fmtarg - args.begin() + 1); + } + break; + } + if (i == fmt.size()) { + log_file_error(fmtarg->filename, fmtarg->first_line, "System task `%s' called with incomplete format specifier in argument %zu.\n", task_name.c_str(), fmtarg - args.begin() + 1); + } + + if (part.padding == '\0') + part.padding = (has_leading_zero && part.justify == FmtPart::RIGHT) ? '0' : ' '; + + if (part.type == FmtPart::INTEGER && part.base != 10 && part.plus) + log_file_error(fmtarg->filename, fmtarg->first_line, "System task `%s' called with invalid format specifier in argument %zu.\n", task_name.c_str(), fmtarg - args.begin() + 1); + + if (part.type == FmtPart::INTEGER && !has_leading_zero) + apply_verilog_automatic_sizing_and_add(part); + else + parts.push_back(part); + part = {}; + } + } + if (!part.str.empty()) { + part.type = FmtPart::STRING; + parts.push_back(part); + } + } else { + FmtPart part = {}; + part.type = FmtPart::STRING; + part.str = arg->str; + parts.push_back(part); + } + break; + } + + default: log_abort(); + } + } +} + +std::vector Fmt::emit_verilog() const +{ + std::vector args; + VerilogFmtArg fmt = {}; + fmt.type = VerilogFmtArg::STRING; + + for (auto &part : parts) { + switch (part.type) { + case FmtPart::STRING: + for (char c : part.str) { + if (c == '%') + fmt.str += "%%"; + else + fmt.str += c; + } + break; + + case FmtPart::INTEGER: { + VerilogFmtArg arg = {}; + arg.type = VerilogFmtArg::INTEGER; + arg.sig = part.sig; + arg.signed_ = part.signed_; + args.push_back(arg); + + fmt.str += '%'; + if (part.plus) + fmt.str += '+'; + if (part.justify == FmtPart::LEFT) + fmt.str += '-'; + if (part.width == 0) { + fmt.str += '0'; + } else if (part.width > 0) { + log_assert(part.padding == ' ' || part.padding == '0'); + if (part.base != 10 || part.padding == '0') + fmt.str += '0'; + fmt.str += std::to_string(part.width); + } + switch (part.base) { + case 2: fmt.str += 'b'; break; + case 8: fmt.str += 'o'; break; + case 10: fmt.str += 'd'; break; + case 16: fmt.str += 'h'; break; + default: log_abort(); + } + break; + } + + case FmtPart::CHARACTER: { + VerilogFmtArg arg; + arg.type = VerilogFmtArg::INTEGER; + arg.sig = part.sig; + args.push_back(arg); + + fmt.str += '%'; + if (part.justify == FmtPart::LEFT) + fmt.str += '-'; + if (part.sig.size() == 8) { + if (part.width > 0) { + log_assert(part.padding == '0' || part.padding == ' '); + if (part.padding == '0') + fmt.str += part.padding; + fmt.str += std::to_string(part.width); + } + fmt.str += 'c'; + } else { + log_assert(part.sig.size() % 8 == 0); + if (part.width > 0) { + log_assert(part.padding == ' '); // no zero padding + fmt.str += std::to_string(part.width); + } + fmt.str += 's'; + } + break; + } + + case FmtPart::TIME: { + VerilogFmtArg arg; + arg.type = VerilogFmtArg::TIME; + if (part.realtime) + arg.realtime = true; + args.push_back(arg); + + fmt.str += '%'; + if (part.plus) + fmt.str += '+'; + if (part.justify == FmtPart::LEFT) + fmt.str += '-'; + log_assert(part.padding == ' ' || part.padding == '0'); + if (part.padding == '0' && part.width > 0) + fmt.str += '0'; + fmt.str += std::to_string(part.width); + fmt.str += 't'; + break; + } + + default: log_abort(); + } + } + + args.insert(args.begin(), fmt); + return args; +} + +void Fmt::emit_cxxrtl(std::ostream &f, std::function emit_sig) const +{ + 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 Fmt::render() const +{ + std::string str; + + for (auto &part : parts) { + switch (part.type) { + case FmtPart::STRING: + str += part.str; + break; + + case FmtPart::INTEGER: + case FmtPart::TIME: + case FmtPart::CHARACTER: { + std::string buf; + if (part.type == FmtPart::INTEGER) { + RTLIL::Const value = part.sig.as_const(); + + if (part.base != 10) { + size_t minimum_size = 0; + for (size_t index = 0; index < (size_t)value.size(); index++) + if (value[index] != State::S0) + minimum_size = index + 1; + value = value.extract(0, minimum_size); + } + + if (part.base == 2) { + buf = value.as_string(); + } else if (part.base == 8 || part.base == 16) { + size_t step = (part.base == 16) ? 4 : 3; + for (size_t index = 0; index < (size_t)value.size(); index += step) { + RTLIL::Const subvalue = value.extract(index, min(step, value.size() - index)); + bool has_x = false, all_x = true, has_z = false, all_z = true; + for (State bit : subvalue) { + if (bit == State::Sx) + has_x = true; + else + all_x = false; + if (bit == State::Sz) + has_z = true; + else + all_z = false; + } + if (all_x) + buf += 'x'; + else if (all_z) + buf += 'z'; + else if (has_x) + buf += 'X'; + else if (has_z) + buf += 'Z'; + else + buf += "0123456789abcdef"[subvalue.as_int()]; + } + std::reverse(buf.begin(), buf.end()); + } else if (part.base == 10) { + bool has_x = false, all_x = true, has_z = false, all_z = true; + for (State bit : value) { + if (bit == State::Sx) + has_x = true; + else + all_x = false; + if (bit == State::Sz) + has_z = true; + else + all_z = false; + } + if (all_x) + buf += 'x'; + else if (all_z) + buf += 'z'; + else if (has_x) + buf += 'X'; + else if (has_z) + buf += 'Z'; + else { + bool negative = part.signed_ && value[value.size() - 1]; + RTLIL::Const absvalue; + if (negative) + absvalue = RTLIL::const_neg(value, {}, part.signed_, {}, value.size() + 1); + else + absvalue = value; + log_assert(absvalue.is_fully_def()); + if (absvalue.is_fully_zero()) + buf += '0'; + while (!absvalue.is_fully_zero()) { + buf += '0' + RTLIL::const_mod(absvalue, 10, false, false, 4).as_int(); + absvalue = RTLIL::const_div(absvalue, 10, false, false, absvalue.size()); + } + if (negative || part.plus) + buf += negative ? '-' : '+'; + std::reverse(buf.begin(), buf.end()); + } + } else log_abort(); + } else if (part.type == FmtPart::CHARACTER) { + buf = part.sig.as_const().decode_string(); + } else if (part.type == FmtPart::TIME) { + // We only render() during initial, so time is always zero. + buf = "0"; + } + + log_assert(part.width == 0 || part.padding != '\0'); + if (part.justify == FmtPart::RIGHT && buf.size() < part.width) { + size_t pad_width = part.width - buf.size(); + if (part.padding == '0' && (buf.front() == '+' || buf.front() == '-')) { + str += buf.front(); + buf.erase(0, 1); + } + str += std::string(pad_width, part.padding); + } + str += buf; + if (part.justify == FmtPart::LEFT && buf.size() < part.width) + str += std::string(part.width - buf.size(), part.padding); + break; + } + } + } + + return str; +} diff --git a/kernel/fmt.h b/kernel/fmt.h new file mode 100644 index 00000000000..39a278c562e --- /dev/null +++ b/kernel/fmt.h @@ -0,0 +1,106 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2020 whitequark + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef FMT_H +#define FMT_H + +#include "kernel/yosys.h" + +YOSYS_NAMESPACE_BEGIN + +// Verilog format argument, such as the arguments in: +// $display("foo %d bar %01x", 4'b0, $signed(2'b11)) +struct VerilogFmtArg { + enum { + STRING = 0, + INTEGER = 1, + TIME = 2, + } type; + + // All types + std::string filename; + unsigned first_line; + + // STRING type + std::string str; + + // INTEGER type + RTLIL::SigSpec sig; + bool signed_ = false; + + // TIME type + bool realtime = false; +}; + +// RTLIL format part, such as the substitutions in: +// "foo {4:> 4du} bar {2:<01hs}" +struct FmtPart { + enum { + STRING = 0, + INTEGER = 1, + CHARACTER = 2, + TIME = 3, + } type; + + // STRING type + std::string str; + + // INTEGER/CHARACTER types + RTLIL::SigSpec sig; + + // INTEGER/CHARACTER/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 + bool realtime = false; +}; + +struct Fmt { +public: + std::vector parts; + + void append_string(const std::string &str); + + void parse_rtlil(const RTLIL::Cell *cell); + void emit_rtlil(RTLIL::Cell *cell) const; + + void parse_verilog(const std::vector &args, bool sformat_like, int default_base, RTLIL::IdString task_name, RTLIL::IdString module_name); + std::vector emit_verilog() const; + + void emit_cxxrtl(std::ostream &f, std::function emit_sig) const; + + std::string render() const; + +private: + void apply_verilog_automatic_sizing_and_add(FmtPart &part); +}; + +YOSYS_NAMESPACE_END + +#endif diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 7011429ff5a..7a59c526275 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -1720,6 +1720,18 @@ namespace { return; } + if (cell->type == ID($print)) { + param(ID(FORMAT)); + param_bool(ID::TRG_ENABLE); + param(ID::TRG_POLARITY); + param(ID::PRIORITY); + port(ID::EN, 1); + port(ID::TRG, param(ID::TRG_WIDTH)); + port(ID::ARGS, param(ID::ARGS_WIDTH)); + check_expected(); + return; + } + if (cell->type == ID($_BUF_)) { port(ID::A,1); port(ID::Y,1); check_expected(); return; } if (cell->type == ID($_NOT_)) { port(ID::A,1); port(ID::Y,1); check_expected(); return; } if (cell->type == ID($_AND_)) { port(ID::A,1); port(ID::B,1); port(ID::Y,1); check_expected(); return; } diff --git a/passes/opt/opt_clean.cc b/passes/opt/opt_clean.cc index cb2490dc72e..4da67cf630f 100644 --- a/passes/opt/opt_clean.cc +++ b/passes/opt/opt_clean.cc @@ -79,6 +79,9 @@ struct keep_cache_t if (!ignore_specify && cell->type.in(ID($specify2), ID($specify3), ID($specrule))) return true; + if (cell->type == ID($print)) + return true; + if (cell->has_keep_attr()) return true; diff --git a/techlibs/common/simlib.v b/techlibs/common/simlib.v index 9cb68e7252f..cdb6e02e762 100644 --- a/techlibs/common/simlib.v +++ b/techlibs/common/simlib.v @@ -1799,6 +1799,24 @@ end endmodule +// -------------------------------------------------------- + +module \$print (EN, TRG, ARGS); + +parameter FORMAT = ""; +parameter ARGS_WIDTH = 0; +parameter PRIORITY = 0; +parameter TRG_ENABLE = 1; + +parameter TRG_WIDTH = 0; +parameter TRG_POLARITY = 0; + +input EN; +input [TRG_WIDTH-1:0] TRG; +input [ARGS_WIDTH-1:0] ARGS; + +endmodule + // -------------------------------------------------------- `ifndef SIMLIB_NOSR diff --git a/tests/fmt/.gitignore b/tests/fmt/.gitignore new file mode 100644 index 00000000000..a36a15ec464 --- /dev/null +++ b/tests/fmt/.gitignore @@ -0,0 +1,3 @@ +*.log +iverilog-* +yosys-* diff --git a/tests/fmt/always_comb.v b/tests/fmt/always_comb.v new file mode 100644 index 00000000000..7147786bebf --- /dev/null +++ b/tests/fmt/always_comb.v @@ -0,0 +1,24 @@ +module top(input clk); + reg a = 0; + reg b = 0; + wire y; + + sub s (.a(a), .b(b), .y(y)); + + always @(posedge clk) begin + a <= (!a && !b) || (a && !b); + b <= (a && !b) || (a && b); + end +endmodule + +module sub(input a, input b, output wire y); + assign y = a & b; + + // Not fit for our purposes: always @* if (a) $display(a, b, y); + // + // We compare output against iverilog, but async iverilog $display fires + // even before values have propagated -- i.e. combinations of a/b/y will be + // shown where a & b are both 1, but y has not yet taken the value 1. We + // don't, so we specify it in the conditional. + always @* if (y & (y == (a & b))) $display(a, b, y); +endmodule diff --git a/tests/fmt/always_comb_tb.cc b/tests/fmt/always_comb_tb.cc new file mode 100644 index 00000000000..f2a8c6b2695 --- /dev/null +++ b/tests/fmt/always_comb_tb.cc @@ -0,0 +1,17 @@ +#include +#include "yosys-always_comb.cc" + +int main() +{ + cxxrtl_design::p_top uut1, uut2; + + for (int i = 0; i < 20; ++i) { + uut1.p_clk.set(!uut1.p_clk); + uut1.step(); + + uut2.p_clk.set(!uut2.p_clk); + uut2.step(); + } + + return 0; +} diff --git a/tests/fmt/always_comb_tb.v b/tests/fmt/always_comb_tb.v new file mode 100644 index 00000000000..3cc4496a04f --- /dev/null +++ b/tests/fmt/always_comb_tb.v @@ -0,0 +1,9 @@ +module tb; + reg clk = 0; + + top uut1 (.clk(clk)); + top uut2 (.clk(clk)); + + always #1 clk <= ~clk; + initial #20 $finish; +endmodule diff --git a/tests/fmt/always_display.v b/tests/fmt/always_display.v new file mode 100644 index 00000000000..593c5afc08c --- /dev/null +++ b/tests/fmt/always_display.v @@ -0,0 +1,17 @@ +module m(input clk, rst, en, input [31:0] data); + +`ifdef EVENT_CLK + always @(posedge clk) +`endif +`ifdef EVENT_CLK_RST + always @(posedge clk or negedge rst) +`endif +`ifdef EVENT_STAR + always @(*) +`endif +`ifdef COND_EN + if (en) +`endif + $display("data=%d", data); + +endmodule diff --git a/tests/fmt/always_full.v b/tests/fmt/always_full.v new file mode 100644 index 00000000000..4d3df7a615c --- /dev/null +++ b/tests/fmt/always_full.v @@ -0,0 +1,236 @@ +module always_full(input clk); + + always @(posedge clk) begin + + $display("==> small unsigned %%d"); + $display(":%d:", 16'haa); + $display(":%-d:", 16'haa); + $display(":%+d:", 16'haa); + $display(":%+-d:", 16'haa); + $display(":%0d:", 16'haa); + $display(":%-0d:", 16'haa); + $display(":%+0d:", 16'haa); + $display(":%+-0d:", 16'haa); + $display(":%20d:", 16'haa); + $display(":%-20d:", 16'haa); + $display(":%+20d:", 16'haa); + $display(":%+-20d:", 16'haa); + $display(":%020d:", 16'haa); + $display(":%-020d:", 16'haa); + $display(":%+020d:", 16'haa); + $display(":%+-020d:", 16'haa); + + $display("==> big unsigned %%d"); + $display(":%d:", 16'haaaa); + $display(":%-d:", 16'haaaa); + $display(":%+d:", 16'haaaa); + $display(":%+-d:", 16'haaaa); + $display(":%0d:", 16'haaaa); + $display(":%-0d:", 16'haaaa); + $display(":%+0d:", 16'haaaa); + $display(":%+-0d:", 16'haaaa); + $display(":%20d:", 16'haaaa); + $display(":%-20d:", 16'haaaa); + $display(":%+20d:", 16'haaaa); + $display(":%+-20d:", 16'haaaa); + $display(":%020d:", 16'haaaa); + $display(":%-020d:", 16'haaaa); + $display(":%+020d:", 16'haaaa); + $display(":%+-020d:", 16'haaaa); + + $display("==> small signed %%d"); + $display(":%d:", 16'shaa); + $display(":%-d:", 16'shaa); + $display(":%+d:", 16'shaa); + $display(":%+-d:", 16'shaa); + $display(":%0d:", 16'shaa); + $display(":%-0d:", 16'shaa); + $display(":%+0d:", 16'shaa); + $display(":%+-0d:", 16'shaa); + $display(":%20d:", 16'shaa); + $display(":%-20d:", 16'shaa); + $display(":%+20d:", 16'shaa); + $display(":%+-20d:", 16'shaa); + $display(":%020d:", 16'shaa); + $display(":%-020d:", 16'shaa); + $display(":%+020d:", 16'shaa); + $display(":%+-020d:", 16'shaa); + + $display("==> big signed %%d"); + $display(":%d:", 16'shaaaa); + $display(":%-d:", 16'shaaaa); + $display(":%+d:", 16'shaaaa); + $display(":%+-d:", 16'shaaaa); + $display(":%0d:", 16'shaaaa); + $display(":%-0d:", 16'shaaaa); + $display(":%+0d:", 16'shaaaa); + $display(":%+-0d:", 16'shaaaa); + $display(":%20d:", 16'shaaaa); + $display(":%-20d:", 16'shaaaa); + $display(":%+20d:", 16'shaaaa); + $display(":%+-20d:", 16'shaaaa); + $display(":%020d:", 16'shaaaa); + $display(":%-020d:", 16'shaaaa); + $display(":%+020d:", 16'shaaaa); + $display(":%+-020d:", 16'shaaaa); + + $display("==> small unsigned %%h"); + $display(":%h:", 16'haa); + $display(":%-h:", 16'haa); + $display(":%0h:", 16'haa); + $display(":%-0h:", 16'haa); + $display(":%20h:", 16'haa); + $display(":%-20h:", 16'haa); + $display(":%020h:", 16'haa); + $display(":%-020h:", 16'haa); + + $display("==> big unsigned %%h"); + $display(":%h:", 16'haaaa); + $display(":%-h:", 16'haaaa); + $display(":%0h:", 16'haaaa); + $display(":%-0h:", 16'haaaa); + $display(":%20h:", 16'haaaa); + $display(":%-20h:", 16'haaaa); + $display(":%020h:", 16'haaaa); + $display(":%-020h:", 16'haaaa); + + $display("==> small signed %%h"); + $display(":%h:", 16'shaa); + $display(":%-h:", 16'shaa); + $display(":%0h:", 16'shaa); + $display(":%-0h:", 16'shaa); + $display(":%20h:", 16'shaa); + $display(":%-20h:", 16'shaa); + $display(":%020h:", 16'shaa); + $display(":%-020h:", 16'shaa); + + $display("==> big signed %%h"); + $display(":%h:", 16'shaaaa); + $display(":%-h:", 16'shaaaa); + $display(":%0h:", 16'shaaaa); + $display(":%-0h:", 16'shaaaa); + $display(":%20h:", 16'shaaaa); + $display(":%-20h:", 16'shaaaa); + $display(":%020h:", 16'shaaaa); + $display(":%-020h:", 16'shaaaa); + + $display("==> small unsigned %%o"); + $display(":%o:", 16'haa); + $display(":%-o:", 16'haa); + $display(":%0o:", 16'haa); + $display(":%-0o:", 16'haa); + $display(":%20o:", 16'haa); + $display(":%-20o:", 16'haa); + $display(":%020o:", 16'haa); + $display(":%-020o:", 16'haa); + + $display("==> big unsigned %%o"); + $display(":%o:", 16'haaaa); + $display(":%-o:", 16'haaaa); + $display(":%0o:", 16'haaaa); + $display(":%-0o:", 16'haaaa); + $display(":%20o:", 16'haaaa); + $display(":%-20o:", 16'haaaa); + $display(":%020o:", 16'haaaa); + $display(":%-020o:", 16'haaaa); + + $display("==> small signed %%o"); + $display(":%o:", 16'shaa); + $display(":%-o:", 16'shaa); + $display(":%0o:", 16'shaa); + $display(":%-0o:", 16'shaa); + $display(":%20o:", 16'shaa); + $display(":%-20o:", 16'shaa); + $display(":%020o:", 16'shaa); + $display(":%-020o:", 16'shaa); + + $display("==> big signed %%o"); + $display(":%o:", 16'shaaaa); + $display(":%-o:", 16'shaaaa); + $display(":%0o:", 16'shaaaa); + $display(":%-0o:", 16'shaaaa); + $display(":%20o:", 16'shaaaa); + $display(":%-20o:", 16'shaaaa); + $display(":%020o:", 16'shaaaa); + $display(":%-020o:", 16'shaaaa); + + $display("==> small unsigned %%b"); + $display(":%b:", 16'haa); + $display(":%-b:", 16'haa); + $display(":%0b:", 16'haa); + $display(":%-0b:", 16'haa); + $display(":%20b:", 16'haa); + $display(":%-20b:", 16'haa); + $display(":%020b:", 16'haa); + $display(":%-020b:", 16'haa); + + $display("==> big unsigned %%b"); + $display(":%b:", 16'haaaa); + $display(":%-b:", 16'haaaa); + $display(":%0b:", 16'haaaa); + $display(":%-0b:", 16'haaaa); + $display(":%20b:", 16'haaaa); + $display(":%-20b:", 16'haaaa); + $display(":%020b:", 16'haaaa); + $display(":%-020b:", 16'haaaa); + + $display("==> small signed %%b"); + $display(":%b:", 16'shaa); + $display(":%-b:", 16'shaa); + $display(":%0b:", 16'shaa); + $display(":%-0b:", 16'shaa); + $display(":%20b:", 16'shaa); + $display(":%-20b:", 16'shaa); + $display(":%020b:", 16'shaa); + $display(":%-020b:", 16'shaa); + + $display("==> big signed %%b"); + $display(":%b:", 16'shaaaa); + $display(":%-b:", 16'shaaaa); + $display(":%0b:", 16'shaaaa); + $display(":%-0b:", 16'shaaaa); + $display(":%20b:", 16'shaaaa); + $display(":%-20b:", 16'shaaaa); + $display(":%020b:", 16'shaaaa); + $display(":%-020b:", 16'shaaaa); + + $display("==> time %%t"); + $display(":%t:", $time); + $display(":%-t:", $time); + $display(":%0t:", $time); + $display(":%-0t:", $time); + $display(":%10t:", $time); + $display(":%-10t:", $time); + $display(":%015t:", $time); + $display(":%-015t:", $time); + + $display("===> %%s"); + $display(":%10s:", "foo"); + $display(":%010s:", "foo"); + $display(":%-10s:", "foo"); + $display(":%-010s:", "foo"); + + $display("===> %%c"); + $display(":%10c:", "foo"); + $display(":%010c:", "foo"); + $display(":%-10c:", "foo"); + $display(":%-010c:", "foo"); + + $display("==> aliases"); + $display(":%x:", 16'shaa); + $display(":%X:", 16'shaa); + $display(":%H:", 16'shaa); + $display(":%O:", 16'shaa); + $display(":%B:", 16'shaa); + + $display("==> default base"); + $displayh(16'haa); + $displayo(16'haa); + $displayb(16'haa); + + $display("==> write/format"); + $display("%d", 1, "%d", 1); + + end + +endmodule diff --git a/tests/fmt/always_full_tb.cc b/tests/fmt/always_full_tb.cc new file mode 100644 index 00000000000..bd98020dec1 --- /dev/null +++ b/tests/fmt/always_full_tb.cc @@ -0,0 +1,10 @@ +#include +#include "yosys-always_full.cc" + +int main() +{ + cxxrtl_design::p_always__full uut; + uut.p_clk.set(!uut.p_clk); + uut.step(); + return 0; +} diff --git a/tests/fmt/always_full_tb.v b/tests/fmt/always_full_tb.v new file mode 100644 index 00000000000..0c2599cc263 --- /dev/null +++ b/tests/fmt/always_full_tb.v @@ -0,0 +1,12 @@ +module always_full_tb; + + reg clk = 0; + + always_full uut (.clk(clk)); + + always begin + #1 clk <= ~clk; + #1 $finish; + end + +endmodule diff --git a/tests/fmt/display_lm.v b/tests/fmt/display_lm.v new file mode 100644 index 00000000000..d96f233f064 --- /dev/null +++ b/tests/fmt/display_lm.v @@ -0,0 +1,12 @@ +module top; + mid mid_uut (); +endmodule + +module mid (); + bot bot_uut (); +endmodule + +module bot (); + initial $display("%%l: %l\n%%m: %m"); + always $display("%%l: %l\n%%m: %m"); +endmodule diff --git a/tests/fmt/display_lm_tb.cc b/tests/fmt/display_lm_tb.cc new file mode 100644 index 00000000000..ebc62f80fad --- /dev/null +++ b/tests/fmt/display_lm_tb.cc @@ -0,0 +1,10 @@ +#include +#include "yosys-display_lm.cc" + +int main() +{ + cxxrtl_design::p_top uut; + + uut.step(); + return 0; +} diff --git a/tests/fmt/fuzz/.gitignore b/tests/fmt/fuzz/.gitignore new file mode 100644 index 00000000000..88ea7e2ef91 --- /dev/null +++ b/tests/fmt/fuzz/.gitignore @@ -0,0 +1,2 @@ +fuzztest +build diff --git a/tests/fmt/fuzz/CMakeLists.txt b/tests/fmt/fuzz/CMakeLists.txt new file mode 100644 index 00000000000..6f7c43da888 --- /dev/null +++ b/tests/fmt/fuzz/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.14) +project(cxxrtl_division_fuzz) + +set(CMAKE_CXX_STANDARD 17) + +add_subdirectory(fuzztest) + +enable_testing() + +include(GoogleTest) + +fuzztest_setup_fuzzing_flags() + +include_directories(../../..) + +add_executable( + x_test + x_test.cc + ../../../libs/bigint/BigUnsigned.cc +) + +link_fuzztest(x_test) +gtest_discover_tests(x_test) diff --git a/tests/fmt/fuzz/x_test.cc b/tests/fmt/fuzz/x_test.cc new file mode 100644 index 00000000000..ee061861f90 --- /dev/null +++ b/tests/fmt/fuzz/x_test.cc @@ -0,0 +1,44 @@ +#include "fuzztest/fuzztest.h" +#include "gtest/gtest.h" + +#include +#include "backends/cxxrtl/cxxrtl.h" +#include "libs/bigint/BigUnsigned.hh" + +using namespace cxxrtl_yosys; + +void Formats128BitIntegers(chunk_t x0, chunk_t x1, chunk_t x2, chunk_t x3, bool signed_) +{ + // Compare output to BigUnsigned. + value<128> v; + v = v.blit<127, 64>(value<64>{x1, x0}); + v = v.blit<63, 0>(value<64>{x3, x2}); + + std::ostringstream oss; + oss << value_formatted<128>(v, false, false, ' ', 0, 10, signed_, false, false); + auto actual = oss.str(); + + BigUnsigned u; + bool negative = signed_ && v.is_neg(); + if (negative) + v = v.neg(); + u.bitShiftLeft(v.slice<127, 64>().val().get(), 64); + u.bitOr(u, v.slice<63, 0>().val().get()); + + std::string expected; + + if (u.isZero()) { + expected = "0"; + } else { + while (!u.isZero()) { + expected += '0' + (u % 10).toInt(); + u /= 10; + } + if (negative) + expected += '-'; + std::reverse(expected.begin(), expected.end()); + } + + EXPECT_EQ(actual, expected); +} +FUZZ_TEST(CxxrtlDivisionFuzz, Formats128BitIntegers); diff --git a/tests/fmt/initial_display.v b/tests/fmt/initial_display.v new file mode 100644 index 00000000000..d3ef9ed7691 --- /dev/null +++ b/tests/fmt/initial_display.v @@ -0,0 +1,255 @@ +module m; + initial $display("<<>>"); + + initial $display("==> small unsigned %%d"); + initial $display(":%d:", 16'haa); + initial $display(":%-d:", 16'haa); + initial $display(":%+d:", 16'haa); + initial $display(":%+-d:", 16'haa); + initial $display(":%0d:", 16'haa); + initial $display(":%-0d:", 16'haa); + initial $display(":%+0d:", 16'haa); + initial $display(":%+-0d:", 16'haa); + initial $display(":%20d:", 16'haa); + initial $display(":%-20d:", 16'haa); + initial $display(":%+20d:", 16'haa); + initial $display(":%+-20d:", 16'haa); + initial $display(":%020d:", 16'haa); + initial $display(":%-020d:", 16'haa); + initial $display(":%+020d:", 16'haa); + initial $display(":%+-020d:", 16'haa); + + initial $display("==> big unsigned %%d"); + initial $display(":%d:", 16'haaaa); + initial $display(":%-d:", 16'haaaa); + initial $display(":%+d:", 16'haaaa); + initial $display(":%+-d:", 16'haaaa); + initial $display(":%0d:", 16'haaaa); + initial $display(":%-0d:", 16'haaaa); + initial $display(":%+0d:", 16'haaaa); + initial $display(":%+-0d:", 16'haaaa); + initial $display(":%20d:", 16'haaaa); + initial $display(":%-20d:", 16'haaaa); + initial $display(":%+20d:", 16'haaaa); + initial $display(":%+-20d:", 16'haaaa); + initial $display(":%020d:", 16'haaaa); + initial $display(":%-020d:", 16'haaaa); + initial $display(":%+020d:", 16'haaaa); + initial $display(":%+-020d:", 16'haaaa); + + initial $display("==> small signed %%d"); + initial $display(":%d:", 16'shaa); + initial $display(":%-d:", 16'shaa); + initial $display(":%+d:", 16'shaa); + initial $display(":%+-d:", 16'shaa); + initial $display(":%0d:", 16'shaa); + initial $display(":%-0d:", 16'shaa); + initial $display(":%+0d:", 16'shaa); + initial $display(":%+-0d:", 16'shaa); + initial $display(":%20d:", 16'shaa); + initial $display(":%-20d:", 16'shaa); + initial $display(":%+20d:", 16'shaa); + initial $display(":%+-20d:", 16'shaa); + initial $display(":%020d:", 16'shaa); + initial $display(":%-020d:", 16'shaa); + initial $display(":%+020d:", 16'shaa); + initial $display(":%+-020d:", 16'shaa); + + initial $display("==> big signed %%d"); + initial $display(":%d:", 16'shaaaa); + initial $display(":%-d:", 16'shaaaa); + initial $display(":%+d:", 16'shaaaa); + initial $display(":%+-d:", 16'shaaaa); + initial $display(":%0d:", 16'shaaaa); + initial $display(":%-0d:", 16'shaaaa); + initial $display(":%+0d:", 16'shaaaa); + initial $display(":%+-0d:", 16'shaaaa); + initial $display(":%20d:", 16'shaaaa); + initial $display(":%-20d:", 16'shaaaa); + initial $display(":%+20d:", 16'shaaaa); + initial $display(":%+-20d:", 16'shaaaa); + initial $display(":%020d:", 16'shaaaa); + initial $display(":%-020d:", 16'shaaaa); + initial $display(":%+020d:", 16'shaaaa); + initial $display(":%+-020d:", 16'shaaaa); + + initial $display("==> small unsigned %%h"); + initial $display(":%h:", 16'haa); + initial $display(":%-h:", 16'haa); + initial $display(":%0h:", 16'haa); + initial $display(":%-0h:", 16'haa); + initial $display(":%20h:", 16'haa); + initial $display(":%-20h:", 16'haa); + initial $display(":%020h:", 16'haa); + initial $display(":%-020h:", 16'haa); + + initial $display("==> big unsigned %%h"); + initial $display(":%h:", 16'haaaa); + initial $display(":%-h:", 16'haaaa); + initial $display(":%0h:", 16'haaaa); + initial $display(":%-0h:", 16'haaaa); + initial $display(":%20h:", 16'haaaa); + initial $display(":%-20h:", 16'haaaa); + initial $display(":%020h:", 16'haaaa); + initial $display(":%-020h:", 16'haaaa); + + initial $display("==> small signed %%h"); + initial $display(":%h:", 16'shaa); + initial $display(":%-h:", 16'shaa); + initial $display(":%0h:", 16'shaa); + initial $display(":%-0h:", 16'shaa); + initial $display(":%20h:", 16'shaa); + initial $display(":%-20h:", 16'shaa); + initial $display(":%020h:", 16'shaa); + initial $display(":%-020h:", 16'shaa); + + initial $display("==> big signed %%h"); + initial $display(":%h:", 16'shaaaa); + initial $display(":%-h:", 16'shaaaa); + initial $display(":%0h:", 16'shaaaa); + initial $display(":%-0h:", 16'shaaaa); + initial $display(":%20h:", 16'shaaaa); + initial $display(":%-20h:", 16'shaaaa); + initial $display(":%020h:", 16'shaaaa); + initial $display(":%-020h:", 16'shaaaa); + + initial $display("==> small unsigned %%o"); + initial $display(":%o:", 16'haa); + initial $display(":%-o:", 16'haa); + initial $display(":%0o:", 16'haa); + initial $display(":%-0o:", 16'haa); + initial $display(":%20o:", 16'haa); + initial $display(":%-20o:", 16'haa); + initial $display(":%020o:", 16'haa); + initial $display(":%-020o:", 16'haa); + + initial $display("==> big unsigned %%o"); + initial $display(":%o:", 16'haaaa); + initial $display(":%-o:", 16'haaaa); + initial $display(":%0o:", 16'haaaa); + initial $display(":%-0o:", 16'haaaa); + initial $display(":%20o:", 16'haaaa); + initial $display(":%-20o:", 16'haaaa); + initial $display(":%020o:", 16'haaaa); + initial $display(":%-020o:", 16'haaaa); + + initial $display("==> small signed %%o"); + initial $display(":%o:", 16'shaa); + initial $display(":%-o:", 16'shaa); + initial $display(":%0o:", 16'shaa); + initial $display(":%-0o:", 16'shaa); + initial $display(":%20o:", 16'shaa); + initial $display(":%-20o:", 16'shaa); + initial $display(":%020o:", 16'shaa); + initial $display(":%-020o:", 16'shaa); + + initial $display("==> big signed %%o"); + initial $display(":%o:", 16'shaaaa); + initial $display(":%-o:", 16'shaaaa); + initial $display(":%0o:", 16'shaaaa); + initial $display(":%-0o:", 16'shaaaa); + initial $display(":%20o:", 16'shaaaa); + initial $display(":%-20o:", 16'shaaaa); + initial $display(":%020o:", 16'shaaaa); + initial $display(":%-020o:", 16'shaaaa); + + initial $display("==> small unsigned %%b"); + initial $display(":%b:", 16'haa); + initial $display(":%-b:", 16'haa); + initial $display(":%0b:", 16'haa); + initial $display(":%-0b:", 16'haa); + initial $display(":%20b:", 16'haa); + initial $display(":%-20b:", 16'haa); + initial $display(":%020b:", 16'haa); + initial $display(":%-020b:", 16'haa); + + initial $display("==> big unsigned %%b"); + initial $display(":%b:", 16'haaaa); + initial $display(":%-b:", 16'haaaa); + initial $display(":%0b:", 16'haaaa); + initial $display(":%-0b:", 16'haaaa); + initial $display(":%20b:", 16'haaaa); + initial $display(":%-20b:", 16'haaaa); + initial $display(":%020b:", 16'haaaa); + initial $display(":%-020b:", 16'haaaa); + + initial $display("==> small signed %%b"); + initial $display(":%b:", 16'shaa); + initial $display(":%-b:", 16'shaa); + initial $display(":%0b:", 16'shaa); + initial $display(":%-0b:", 16'shaa); + initial $display(":%20b:", 16'shaa); + initial $display(":%-20b:", 16'shaa); + initial $display(":%020b:", 16'shaa); + initial $display(":%-020b:", 16'shaa); + + initial $display("==> big signed %%b"); + initial $display(":%b:", 16'shaaaa); + initial $display(":%-b:", 16'shaaaa); + initial $display(":%0b:", 16'shaaaa); + initial $display(":%-0b:", 16'shaaaa); + initial $display(":%20b:", 16'shaaaa); + initial $display(":%-20b:", 16'shaaaa); + initial $display(":%020b:", 16'shaaaa); + initial $display(":%-020b:", 16'shaaaa); + + initial $display("==> time %%t"); + initial $display(":%t:", $time); + initial $display(":%-t:", $time); + initial $display(":%0t:", $time); + initial $display(":%-0t:", $time); + initial $display(":%10t:", $time); + initial $display(":%-10t:", $time); + initial $display(":%015t:", $time); + initial $display(":%-015t:", $time); + + initial $display("===> %%s"); + initial $display(":%10s:", "foo"); + initial $display(":%010s:", "foo"); + initial $display(":%-10s:", "foo"); + initial $display(":%-010s:", "foo"); + + initial $display("===> %%c"); + initial $display(":%10c:", "foo"); + initial $display(":%010c:", "foo"); + initial $display(":%-10c:", "foo"); + initial $display(":%-010c:", "foo"); + + initial $display("==> aliases"); + initial $display(":%x:", 16'shaa); + initial $display(":%X:", 16'shaa); + initial $display(":%H:", 16'shaa); + initial $display(":%O:", 16'shaa); + initial $display(":%B:", 16'shaa); + + initial $display("==> x/z"); + initial $display(":%d:", 16'b1010101010101010); + initial $display(":%d:", 16'b101010101010101x); + initial $display(":%d:", 16'b101010101010101z); + initial $display(":%x:", 16'b1010101010101010); + initial $display(":%x:", 16'b101010101010101x); + initial $display(":%x:", 16'b101010101010101z); + initial $display(":%x:", 16'b101010101010xxxx); + initial $display(":%x:", 16'b101010101010zzzz); + initial $display(":%o:", 16'b1010101010101010); + initial $display(":%o:", 16'b101010101010101x); + initial $display(":%o:", 16'b101010101010101z); + initial $display(":%o:", 16'b1010101010101xxx); + initial $display(":%o:", 16'b1010101010101zzz); + initial $display(":%b:", 16'b1010101010101010); + initial $display(":%b:", 16'b101010101010101x); + initial $display(":%b:", 16'b101010101010101z); + + initial $display("==> default base"); + initial $displayh(16'haa); + initial $displayo(16'haa); + initial $displayb(16'haa); + + initial $display("==> write/format"); + initial $display("%d", 1, "%d", 1); + // this one hits a bug in iverilog: + // initial $display("%s", $sformatf("%d", 1, "%d", 1)); + + initial $display("<<>>"); + +endmodule diff --git a/tests/fmt/roundtrip.v b/tests/fmt/roundtrip.v new file mode 100644 index 00000000000..7b50039cd26 --- /dev/null +++ b/tests/fmt/roundtrip.v @@ -0,0 +1,22 @@ +module m(input clk, input `SIGN [31:0] data); + + always @(posedge clk) + // All on a single line to avoid order effects. +`ifdef BASE_DEC + $display(":%d:%-d:%+d:%+-d:%0d:%-0d:%+0d:%+-0d:%20d:%-20d:%+20d:%+-20d:%020d:%-020d:%+020d:%+-020d:", + data, data, data, data, data, data, data, data, data, data, data, data, data, data, data, data); +`endif +`ifdef BASE_HEX + $display(":%h:%-h:%0h:%-0h:%20h:%-20h:%020h:%-020h:", + data, data, data, data, data, data, data, data); +`endif +`ifdef BASE_OCT + $display(":%o:%-o:%0o:%-0o:%20o:%-20o:%020o:%-020o:", + data, data, data, data, data, data, data, data); +`endif +`ifdef BASE_BIN + $display(":%b:%-b:%0b:%-0b:%20b:%-20b:%020b:%-020b:", + data, data, data, data, data, data, data, data); +`endif + +endmodule diff --git a/tests/fmt/roundtrip_tb.v b/tests/fmt/roundtrip_tb.v new file mode 100644 index 00000000000..988b8d8c2c0 --- /dev/null +++ b/tests/fmt/roundtrip_tb.v @@ -0,0 +1,13 @@ +module tb; + reg clk = 1'b0; + reg [31:0] data; + + m dut(.clk(clk), .data(data)); + + initial begin + data = 32'haa; + #10; clk = 1; #10; clk = 0; + data = 32'haaaa; + #10; clk = 1; #10; clk = 0; + end +endmodule diff --git a/tests/fmt/run-test.sh b/tests/fmt/run-test.sh new file mode 100644 index 00000000000..914a7234747 --- /dev/null +++ b/tests/fmt/run-test.sh @@ -0,0 +1,77 @@ +#!/bin/bash + +set -ex + +../../yosys -p 'read_verilog initial_display.v' | awk '/<<>>/,/<<>>/ {print $0}' >yosys-initial_display.log +iverilog -o iverilog-initial_display initial_display.v +./iverilog-initial_display >iverilog-initial_display.log +diff yosys-initial_display.log iverilog-initial_display.log + +test_always_display () { + local subtest=$1; shift + ../../yosys -p "read_verilog $* always_display.v; proc; opt_expr -mux_bool; clean" -o yosys-always_display-${subtest}-1.v + ../../yosys -p "read_verilog yosys-always_display-${subtest}-1.v; proc; opt_expr -mux_bool; clean" -o yosys-always_display-${subtest}-2.v + diff yosys-always_display-${subtest}-1.v yosys-always_display-${subtest}-2.v +} + +test_always_display clk -DEVENT_CLK +test_always_display clk_rst -DEVENT_CLK_RST +test_always_display star -DEVENT_STAR + +test_always_display clk_en -DEVENT_CLK -DCOND_EN +test_always_display clk_rst_en -DEVENT_CLK_RST -DCOND_EN +test_always_display star_en -DEVENT_STAR -DCOND_EN + +test_roundtrip () { + local subtest=$1; shift + ../../yosys -p "read_verilog $* roundtrip.v; proc; clean" -o yosys-roundtrip-${subtest}-1.v + ../../yosys -p "read_verilog yosys-roundtrip-${subtest}-1.v; proc; clean" -o yosys-roundtrip-${subtest}-2.v + diff yosys-roundtrip-${subtest}-1.v yosys-roundtrip-${subtest}-2.v + + iverilog $* -o iverilog-roundtrip-${subtest} roundtrip.v roundtrip_tb.v + ./iverilog-roundtrip-${subtest} >iverilog-roundtrip-${subtest}.log + iverilog $* -o iverilog-roundtrip-${subtest}-1 yosys-roundtrip-${subtest}-1.v roundtrip_tb.v + ./iverilog-roundtrip-${subtest}-1 >iverilog-roundtrip-${subtest}-1.log + iverilog $* -o iverilog-roundtrip-${subtest}-2 yosys-roundtrip-${subtest}-2.v roundtrip_tb.v + ./iverilog-roundtrip-${subtest}-1 >iverilog-roundtrip-${subtest}-2.log + diff iverilog-roundtrip-${subtest}.log iverilog-roundtrip-${subtest}-1.log + diff iverilog-roundtrip-${subtest}-1.log iverilog-roundtrip-${subtest}-2.log +} + +test_roundtrip dec_unsigned -DBASE_DEC -DSIGN="" +test_roundtrip dec_signed -DBASE_DEC -DSIGN="signed" +test_roundtrip hex_unsigned -DBASE_HEX -DSIGN="" +test_roundtrip hex_signed -DBASE_HEX -DSIGN="signed" +test_roundtrip oct_unsigned -DBASE_HEX -DSIGN="" +test_roundtrip oct_signed -DBASE_HEX -DSIGN="signed" +test_roundtrip bin_unsigned -DBASE_HEX -DSIGN="" +test_roundtrip bin_signed -DBASE_HEX -DSIGN="signed" + +test_cxxrtl () { + local subtest=$1; shift + + ../../yosys -p "read_verilog ${subtest}.v; proc; clean; write_cxxrtl -print-output std::cerr yosys-${subtest}.cc" + ${CC:-gcc} -std=c++11 -o yosys-${subtest} -I../.. ${subtest}_tb.cc -lstdc++ + ./yosys-${subtest} 2>yosys-${subtest}.log + iverilog -o iverilog-${subtest} ${subtest}.v ${subtest}_tb.v + ./iverilog-${subtest} |grep -v '\$finish called' >iverilog-${subtest}.log + diff iverilog-${subtest}.log yosys-${subtest}.log +} + +test_cxxrtl always_full +test_cxxrtl always_comb + +# Ensure Verilog backend preserves behaviour of always block with multiple $displays. +../../yosys -p "read_verilog always_full.v; prep; clean" -o yosys-always_full-1.v +iverilog -o iverilog-always_full-1 yosys-always_full-1.v always_full_tb.v +./iverilog-always_full-1 |grep -v '\$finish called' >iverilog-always_full-1.log +diff iverilog-always_full.log iverilog-always_full-1.log + +../../yosys -p "read_verilog display_lm.v" >yosys-display_lm.log +../../yosys -p "read_verilog display_lm.v; write_cxxrtl yosys-display_lm.cc" +${CC:-gcc} -std=c++11 -o yosys-display_lm_cc -I../.. display_lm_tb.cc -lstdc++ +./yosys-display_lm_cc >yosys-display_lm_cc.log +for log in yosys-display_lm.log yosys-display_lm_cc.log; do + grep "^%l: \\\\bot\$" "$log" + grep "^%m: \\\\bot\$" "$log" +done diff --git a/tests/various/sformatf.ys b/tests/various/sformatf.ys index 66d6b0dbe9e..f281a9cd530 100644 --- a/tests/various/sformatf.ys +++ b/tests/various/sformatf.ys @@ -5,7 +5,7 @@ module top; localparam b = $sformatf("%d", 4'b011); generate if (a != "0x5a") $error("a incorrect!"); - if (b != "3") $error("b incorrect!"); + if (b != " 3") $error("b incorrect!"); endgenerate endmodule