From 092214256791007a433999635ed39fb9fc5d00ad Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Mon, 15 Apr 2024 13:55:54 +0200 Subject: [PATCH 01/92] Add generic topological sort and SCC detection This adds a generic non-recursive implementation of Tarjan's linear time SCC algorithm that produces components in topological order. It can be instantiated to work directly on any graph representation for which the enumerate_nodes and enumerate_successors interface can be implemented. --- kernel/topo_scc.h | 328 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 328 insertions(+) create mode 100644 kernel/topo_scc.h diff --git a/kernel/topo_scc.h b/kernel/topo_scc.h new file mode 100644 index 00000000000..0ae0e696b47 --- /dev/null +++ b/kernel/topo_scc.h @@ -0,0 +1,328 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Jannis Harder + * + * 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 TOPO_SCC_H +#define TOPO_SCC_H + +#include "kernel/yosys.h" + +YOSYS_NAMESPACE_BEGIN + +class SigCellGraph { +public: + typedef int node_type; + + struct successor_enumerator { + std::vector>::const_iterator current, end; + bool finished() const { return current == end; } + node_type next() { + log_assert(!finished()); + node_type result = current->second; + ++current; + return result; + } + }; + + struct node_enumerator { + int current, end; + bool finished() const { return current == end; } + node_type next() { + log_assert(!finished()); + node_type result = current; + ++current; + return result; + } + }; + +private: + idict cell_ids; + idict sig_ids; + std::vector> edges; + std::vector> edge_ranges; + std::vector indices_; + int offset; + bool computed = false; + + void compute() { + offset = GetSize(sig_ids); + edge_ranges.clear(); + indices_.clear(); + indices_.resize(GetSize(sig_ids) + GetSize(cell_ids), -1); + + std::sort(edges.begin(), edges.end()); + auto last = std::unique(edges.begin(), edges.end()); + edges.erase(last, edges.end()); + auto edge = edges.begin(); + auto edge_end = edges.end(); + int range_begin = 0; + for (int node = -offset, node_end = GetSize(cell_ids); node != node_end; ++node) { + while (edge != edge_end && edge->first <= node) + ++edge; + int range_end = edge - edges.begin(); + edge_ranges.emplace_back(std::make_pair(range_begin, range_end)); + range_begin = range_end; + } + } + +public: + node_type node(RTLIL::Cell *cell) { return cell_ids(cell); } + node_type node(SigBit const &bit) { return ~sig_ids(bit); } + + bool is_cell(node_type node) { return node >= 0; } + bool is_sig(node_type node) { return node < 0; } + + Cell *cell(node_type node) { return node >= 0 ? cell_ids[node] : nullptr; } + SigBit sig(node_type node) { return node < 0 ? sig_ids[~node] : SigBit(); } + + template + void add_edge(Src &&src, Dst &&dst) { + computed = false; + node_type src_node = node(std::forward(src)); + node_type dst_node = node(std::forward(dst)); + edges.emplace_back(std::make_pair(src_node, dst_node)); + } + + node_enumerator enumerate_nodes() { + if (!computed) compute(); + return {-GetSize(sig_ids), GetSize(cell_ids)}; + } + + successor_enumerator enumerate_successors(node_type const &node) const { + auto range = edge_ranges[node + offset]; + return {edges.begin() + range.first, edges.begin() + range.second}; + } + + int &dfs_index(node_type const &node) { + return indices_[node + offset]; + } +}; + + +class IntGraph { +public: + typedef int node_type; + + struct successor_enumerator { + std::vector>::const_iterator current, end; + bool finished() const { return current == end; } + node_type next() { + log_assert(!finished()); + node_type result = current->second; + ++current; + return result; + } + }; + + struct node_enumerator { + int current, end; + bool finished() const { return current == end; } + node_type next() { + log_assert(!finished()); + node_type result = current; + ++current; + return result; + } + }; + +private: + std::vector> edges; + std::vector> edge_ranges; + std::vector indices_; + bool computed = false; + + void compute() { + edge_ranges.clear(); + + int node_end = 0; + for (auto const &edge : edges) + node_end = std::max(node_end, std::max(edge.first, edge.second) + 1); + indices_.clear(); + indices_.resize(node_end, -1); + + std::sort(edges.begin(), edges.end()); + auto last = std::unique(edges.begin(), edges.end()); + edges.erase(last, edges.end()); + auto edge = edges.begin(); + auto edge_end = edges.end(); + int range_begin = 0; + for (int node = 0; node != node_end; ++node) { + while (edge != edge_end && edge->first <= node) + ++edge; + int range_end = edge - edges.begin(); + edge_ranges.emplace_back(std::make_pair(range_begin, range_end)); + range_begin = range_end; + } + } + +public: + void add_edge(int src, int dst) { + log_assert(src >= 0); + log_assert(dst >= 0); + computed = false; + edges.emplace_back(std::make_pair(src, dst)); + } + + node_enumerator enumerate_nodes() { + if (!computed) compute(); + return {0, GetSize(indices_)}; + } + + successor_enumerator enumerate_successors(int node) const { + auto range = edge_ranges[node]; + return {edges.begin() + range.first, edges.begin() + range.second}; + } + + int &dfs_index(node_type const &node) { + return indices_[node]; + } +}; + +template +void topo_sorted_sccs(G &graph, ComponentCallback component) +{ + typedef typename G::node_enumerator node_enumerator; + typedef typename G::successor_enumerator successor_enumerator; + typedef typename G::node_type node_type; + + struct dfs_entry { + node_type node; + successor_enumerator successors; + int lowlink; + + dfs_entry(node_type node, successor_enumerator successors, int lowlink) : + node(node), successors(successors), lowlink(lowlink) + {} + }; + + std::vector dfs_stack; + std::vector component_stack; + int next_index = 0; + + + node_enumerator nodes = graph.enumerate_nodes(); + + // iterate over all nodes to ensure we process the whole graph + while (!nodes.finished()) { + node_type node = nodes.next(); + // only start a new search if the node wasn't visited yet + if (graph.dfs_index(node) >= 0) + continue; + + while (true) { + // at this point we're visiting the node for the first time during + // the DFS search + + // we record the timestamp of when we first visited the node as the + // dfs_index + int lowlink = next_index; + next_index++; + graph.dfs_index(node) = lowlink; + + // and we add the node to the component stack where it will remain + // until all nodes of the component containing this node are popped + component_stack.push_back(node); + + // then we start iterating over the successors of this node + successor_enumerator successors = graph.enumerate_successors(node); + while (true) { + if (successors.finished()) { + // when we processed all successors, i.e. when we visited + // the complete DFS subtree rooted at the current node, we + // first check whether the current node is a SCC root + // + // (why this check identifies SCC roots is out of scope for + // this comment, see other material on Tarjan's SCC + // algorithm) + if (lowlink == graph.dfs_index(node)) { + // the SCC containing the current node is at the top of + // the component stack, with the current node at the bottom + int current = GetSize(component_stack); + do { + --current; + } while (component_stack[current] != node); + + // we invoke the callback with a pointer range of the + // nodes in the SCC + + node_type *stack_ptr = component_stack.data(); + node_type *component_begin = stack_ptr + current; + node_type *component_end = stack_ptr + component_stack.size(); + + // note that we allow the callback to permute the nodes + // in this range as well as to modify dfs_index of the + // nodes in the SCC. + component(component_begin, component_end); + + // by setting the dfs_index of all already emitted + // nodes to INT_MAX, we don't need a separate check for + // whether successor nodes are still on the component + // stack before updating the lowlink value + for (; component_begin != component_end; ++component_begin) + graph.dfs_index(*component_begin) = INT_MAX; + component_stack.resize(current); + } + + // after checking for a completed SCC the DFS either + // continues the search at the parent node or returns to + // the outer loop if we already are at the root node. + if (dfs_stack.empty()) + goto next_search; + auto &dfs_top = dfs_stack.back(); + + node = dfs_top.node; + successors = std::move(dfs_top.successors); + + // the parent's lowlink is updated when returning + lowlink = min(lowlink, dfs_top.lowlink); + dfs_stack.pop_back(); + // continue checking the remaining successors of the parent node. + } else { + node_type succ = successors.next(); + if (graph.dfs_index(succ) < 0) { + // if the successor wasn't visted yet, the DFS recurses + // into the successor + + // we save the state for this node and make the + // successor the current node. + dfs_stack.emplace_back(node, std::move(successors), lowlink); + node = succ; + + // this break gets us to the section corresponding to + // the function entry in the recursive version + break; + } else { + // the textbook version guards this update with a check + // whether the successor is still on the component + // stack. If the successor node was already visisted + // but is not on the component stack, it must be part + // of an already emitted SCC. We can avoid this check + // by setting the DFS index of all nodes in a SCC to + // INT_MAX when the SCC is emitted. + lowlink = min(lowlink, graph.dfs_index(succ)); + } + } + } + } + next_search:; + } +} + +YOSYS_NAMESPACE_END + +#endif From c73c8a39cf27c1daae8b28b1874fd856efb2509b Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Mon, 15 Apr 2024 14:01:10 +0200 Subject: [PATCH 02/92] kernel/log: Add log_str helper for custom log_* functions/overloads When implementing custom log_... functions or custom overloads for the core log functions like log_signal it is necessary to return `char *` that are valid long enough. The log_... functions implemented in log.cc use either `log_id_cache` or `string_buf` which both are cleared on log_pop. This commit adds a public `log_str` function which stores its argument in the `log_id_cache` and returns the stored copy, such that custom log functions outside of log.cc can also create strings that remain valid until the next `log_pop`. --- kernel/log.cc | 10 ++++++++++ kernel/log.h | 2 ++ 2 files changed, 12 insertions(+) diff --git a/kernel/log.cc b/kernel/log.cc index 9a61e8f08b3..55895da06d7 100644 --- a/kernel/log.cc +++ b/kernel/log.cc @@ -662,6 +662,16 @@ const char *log_id(const RTLIL::IdString &str) return p+1; } +const char *log_str(const char *str) +{ + log_id_cache.push_back(strdup(str)); + return log_id_cache.back(); +} + +const char *log_str(std::string const &str) { + return log_str(str.c_str()); +} + void log_module(RTLIL::Module *module, std::string indent) { std::stringstream buf; diff --git a/kernel/log.h b/kernel/log.h index 53aae58c6bc..4b90cf9dceb 100644 --- a/kernel/log.h +++ b/kernel/log.h @@ -206,6 +206,8 @@ void log_check_expected(); const char *log_signal(const RTLIL::SigSpec &sig, bool autoint = true); const char *log_const(const RTLIL::Const &value, bool autoint = true); const char *log_id(const RTLIL::IdString &id); +const char *log_str(const char *str); +const char *log_str(std::string const &str); template static inline const char *log_id(T *obj, const char *nullstr = nullptr) { if (nullstr && obj == nullptr) From f24e2536c657346853ac71b8483eca004f695d0e Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Mon, 15 Apr 2024 14:08:58 +0200 Subject: [PATCH 03/92] kernel/rtlil: Add `SigBit operator[](int offset)` to `SigChunk` This is already supported by `SigSpec` and since both `SigChunk` and `SigSpec` implement `extract` which is the multi-bit variant of this, there is no good reason for `SigChunk` to not support `SigBit operator[](int offset)`. --- kernel/rtlil.cc | 14 ++++++++++++++ kernel/rtlil.h | 1 + 2 files changed, 15 insertions(+) diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index d3946a620de..b77f8f5fe73 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -3754,6 +3754,20 @@ RTLIL::SigChunk RTLIL::SigChunk::extract(int offset, int length) const return ret; } +RTLIL::SigBit RTLIL::SigChunk::operator[](int offset) const +{ + log_assert(offset >= 0); + log_assert(offset <= width); + RTLIL::SigBit ret; + if (wire) { + ret.wire = wire; + ret.offset = this->offset + offset; + } else { + ret.data = data[offset]; + } + return ret; +} + bool RTLIL::SigChunk::operator <(const RTLIL::SigChunk &other) const { if (wire && other.wire) diff --git a/kernel/rtlil.h b/kernel/rtlil.h index f9da2949508..685acf90426 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -769,6 +769,7 @@ struct RTLIL::SigChunk SigChunk(const RTLIL::SigBit &bit); RTLIL::SigChunk extract(int offset, int length) const; + RTLIL::SigBit operator[](int offset) const; inline int size() const { return width; } inline bool is_wire() const { return wire != NULL; } From 56572978f51a5f81cfb6b706fbd5c123b9133bc6 Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Mon, 15 Apr 2024 14:12:16 +0200 Subject: [PATCH 04/92] drivertools: Utility code for indexing and traversing signal drivers It adds `DriveBit`, `DriveChunk` and `DriveSpec` types which are similar to `SigBit`, `SigChunk` and `SigSpec` but can also directly represent cell ports, undriven bits and multiple drivers. For indexing an RTLIL module and for querying signal drivers it comes with a `DriverMap` type which is somewhat similar to a `SigMap` but is guaranteed to produce signal drivers as returned representatives. A `DriverMap` can also optionally preserve connections via intermediate wires (e.g. querying the driver of a cell input port will return a connected intermediate wire, querying the driver of that wire will return the cell output port that's driving the wire). --- Makefile | 2 + kernel/drivertools.cc | 946 +++++++++++++++++++++++++++++ kernel/drivertools.h | 1317 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 2265 insertions(+) create mode 100644 kernel/drivertools.cc create mode 100644 kernel/drivertools.h diff --git a/Makefile b/Makefile index d5e7e52356a..812a69271a7 100644 --- a/Makefile +++ b/Makefile @@ -598,6 +598,7 @@ $(eval $(call add_include_file,kernel/celltypes.h)) $(eval $(call add_include_file,kernel/consteval.h)) $(eval $(call add_include_file,kernel/constids.inc)) $(eval $(call add_include_file,kernel/cost.h)) +$(eval $(call add_include_file,kernel/drivertools.h)) $(eval $(call add_include_file,kernel/ff.h)) $(eval $(call add_include_file,kernel/ffinit.h)) $(eval $(call add_include_file,kernel/ffmerge.h)) @@ -638,6 +639,7 @@ $(eval $(call add_include_file,backends/rtlil/rtlil_backend.h)) OBJS += kernel/driver.o kernel/register.o kernel/rtlil.o kernel/log.o kernel/calc.o kernel/yosys.o OBJS += kernel/binding.o OBJS += kernel/cellaigs.o kernel/celledges.o kernel/cost.o kernel/satgen.o kernel/scopeinfo.o kernel/qcsat.o kernel/mem.o kernel/ffmerge.o kernel/ff.o kernel/yw.o kernel/json.o kernel/fmt.o +OBJS += kernel/drivertools.o ifeq ($(ENABLE_ZLIB),1) OBJS += kernel/fstdata.o endif diff --git a/kernel/drivertools.cc b/kernel/drivertools.cc new file mode 100644 index 00000000000..499a9399c46 --- /dev/null +++ b/kernel/drivertools.cc @@ -0,0 +1,946 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Jannis Harder + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/drivertools.h" + +YOSYS_NAMESPACE_BEGIN + +DriveBit::DriveBit(SigBit const &bit) +{ + if (bit.is_wire()) + *this = DriveBitWire(bit.wire, bit.offset); + else + *this = bit.data; +} + +void DriveBit::merge(DriveBit const &other) +{ + if (other.type_ == DriveType::NONE) + return; + if (type_ == DriveType::NONE) { + *this = other; + return; + } + if (type_ != DriveType::MULTIPLE) { + DriveBitMultiple multi(std::move(*this)); + *this = std::move(multi); + } + multiple().merge(other); +} + + +void DriveBitMultiple::merge(DriveBit const &single) +{ + if (single.type() == DriveType::NONE) + return; + if (single.type() == DriveType::MULTIPLE) { + merge(single.multiple()); + return; + } + multiple_.emplace(single); +} + +void DriveBitMultiple::merge(DriveBit &&single) +{ + if (single.type() == DriveType::NONE) + return; + if (single.type() == DriveType::MULTIPLE) { + merge(std::move(single.multiple())); + return; + } + multiple_.emplace(std::move(single)); +} + +DriveBitMultiple DriveChunkMultiple::operator[](int i) const +{ + DriveBitMultiple result; + for (auto const &single : multiple_) + result.merge(single[i]); + return result; +} + + +bool DriveChunkWire::can_append(DriveBitWire const &bit) const +{ + return bit.wire == wire && bit.offset == offset + width; +} + +bool DriveChunkWire::try_append(DriveBitWire const &bit) +{ + if (!can_append(bit)) + return false; + width += 1; + return true; +} + +bool DriveChunkWire::try_append(DriveChunkWire const &chunk) +{ + if (chunk.wire != wire || chunk.offset != offset + width) + return false; + width += chunk.width; + return true; +} + +bool DriveChunkPort::can_append(DriveBitPort const &bit) const +{ + return bit.cell == cell && bit.port == port && bit.offset == offset + width; +} + +bool DriveChunkPort::try_append(DriveBitPort const &bit) +{ + if (!can_append(bit)) + return false; + width += 1; + return true; +} + +bool DriveChunkPort::try_append(DriveChunkPort const &chunk) +{ + if (chunk.cell != cell || chunk.port != port || chunk.offset != offset + width) + return false; + width += chunk.width; + return true; +} + +bool DriveChunkMarker::can_append(DriveBitMarker const &bit) const +{ + return bit.marker == marker && bit.offset == offset + width; +} + +bool DriveChunkMarker::try_append(DriveBitMarker const &bit) +{ + if (!can_append(bit)) + return false; + width += 1; + return true; +} + +bool DriveChunkMarker::try_append(DriveChunkMarker const &chunk) +{ + if (chunk.marker != marker || chunk.offset != offset + width) + return false; + width += chunk.width; + return true; +} + + +bool DriveChunkMultiple::can_append(DriveBitMultiple const &bit) const +{ + if (bit.multiple().size() != multiple_.size()) + return false; + + int const_drivers = 0; + for (DriveChunk const &single : multiple_) + if (single.is_constant()) + const_drivers += 1; + + if (const_drivers > 1) + return false; + + for (DriveBit const &single : bit.multiple()) + if (single.is_constant()) + const_drivers -= 1; + + if (const_drivers != 0) + return false; + + for (DriveChunk const &single : multiple_) + { + switch (single.type()) + { + case DriveType::CONSTANT: { + } break; + case DriveType::WIRE: { + auto const &wire = single.wire(); + DriveBit next = DriveBitWire(wire.wire, wire.offset + wire.width); + if (!bit.multiple().count(next)) + return false; + } break; + case DriveType::PORT: { + auto const &port = single.port(); + DriveBit next = DriveBitPort(port.cell, port.port, port.offset + port.width); + if (!bit.multiple().count(next)) + return false; + } break; + case DriveType::MARKER: { + auto const &marker = single.marker(); + DriveBit next = DriveBitMarker(marker.marker, marker.offset + marker.width); + if (!bit.multiple().count(next)) + return false; + } break; + default: + return false; + } + } + + return true; +} + +bool DriveChunkMultiple::can_append(DriveChunkMultiple const &chunk) const +{ + if (chunk.multiple().size() != multiple_.size()) + return false; + + int const_drivers = 0; + for (DriveChunk const &single : multiple_) + if (single.is_constant()) + const_drivers += 1; + + if (const_drivers > 1) + return false; + + for (DriveChunk const &single : chunk.multiple()) + if (single.is_constant()) + const_drivers -= 1; + + if (const_drivers != 0) + return false; + + for (DriveChunk const &single : multiple_) + { + switch (single.type()) + { + case DriveType::CONSTANT: { + } break; + case DriveType::WIRE: { + auto const &wire = single.wire(); + DriveChunk next = DriveChunkWire(wire.wire, wire.offset + wire.width, chunk.size()); + if (!chunk.multiple().count(next)) + return false; + } break; + case DriveType::PORT: { + auto const &port = single.port(); + DriveChunk next = DriveChunkPort(port.cell, port.port, port.offset + port.width, chunk.size()); + if (!chunk.multiple().count(next)) + return false; + } break; + case DriveType::MARKER: { + auto const &marker = single.marker(); + DriveChunk next = DriveChunkMarker(marker.marker, marker.offset + marker.width, chunk.size()); + if (!chunk.multiple().count(next)) + return false; + } break; + default: + return false; + } + } + + return true; +} + +bool DriveChunkMultiple::try_append(DriveBitMultiple const &bit) +{ + if (!can_append(bit)) + return false; + width_ += 1; + State constant; + + for (DriveBit const &single : bit.multiple()) + if (single.is_constant()) + constant = single.constant(); + + for (DriveChunk &single : multiple_) + { + switch (single.type()) + { + case DriveType::CONSTANT: { + single.constant().bits.push_back(constant); + } break; + case DriveType::WIRE: { + single.wire().width += 1; + } break; + case DriveType::PORT: { + single.port().width += 1; + } break; + case DriveType::MARKER: { + single.marker().width += 1; + } break; + default: + log_abort(); + } + } + return true; +} + +bool DriveChunkMultiple::try_append(DriveChunkMultiple const &chunk) +{ + if (!can_append(chunk)) + return false; + int width = chunk.size(); + width_ += width; + Const constant; + + for (DriveChunk const &single : chunk.multiple()) + if (single.is_constant()) + constant = single.constant(); + + for (DriveChunk &single : multiple_) + { + switch (single.type()) + { + case DriveType::CONSTANT: { + auto &bits = single.constant().bits; + bits.insert(bits.end(), constant.bits.begin(), constant.bits.end()); + } break; + case DriveType::WIRE: { + single.wire().width += width; + } break; + case DriveType::PORT: { + single.port().width += width; + } break; + case DriveType::MARKER: { + single.marker().width += width; + } break; + default: + log_abort(); + } + } + return true; +} + +bool DriveChunk::can_append(DriveBit const &bit) const +{ + if (size() == 0) + return true; + if (bit.type() != type_) + return false; + switch (type_) + { + case DriveType::NONE: + return true; + case DriveType::CONSTANT: + return true; + case DriveType::WIRE: + return wire_.can_append(bit.wire()); + case DriveType::PORT: + return port_.can_append(bit.port()); + case DriveType::MULTIPLE: + return multiple_.can_append(bit.multiple()); + default: + log_abort(); + } +} + +bool DriveChunk::try_append(DriveBit const &bit) +{ + if (size() == 0) + *this = bit; + if (bit.type() != type_) + return false; + switch (type_) + { + case DriveType::NONE: + none_ += 1; + return true; + case DriveType::CONSTANT: + constant_.bits.push_back(bit.constant()); + return true; + case DriveType::WIRE: + return wire_.try_append(bit.wire()); + case DriveType::PORT: + return port_.try_append(bit.port()); + case DriveType::MULTIPLE: + return multiple_.try_append(bit.multiple()); + default: + log_abort(); + } +} + + +bool DriveChunk::try_append(DriveChunk const &chunk) +{ + if (size() == 0) + *this = chunk; + if (chunk.type_ != type_) + return false; + switch (type_) + { + case DriveType::NONE: + none_ += chunk.none_; + return true; + case DriveType::CONSTANT: + constant_.bits.insert(constant_.bits.end(), chunk.constant_.bits.begin(), chunk.constant_.bits.end()); + return true; + case DriveType::WIRE: + return wire_.try_append(chunk.wire()); + case DriveType::PORT: + return port_.try_append(chunk.port()); + case DriveType::MARKER: + return marker_.try_append(chunk.marker()); + case DriveType::MULTIPLE: + return multiple_.try_append(chunk.multiple()); + } + log_abort(); +} + +void DriveSpec::append(DriveBit const &bit) +{ + hash_ = 0; + if (!packed()) { + bits_.push_back(bit); + width_ += 1; + return; + } + + if (chunks_.empty() || !chunks_.back().try_append(bit)) + chunks_.emplace_back(bit); + width_ += 1; +} + +void DriveSpec::append(DriveChunk const &chunk) +{ + hash_ = 0; + pack(); + if (chunks_.empty() || !chunks_.back().try_append(chunk)) + chunks_.emplace_back(chunk); + width_ += chunk.size(); +} + +void DriveSpec::pack() const { + if (bits_.empty()) + return; + std::vector bits(std::move(bits_)); + for (auto &bit : bits) + if (chunks_.empty() || !chunks_.back().try_append(bit)) + chunks_.emplace_back(std::move(bit)); +} + +void DriveSpec::unpack() const { + if (chunks_.empty()) + return; + for (auto &chunk : chunks_) + { + for (int i = 0, width = chunk.size(); i != width; ++i) + { + bits_.emplace_back(chunk[i]); + } + } + chunks_.clear(); +} + +void DriveSpec::compute_width() +{ + width_ = 0; + for (auto const &chunk : chunks_) + width_ += chunk.size(); +} + +void DriverMap::DriveBitGraph::add_edge(DriveBitId src, DriveBitId dst) +{ + if (first_edges.emplace(src, dst).first->second == dst) + return; + if (second_edges.emplace(src, dst).first->second == dst) + return; + more_edges[src].emplace(dst); +} + +DriverMap::DriveBitId DriverMap::DriveBitGraph::pop_edge(DriveBitId src) +{ + // TODO unused I think? + auto found_more = more_edges.find(src); + if (found_more != more_edges.end()) { + auto result = found_more->second.pop(); + if (found_more->second.empty()) + more_edges.erase(found_more); + return result; + } + + auto found_second = second_edges.find(src); + if (found_second != second_edges.end()) { + auto result = found_second->second; + second_edges.erase(found_second); + return result; + } + + auto found_first = first_edges.find(src); + if (found_first != first_edges.end()) { + auto result = found_first->second; + first_edges.erase(found_first); + return result; + } + + return DriveBitId(); +} + +void DriverMap::DriveBitGraph::clear(DriveBitId src) +{ + first_edges.erase(src); + second_edges.erase(src); + more_edges.erase(src); +} + +bool DriverMap::DriveBitGraph::contains(DriveBitId src) +{ + return first_edges.count(src); +} + +int DriverMap::DriveBitGraph::count(DriveBitId src) +{ + if (!first_edges.count(src)) + return 0; + if (!second_edges.count(src)) + return 1; + auto found = more_edges.find(src); + if (found == more_edges.end()) + return 2; + return GetSize(found->second); +} + +DriverMap::DriveBitId DriverMap::DriveBitGraph::at(DriveBitId src, int index) +{ + if (index == 0) + return first_edges.at(src); + else if (index == 1) + return second_edges.at(src); + else + return *more_edges.at(src).element(index - 2); +} + + +DriverMap::BitMode DriverMap::bit_mode(DriveBit const &bit) +{ + switch (bit.type()) + { + case DriveType::NONE: + return BitMode::NONE; + case DriveType::CONSTANT: + // TODO how to handle Sx here? + return bit.constant() == State::Sz ? BitMode::NONE : BitMode::DRIVER; + case DriveType::WIRE: { + auto const &wire = bit.wire(); + bool driver = wire.wire->port_input; + bool driven = wire.wire->port_output; + + if (driver && !driven) + return BitMode::DRIVER; + else if (driven && !driver) + return BitMode::DRIVEN; + else if (driver && driven) + return BitMode::TRISTATE; + else + return keep_wire(bit.wire().wire) ? BitMode::KEEP : BitMode::NONE; + } + case DriveType::PORT: { + auto const &port = bit.port(); + bool driver = celltypes.cell_output(port.cell->type, port.port); + bool driven = celltypes.cell_input(port.cell->type, port.port); + if (driver && !driven) + return BitMode::DRIVER; + else if (driven && !driver) + return BitMode::DRIVEN_UNIQUE; + else + return BitMode::TRISTATE; + } + case DriveType::MARKER: { + // TODO user supplied classification + log_abort(); + } + default: + log_abort(); + } +} + +DriverMap::DriveBitId DriverMap::id_from_drive_bit(DriveBit const &bit) +{ + switch (bit.type()) + { + case DriveType::NONE: + return -1; + case DriveType::CONSTANT: + return (int)bit.constant(); + case DriveType::WIRE: { + auto const &wire_bit = bit.wire(); + int offset = next_offset; + auto insertion = wire_offsets.emplace(wire_bit.wire, offset); + if (insertion.second) { + if (wire_bit.wire->width == 1) { + log_assert(wire_bit.offset == 0); + isolated_drive_bits.emplace(offset, bit); + } else + drive_bits.emplace(offset, DriveBitWire(wire_bit.wire, 0)); + next_offset += wire_bit.wire->width; + } + return insertion.first->second.id + wire_bit.offset; + } + case DriveType::PORT: { + auto const &port_bit = bit.port(); + auto key = std::make_pair(port_bit.cell, port_bit.port); + int offset = next_offset; + auto insertion = port_offsets.emplace(key, offset); + if (insertion.second) { + int width = port_bit.cell->connections().at(port_bit.port).size(); + if (width == 1 && offset == 0) { + log_assert(port_bit.offset == 0); + isolated_drive_bits.emplace(offset, bit); + } else + drive_bits.emplace(offset, DriveBitPort(port_bit.cell, port_bit.port, 0)); + next_offset += width; + } + return insertion.first->second.id + port_bit.offset; + } + default: + log_assert(false && "unsupported DriveType in DriverMap"); + } + log_abort(); +} + +DriveBit DriverMap::drive_bit_from_id(DriveBitId id) +{ + auto found_isolated = isolated_drive_bits.find(id); + if (found_isolated != isolated_drive_bits.end()) + return found_isolated->second; + + auto found = drive_bits.upper_bound(id); + if (found == drive_bits.begin()) { + return id < 0 ? DriveBit() : DriveBit((State) id.id); + } + --found; + DriveBit result = found->second; + if (result.is_wire()) { + result.wire().offset += id.id - found->first.id; + } else { + log_assert(result.is_port()); + result.port().offset += id.id - found->first.id; + } + return result; +} + +void DriverMap::connect_directed_merge(DriveBitId driven_id, DriveBitId driver_id) +{ + if (driven_id == driver_id) + return; + + same_driver.merge(driven_id, driver_id); + + for (int i = 0, end = connected_drivers.count(driven_id); i != end; ++i) + connected_drivers.add_edge(driver_id, connected_drivers.at(driven_id, i)); + + connected_drivers.clear(driven_id); + + for (int i = 0, end = connected_undirected.count(driven_id); i != end; ++i) + connected_undirected.add_edge(driver_id, connected_undirected.at(driven_id, i)); + + connected_undirected.clear(driven_id); +} + +void DriverMap::connect_directed_buffer(DriveBitId driven_id, DriveBitId driver_id) +{ + connected_drivers.add_edge(driven_id, driver_id); +} + +void DriverMap::connect_undirected(DriveBitId a_id, DriveBitId b_id) +{ + connected_undirected.add_edge(a_id, b_id); + connected_undirected.add_edge(b_id, a_id); +} + +void DriverMap::add(Module *module) +{ + for (auto const &conn : module->connections()) + add(conn.first, conn.second); + + for (auto cell : module->cells()) + for (auto const &conn : cell->connections()) + add_port(cell, conn.first, conn.second); +} + +// Add a single bit connection to the driver map. +void DriverMap::add(DriveBit const &a, DriveBit const &b) +{ + DriveBitId a_id = id_from_drive_bit(a); + DriveBitId b_id = id_from_drive_bit(b); + + DriveBitId orig_a_id = a_id; + DriveBitId orig_b_id = b_id; + + a_id = same_driver.find(a_id); + b_id = same_driver.find(b_id); + + if (a_id == b_id) + return; + + BitMode a_mode = bit_mode(orig_a_id == a_id ? a : drive_bit_from_id(a_id)); + BitMode b_mode = bit_mode(orig_b_id == b_id ? b : drive_bit_from_id(b_id)); + + // If either bit is just a wire that we don't need to keep, merge and + // use the other end as representative bit. + if (a_mode == BitMode::NONE) + connect_directed_merge(a_id, b_id); + else if (b_mode == BitMode::NONE) + connect_directed_merge(b_id, a_id); + // If either bit requires a driven value and has a unique driver, merge + // and use the other end as representative bit. + else if (a_mode == BitMode::DRIVEN_UNIQUE && !(b_mode == BitMode::DRIVEN_UNIQUE || b_mode == BitMode::DRIVEN)) + connect_directed_buffer(a_id, b_id); + else if (b_mode == BitMode::DRIVEN_UNIQUE && !(b_mode == BitMode::DRIVEN_UNIQUE || b_mode == BitMode::DRIVEN)) + connect_directed_buffer(b_id, a_id); + // If either bit only drives a value, store a directed connection from + // it to the other bit. + else if (a_mode == BitMode::DRIVER) + connect_directed_buffer(b_id, a_id); + else if (b_mode == BitMode::DRIVER) + connect_directed_buffer(a_id, b_id); + // Otherwise we store an undirected connection which we will resolve + // during querying. + else + connect_undirected(a_id, b_id); + + return; +} + +// Specialized version that avoids unpacking +void DriverMap::add(SigSpec const &a, SigSpec const &b) +{ + log_assert(a.size() == b.size()); + auto const &a_chunks = a.chunks(); + auto const &b_chunks = b.chunks(); + + auto a_chunk = a_chunks.begin(); + auto a_end = a_chunks.end(); + int a_offset = 0; + + auto b_chunk = b_chunks.begin(); + int b_offset = 0; + + SigChunk tmp_a, tmp_b; + while (a_chunk != a_end) { + int a_width = a_chunk->width - a_offset; + if (a_width == 0) { + a_offset = 0; + ++a_chunk; + continue; + } + int b_width = b_chunk->width - b_offset; + if (b_width == 0) { + b_offset = 0; + ++b_chunk; + continue; + } + int width = std::min(a_width, b_width); + log_assert(width > 0); + + SigChunk const &a_subchunk = + a_offset == 0 && a_width == width ? *a_chunk : a_chunk->extract(a_offset, width); + SigChunk const &b_subchunk = + b_offset == 0 && b_width == width ? *b_chunk : b_chunk->extract(b_offset, width); + + add(a_subchunk, b_subchunk); + + a_offset += width; + b_offset += width; + } +} + +void DriverMap::add_port(Cell *cell, IdString const &port, SigSpec const &b) +{ + int offset = 0; + for (auto const &chunk : b.chunks()) { + add(chunk, DriveChunkPort(cell, port, offset, chunk.width)); + offset += chunk.size(); + } +} + +void DriverMap::orient_undirected(DriveBitId id) +{ + pool &seen = orient_undirected_seen; + pool &drivers = orient_undirected_drivers; + dict &distance = orient_undirected_distance; + seen.clear(); + drivers.clear(); + + seen.emplace(id); + + for (int pos = 0; pos < GetSize(seen); ++pos) { + DriveBitId current = *seen.element(seen.size() - 1 - pos); + DriveBit bit = drive_bit_from_id(current); + + BitMode mode = bit_mode(bit); + + if (mode == BitMode::DRIVER || mode == BitMode::TRISTATE) + drivers.emplace(current); + + if (connected_drivers.contains(current)) + drivers.emplace(current); + + int undirected_driver_count = connected_undirected.count(current); + + for (int i = 0; i != undirected_driver_count; ++i) + seen.emplace(same_driver.find(connected_undirected.at(current, i))); + } + + if (drivers.empty()) + for (auto seen_id : seen) + drivers.emplace(seen_id); + + for (auto driver : drivers) + { + distance.clear(); + distance.emplace(driver, 0); + + for (int pos = 0; pos < GetSize(distance); ++pos) { + auto current_it = distance.element(distance.size() - 1 - pos); + + DriveBitId current = current_it->first; + int undirected_driver_count = connected_undirected.count(current); + + for (int i = 0; i != undirected_driver_count; ++i) + { + DriveBitId next = same_driver.find(connected_undirected.at(current, i)); + auto emplaced = distance.emplace(next, current_it->second + 1); + if (emplaced.first->second == current_it->second + 1) + connected_oriented.add_edge(next, current); + } + } + } + + for (auto seen_id : seen) + oriented_present.emplace(seen_id); +} + +DriveBit DriverMap::operator()(DriveBit const &bit) +{ + if (bit.type() == DriveType::MARKER || bit.type() == DriveType::NONE) + return bit; + if (bit.type() == DriveType::MULTIPLE) + { + DriveBit result; + for (auto const &inner : bit.multiple().multiple()) + result.merge((*this)(inner)); + return result; + } + + DriveBitId bit_id = id_from_drive_bit(bit); + + bit_id = same_driver.find(bit_id); + + DriveBit bit_repr = drive_bit_from_id(bit_id); + + BitMode mode = bit_mode(bit_repr); + + int implicit_driver_count = connected_drivers.count(bit_id); + if (connected_undirected.contains(bit_id) && !oriented_present.count(bit_id)) + orient_undirected(bit_id); + + DriveBit driver; + + if (mode == BitMode::DRIVER || mode == BitMode::TRISTATE) + driver = bit_repr; + + for (int i = 0; i != implicit_driver_count; ++i) + driver.merge(drive_bit_from_id(connected_drivers.at(bit_id, i))); + + int oriented_driver_count = connected_oriented.count(bit_id); + for (int i = 0; i != oriented_driver_count; ++i) + driver.merge(drive_bit_from_id(connected_oriented.at(bit_id, i))); + + return driver; +} + +DriveSpec DriverMap::operator()(DriveSpec spec) +{ + DriveSpec result; + + for (int i = 0, width = spec.size(); i != width; ++i) + result.append((*this)(spec[i])); + + return result; +} + +const char *log_signal(DriveChunkWire const &chunk) +{ + const char *id = log_id(chunk.wire->name); + if (chunk.is_whole()) + return id; + if (chunk.width == 1) + return log_str(stringf("%s [%d]", id, chunk.offset)); + return log_str(stringf("%s [%d:%d]", id, chunk.offset + chunk.width - 1, chunk.offset)); +} + + +const char *log_signal(DriveChunkPort const &chunk) +{ + const char *cell_id = log_id(chunk.cell->name); + const char *port_id = log_id(chunk.port); + if (chunk.is_whole()) + return log_str(stringf("%s <%s>", cell_id, port_id)); + if (chunk.width == 1) + return log_str(stringf("%s <%s> [%d]", cell_id, port_id, chunk.offset)); + return log_str(stringf("%s <%s> [%d:%d]", cell_id, port_id, chunk.offset + chunk.width - 1, chunk.offset)); +} + +const char *log_signal(DriveChunkMarker const &chunk) +{ + if (chunk.width == 1) + return log_str(stringf(" [%d]", chunk.marker, chunk.offset)); + return log_str(stringf(" [%d:%d]", chunk.marker, chunk.offset + chunk.width - 1, chunk.offset)); +} + +const char *log_signal(DriveChunk const &chunk) +{ + switch (chunk.type()) + { + case DriveType::NONE: + return log_str(stringf("", chunk.size())); + case DriveType::CONSTANT: + return log_const(chunk.constant()); + case DriveType::WIRE: + return log_signal(chunk.wire()); + case DriveType::PORT: + return log_signal(chunk.port()); + case DriveType::MARKER: + return log_signal(chunk.marker()); + case DriveType::MULTIPLE: { + std::string str = " + * + * 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 DRIVERTOOLS_H +#define DRIVERTOOLS_H + +#include + +#include "kernel/rtlil.h" +#include "kernel/sigtools.h" +#include "kernel/celltypes.h" + +YOSYS_NAMESPACE_BEGIN + +// TODO move implementation into a .cc file + +struct DriveBit; + +struct DriveChunkWire; +struct DriveChunkPort; +struct DriveChunkMarker; +struct DriveChunk; + +struct DriveSpec; + +const char *log_signal(DriveChunkWire const &chunk); +const char *log_signal(DriveChunkPort const &chunk); +const char *log_signal(DriveChunkMarker const &chunk); +const char *log_signal(DriveChunk const &chunk); +const char *log_signal(DriveSpec const &chunk); + +enum class DriveType : unsigned char +{ + NONE, + CONSTANT, + WIRE, + PORT, + MULTIPLE, + MARKER, +}; + +struct DriveBitWire +{ + Wire *wire; + int offset; + + DriveBitWire(Wire *wire, int offset) : wire(wire), offset(offset) {} + + bool operator==(const DriveBitWire &other) const + { + return wire == other.wire && offset == other.offset; + } + + bool operator<(const DriveBitWire &other) const + { + if (wire != other.wire) + return wire->name < other.wire->name; + return offset < other.offset; + } + + unsigned int hash() const + { + return mkhash_add(wire->name.hash(), offset); + } + + operator SigBit() const + { + return SigBit(wire, offset); + } +}; + +struct DriveBitPort +{ + Cell *cell; + IdString port; + int offset; + + DriveBitPort(Cell *cell, IdString port, int offset) : cell(cell), port(port), offset(offset) {} + + bool operator==(const DriveBitPort &other) const + { + return cell == other.cell && port == other.port && offset == other.offset; + } + + bool operator<(const DriveBitPort &other) const + { + if (cell != other.cell) + return cell->name < other.cell->name; + if (port != other.port) + return port < other.port; + return offset < other.offset; + } + + unsigned int hash() const + { + return mkhash_add(mkhash(cell->name.hash(), port.hash()), offset); + } +}; + + +struct DriveBitMarker +{ + int marker; + int offset; + + DriveBitMarker(int marker, int offset) : marker(marker), offset(offset) {} + + bool operator==(const DriveBitMarker &other) const + { + return marker == other.marker && offset == other.offset; + } + + bool operator<(const DriveBitMarker &other) const + { + if (marker != other.marker) + return marker < other.marker; + return offset < other.offset; + } + + unsigned int hash() const + { + return mkhash_add(marker, offset); + } + +}; + +struct DriveBitMultiple +{ +private: + pool multiple_; + +public: + DriveBitMultiple() {} + DriveBitMultiple(DriveBit const &single) + { + multiple_.emplace(single); + } + + pool const &multiple() const { return multiple_; } + + void merge(DriveBitMultiple const &other) + { + for (DriveBit const &single : other.multiple_) + merge(single); + } + + void merge(DriveBitMultiple &&other) + { + for (DriveBit &single : other.multiple_) + merge(std::move(single)); + } + + void merge(DriveBit const &single); + void merge(DriveBit &&single); + + bool operator==(const DriveBitMultiple &other) const + { + return multiple_ == other.multiple_; + } + + unsigned int hash() const + { + return multiple_.hash(); + } +}; + +struct DriveBit +{ +private: + DriveType type_ = DriveType::NONE; + union + { + State constant_; + DriveBitWire wire_; + DriveBitPort port_; + DriveBitMarker marker_; + DriveBitMultiple multiple_; + }; +public: + DriveBit() {} + + DriveBit(SigBit const &bit); + + DriveBit(DriveBit const &other) { *this = other; } + DriveBit(DriveBit &&other) { *this = other; } + + + DriveBit(State constant) { *this = constant; } + DriveBit(DriveBitWire const &wire) { *this = wire; } + DriveBit(DriveBitWire &&wire) { *this = wire; } + DriveBit(DriveBitPort const &port) { *this = port; } + DriveBit(DriveBitPort &&port) { *this = port; } + DriveBit(DriveBitMarker const &marker) { *this = marker; } + DriveBit(DriveBitMarker &&marker) { *this = marker; } + DriveBit(DriveBitMultiple const &multiple) { *this = multiple; } + DriveBit(DriveBitMultiple &&multiple) { *this = multiple; } + + ~DriveBit() { set_none(); } + + void set_none() + { + switch (type_) + { + case DriveType::NONE: + break; + case DriveType::CONSTANT: + break; + case DriveType::WIRE: + wire_.~DriveBitWire(); + break; + case DriveType::PORT: + port_.~DriveBitPort(); + break; + case DriveType::MARKER: + marker_.~DriveBitMarker(); + break; + case DriveType::MULTIPLE: + multiple_.~DriveBitMultiple(); + break; + } + type_ = DriveType::NONE; + } + + DriveBit &operator=(DriveBit const &other) + { + switch (other.type_) + { + case DriveType::NONE: + set_none(); + break; + case DriveType::CONSTANT: + *this = other.constant_; + break; + case DriveType::WIRE: + *this = other.wire_; + break; + case DriveType::PORT: + *this = other.port_; + break; + case DriveType::MARKER: + *this = other.marker_; + break; + case DriveType::MULTIPLE: + *this = other.multiple_; + break; + } + return *this; + } + + DriveBit &operator=(DriveBit &&other) + { + switch (other.type_) + { + case DriveType::NONE: + set_none(); + break; + case DriveType::CONSTANT: + *this = std::move(other.constant_); + break; + case DriveType::WIRE: + *this = std::move(other.wire_); + break; + case DriveType::PORT: + *this = std::move(other.port_); + break; + case DriveType::MARKER: + *this = std::move(other.marker_); + break; + case DriveType::MULTIPLE: + *this = std::move(other.multiple_); + break; + } + return *this; + } + + DriveBit &operator=(State constant) + { + set_none(); + constant_ = constant; + type_ = DriveType::CONSTANT; + return *this; + } + + DriveBit &operator=(DriveBitWire const &wire) + { + set_none(); + new (&wire_) DriveBitWire(wire); + type_ = DriveType::WIRE; + return *this; + } + + DriveBit &operator=(DriveBitWire &&wire) + { + set_none(); + new (&wire_) DriveBitWire(wire); + type_ = DriveType::WIRE; + return *this; + } + + DriveBit &operator=(DriveBitPort const &port) + { + set_none(); + new (&port_) DriveBitPort(port); + type_ = DriveType::PORT; + return *this; + } + + DriveBit &operator=(DriveBitPort &&port) + { + set_none(); + new (&port_) DriveBitPort(port); + type_ = DriveType::PORT; + return *this; + } + + DriveBit &operator=(DriveBitMarker const &marker) + { + set_none(); + new (&marker_) DriveBitMarker(marker); + type_ = DriveType::MARKER; + return *this; + } + + DriveBit &operator=(DriveBitMarker &&marker) + { + set_none(); + new (&marker_) DriveBitMarker(marker); + type_ = DriveType::MARKER; + return *this; + } + + DriveBit &operator=(DriveBitMultiple const &multiple) + { + set_none(); + if (multiple.multiple().empty()) + return *this; + new (&multiple_) DriveBitMultiple(multiple); + type_ = DriveType::MULTIPLE; + return *this; + } + + DriveBit &operator=(DriveBitMultiple &&multiple) + { + set_none(); + if (multiple.multiple().empty()) + return *this; + new (&multiple_) DriveBitMultiple(multiple); + type_ = DriveType::MULTIPLE; + return *this; + } + + unsigned int hash() const + { + unsigned int inner; + switch (type_) + { + case DriveType::NONE: + inner = 0; + break; + case DriveType::CONSTANT: + inner = constant_; + break; + case DriveType::WIRE: + inner = wire_.hash(); + break; + case DriveType::PORT: + inner = port_.hash(); + break; + case DriveType::MARKER: + inner = marker_.hash(); + break; + case DriveType::MULTIPLE: + inner = multiple_.hash(); + break; + } + return mkhash((unsigned int)type_, inner); + } + + bool operator==(const DriveBit &other) const + { + if (type_ != other.type_) + return false; + + switch (type_) + { + case DriveType::NONE: + return true; + case DriveType::CONSTANT: + return constant_ == other.constant_; + case DriveType::WIRE: + return wire_ == other.wire_; + case DriveType::PORT: + return port_ == other.port_; + case DriveType::MARKER: + return marker_ == other.marker_; + case DriveType::MULTIPLE: + return multiple_ == other.multiple_; + } + log_assert(false); + } + + bool operator!=(const DriveBit &other) const + { + return !(*this == other); + } + + bool operator<(const DriveBit &other) const + { + if (type_ != other.type_) + return type_ < other.type_; + switch (type_) + { + case DriveType::NONE: + return false; + case DriveType::CONSTANT: + return constant_ < other.constant_; + case DriveType::WIRE: + return wire_ < other.wire_; + case DriveType::PORT: + return port_ < other.port_; + case DriveType::MARKER: + return marker_ < other.marker_; + case DriveType::MULTIPLE: + log_assert(!"TODO"); + } + log_abort(); + } + + + DriveType type() const { return type_; } + + bool is_none() const { return type_ == DriveType::NONE; } + bool is_constant() const { return type_ == DriveType::CONSTANT; } + bool is_wire() const { return type_ == DriveType::WIRE; } + bool is_port() const { return type_ == DriveType::PORT; } + bool is_marker() const { return type_ == DriveType::MARKER; } + bool is_multiple() const { return type_ == DriveType::MULTIPLE; } + + State &constant() { log_assert(is_constant()); return constant_; } + State const &constant() const { log_assert(is_constant()); return constant_; } + DriveBitWire &wire() { log_assert(is_wire()); return wire_; } + DriveBitWire const &wire() const { log_assert(is_wire()); return wire_; } + DriveBitPort &port() { log_assert(is_port()); return port_; } + DriveBitPort const &port() const { log_assert(is_port()); return port_; } + DriveBitMarker &marker() { log_assert(is_marker()); return marker_; } + DriveBitMarker const &marker() const { log_assert(is_marker()); return marker_; } + DriveBitMultiple &multiple() { log_assert(is_multiple()); return multiple_; } + DriveBitMultiple const &multiple() const { log_assert(is_multiple()); return multiple_; } + + void merge(DriveBit const &other); + +}; + + +struct DriveChunkWire +{ + Wire *wire; + int offset; + int width; + + DriveChunkWire(Wire *wire, int offset, int width) : wire(wire), offset(offset), width(width) {} + DriveChunkWire(DriveBitWire const &bit) : wire(bit.wire), offset(bit.offset), width(1) {} + + int size() const { return width; } + + DriveBitWire operator[](int i) const + { + log_assert(i >= 0 && i < width); + return DriveBitWire(wire, offset + i); + } + + bool can_append(DriveBitWire const &bit) const; + bool try_append(DriveBitWire const &bit); + bool try_append(DriveChunkWire const &chunk); + + // Whether this chunk is a whole wire + bool is_whole() const { return offset == 0 && width == wire->width; } + + bool operator==(const DriveChunkWire &other) const + { + return wire == other.wire && offset == other.offset && width == other.width; + } + + bool operator<(const DriveChunkWire &other) const + { + if (wire != other.wire) + return wire->name < other.wire->name; + if (width != other.width) + return width < other.width; + return offset < other.offset; + } + + unsigned int hash() const + { + return mkhash_add(mkhash(wire->name.hash(), width), offset); + } + + explicit operator SigChunk() const + { + return SigChunk(wire, offset, width); + } +}; + +struct DriveChunkPort +{ + Cell *cell; + IdString port; + int offset; + int width; + + DriveChunkPort(Cell *cell, IdString port, int offset, int width) : + cell(cell), port(port), offset(offset), width(width) { } + DriveChunkPort(Cell *cell, IdString port) : + cell(cell), port(port), offset(0), width(GetSize(cell->connections().at(port))) { } + DriveChunkPort(Cell *cell, std::pair const &conn) : + cell(cell), port(conn.first), offset(0), width(GetSize(conn.second)) { } + DriveChunkPort(DriveBitPort const &bit) : + cell(bit.cell), port(bit.port), offset(bit.offset), width(1) { } + + int size() const { return width; } + + DriveBitPort operator[](int i) const + { + log_assert(i >= 0 && i < width); + return DriveBitPort(cell, port, offset + i); + } + + bool can_append(DriveBitPort const &bit) const; + bool try_append(DriveBitPort const &bit); + bool try_append(DriveChunkPort const &chunk); + + // Whether this chunk is a whole port + bool is_whole() const + { + return offset == 0 && width == cell->connections().at(port).size(); + } + + bool operator==(const DriveChunkPort &other) const + { + return cell == other.cell && port == other.port && offset == other.offset && width == other.width; + } + + bool operator<(const DriveChunkPort &other) const + { + if (cell != other.cell) + return cell->name < other.cell->name; + if (port != other.port) + return port < other.port; + if (width != other.width) + return width < other.width; + return offset < other.offset; + } + + unsigned int hash() const + { + return mkhash_add(mkhash(mkhash(cell->name.hash(), port.hash()), width), offset); + } +}; + + +struct DriveChunkMarker +{ + int marker; + int offset; + int width; + + DriveChunkMarker(int marker, int offset, int width) : + marker(marker), offset(offset), width(width) {} + DriveChunkMarker(DriveBitMarker const &bit) : + marker(bit.marker), offset(bit.offset), width(1) {} + + int size() const { return width; } + + DriveBitMarker operator[](int i) const + { + log_assert(i >= 0 && i < width); + return DriveBitMarker(marker, offset + i); + } + + bool can_append(DriveBitMarker const &bit) const; + bool try_append(DriveBitMarker const &bit); + bool try_append(DriveChunkMarker const &chunk); + + bool operator==(const DriveChunkMarker &other) const + { + return marker == other.marker && offset == other.offset && width == other.width; + } + + bool operator<(const DriveChunkMarker &other) const + { + if (marker != other.marker) + return marker < other.marker; + if (width != other.width) + return width < other.width; + return offset < other.offset; + } + + unsigned int hash() const + { + return mkhash_add(mkhash(marker, width), offset); + } +}; + +struct DriveChunkMultiple +{ +private: + mutable pool multiple_; + int width_; + +public: + pool const &multiple() const { return multiple_; } + + DriveChunkMultiple(DriveBitMultiple const &bit) : width_(1) { + for (auto const &bit : bit.multiple()) + multiple_.emplace(bit); + } + + int size() const { return width_; } + + DriveBitMultiple operator[](int i) const; + + bool can_append(DriveBitMultiple const &bit) const; + + bool try_append(DriveBitMultiple const &bit); + + + bool can_append(DriveChunkMultiple const &bit) const; + + bool try_append(DriveChunkMultiple const &bit); + + bool operator==(const DriveChunkMultiple &other) const + { + return width_ == other.width_ && multiple_ == other.multiple_; + } + + bool operator<(const DriveChunkMultiple &other) const + { + if (multiple_.size() < other.multiple_.size()) + + multiple_.sort(); + return false; // TODO implement, canonicalize order + } + + unsigned int hash() const + { + return mkhash(width_, multiple_.hash()); + } +}; + +struct DriveChunk +{ +private: + DriveType type_ = DriveType::NONE; + union + { + int none_; + Const constant_; + DriveChunkWire wire_; + DriveChunkPort port_; + DriveChunkMarker marker_; + DriveChunkMultiple multiple_; + }; + +public: + DriveChunk() { set_none(); } + + DriveChunk(DriveChunk const &other) { *this = other; } + DriveChunk(DriveChunk &&other) { *this = other; } + + DriveChunk(DriveBit const &other) { *this = other; } + + DriveChunk(Const const &constant) { *this = constant; } + DriveChunk(Const &&constant) { *this = constant; } + DriveChunk(DriveChunkWire const &wire) { *this = wire; } + DriveChunk(DriveChunkWire &&wire) { *this = wire; } + DriveChunk(DriveChunkPort const &port) { *this = port; } + DriveChunk(DriveChunkPort &&port) { *this = port; } + DriveChunk(DriveChunkMarker const &marker) { *this = marker; } + DriveChunk(DriveChunkMarker &&marker) { *this = marker; } + DriveChunk(DriveChunkMultiple const &multiple) { *this = multiple; } + DriveChunk(DriveChunkMultiple &&multiple) { *this = multiple; } + + ~DriveChunk() { set_none(); } + + DriveBit operator[](int i) const + { + switch (type_) + { + case DriveType::NONE: + return DriveBit(); + case DriveType::CONSTANT: + return constant_[i]; + case DriveType::WIRE: + return wire_[i]; + case DriveType::PORT: + return port_[i]; + case DriveType::MARKER: + return marker_[i]; + case DriveType::MULTIPLE: + return multiple_[i]; + } + log_abort(); + } + + void set_none(int width = 0) + { + switch (type_) + { + case DriveType::NONE: + none_ = width; + break; + case DriveType::CONSTANT: + constant_.~Const(); + break; + case DriveType::WIRE: + wire_.~DriveChunkWire(); + break; + case DriveType::PORT: + port_.~DriveChunkPort(); + break; + case DriveType::MARKER: + marker_.~DriveChunkMarker(); + break; + case DriveType::MULTIPLE: + multiple_.~DriveChunkMultiple(); + break; + } + type_ = DriveType::NONE; + none_ = width; + } + + DriveChunk &operator=(DriveChunk const &other) + { + switch (other.type_) + { + case DriveType::NONE: + set_none(other.none_); + break; + case DriveType::CONSTANT: + *this = other.constant_; + break; + case DriveType::WIRE: + *this = other.wire_; + break; + case DriveType::PORT: + *this = other.port_; + break; + case DriveType::MARKER: + *this = other.marker_; + break; + case DriveType::MULTIPLE: + *this = other.multiple_; + break; + } + return *this; + } + + DriveChunk &operator=(DriveChunk &&other) + { + switch (other.type_) + { + case DriveType::NONE: + set_none(other.none_); + break; + case DriveType::CONSTANT: + *this = std::move(other.constant_); + break; + case DriveType::WIRE: + *this = std::move(other.wire_); + break; + case DriveType::PORT: + *this = std::move(other.port_); + break; + case DriveType::MARKER: + *this = std::move(other.marker_); + break; + case DriveType::MULTIPLE: + *this = std::move(other.multiple_); + break; + } + return *this; + } + + DriveChunk &operator=(Const const &constant) + { + set_none(); + new (&constant_) Const(constant); + type_ = DriveType::CONSTANT; + return *this; + } + + DriveChunk &operator=(Const &&constant) + { + set_none(); + new (&constant_) Const(std::move(constant)); + type_ = DriveType::CONSTANT; + return *this; + } + + DriveChunk &operator=(DriveChunkWire const &wire) + { + set_none(); + new (&wire_) DriveChunkWire(wire); + type_ = DriveType::WIRE; + return *this; + } + + DriveChunk &operator=(DriveChunkWire &&wire) + { + set_none(); + new (&wire_) DriveChunkWire(wire); + type_ = DriveType::WIRE; + return *this; + } + + DriveChunk &operator=(DriveChunkPort const &port) + { + set_none(); + new (&port_) DriveChunkPort(port); + type_ = DriveType::PORT; + return *this; + } + + DriveChunk &operator=(DriveChunkPort &&port) + { + set_none(); + new (&port_) DriveChunkPort(port); + type_ = DriveType::PORT; + return *this; + } + + DriveChunk &operator=(DriveChunkMarker const &marker) + { + set_none(); + new (&marker_) DriveChunkMarker(marker); + type_ = DriveType::MARKER; + return *this; + } + + DriveChunk &operator=(DriveChunkMarker &&marker) + { + set_none(); + new (&marker_) DriveChunkMarker(marker); + type_ = DriveType::MARKER; + return *this; + } + + DriveChunk &operator=(DriveChunkMultiple const &multiple) + { + set_none(multiple.size()); + if (multiple.multiple().empty()) + return *this; + new (&multiple_) DriveChunkMultiple(multiple); + type_ = DriveType::MULTIPLE; + return *this; + } + + DriveChunk &operator=(DriveChunkMultiple &&multiple) + { + set_none(multiple.size()); + if (multiple.multiple().empty()) + return *this; + new (&multiple_) DriveChunkMultiple(multiple); + type_ = DriveType::MULTIPLE; + return *this; + } + + DriveChunk &operator=(DriveBit const &other) + { + switch (other.type()) + { + case DriveType::NONE: + set_none(1); + break; + case DriveType::CONSTANT: + *this = Const(other.constant()); + break; + case DriveType::WIRE: + *this = DriveChunkWire(other.wire()); + break; + case DriveType::PORT: + *this = DriveChunkPort(other.port()); + break; + case DriveType::MARKER: + *this = DriveChunkMarker(other.marker()); + break; + case DriveType::MULTIPLE: + *this = DriveChunkMultiple(other.multiple()); + break; + } + return *this; + } + + bool can_append(DriveBit const &bit) const; + bool try_append(DriveBit const &bit); + bool try_append(DriveChunk const &chunk); + + unsigned int hash() const + { + unsigned int inner; + switch (type_) + { + case DriveType::NONE: + inner = 0; + break; + case DriveType::CONSTANT: + inner = constant_.hash(); + break; + case DriveType::WIRE: + inner = wire_.hash(); + break; + case DriveType::PORT: + inner = port_.hash(); + break; + case DriveType::MARKER: + inner = marker_.hash(); + break; + case DriveType::MULTIPLE: + inner = multiple_.hash(); + break; + } + return mkhash((unsigned int)type_, inner); + } + + bool operator==(const DriveChunk &other) const + { + if (type_ != other.type_) + return false; + + switch (type_) + { + case DriveType::NONE: + return true; + case DriveType::CONSTANT: + return constant_ == other.constant_; + case DriveType::WIRE: + return wire_ == other.wire_; + case DriveType::PORT: + return port_ == other.port_; + case DriveType::MARKER: + return marker_ == other.marker_; + case DriveType::MULTIPLE: + return multiple_ == other.multiple_; + } + log_assert(false); + } + + bool operator!=(const DriveChunk &other) const + { + return !(*this == other); + } + + bool operator<(const DriveChunk &other) const + { + if (type_ != other.type_) + return type_ < other.type_; + switch (type_) + { + case DriveType::NONE: + return false; + case DriveType::CONSTANT: + return constant_ < other.constant_; + case DriveType::WIRE: + return wire_ < other.wire_; + case DriveType::PORT: + return port_ < other.port_; + case DriveType::MARKER: + return marker_ < other.marker_; + case DriveType::MULTIPLE: + return multiple_ < other.multiple_; + } + log_assert(false); + } + + DriveType type() const { return type_; } + + bool is_none() const { return type_ == DriveType::NONE; } + bool is_constant() const { return type_ == DriveType::CONSTANT; } + bool is_wire() const { return type_ == DriveType::WIRE; } + bool is_port() const { return type_ == DriveType::PORT; } + bool is_marker() const { return type_ == DriveType::MARKER; } + bool is_multiple() const { return type_ == DriveType::MULTIPLE; } + + Const &constant() { log_assert(is_constant()); return constant_; } + Const const &constant() const { log_assert(is_constant()); return constant_; } + DriveChunkWire &wire() { log_assert(is_wire()); return wire_; } + DriveChunkWire const &wire() const { log_assert(is_wire()); return wire_; } + DriveChunkPort &port() { log_assert(is_port()); return port_; } + DriveChunkPort const &port() const { log_assert(is_port()); return port_; } + DriveChunkMarker &marker() { log_assert(is_marker()); return marker_; } + DriveChunkMarker const &marker() const { log_assert(is_marker()); return marker_; } + DriveChunkMultiple &multiple() { log_assert(is_multiple()); return multiple_; } + DriveChunkMultiple const &multiple() const { log_assert(is_multiple()); return multiple_; } + + + int size() const + { + switch (type_) + { + case DriveType::NONE: + return none_; + case DriveType::CONSTANT: + return constant_.size(); + case DriveType::WIRE: + return wire_.size(); + case DriveType::PORT: + return port_.size(); + case DriveType::MARKER: + return marker_.size(); + case DriveType::MULTIPLE: + return multiple_.size(); + } + } +}; + +struct DriveSpec +{ +private: + int width_ = 0; + mutable std::vector chunks_; + mutable std::vector bits_; + mutable unsigned int hash_ = 0; +public: + + inline bool packed() const { + return bits_.empty(); + } + + DriveSpec() {} + + DriveSpec(DriveChunk const &chunk) { *this = chunk; } + DriveSpec(DriveChunkWire const &chunk) { *this = chunk; } + DriveSpec(DriveChunkPort const &chunk) { *this = chunk; } + DriveSpec(DriveChunkMarker const &chunk) { *this = chunk; } + DriveSpec(DriveChunkMultiple const &chunk) { *this = chunk; } + + DriveSpec(DriveBit const &bit) { *this = bit; } + DriveSpec(DriveBitWire const &bit) { *this = bit; } + DriveSpec(DriveBitPort const &bit) { *this = bit; } + DriveSpec(DriveBitMarker const &bit) { *this = bit; } + DriveSpec(DriveBitMultiple const &bit) { *this = bit; } + + DriveSpec(std::vector const &chunks) : chunks_(chunks) { compute_width(); } + + DriveSpec(std::vector const &bits) + { + for (auto const &bit : bits) + append(bit); + } + + std::vector const &chunks() const { pack(); return chunks_; } + std::vector const &bits() const { unpack(); return bits_; } + + int size() const { return width_; } + + void append(DriveBit const &bit); + + void append(DriveChunk const &chunk); + + void pack() const; + + void unpack() const; + + DriveBit &operator[](int index) + { + log_assert(index >= 0 && index < size()); + unpack(); + return bits_[index]; + } + + const DriveBit &operator[](int index) const + { + log_assert(index >= 0 && index < size()); + unpack(); + return bits_[index]; + } + + void clear() + { + chunks_.clear(); + bits_.clear(); + width_ = 0; + } + + DriveSpec &operator=(DriveChunk const &chunk) + { + chunks_.clear(); + bits_.clear(); + append(chunk); + return *this; + } + + DriveSpec &operator=(DriveChunkWire const &chunk) { return *this = DriveChunk(chunk); } + DriveSpec &operator=(DriveChunkPort const &chunk) { return *this = DriveChunk(chunk); } + DriveSpec &operator=(DriveChunkMarker const &chunk) { return *this = DriveChunk(chunk); } + DriveSpec &operator=(DriveChunkMultiple const &chunk) { return *this = DriveChunk(chunk); } + + DriveSpec &operator=(DriveBit const &bit) + { + chunks_.clear(); + bits_.clear(); + append(bit); + return *this; + } + + DriveSpec &operator=(DriveBitWire const &bit) { return *this = DriveBit(bit); } + DriveSpec &operator=(DriveBitPort const &bit) { return *this = DriveBit(bit); } + DriveSpec &operator=(DriveBitMarker const &bit) { return *this = DriveBit(bit); } + DriveSpec &operator=(DriveBitMultiple const &bit) { return *this = DriveBit(bit); } + + unsigned int hash() const { + if (hash_ != 0) return hash_; + + pack(); + hash_ = hash_ops>().hash(chunks_); + hash_ |= (hash_ == 0); + return hash_; + } + + bool operator==(DriveSpec const &other) const { + if (size() != other.size() || hash() != other.hash()) + return false; + return chunks() == other.chunks(); + } + +private: + void compute_width(); +}; + + + +struct DriverMap +{ + CellTypes celltypes; + + DriverMap() { celltypes.setup(); } + DriverMap(Design *design) { celltypes.setup(); celltypes.setup_design(design); } + +private: + + // Internally we represent all DriveBits by mapping them to DriveBitIds + // which use less memory and are cheaper to compare. + struct DriveBitId + { + int id = -1; + + DriveBitId() {}; + + DriveBitId(int id) : id(id) { } + + bool operator==(const DriveBitId &other) const { return id == other.id; } + bool operator<(const DriveBitId &other) const { return id < other.id; } + unsigned int hash() const { return id; } + }; + // Essentially a dict> but using less memory + // and fewer allocations + struct DriveBitGraph { + dict first_edges; + dict second_edges; + dict> more_edges; + + void add_edge(DriveBitId src, DriveBitId dst); + DriveBitId pop_edge(DriveBitId src); + void clear(DriveBitId src); + bool contains(DriveBitId src); + int count(DriveBitId src); + + DriveBitId at(DriveBitId src, int index); + }; + + // The following two maps maintain a sparse DriveBit to DriveBitId mapping. + // This saves a lot of memory compared to a `dict` or + // `idict`. + + // Maps wires to the first DriveBitId of the consecutive range used for + // that wire. + dict wire_offsets; + + // Maps cell ports to a the first DriveBitId of the consecutive range used + // for that cell port. + dict, DriveBitId> port_offsets; + + // For the inverse map that maps DriveBitIds back to DriveBits we use a + // sorted map containing only the first DriveBit for each wire and cell + // port. + std::map drive_bits; + + // As a memory optimization for gate level net lists we store single-bit + // wires and cell ports in a `dict` which requires less memory and fewer + // allocations than `std::map` but doesn't support the kind of lookups we + // need for a sparse coarse grained mapping. + dict isolated_drive_bits; + + // Used for allocating DriveBitIds, none and constant states use a fixewd + // mapping to the first few ids, which we need to skip. + int next_offset = 1 + (int)State::Sm; + + // Union-Find over directly connected bits that share the same single + // driver or are undriven. We never merge connections between drivers + // and/or kept wires. + mfp same_driver; + + // For each bit, store a set of connected driver bits for which the + // explicit connection should be preserved and the driving direction is + // locally unambiguous (one side only drives or requires a driven value). + DriveBitGraph connected_drivers; + + // For each bit, store a set of connected driver bits for which the + // explicit connection should be preserved and the driving direction is + // locally ambiguous. Every such ambiguous connection is also present in + // the reverse direction and has to be resolved when querying drivers. + DriveBitGraph connected_undirected; + + // Subset of `connected_undirected` for caching the resolved driving + // direction. In case multiple drivers are present this can still contain + // both orientations of a single connection, but for a single driver only + // one will be present. + DriveBitGraph connected_oriented; + + // Stores for which bits we already resolved the orientation (cached in + // `connected_oriented`). + pool oriented_present; + + + enum class BitMode { + NONE = 0, // Not driven, no need to keep wire + DRIVEN = 1, // Not driven, uses a value driven elsewhere + DRIVEN_UNIQUE = 2, // Uses a value driven elsewhere, has at most one direct connection + KEEP = 3, // Wire that should be kept + TRISTATE = 4, // Can drive a value but can also use a value driven elsewhere + DRIVER = 5, // Drives a value + }; + + BitMode bit_mode(DriveBit const &bit); + DriveBitId id_from_drive_bit(DriveBit const &bit); + DriveBit drive_bit_from_id(DriveBitId id); + + void connect_directed_merge(DriveBitId driven_id, DriveBitId driver_id); + void connect_directed_buffer(DriveBitId driven_id, DriveBitId driver_id); + void connect_undirected(DriveBitId a_id, DriveBitId b_id); + +public: + + void add(Module *module); + + // Add a single bit connection to the driver map. + void add(DriveBit const &a, DriveBit const &b); + + template + static constexpr bool is_sig_type() { + return + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value; + } + + // We use the enable_if to produce better compiler errors when unsupported + // types are used + template + typename std::enable_if() && is_sig_type()>::type + add(T const &a, U const &b) + { + log_assert(a.size() == b.size()); + for (int i = 0; i != GetSize(a); ++i) + add(DriveBit(a[i]), DriveBit(b[i])); + } + + + // Specialized version that avoids unpacking + void add(SigSpec const &a, SigSpec const &b); + +private: + void add_port(Cell *cell, IdString const &port, SigSpec const &b); + + // Only used a local variables in `orient_undirected`, always cleared, only + // stored to reduce allocations. + pool orient_undirected_seen; + pool orient_undirected_drivers; + dict orient_undirected_distance; + + void orient_undirected(DriveBitId id); + +public: + DriveBit operator()(DriveBit const &bit); + + DriveSpec operator()(DriveSpec spec); + +private: + bool keep_wire(Wire *wire) { + // TODO configurable + return wire->has_attribute(ID(keep)); + } +}; + +YOSYS_NAMESPACE_END + +#endif From 68c3a47945972a685f65620eef73f10c86b87741 Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Mon, 15 Apr 2024 14:14:50 +0200 Subject: [PATCH 05/92] WIP temporary drivertools example --- passes/cmds/Makefile.inc | 1 + passes/cmds/example_dt.cc | 140 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 passes/cmds/example_dt.cc diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc index 33e3ae1bc5a..0918a07df0f 100644 --- a/passes/cmds/Makefile.inc +++ b/passes/cmds/Makefile.inc @@ -49,3 +49,4 @@ OBJS += passes/cmds/xprop.o OBJS += passes/cmds/dft_tag.o OBJS += passes/cmds/future.o OBJS += passes/cmds/box_derive.o +OBJS += passes/cmds/example_dt.o diff --git a/passes/cmds/example_dt.cc b/passes/cmds/example_dt.cc new file mode 100644 index 00000000000..de84fa3cda8 --- /dev/null +++ b/passes/cmds/example_dt.cc @@ -0,0 +1,140 @@ +#include "kernel/yosys.h" +#include "kernel/drivertools.h" +#include "kernel/topo_scc.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + + + + +struct ExampleWorker +{ + DriverMap dm; + Module *module; + + ExampleWorker(Module *module) : module(module) { + dm.celltypes.setup(); + } +}; + +struct ExampleDtPass : public Pass +{ + ExampleDtPass() : Pass("example_dt", "drivertools example") {} + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + } + + + void execute(std::vector args, RTLIL::Design *design) override + { + size_t argidx = 1; + extra_args(args, argidx, design); + + for (auto module : design->selected_modules()) { + ExampleWorker worker(module); + DriverMap dm; + + dm.add(module); + + idict queue; + idict cells; + + IntGraph edges; + + + for (auto cell : module->cells()) { + if (cell->type.in(ID($assert), ID($assume), ID($cover), ID($check))) + queue(DriveBitMarker(cells(cell), 0)); + } + + for (auto wire : module->wires()) { + if (!wire->port_output) + continue; + queue(DriveChunk(DriveChunkWire(wire, 0, wire->width))); + } + +#define emit log +// #define emit(X...) do {} while (false) + + for (int i = 0; i != GetSize(queue); ++i) + { + emit("n%d: ", i); + DriveSpec spec = queue[i]; + if (spec.chunks().size() > 1) { + emit("concat %s <-\n", log_signal(spec)); + for (auto const &chunk : spec.chunks()) { + emit(" * %s\n", log_signal(chunk)); + edges.add_edge(i, queue(chunk)); + } + } else if (spec.chunks().size() == 1) { + DriveChunk chunk = spec.chunks()[0]; + if (chunk.is_wire()) { + DriveChunkWire wire_chunk = chunk.wire(); + if (wire_chunk.is_whole()) { + if (wire_chunk.wire->port_input) { + emit("input %s\n", log_signal(spec)); + } else { + DriveSpec driver = dm(DriveSpec(wire_chunk)); + edges.add_edge(i, queue(driver)); + emit("wire driver %s <- %s\n", log_signal(spec), log_signal(driver)); + } + } else { + DriveChunkWire whole_wire(wire_chunk.wire, 0, wire_chunk.width); + edges.add_edge(i, queue(whole_wire)); + emit("wire slice %s <- %s\n", log_signal(spec), log_signal(DriveSpec(whole_wire))); + } + } else if (chunk.is_port()) { + DriveChunkPort port_chunk = chunk.port(); + if (port_chunk.is_whole()) { + if (dm.celltypes.cell_output(port_chunk.cell->type, port_chunk.port)) { + int cell_marker = queue(DriveBitMarker(cells(port_chunk.cell), 0)); + if (!port_chunk.cell->type.in(ID($dff), ID($ff))) + edges.add_edge(i, cell_marker); + emit("cell output %s %s\n", log_id(port_chunk.cell), log_id(port_chunk.port)); + } else { + DriveSpec driver = dm(DriveSpec(port_chunk)); + edges.add_edge(i, queue(driver)); + emit("cell port driver %s <- %s\n", log_signal(spec), log_signal(driver)); + } + + } else { + DriveChunkPort whole_port(port_chunk.cell, port_chunk.port, 0, GetSize(port_chunk.cell->connections().at(port_chunk.port))); + edges.add_edge(i, queue(whole_port)); + emit("port slice %s <- %s\n", log_signal(spec), log_signal(DriveSpec(whole_port))); + } + } else if (chunk.is_constant()) { + emit("constant %s <- %s\n", log_signal(spec), log_const(chunk.constant())); + } else if (chunk.is_marker()) { + Cell *cell = cells[chunk.marker().marker]; + emit("cell %s %s\n", log_id(cell->type), log_id(cell)); + for (auto const &conn : cell->connections()) { + if (!dm.celltypes.cell_input(cell->type, conn.first)) + continue; + emit(" * %s <- %s\n", log_id(conn.first), log_signal(conn.second)); + edges.add_edge(i, queue(DriveChunkPort(cell, conn))); + } + } else { + log_abort(); + } + } else { + log_abort(); + } + } + + topo_sorted_sccs(edges, [&](int *begin, int *end) { + emit("scc:"); + for (int *i = begin; i != end; ++i) + emit(" n%d", *i); + emit("\n"); + }); + + } + log("Plugin test passed!\n"); + } +} ExampleDtPass; + +PRIVATE_NAMESPACE_END From f29422f745a534d09cb0917a8587639ac5b08e10 Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Thu, 11 Apr 2024 13:47:14 +0200 Subject: [PATCH 06/92] topo_scc: Add sources_first option --- kernel/topo_scc.h | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/kernel/topo_scc.h b/kernel/topo_scc.h index 0ae0e696b47..89df8928aa2 100644 --- a/kernel/topo_scc.h +++ b/kernel/topo_scc.h @@ -194,7 +194,7 @@ class IntGraph { }; template -void topo_sorted_sccs(G &graph, ComponentCallback component) +void topo_sorted_sccs(G &graph, ComponentCallback component, bool sources_first = false) { typedef typename G::node_enumerator node_enumerator; typedef typename G::successor_enumerator successor_enumerator; @@ -217,6 +217,23 @@ void topo_sorted_sccs(G &graph, ComponentCallback component) node_enumerator nodes = graph.enumerate_nodes(); + if (sources_first) { + while (!nodes.finished()) { + node_type node = nodes.next(); + successor_enumerator successors = graph.enumerate_successors(node); + if (successors.finished()) + { + graph.dfs_index(node) = next_index; + next_index++; + component_stack.push_back(node); + component(component_stack.data(), component_stack.data() + 1); + component_stack.clear(); + graph.dfs_index(node) = INT_MAX; + } + } + nodes = graph.enumerate_nodes(); + } + // iterate over all nodes to ensure we process the whole graph while (!nodes.finished()) { node_type node = nodes.next(); From d4e3daa9d0037e2397dc74e3862b5b41c67716e5 Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Thu, 11 Apr 2024 13:48:25 +0200 Subject: [PATCH 07/92] ComputeGraph datatype for the upcoming functional backend --- kernel/functional.h | 369 ++++++++++++++++++++++++++++++++++++++ passes/cmds/example_dt.cc | 178 ++++++++++++++---- 2 files changed, 515 insertions(+), 32 deletions(-) create mode 100644 kernel/functional.h diff --git a/kernel/functional.h b/kernel/functional.h new file mode 100644 index 00000000000..e5ee8824099 --- /dev/null +++ b/kernel/functional.h @@ -0,0 +1,369 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Jannis Harder + * + * 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 FUNCTIONAL_H +#define FUNCTIONAL_H + +#include +#include "kernel/yosys.h" + +YOSYS_NAMESPACE_BEGIN + +template< + typename Fn, // Function type (deduplicated across whole graph) + typename Attr = std::tuple<>, // Call attributes (present in every node) + typename SparseAttr = std::tuple<>, // Sparse call attributes (optional per node) + typename Key = std::tuple<> // Stable keys to refer to nodes +> +struct ComputeGraph +{ + struct Ref; +private: + + // Functions are deduplicated by assigning unique ids + idict functions; + + struct Node { + int fn_index; + int arg_offset; + int arg_count; + Attr attr; + + Node(int fn_index, Attr &&attr, int arg_offset, int arg_count = 0) + : fn_index(fn_index), arg_offset(arg_offset), arg_count(arg_count), attr(std::move(attr)) {} + + Node(int fn_index, Attr const &attr, int arg_offset, int arg_count = 0) + : fn_index(fn_index), arg_offset(arg_offset), arg_count(arg_count), attr(attr) {} + }; + + + std::vector nodes; + std::vector args; + dict keys_; + dict sparse_attrs; + +public: + template + struct BaseRef + { + protected: + friend struct ComputeGraph; + Graph *graph_; + int index_; + BaseRef(Graph *graph, int index) : graph_(graph), index_(index) { + log_assert(index_ >= 0); + check(); + } + + void check() const { log_assert(index_ < graph_->size()); } + + Node const &deref() const { check(); return graph_->nodes[index_]; } + + public: + ComputeGraph const &graph() const { return graph_; } + int index() const { return index_; } + + int size() const { return deref().arg_count; } + + BaseRef arg(int n) const + { + Node const &node = deref(); + log_assert(n >= 0 && n < node.arg_count); + return BaseRef(graph_, graph_->args[node.arg_offset + n]); + } + + std::vector::const_iterator arg_indices_cbegin() const + { + Node const &node = deref(); + return graph_->args.cbegin() + node.arg_offset; + } + + std::vector::const_iterator arg_indices_cend() const + { + Node const &node = deref(); + return graph_->args.cbegin() + node.arg_offset + node.arg_count; + } + + Fn const &function() const { return graph_->functions[deref().fn_index]; } + Attr const &attr() const { return deref().attr; } + + bool has_sparse_attr() const { return graph_->sparse_attrs.count(index_); } + + SparseAttr const &sparse_attr() const + { + auto found = graph_->sparse_attrs.find(index_); + log_assert(found != graph_->sparse_attrs.end()); + return *found; + } + }; + + using ConstRef = BaseRef; + + struct Ref : public BaseRef + { + private: + friend struct ComputeGraph; + Ref(ComputeGraph *graph, int index) : BaseRef(graph, index) {} + Node &deref() const { this->check(); return this->graph_->nodes[this->index_]; } + + public: + void set_function(Fn const &function) const + { + deref().fn_index = this->graph_->functions(function); + } + + Attr &attr() const { return deref().attr; } + + void append_arg(ConstRef arg) const + { + log_assert(arg.graph_ == this->graph_); + append_arg(arg.index()); + } + + void append_arg(int arg) const + { + log_assert(arg >= 0 && arg < this->graph_->size()); + Node &node = deref(); + if (node.arg_offset + node.arg_count != GetSize(this->graph_->args)) + move_args(node); + this->graph_->args.push_back(arg); + node.arg_count++; + } + + operator ConstRef() const + { + return ConstRef(this->graph_, this->index_); + } + + SparseAttr &sparse_attr() const + { + return this->graph_->sparse_attrs[this->index_]; + } + + void clear_sparse_attr() const + { + this->graph_->sparse_attrs.erase(this->index_); + } + + void assign_key(Key const &key) const + { + this->graph_->keys_.emplace(key, this->index_); + } + + private: + void move_args(Node &node) const + { + auto &args = this->graph_->args; + int old_offset = node.arg_offset; + node.arg_offset = GetSize(args); + for (int i = 0; i != node.arg_count; ++i) + args.push_back(args[old_offset + i]); + } + + }; + + bool has_key(Key const &key) const + { + return keys_.count(key); + } + + dict const &keys() const + { + return keys_; + } + + ConstRef operator()(Key const &key) const + { + auto it = keys_.find(key); + log_assert(it != keys_.end()); + return (*this)[it->second]; + } + + Ref operator()(Key const &key) + { + auto it = keys_.find(key); + log_assert(it != keys_.end()); + return (*this)[it->second]; + } + + int size() const { return GetSize(nodes); } + + ConstRef operator[](int index) const { return ConstRef(this, index); } + Ref operator[](int index) { return Ref(this, index); } + + Ref add(Fn const &function, Attr &&attr) + { + int index = GetSize(nodes); + int fn_index = functions(function); + nodes.emplace_back(fn_index, std::move(attr), GetSize(args)); + return Ref(this, index); + } + + Ref add(Fn const &function, Attr const &attr) + { + int index = GetSize(nodes); + int fn_index = functions(function); + nodes.emplace_back(fn_index, attr, GetSize(args)); + return Ref(this, index); + } + + template + Ref add(Fn const &function, Attr const &attr, T const &args) + { + Ref added = add(function, attr); + for (auto arg : args) + added.append_arg(arg); + return added; + } + + template + Ref add(Fn const &function, Attr &&attr, T const &args) + { + Ref added = add(function, std::move(attr)); + for (auto arg : args) + added.append_arg(arg); + return added; + } + + template + Ref add(Fn const &function, Attr const &attr, T begin, T end) + { + Ref added = add(function, attr); + for (; begin != end; ++begin) + added.append_arg(*begin); + return added; + } + + void permute(std::vector const &perm) + { + log_assert(perm.size() <= nodes.size()); + std::vector inv_perm; + inv_perm.resize(nodes.size(), -1); + for (int i = 0; i < GetSize(perm); ++i) + { + int j = perm[i]; + log_assert(j >= 0 && j < GetSize(perm)); + log_assert(inv_perm[j] == -1); + inv_perm[j] = i; + } + permute(perm, inv_perm); + } + + void permute(std::vector const &perm, std::vector const &inv_perm) + { + log_assert(inv_perm.size() == nodes.size()); + std::vector new_nodes; + new_nodes.reserve(perm.size()); + dict new_sparse_attrs; + for (int i : perm) + { + int j = GetSize(new_nodes); + new_nodes.emplace_back(std::move(nodes[i])); + auto found = sparse_attrs.find(i); + if (found != sparse_attrs.end()) + new_sparse_attrs.emplace(j, std::move(found->second)); + } + + std::swap(nodes, new_nodes); + std::swap(sparse_attrs, new_sparse_attrs); + + for (int &arg : args) + { + log_assert(arg < GetSize(inv_perm)); + arg = inv_perm[arg]; + } + + for (auto &key : keys_) + { + log_assert(key.second < GetSize(inv_perm)); + key.second = inv_perm[key.second]; + } + } + + struct SccAdaptor + { + private: + ComputeGraph const &graph_; + std::vector indices_; + public: + SccAdaptor(ComputeGraph const &graph) : graph_(graph) + { + indices_.resize(graph.size(), -1); + } + + + typedef int node_type; + + struct node_enumerator { + private: + friend struct SccAdaptor; + int current, end; + node_enumerator(int current, int end) : current(current), end(end) {} + + public: + + bool finished() const { return current == end; } + node_type next() { + log_assert(!finished()); + node_type result = current; + ++current; + return result; + } + }; + + node_enumerator enumerate_nodes() { + return node_enumerator(0, GetSize(indices_)); + } + + + struct successor_enumerator { + private: + friend struct SccAdaptor; + std::vector::const_iterator current, end; + successor_enumerator(std::vector::const_iterator current, std::vector::const_iterator end) : + current(current), end(end) {} + + public: + bool finished() const { return current == end; } + node_type next() { + log_assert(!finished()); + node_type result = *current; + ++current; + return result; + } + }; + + successor_enumerator enumerate_successors(int index) const { + auto const &ref = graph_[index]; + return successor_enumerator(ref.arg_indices_cbegin(), ref.arg_indices_cend()); + } + + int &dfs_index(node_type const &node) { return indices_[node]; } + + std::vector const &dfs_indices() { return indices_; } + }; + +}; + + + +YOSYS_NAMESPACE_END + + +#endif diff --git a/passes/cmds/example_dt.cc b/passes/cmds/example_dt.cc index de84fa3cda8..dec554d6c71 100644 --- a/passes/cmds/example_dt.cc +++ b/passes/cmds/example_dt.cc @@ -1,6 +1,7 @@ #include "kernel/yosys.h" #include "kernel/drivertools.h" #include "kernel/topo_scc.h" +#include "kernel/functional.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN @@ -38,86 +39,137 @@ struct ExampleDtPass : public Pass ExampleWorker worker(module); DriverMap dm; + struct ExampleFn { + IdString name; + dict parameters; + + ExampleFn(IdString name) : name(name) {} + ExampleFn(IdString name, dict parameters) : name(name), parameters(parameters) {} + + bool operator==(ExampleFn const &other) const { + return name == other.name && parameters == other.parameters; + } + + unsigned int hash() const { + return mkhash(name.hash(), parameters.hash()); + } + }; + + typedef ComputeGraph ExampleGraph; + + ExampleGraph compute_graph; + + dm.add(module); idict queue; idict cells; IntGraph edges; + std::vector graph_nodes; + auto enqueue = [&](DriveSpec const &spec) { + int index = queue(spec); + if (index == GetSize(graph_nodes)) + graph_nodes.emplace_back(compute_graph.add(ID($pending), index).index()); + //if (index >= GetSize(graph_nodes)) + return compute_graph[graph_nodes[index]]; + }; for (auto cell : module->cells()) { if (cell->type.in(ID($assert), ID($assume), ID($cover), ID($check))) - queue(DriveBitMarker(cells(cell), 0)); + enqueue(DriveBitMarker(cells(cell), 0)); } for (auto wire : module->wires()) { if (!wire->port_output) continue; - queue(DriveChunk(DriveChunkWire(wire, 0, wire->width))); + enqueue(DriveChunk(DriveChunkWire(wire, 0, wire->width))).assign_key(wire->name); } -#define emit log -// #define emit(X...) do {} while (false) - for (int i = 0; i != GetSize(queue); ++i) { - emit("n%d: ", i); DriveSpec spec = queue[i]; + ExampleGraph::Ref node = compute_graph[i]; + if (spec.chunks().size() > 1) { - emit("concat %s <-\n", log_signal(spec)); + node.set_function(ID($$concat)); + for (auto const &chunk : spec.chunks()) { - emit(" * %s\n", log_signal(chunk)); - edges.add_edge(i, queue(chunk)); + node.append_arg(enqueue(chunk)); } } else if (spec.chunks().size() == 1) { DriveChunk chunk = spec.chunks()[0]; if (chunk.is_wire()) { DriveChunkWire wire_chunk = chunk.wire(); if (wire_chunk.is_whole()) { + node.sparse_attr() = wire_chunk.wire->name; if (wire_chunk.wire->port_input) { - emit("input %s\n", log_signal(spec)); + node.set_function(ExampleFn(ID($$input), {{wire_chunk.wire->name, {}}})); } else { DriveSpec driver = dm(DriveSpec(wire_chunk)); - edges.add_edge(i, queue(driver)); - emit("wire driver %s <- %s\n", log_signal(spec), log_signal(driver)); + node.set_function(ID($$buf)); + + node.append_arg(enqueue(driver)); } } else { DriveChunkWire whole_wire(wire_chunk.wire, 0, wire_chunk.width); - edges.add_edge(i, queue(whole_wire)); - emit("wire slice %s <- %s\n", log_signal(spec), log_signal(DriveSpec(whole_wire))); + node.set_function(ExampleFn(ID($$slice), {{ID(offset), wire_chunk.offset}, {ID(width), wire_chunk.width}})); + node.append_arg(enqueue(whole_wire)); } } else if (chunk.is_port()) { DriveChunkPort port_chunk = chunk.port(); if (port_chunk.is_whole()) { if (dm.celltypes.cell_output(port_chunk.cell->type, port_chunk.port)) { - int cell_marker = queue(DriveBitMarker(cells(port_chunk.cell), 0)); - if (!port_chunk.cell->type.in(ID($dff), ID($ff))) - edges.add_edge(i, cell_marker); - emit("cell output %s %s\n", log_id(port_chunk.cell), log_id(port_chunk.port)); + if (port_chunk.cell->type.in(ID($dff), ID($ff))) + { + Cell *cell = port_chunk.cell; + node.set_function(ExampleFn(ID($$state), {{cell->name, {}}})); + for (auto const &conn : cell->connections()) { + if (!dm.celltypes.cell_input(cell->type, conn.first)) + continue; + enqueue(DriveChunkPort(cell, conn)).assign_key(cell->name); + } + } + else + { + node.set_function(ExampleFn(ID($$cell_output), {{port_chunk.port, {}}})); + node.append_arg(enqueue(DriveBitMarker(cells(port_chunk.cell), 0))); + } } else { + node.set_function(ID($$buf)); + DriveSpec driver = dm(DriveSpec(port_chunk)); - edges.add_edge(i, queue(driver)); - emit("cell port driver %s <- %s\n", log_signal(spec), log_signal(driver)); + node.append_arg(enqueue(driver)); } } else { DriveChunkPort whole_port(port_chunk.cell, port_chunk.port, 0, GetSize(port_chunk.cell->connections().at(port_chunk.port))); - edges.add_edge(i, queue(whole_port)); - emit("port slice %s <- %s\n", log_signal(spec), log_signal(DriveSpec(whole_port))); + node.set_function(ID($$buf)); + node.append_arg(enqueue(whole_port)); } } else if (chunk.is_constant()) { - emit("constant %s <- %s\n", log_signal(spec), log_const(chunk.constant())); + node.set_function(ExampleFn(ID($$const), {{ID(value), chunk.constant()}})); + + } else if (chunk.is_multiple()) { + node.set_function(ID($$multi)); + for (auto const &driver : chunk.multiple().multiple()) + node.append_arg(enqueue(driver)); } else if (chunk.is_marker()) { Cell *cell = cells[chunk.marker().marker]; - emit("cell %s %s\n", log_id(cell->type), log_id(cell)); + + node.set_function(ExampleFn(cell->type, cell->parameters)); for (auto const &conn : cell->connections()) { if (!dm.celltypes.cell_input(cell->type, conn.first)) continue; - emit(" * %s <- %s\n", log_id(conn.first), log_signal(conn.second)); - edges.add_edge(i, queue(DriveChunkPort(cell, conn))); + + node.append_arg(enqueue(DriveChunkPort(cell, conn))); } + } else if (chunk.is_none()) { + node.set_function(ID($$undriven)); + } else { + log_error("unhandled drivespec: %s\n", log_signal(chunk)); log_abort(); } } else { @@ -125,13 +177,75 @@ struct ExampleDtPass : public Pass } } - topo_sorted_sccs(edges, [&](int *begin, int *end) { - emit("scc:"); - for (int *i = begin; i != end; ++i) - emit(" n%d", *i); - emit("\n"); - }); + // Perform topo sort and detect SCCs + ExampleGraph::SccAdaptor compute_graph_scc(compute_graph); + + + std::vector perm; + topo_sorted_sccs(compute_graph_scc, [&](int *begin, int *end) { + perm.insert(perm.end(), begin, end); + if (end > begin + 1) + { + log_warning("SCC:"); + for (int *i = begin; i != end; ++i) + log(" %d", *i); + log("\n"); + } + }, /* sources_first */ true); + compute_graph.permute(perm); + + + // Forward $$buf unless we have a name in the sparse attribute + std::vector alias; + perm.clear(); + + for (int i = 0; i < compute_graph.size(); ++i) + { + if (compute_graph[i].function().name == ID($$buf) && !compute_graph[i].has_sparse_attr() && compute_graph[i].arg(0).index() < i) + { + + alias.push_back(alias[compute_graph[i].arg(0).index()]); + } + else + { + alias.push_back(GetSize(perm)); + perm.push_back(i); + } + } + compute_graph.permute(perm, alias); + + // Dump the compute graph + for (int i = 0; i < compute_graph.size(); ++i) + { + auto ref = compute_graph[i]; + log("n%d ", i); + log("%s", log_id(ref.function().name)); + for (auto const ¶m : ref.function().parameters) + { + if (param.second.empty()) + log("[%s]", log_id(param.first)); + else + log("[%s=%s]", log_id(param.first), log_const(param.second)); + } + log("("); + + for (int i = 0, end = ref.size(); i != end; ++i) + { + if (i > 0) + log(", "); + log("n%d", ref.arg(i).index()); + } + log(")\n"); + if (ref.has_sparse_attr()) + log("// wire %s\n", log_id(ref.sparse_attr())); + log("// was #%d %s\n", ref.attr(), log_signal(queue[ref.attr()])); + } + + for (auto const &key : compute_graph.keys()) + { + log("return %d as %s \n", key.second, log_id(key.first)); + } } log("Plugin test passed!\n"); } From d90268f6104d8b88294629c636ec0ab979785c44 Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Wed, 17 Apr 2024 14:52:36 +0200 Subject: [PATCH 08/92] fixup! drivertools: Utility code for indexing and traversing signal drivers --- kernel/drivertools.cc | 23 +++++++++++++---------- kernel/drivertools.h | 1 + 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/kernel/drivertools.cc b/kernel/drivertools.cc index 499a9399c46..286c913a246 100644 --- a/kernel/drivertools.cc +++ b/kernel/drivertools.cc @@ -499,7 +499,7 @@ int DriverMap::DriveBitGraph::count(DriveBitId src) auto found = more_edges.find(src); if (found == more_edges.end()) return 2; - return GetSize(found->second); + return GetSize(found->second) + 2; } DriverMap::DriveBitId DriverMap::DriveBitGraph::at(DriveBitId src, int index) @@ -688,7 +688,7 @@ void DriverMap::add(DriveBit const &a, DriveBit const &b) // and use the other end as representative bit. else if (a_mode == BitMode::DRIVEN_UNIQUE && !(b_mode == BitMode::DRIVEN_UNIQUE || b_mode == BitMode::DRIVEN)) connect_directed_buffer(a_id, b_id); - else if (b_mode == BitMode::DRIVEN_UNIQUE && !(b_mode == BitMode::DRIVEN_UNIQUE || b_mode == BitMode::DRIVEN)) + else if (b_mode == BitMode::DRIVEN_UNIQUE && !(a_mode == BitMode::DRIVEN_UNIQUE || a_mode == BitMode::DRIVEN)) connect_directed_buffer(b_id, a_id); // If either bit only drives a value, store a directed connection from // it to the other bit. @@ -827,15 +827,18 @@ DriveBit DriverMap::operator()(DriveBit const &bit) DriveBitId bit_id = id_from_drive_bit(bit); - bit_id = same_driver.find(bit_id); + DriveBitId bit_repr_id = same_driver.find(bit_id); - DriveBit bit_repr = drive_bit_from_id(bit_id); + DriveBit bit_repr = drive_bit_from_id(bit_repr_id); BitMode mode = bit_mode(bit_repr); - int implicit_driver_count = connected_drivers.count(bit_id); - if (connected_undirected.contains(bit_id) && !oriented_present.count(bit_id)) - orient_undirected(bit_id); + if (mode == BitMode::KEEP && bit_repr_id != bit_id) + return bit_repr; + + int implicit_driver_count = connected_drivers.count(bit_repr_id); + if (connected_undirected.contains(bit_repr_id) && !oriented_present.count(bit_repr_id)) + orient_undirected(bit_repr_id); DriveBit driver; @@ -843,11 +846,11 @@ DriveBit DriverMap::operator()(DriveBit const &bit) driver = bit_repr; for (int i = 0; i != implicit_driver_count; ++i) - driver.merge(drive_bit_from_id(connected_drivers.at(bit_id, i))); + driver.merge(drive_bit_from_id(connected_drivers.at(bit_repr_id, i))); - int oriented_driver_count = connected_oriented.count(bit_id); + int oriented_driver_count = connected_oriented.count(bit_repr_id); for (int i = 0; i != oriented_driver_count; ++i) - driver.merge(drive_bit_from_id(connected_oriented.at(bit_id, i))); + driver.merge(drive_bit_from_id(connected_oriented.at(bit_repr_id, i))); return driver; } diff --git a/kernel/drivertools.h b/kernel/drivertools.h index b440afddaa2..1cb835df2cc 100644 --- a/kernel/drivertools.h +++ b/kernel/drivertools.h @@ -1165,6 +1165,7 @@ struct DriverMap DriveBitId(int id) : id(id) { } bool operator==(const DriveBitId &other) const { return id == other.id; } + bool operator!=(const DriveBitId &other) const { return id != other.id; } bool operator<(const DriveBitId &other) const { return id < other.id; } unsigned int hash() const { return id; } }; From dd5ec84a26725a085038a3afcc1e211ae7cdcd5c Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Wed, 1 May 2024 11:47:16 +0100 Subject: [PATCH 09/92] fix bugs in drivertools --- kernel/drivertools.cc | 4 ++-- passes/cmds/example_dt.cc | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/kernel/drivertools.cc b/kernel/drivertools.cc index 286c913a246..9627a467015 100644 --- a/kernel/drivertools.cc +++ b/kernel/drivertools.cc @@ -680,9 +680,9 @@ void DriverMap::add(DriveBit const &a, DriveBit const &b) // If either bit is just a wire that we don't need to keep, merge and // use the other end as representative bit. - if (a_mode == BitMode::NONE) + if (a_mode == BitMode::NONE && !(b_mode == BitMode::DRIVEN_UNIQUE || b_mode == BitMode::DRIVEN)) connect_directed_merge(a_id, b_id); - else if (b_mode == BitMode::NONE) + else if (b_mode == BitMode::NONE && !(a_mode == BitMode::DRIVEN_UNIQUE || a_mode == BitMode::DRIVEN)) connect_directed_merge(b_id, a_id); // If either bit requires a driven value and has a unique driver, merge // and use the other end as representative bit. diff --git a/passes/cmds/example_dt.cc b/passes/cmds/example_dt.cc index dec554d6c71..859b0b7ba4b 100644 --- a/passes/cmds/example_dt.cc +++ b/passes/cmds/example_dt.cc @@ -113,7 +113,7 @@ struct ExampleDtPass : public Pass node.append_arg(enqueue(driver)); } } else { - DriveChunkWire whole_wire(wire_chunk.wire, 0, wire_chunk.width); + DriveChunkWire whole_wire(wire_chunk.wire, 0, wire_chunk.wire->width); node.set_function(ExampleFn(ID($$slice), {{ID(offset), wire_chunk.offset}, {ID(width), wire_chunk.width}})); node.append_arg(enqueue(whole_wire)); } @@ -145,7 +145,7 @@ struct ExampleDtPass : public Pass } else { DriveChunkPort whole_port(port_chunk.cell, port_chunk.port, 0, GetSize(port_chunk.cell->connections().at(port_chunk.port))); - node.set_function(ID($$buf)); + node.set_function(ExampleFn(ID($$slice), {{ID(offset), port_chunk.offset}})); node.append_arg(enqueue(whole_port)); } } else if (chunk.is_constant()) { From 63dea89facd9676b14da11f2e6bb79aa0ff3c583 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Wed, 1 May 2024 11:48:04 +0100 Subject: [PATCH 10/92] add initial version of functional C++ backend --- backends/functional/Makefile.inc | 1 + backends/functional/cxx.cc | 417 ++++++++++++++++++++++++++ backends/functional/cxx_runtime/sim.h | 366 ++++++++++++++++++++++ kernel/graphtools.h | 332 ++++++++++++++++++++ 4 files changed, 1116 insertions(+) create mode 100644 backends/functional/Makefile.inc create mode 100644 backends/functional/cxx.cc create mode 100644 backends/functional/cxx_runtime/sim.h create mode 100644 kernel/graphtools.h diff --git a/backends/functional/Makefile.inc b/backends/functional/Makefile.inc new file mode 100644 index 00000000000..011e662646c --- /dev/null +++ b/backends/functional/Makefile.inc @@ -0,0 +1 @@ +OBJS += backends/functional/cxx.o diff --git a/backends/functional/cxx.cc b/backends/functional/cxx.cc new file mode 100644 index 00000000000..46ce88bd62c --- /dev/null +++ b/backends/functional/cxx.cc @@ -0,0 +1,417 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Emily Schmidt + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/yosys.h" +#include "kernel/drivertools.h" +#include "kernel/topo_scc.h" +#include "kernel/functional.h" +#include "kernel/graphtools.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +const char *reserved_keywords[] = { + "alignas","alignof","and","and_eq","asm","atomic_cancel","atomic_commit", + "atomic_noexcept","auto","bitand","bitor","bool","break","case", + "catch","char","char16_t","char32_t","char8_t","class","co_await", + "co_return","co_yield","compl","concept","const","const_cast","consteval", + "constexpr","constinit","continue","decltype","default","delete", + "do","double","dynamic_cast","else","enum","explicit","export", + "extern","false","float","for","friend","goto","if","inline", + "int","long","mutable","namespace","new","noexcept","not","not_eq", + "nullptr","operator","or","or_eq","private","protected","public", + "reflexpr","register","reinterpret_cast","requires","return","short", + "signed","sizeof","static","static_assert","static_cast","struct", + "switch","synchronized","template","this","thread_local","throw", + "true","try","typedef","typeid","typename","union","unsigned", + "using","virtual","void","volatile","wchar_t","while","xor","xor_eq", + nullptr +}; + +struct CxxScope { + pool used_names; + dict name_map; + + CxxScope() { + for(const char **p = reserved_keywords; *p != nullptr; p++) + reserve(*p); + } + void reserve(std::string name) { + used_names.insert(name); + } + std::string insert(IdString id) { + std::string str = RTLIL::unescape_id(id); + for(size_t i = 0; i < str.size(); i++) + if(strchr("!\"#%&'()*+,-./:;<=>?@[]\\^`{|}~ ", str[i])) + str[i] = '_'; + if(used_names.count(str) == 0){ + used_names.insert(str); + name_map.insert({id, str}); + return str; + } + for (int idx = 0 ; ; idx++){ + std::string suffixed = str + "_" + std::to_string(idx); + if (used_names.count(suffixed) == 0) { + used_names.insert(suffixed); + if(name_map.count(id) == 0) + name_map.insert({id, suffixed}); + return suffixed; + } + } + } + std::string operator[](IdString id) { + if(name_map.count(id) > 0) + return name_map[id]; + else + return insert(id); + } +}; + +struct CxxWriter { + std::ostream &f; + CxxWriter(std::ostream &out) : f(out) {} + void printf(const char *fmt, ...) + { + va_list va; + va_start(va, fmt); + f << vstringf(fmt, va); + va_end(va); + } +}; + +struct CxxStruct { + std::string name; + dict types; + CxxScope scope; + CxxStruct(std::string name) : name(name) { + scope.reserve("out"); + scope.reserve("dump"); + } + void insert(IdString name, std::string type) { + scope.insert(name); + types.insert({name, type}); + } + void print(CxxWriter &f) { + f.printf("struct %s {\n", name.c_str()); + for (auto p : types) { + f.printf("\t%s %s;\n", p.second.c_str(), scope[p.first].c_str()); + } + f.printf("\n\ttemplate void dump(T &out) {\n"); + for (auto p : types) { + f.printf("\t\tout(\"%s\", %s);\n", RTLIL::unescape_id(p.first).c_str(), scope[p.first].c_str()); + } + f.printf("\t}\n};\n\n"); + } + std::string operator[](IdString field) { + return scope[field]; + } +}; + +struct CxxFunction { + IdString name; + int width; + dict parameters; + + CxxFunction(IdString name, int width) : name(name), width(width) {} + CxxFunction(IdString name, int width, dict parameters) : name(name), width(width), parameters(parameters) {} + + bool operator==(CxxFunction const &other) const { + return name == other.name && parameters == other.parameters && width == other.width; + } + + unsigned int hash() const { + return mkhash(name.hash(), parameters.hash()); + } +}; + +typedef ComputeGraph CxxComputeGraph; + +class CxxComputeGraphFactory { + CxxComputeGraph &graph; + using T = CxxComputeGraph::Ref; + static bool is_single_output(IdString type) + { + auto it = yosys_celltypes.cell_types.find(type); + return it != yosys_celltypes.cell_types.end() && it->second.outputs.size() <= 1; + } +public: + CxxComputeGraphFactory(CxxComputeGraph &g) : graph(g) {} + T slice(T a, int in_width, int offset, int out_width) { + assert(offset + out_width <= in_width); + return graph.add(CxxFunction(ID($$slice), out_width, {{ID(offset), offset}}), 0, std::array{a}); + } + T extend(T a, int in_width, int out_width, bool is_signed) { + assert(in_width < out_width); + if(is_signed) + return graph.add(CxxFunction(ID($sign_extend), out_width, {{ID(WIDTH), out_width}}), 0, std::array{a}); + else + return graph.add(CxxFunction(ID($zero_extend), out_width, {{ID(WIDTH), out_width}}), 0, std::array{a}); + } + T concat(T a, int a_width, T b, int b_width) { + return graph.add(CxxFunction(ID($$concat), a_width + b_width), 0, std::array{a, b}); + } + T add(T a, T b, int width) { return graph.add(CxxFunction(ID($add), width), 0, std::array{a, b}); } + T sub(T a, T b, int width) { return graph.add(CxxFunction(ID($sub), width), 0, std::array{a, b}); } + T bitwise_and(T a, T b, int width) { return graph.add(CxxFunction(ID($and), width), 0, std::array{a, b}); } + T bitwise_or(T a, T b, int width) { return graph.add(CxxFunction(ID($or), width), 0, std::array{a, b}); } + T bitwise_xor(T a, T b, int width) { return graph.add(CxxFunction(ID($xor), width), 0, std::array{a, b}); } + T bitwise_not(T a, int width) { return graph.add(CxxFunction(ID($not), width), 0, std::array{a}); } + T neg(T a, int width) { return graph.add(CxxFunction(ID($neg), width), 0, std::array{a}); } + T mux(T a, T b, T s, int width) { return graph.add(CxxFunction(ID($mux), width), 0, std::array{a, b, s}); } + T pmux(T a, T b, T s, int width, int) { return graph.add(CxxFunction(ID($pmux), width), 0, std::array{a, b, s}); } + T reduce_and(T a, int) { return graph.add(CxxFunction(ID($reduce_and), 1), 0, std::array{a}); } + T reduce_or(T a, int) { return graph.add(CxxFunction(ID($reduce_or), 1), 0, std::array{a}); } + T reduce_xor(T a, int) { return graph.add(CxxFunction(ID($reduce_xor), 1), 0, std::array{a}); } + T eq(T a, T b, int) { return graph.add(CxxFunction(ID($eq), 1), 0, std::array{a, b}); } + T ne(T a, T b, int) { return graph.add(CxxFunction(ID($ne), 1), 0, std::array{a, b}); } + T gt(T a, T b, int) { return graph.add(CxxFunction(ID($gt), 1), 0, std::array{a, b}); } + T ge(T a, T b, int) { return graph.add(CxxFunction(ID($ge), 1), 0, std::array{a, b}); } + T ugt(T a, T b, int) { return graph.add(CxxFunction(ID($ugt), 1), 0, std::array{a, b}); } + T uge(T a, T b, int) { return graph.add(CxxFunction(ID($uge), 1), 0, std::array{a, b}); } + T logical_shift_left(T a, T b, int y_width, int) { return graph.add(CxxFunction(ID($shl), y_width, {{ID(WIDTH), y_width}}), 0, std::array{a, b}); } + T logical_shift_right(T a, T b, int y_width, int) { return graph.add(CxxFunction(ID($shr), y_width, {{ID(WIDTH), y_width}}), 0, std::array{a, b}); } + T arithmetic_shift_right(T a, T b, int y_width, int) { return graph.add(CxxFunction(ID($asr), y_width, {{ID(WIDTH), y_width}}), 0, std::array{a, b}); } + + T constant(RTLIL::Const value) { + return graph.add(CxxFunction(ID($$const), value.size(), {{ID(value), value}}), 0); + } + T input(IdString name, int width) { return graph.add(CxxFunction(ID($$input), width, {{name, {}}}), 0); } + T state(IdString name, int width) { return graph.add(CxxFunction(ID($$state), width, {{name, {}}}), 0); } + T cell_output(T cell, IdString type, IdString name, int width) { + if (is_single_output(type)) + return cell; + else + return graph.add(CxxFunction(ID($$cell_output), width, {{name, {}}}), 0, std::array{cell}); + } + T multiple(vector args, int width) { + return graph.add(CxxFunction(ID($$multiple), width), 0, args); + } + T undriven(int width) { + return graph.add(CxxFunction(ID($$undriven), width), 0); + } + + T create_pending(int width) { + return graph.add(CxxFunction(ID($$pending), width), 0); + } + void update_pending(T pending, T node) { + assert(pending.function().name == ID($$pending)); + pending.set_function(CxxFunction(ID($$buf), pending.function().width)); + pending.append_arg(node); + } + void declare_output(T node, IdString name) { + node.assign_key(name); + } + void declare_state(T node, IdString name) { + node.assign_key(name); + } + void suggest_name(T node, IdString name) { + node.sparse_attr() = name; + } +}; + +struct FunctionalCxxBackend : public Backend +{ + FunctionalCxxBackend() : Backend("functional_cxx", "convert design to C++ using the functional backend") {} + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + } + + CxxComputeGraph calculate_compute_graph(RTLIL::Module *module) + { + CxxComputeGraph compute_graph; + CxxComputeGraphFactory factory(compute_graph); + ComputeGraphConstruction construction(factory); + construction.add_module(module); + construction.process_queue(); + + // Perform topo sort and detect SCCs + CxxComputeGraph::SccAdaptor compute_graph_scc(compute_graph); + + bool scc = false; + std::vector perm; + topo_sorted_sccs(compute_graph_scc, [&](int *begin, int *end) { + perm.insert(perm.end(), begin, end); + if (end > begin + 1) + { + log_warning("SCC:"); + for (int *i = begin; i != end; ++i) + log(" %d(%s)(%s)", *i, compute_graph[*i].function().name.c_str(), compute_graph[*i].has_sparse_attr() ? compute_graph[*i].sparse_attr().c_str() : ""); + log("\n"); + scc = true; + } + }, /* sources_first */ true); + compute_graph.permute(perm); + if(scc) log_error("combinational loops, aborting\n"); + + // Forward $$buf + std::vector alias; + perm.clear(); + + for (int i = 0; i < compute_graph.size(); ++i) + { + auto node = compute_graph[i]; + if (node.function().name == ID($$buf) && node.arg(0).index() < i) + { + int target_index = alias[node.arg(0).index()]; + auto target_node = compute_graph[perm[target_index]]; + if(!target_node.has_sparse_attr() && node.has_sparse_attr()) + target_node.sparse_attr() = node.sparse_attr(); + alias.push_back(target_index); + } + else + { + alias.push_back(GetSize(perm)); + perm.push_back(i); + } + } + compute_graph.permute(perm, alias); + return compute_graph; + } + + void printCxx(std::ostream &stream, std::string, std::string const & name, CxxComputeGraph &compute_graph) + { + dict inputs, state; + CxxWriter f(stream); + + // Dump the compute graph + for (int i = 0; i < compute_graph.size(); ++i) + { + auto ref = compute_graph[i]; + if(ref.function().name == ID($$input)) + inputs[ref.function().parameters.begin()->first] = ref.function().width; + if(ref.function().name == ID($$state)) + state[ref.function().parameters.begin()->first] = ref.function().width; + } + f.printf("#include \"sim.h\"\n"); + CxxStruct input_struct(name + "_Inputs"); + for (auto const &input : inputs) + input_struct.insert(input.first, "Signal<" + std::to_string(input.second) + ">"); + CxxStruct output_struct(name + "_Outputs"); + for (auto const &key : compute_graph.keys()) + if(state.count(key.first) == 0) + output_struct.insert(key.first, "Signal<" + std::to_string(compute_graph[key.second].function().width) + ">"); + CxxStruct state_struct(name + "_State"); + for (auto const &state_var : state) + state_struct.insert(state_var.first, "Signal<" + std::to_string(state_var.second) + ">"); + + idict node_names; + CxxScope locals; + + input_struct.print(f); + output_struct.print(f); + state_struct.print(f); + + f.printf("void %s(%s_Inputs const &input, %s_Outputs &output, %s_State const ¤t_state, %s_State &next_state)\n{\n", name.c_str(), name.c_str(), name.c_str(), name.c_str(), name.c_str()); + locals.reserve("input"); + locals.reserve("output"); + locals.reserve("current_state"); + locals.reserve("next_state"); + for (int i = 0; i < compute_graph.size(); ++i) + { + auto ref = compute_graph[i]; + int width = ref.function().width; + std::string name; + if(ref.has_sparse_attr()) + name = locals.insert(ref.sparse_attr()); + else + name = locals.insert("\\n" + std::to_string(i)); + node_names(name); + if(ref.function().name == ID($$input)) + f.printf("\tSignal<%d> %s = input.%s;\n", width, name.c_str(), input_struct[ref.function().parameters.begin()->first].c_str()); + else if(ref.function().name == ID($$state)) + f.printf("\tSignal<%d> %s = current_state.%s;\n", width, name.c_str(), state_struct[ref.function().parameters.begin()->first].c_str()); + else if(ref.function().name == ID($$buf)) + f.printf("\tSignal<%d> %s = %s;\n", width, name.c_str(), node_names[ref.arg(0).index()].c_str()); + else if(ref.function().name == ID($$cell_output)) + f.printf("\tSignal<%d> %s = %s.%s;\n", width, name.c_str(), node_names[ref.arg(0).index()].c_str(), RTLIL::unescape_id(ref.function().parameters.begin()->first).c_str()); + else if(ref.function().name == ID($$const)){ + auto c = ref.function().parameters.begin()->second; + if(c.size() <= 32){ + f.printf("\tSignal<%d> %s = $const<%d>(%#x);\n", width, name.c_str(), width, (uint32_t) c.as_int()); + }else{ + f.printf("\tSignal<%d> %s = $const<%d>({%#x", width, name.c_str(), width, (uint32_t) c.as_int()); + while(c.size() > 32){ + c = c.extract(32, c.size() - 32); + f.printf(", %#x", c.as_int()); + } + f.printf("});\n"); + } + }else if(ref.function().name == ID($$undriven)) + f.printf("\tSignal<%d> %s; //undriven\n", width, name.c_str()); + else if(ref.function().name == ID($$slice)) + f.printf("\tSignal<%d> %s = slice<%d>(%s, %d);\n", width, name.c_str(), width, node_names[ref.arg(0).index()].c_str(), ref.function().parameters.at(ID(offset)).as_int()); + else if(ref.function().name == ID($$concat)){ + f.printf("\tauto %s = concat(", name.c_str()); + for (int i = 0, end = ref.size(); i != end; ++i){ + if(i > 0) + f.printf(", "); + f.printf("%s", node_names[ref.arg(i).index()].c_str()); + } + f.printf(");\n"); + }else{ + f.printf("\t"); + if(ref.function().width > 0) + f.printf("Signal<%d>", ref.function().width); + else + f.printf("%s_Outputs", log_id(ref.function().name)); + f.printf(" %s = %s", name.c_str(), log_id(ref.function().name)); + if(ref.function().parameters.count(ID(WIDTH))){ + f.printf("<%d>", ref.function().parameters.at(ID(WIDTH)).as_int()); + } + f.printf("("); + for (int i = 0, end = ref.size(); i != end; ++i) + f.printf("%s%s", i>0?", ":"", node_names[ref.arg(i).index()].c_str()); + f.printf("); //"); + for (auto const ¶m : ref.function().parameters) + { + if (param.second.empty()) + f.printf("[%s]", log_id(param.first)); + else + f.printf("[%s=%s]", log_id(param.first), log_const(param.second)); + } + f.printf("\n"); + } + } + + for (auto const &key : compute_graph.keys()) + { + f.printf("\t%s.%s = %s;\n", state.count(key.first) > 0 ? "next_state" : "output", state_struct[key.first].c_str(), node_names[key.second].c_str()); + } + f.printf("}\n"); + } + + void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override + { + log_header(design, "Executing Functional C++ backend.\n"); + + size_t argidx = 1; + extra_args(f, filename, args, argidx, design); + + for (auto module : design->selected_modules()) { + log("Dumping module `%s'.\n", module->name.c_str()); + auto compute_graph = calculate_compute_graph(module); + printCxx(*f, filename, RTLIL::unescape_id(module->name), compute_graph); + } + } +} FunctionalCxxBackend; + +PRIVATE_NAMESPACE_END diff --git a/backends/functional/cxx_runtime/sim.h b/backends/functional/cxx_runtime/sim.h new file mode 100644 index 00000000000..4e7d9b60142 --- /dev/null +++ b/backends/functional/cxx_runtime/sim.h @@ -0,0 +1,366 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Emily Schmidt + * + * 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 SIM_H +#define SIM_H + +#include + +template +using Signal = std::array; + +template +Signal slice(Signal const& a, size_t offset) +{ + Signal ret; + + std::copy(a.begin() + offset, a.begin() + offset + n, ret.begin()); + return ret; +} + +template +Signal $const(uint32_t val) +{ + size_t i; + Signal ret; + + for(i = 0; i < n; i++) + if(i < 32) + ret[i] = val & (1< +Signal $const(std::initializer_list vals) +{ + size_t k, i; + Signal ret; + + k = 0; + for (auto val : vals) { + for(i = 0; i < 32; i++) + if(i + k < n) + ret[i + k] = val & (1< +bool as_bool(Signal sig) +{ + for(int i = 0; i < n; i++) + if(sig[i]) + return true; + return false; +} + +template +uint32_t as_int(Signal sig) +{ + uint32_t ret = 0; + for(int i = 0; i < n; i++) + if(sig[i] && i < 32) + ret |= 1< +Signal $mux(Signal const& a, Signal const &b, Signal<1> const &s) +{ + return s[0] ? b : a; +} + +template +Signal $not(Signal const& a) +{ + Signal ret; + for(size_t i = 0; i < n; i++) + ret[i] = !a[i]; + return ret; +} + +template +Signal $neg(Signal const& a) +{ + Signal ret; + bool carry = true; + for(size_t i = 0; i < n; i++) { + int r = !a[i] + carry; + ret[i] = (r & 1) != 0; + carry = (r >> 1) != 0; + } + return ret; +} + +template +Signal<1> $reduce_or(Signal const& a) +{ + return { as_bool(a) }; +} + +template +Signal<1> $reduce_and(Signal const& a) +{ + for(size_t i = 0; i < n; i++) + if(!a[i]) + return { false }; + return { true }; +} + +template +Signal<1> $reduce_bool(Signal const& a) +{ + return { as_bool(a) }; +} + +template +Signal<1> $logic_and(Signal const& a, Signal const& b) +{ + return { as_bool(a) && as_bool(b) }; +} + +template +Signal<1> $logic_or(Signal const& a, Signal const& b) +{ + return { as_bool(a) || as_bool(b) }; +} + +template +Signal<1> $logic_not(Signal const& a) +{ + return { !as_bool(a) }; +} + +template +Signal $add(Signal const& a, Signal const &b) +{ + Signal ret; + size_t i; + int x = 0; + for(i = 0; i < n; i++){ + x += (int)a[i] + (int)b[i]; + ret[i] = x & 1; + x >>= 1; + } + return ret; +} +template +Signal $sub(Signal const& a, Signal const &b) +{ + Signal ret; + int x = 1; + for(size_t i = 0; i < n; i++){ + x += (int)a[i] + (int)!b[i]; + ret[i] = x & 1; + x >>= 1; + } + return ret; +} + +template +Signal<1> $uge(Signal const& a, Signal const &b) +{ + for(size_t i = n; i-- != 0; ) + if(a[i] != b[i]) + return { a[i] }; + return { true }; +} + +template +Signal<1> $ugt(Signal const& a, Signal const &b) +{ + for(size_t i = n; i-- != 0; ) + if(a[i] != b[i]) + return { a[i] }; + return { false }; +} + +template +Signal<1> $ge(Signal const& a, Signal const &b) +{ + if(a[n-1] != b[n-1]) + return { b[n-1] }; + return $uge(a, b); +} + +template +Signal<1> $gt(Signal const& a, Signal const &b) +{ + if(a[n-1] != b[n-1]) + return { b[n-1] }; + return $ugt(a, b); +} + +template Signal<1> $ule(Signal const& a, Signal const &b) { return $uge(b, a); } +template Signal<1> $ult(Signal const& a, Signal const &b) { return $ugt(b, a); } +template Signal<1> $le(Signal const& a, Signal const &b) { return $ge(b, a); } +template Signal<1> $lt(Signal const& a, Signal const &b) { return $gt(b, a); } + +template +Signal $and(Signal const& a, Signal const &b) +{ + Signal ret; + for(size_t i = 0; i < n; i++) + ret[i] = a[i] && b[i]; + return ret; +} + +template +Signal $or(Signal const& a, Signal const &b) +{ + Signal ret; + for(size_t i = 0; i < n; i++) + ret[i] = a[i] || b[i]; + return ret; +} + +template +Signal $xor(Signal const& a, Signal const &b) +{ + Signal ret; + for(size_t i = 0; i < n; i++) + ret[i] = a[i] != b[i]; + return ret; +} + +template +Signal $shl(Signal const& a, Signal const &b) +{ + if(nb >= sizeof(int) * 8 - 1) + for(size_t i = sizeof(int) * 8 - 1; i < nb; i++) + assert(!b[i]); + size_t amount = as_int(b); + Signal ret = $const(0); + if(amount < n){ + if(amount + na > n) + std::copy(a.begin(), a.begin() + (n - amount), ret.begin() + amount); + else + std::copy(a.begin(), a.end(), ret.begin() + amount); + } + return ret; +} + +template +Signal $shr(Signal const& a, Signal const &b) +{ + if(nb >= sizeof(int) * 8 - 1) + for(size_t i = sizeof(int) * 8 - 1; i < nb; i++) + assert(!b[i]); + size_t amount = as_int(b); + Signal ret; + for (size_t i = 0; i < n; i++) { + if(i + amount < n) + ret[i] = a[i + amount]; + else + ret[i] = false; + } + return ret; +} + +template +Signal $asr(Signal const& a, Signal const &b) +{ + if(nb >= sizeof(int) * 8 - 1) + for(size_t i = sizeof(int) * 8 - 1; i < nb; i++) + assert(!b[i]); + size_t amount = as_int(b); + Signal ret; + for (size_t i = 0; i < n; i++) { + if(i + amount < n) + ret[i] = a[i + amount]; + else + ret[i] = a[n - 1]; + } + return ret; +} + +template +Signal<1> $eq(Signal const& a, Signal const &b) +{ + for(size_t i = 0; i < n; i++) + if(a[i] != b[i]) + return { false }; + return { true }; +} + +template +Signal<1> $ne(Signal const& a, Signal const &b) +{ + for(size_t i = 0; i < n; i++) + if(a[i] != b[i]) + return { true }; + return { false }; +} + +template +Signal $pmux(Signal const& a, Signal const &b, Signal const &s) +{ + bool found; + Signal ret; + + found = false; + ret = a; + for(size_t i = 0; i < ns; i++){ + if(s[i]){ + if(found) + return $const(0); + found = true; + ret = slice(b, n * i); + } + } + return ret; +} + +template +Signal concat(Signal const& a, Signal const& b) +{ + Signal ret; + std::copy(a.begin(), a.end(), ret.begin()); + std::copy(b.begin(), b.end(), ret.begin() + n); + return ret; +} + +template +Signal $zero_extend(Signal const& a) +{ + assert(n >= m); + Signal ret; + std::copy(a.begin(), a.end(), ret.begin()); + for(size_t i = m; i < n; i++) + ret[i] = false; + return ret; +} + +template +Signal $sign_extend(Signal const& a) +{ + assert(n >= m); + Signal ret; + std::copy(a.begin(), a.end(), ret.begin()); + for(size_t i = m; i < n; i++) + ret[i] = a[m-1]; + return ret; +} + +#endif diff --git a/kernel/graphtools.h b/kernel/graphtools.h new file mode 100644 index 00000000000..1a59ef1b537 --- /dev/null +++ b/kernel/graphtools.h @@ -0,0 +1,332 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Emily Schmidt + * + * 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 GRAPHTOOLS_H +#define GRAPHTOOLS_H + +#include "kernel/yosys.h" +#include "kernel/drivertools.h" +#include "kernel/functional.h" + +USING_YOSYS_NAMESPACE +YOSYS_NAMESPACE_BEGIN + +template +class CellSimplifier { + Factory &factory; + T reduce_shift_width(T b, int b_width, int y_width, int &reduced_b_width) { + log_assert(y_width > 0); + int new_width = sizeof(int) * 8 - __builtin_clz(y_width); + if (b_width <= new_width) { + reduced_b_width = b_width; + return b; + } else { + reduced_b_width = new_width; + T lower_b = factory.slice(b, b_width, 0, new_width); + T overflow = factory.gt(b, factory.constant(RTLIL::Const(y_width, b_width)), b_width); + return factory.mux(lower_b, factory.constant(RTLIL::Const(y_width, new_width)), overflow, new_width); + } + } +public: + T reduce_or(T a, int width) { + if (width == 1) + return a; + return factory.reduce_or(a, width); + } + T extend(T a, int in_width, int out_width, bool is_signed) { + if(in_width == out_width) + return a; + if(in_width > out_width) + return factory.slice(a, in_width, 0, out_width); + return factory.extend(a, in_width, out_width, is_signed); + } + T logical_shift_left(T a, T b, int y_width, int b_width) { + int reduced_b_width; + T reduced_b = reduce_shift_width(b, b_width, y_width, reduced_b_width); + return factory.logical_shift_left(a, reduced_b, y_width, reduced_b_width); + } + T logical_shift_right(T a, T b, int y_width, int b_width) { + int reduced_b_width; + T reduced_b = reduce_shift_width(b, b_width, y_width, reduced_b_width); + return factory.logical_shift_right(a, reduced_b, y_width, reduced_b_width); + } + T arithmetic_shift_right(T a, T b, int y_width, int b_width) { + int reduced_b_width; + T reduced_b = reduce_shift_width(b, b_width, y_width, reduced_b_width); + return factory.arithmetic_shift_right(a, reduced_b, y_width, reduced_b_width); + } + CellSimplifier(Factory &f) : factory(f) {} + T handle(IdString cellType, dict parameters, dict inputs) + { + int a_width = parameters.at(ID(A_WIDTH), Const(-1)).as_int(); + int b_width = parameters.at(ID(B_WIDTH), Const(-1)).as_int(); + int y_width = parameters.at(ID(Y_WIDTH), Const(-1)).as_int(); + bool a_signed = parameters.at(ID(A_SIGNED), Const(0)).as_bool(); + bool b_signed = parameters.at(ID(B_SIGNED), Const(0)).as_bool(); + if(cellType.in({ID($add), ID($sub), ID($and), ID($or), ID($xor), ID($xnor)})){ + bool is_signed = a_signed && b_signed; + T a = extend(inputs.at(ID(A)), a_width, y_width, is_signed); + T b = extend(inputs.at(ID(B)), b_width, y_width, is_signed); + if(cellType == ID($add)) + return factory.add(a, b, y_width); + else if(cellType == ID($sub)) + return factory.sub(a, b, y_width); + else if(cellType == ID($and)) + return factory.bitwise_and(a, b, y_width); + else if(cellType == ID($or)) + return factory.bitwise_or(a, b, y_width); + else if(cellType == ID($xor)) + return factory.bitwise_xor(a, b, y_width); + else if(cellType == ID($xnor)) + return factory.bitwise_not(factory.bitwise_xor(a, b, y_width), y_width); + else + log_abort(); + }else if(cellType.in({ID($eq), ID($ne), ID($eqx), ID($nex), ID($le), ID($lt), ID($ge), ID($gt)})){ + bool is_signed = a_signed && b_signed; + int width = max(a_width, b_width); + T a = extend(inputs.at(ID(A)), a_width, width, is_signed); + T b = extend(inputs.at(ID(B)), b_width, width, is_signed); + if(cellType.in({ID($eq), ID($eqx)})) + return extend(factory.eq(a, b, width), 1, y_width, false); + if(cellType.in({ID($ne), ID($nex)})) + return extend(factory.ne(a, b, width), 1, y_width, false); + else if(cellType == ID($lt)) + return extend(is_signed ? factory.gt(b, a, width) : factory.ugt(b, a, width), 1, y_width, false); + else if(cellType == ID($le)) + return extend(is_signed ? factory.ge(b, a, width) : factory.uge(b, a, width), 1, y_width, false); + else if(cellType == ID($gt)) + return extend(is_signed ? factory.gt(a, b, width) : factory.ugt(a, b, width), 1, y_width, false); + else if(cellType == ID($ge)) + return extend(is_signed ? factory.ge(a, b, width) : factory.uge(a, b, width), 1, y_width, false); + else + log_abort(); + }else if(cellType.in({ID($logic_or), ID($logic_and)})){ + T a = reduce_or(inputs.at(ID(A)), a_width); + T b = reduce_or(inputs.at(ID(B)), b_width); + T y = cellType == ID($logic_and) ? factory.bitwise_and(a, b, 1) : factory.bitwise_or(a, b, 1); + return extend(y, 1, y_width, false); + }else if(cellType == ID($not)){ + T a = extend(inputs.at(ID(A)), a_width, y_width, a_signed); + return factory.bitwise_not(a, y_width); + }else if(cellType == ID($pos)){ + return extend(inputs.at(ID(A)), a_width, y_width, a_signed); + }else if(cellType == ID($neg)){ + T a = extend(inputs.at(ID(A)), a_width, y_width, a_signed); + return factory.neg(a, y_width); + }else if(cellType == ID($logic_not)){ + T a = reduce_or(inputs.at(ID(A)), a_width); + T y = factory.bitwise_not(a, 1); + return extend(y, 1, y_width, false); + }else if(cellType.in({ID($reduce_or), ID($reduce_bool)})){ + T a = reduce_or(inputs.at(ID(A)), a_width); + return extend(a, 1, y_width, false); + }else if(cellType == ID($reduce_and)){ + T a = factory.reduce_and(inputs.at(ID(A)), a_width); + return extend(a, 1, y_width, false); + }else if(cellType.in({ID($reduce_xor), ID($reduce_xnor)})){ + T a = factory.reduce_xor(inputs.at(ID(A)), a_width); + T y = cellType == ID($reduce_xnor) ? factory.bitwise_not(a, 1) : a; + return extend(y, 1, y_width, false); + }else if(cellType == ID($shl) || cellType == ID($sshl)){ + T a = extend(inputs.at(ID(A)), a_width, y_width, a_signed); + T b = inputs.at(ID(B)); + return logical_shift_left(a, b, y_width, b_width); + }else if(cellType == ID($shr) || cellType == ID($sshr)){ + int width = max(a_width, y_width); + T a = extend(inputs.at(ID(A)), a_width, width, a_signed); + T b = inputs.at(ID(B)); + T y = a_signed && cellType == ID($sshr) ? + arithmetic_shift_right(a, b, width, b_width) : + logical_shift_right(a, b, width, b_width); + return extend(y, width, y_width, a_signed); + }else if(cellType == ID($shiftx) || cellType == ID($shift)){ + int width = max(a_width, y_width); + T a = extend(inputs.at(ID(A)), a_width, width, cellType == ID($shift) && a_signed); + T b = inputs.at(ID(B)); + T shr = logical_shift_right(a, b, width, b_width); + if(b_signed) { + T sign_b = factory.slice(b, b_width, b_width - 1, 1); + T shl = logical_shift_left(a, factory.neg(b, b_width), width, b_width); + T y = factory.mux(shr, shl, sign_b, width); + return extend(y, width, y_width, false); + } else { + return extend(shr, width, y_width, false); + } + }else if(cellType == ID($mux)){ + int width = parameters.at(ID(WIDTH)).as_int(); + return factory.mux(inputs.at(ID(A)), inputs.at(ID(B)), inputs.at(ID(S)), width); + }else if(cellType == ID($pmux)){ + int width = parameters.at(ID(WIDTH)).as_int(); + int s_width = parameters.at(ID(S_WIDTH)).as_int(); + return factory.pmux(inputs.at(ID(A)), inputs.at(ID(B)), inputs.at(ID(S)), width, s_width); + }else if(cellType == ID($concat)){ + T a = inputs.at(ID(A)); + T b = inputs.at(ID(B)); + return factory.concat(a, a_width, b, b_width); + }else if(cellType == ID($slice)){ + int offset = parameters.at(ID(OFFSET)).as_int(); + T a = inputs.at(ID(A)); + return factory.slice(a, a_width, offset, y_width); + }else{ + log_error("unhandled cell in CellSimplifier %s\n", cellType.c_str()); + } + } +}; + +template +class ComputeGraphConstruction { + std::deque queue; + dict graph_nodes; + idict cells; + DriverMap driver_map; + Factory& factory; + CellSimplifier simplifier; + + T enqueue(DriveSpec const &spec) + { + auto it = graph_nodes.find(spec); + if(it == graph_nodes.end()){ + auto node = factory.create_pending(spec.size()); + graph_nodes.insert({spec, node}); + queue.emplace_back(spec); + return node; + }else + return it->second; + } +public: + ComputeGraphConstruction(Factory &f) : factory(f), simplifier(f) {} + void add_module(Module *module) + { + driver_map.add(module); + for (auto cell : module->cells()) { + if (cell->type.in(ID($assert), ID($assume), ID($cover), ID($check))) + enqueue(DriveBitMarker(cells(cell), 0)); + } + for (auto wire : module->wires()) { + if (wire->port_output) { + T node = enqueue(DriveChunk(DriveChunkWire(wire, 0, wire->width))); + factory.declare_output(node, wire->name); + } + } + } + void process_queue() + { + for (; !queue.empty(); queue.pop_front()) { + DriveSpec spec = queue.front(); + T pending = graph_nodes.at(spec); + + if (spec.chunks().size() > 1) { + auto chunks = spec.chunks(); + T node = enqueue(chunks[0]); + int width = chunks[0].size(); + for(size_t i = 1; i < chunks.size(); i++) { + node = factory.concat(node, width, enqueue(chunks[i]), chunks[i].size()); + width += chunks[i].size(); + } + factory.update_pending(pending, node); + } else if (spec.chunks().size() == 1) { + DriveChunk chunk = spec.chunks()[0]; + if (chunk.is_wire()) { + DriveChunkWire wire_chunk = chunk.wire(); + if (wire_chunk.is_whole()) { + if (wire_chunk.wire->port_input) { + T node = factory.input(wire_chunk.wire->name, wire_chunk.width); + factory.suggest_name(node, wire_chunk.wire->name); + factory.update_pending(pending, node); + } else { + DriveSpec driver = driver_map(DriveSpec(wire_chunk)); + T node = enqueue(driver); + factory.suggest_name(node, wire_chunk.wire->name); + factory.update_pending(pending, node); + } + } else { + DriveChunkWire whole_wire(wire_chunk.wire, 0, wire_chunk.wire->width); + T node = factory.slice(enqueue(whole_wire), wire_chunk.wire->width, wire_chunk.offset, wire_chunk.width); + factory.update_pending(pending, node); + } + } else if (chunk.is_port()) { + DriveChunkPort port_chunk = chunk.port(); + if (port_chunk.is_whole()) { + if (driver_map.celltypes.cell_output(port_chunk.cell->type, port_chunk.port)) { + if (port_chunk.cell->type.in(ID($dff), ID($ff))) + { + Cell *cell = port_chunk.cell; + T node = factory.state(cell->name, port_chunk.width); + factory.suggest_name(node, port_chunk.cell->name); + factory.update_pending(pending, node); + for (auto const &conn : cell->connections()) { + if (driver_map.celltypes.cell_input(cell->type, conn.first)) { + T node = enqueue(DriveChunkPort(cell, conn)); + factory.declare_state(node, cell->name); + } + } + } + else + { + T cell = enqueue(DriveChunkMarker(cells(port_chunk.cell), 0, port_chunk.width)); + factory.suggest_name(cell, port_chunk.cell->name); + T node = factory.cell_output(cell, port_chunk.cell->type, port_chunk.port, port_chunk.width); + factory.suggest_name(node, port_chunk.cell->name.str() + "$" + port_chunk.port.str()); + factory.update_pending(pending, node); + } + } else { + DriveSpec driver = driver_map(DriveSpec(port_chunk)); + factory.update_pending(pending, enqueue(driver)); + } + } else { + DriveChunkPort whole_port(port_chunk.cell, port_chunk.port, 0, GetSize(port_chunk.cell->connections().at(port_chunk.port))); + T node = factory.slice(enqueue(whole_port), whole_port.width, port_chunk.offset, port_chunk.width); + factory.update_pending(pending, node); + } + } else if (chunk.is_constant()) { + T node = factory.constant(chunk.constant()); + factory.suggest_name(node, "$const" + std::to_string(chunk.size()) + "b" + chunk.constant().as_string()); + factory.update_pending(pending, node); + } else if (chunk.is_multiple()) { + vector args; + for (auto const &driver : chunk.multiple().multiple()) + args.push_back(enqueue(driver)); + T node = factory.multiple(args, chunk.size()); + factory.update_pending(pending, node); + } else if (chunk.is_marker()) { + Cell *cell = cells[chunk.marker().marker]; + dict connections; + for(auto const &conn : cell->connections()) { + if(driver_map.celltypes.cell_input(cell->type, conn.first)) + connections.insert({ conn.first, enqueue(DriveChunkPort(cell, conn)) }); + } + T node = simplifier.handle(cell->type, cell->parameters, connections); + factory.update_pending(pending, node); + } else if (chunk.is_none()) { + T node = factory.undriven(chunk.size()); + factory.update_pending(pending, node); + } else { + log_error("unhandled drivespec: %s\n", log_signal(chunk)); + log_abort(); + } + } else { + log_abort(); + } + } + } +}; + +YOSYS_NAMESPACE_END + +#endif From 7611dda2ebfbbc29623e13bc8850910d21398a98 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Thu, 23 May 2024 11:40:41 +0100 Subject: [PATCH 11/92] add initial version of functional smtlib backend --- backends/functional/Makefile.inc | 1 + backends/functional/cxx.cc | 4 +- backends/functional/smtlib.cc | 541 +++++++++++++++++++++++++++++++ kernel/graphtools.h | 6 +- 4 files changed, 547 insertions(+), 5 deletions(-) create mode 100644 backends/functional/smtlib.cc diff --git a/backends/functional/Makefile.inc b/backends/functional/Makefile.inc index 011e662646c..c712d2aefe5 100644 --- a/backends/functional/Makefile.inc +++ b/backends/functional/Makefile.inc @@ -1 +1,2 @@ OBJS += backends/functional/cxx.o +OBJS += backends/functional/smtlib.o diff --git a/backends/functional/cxx.cc b/backends/functional/cxx.cc index 46ce88bd62c..c0444e7fe5a 100644 --- a/backends/functional/cxx.cc +++ b/backends/functional/cxx.cc @@ -214,10 +214,10 @@ class CxxComputeGraphFactory { pending.set_function(CxxFunction(ID($$buf), pending.function().width)); pending.append_arg(node); } - void declare_output(T node, IdString name) { + void declare_output(T node, IdString name, int) { node.assign_key(name); } - void declare_state(T node, IdString name) { + void declare_state(T node, IdString name, int) { node.assign_key(name); } void suggest_name(T node, IdString name) { diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc new file mode 100644 index 00000000000..f6af797919b --- /dev/null +++ b/backends/functional/smtlib.cc @@ -0,0 +1,541 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Emily Schmidt + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/yosys.h" +#include "kernel/drivertools.h" +#include "kernel/topo_scc.h" +#include "kernel/functional.h" +#include "kernel/graphtools.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct SmtlibScope { + pool used_names; + dict name_map; + + SmtlibScope() { + /*for(const char **p = reserved_keywords; *p != nullptr; p++) + reserve(*p);*/ + } + void reserve(std::string name) { + used_names.insert(name); + } + std::string insert(IdString id) { + std::string str = RTLIL::unescape_id(id); + for(size_t i = 0; i < str.size(); i++) + if(!((unsigned char)str[i] < 0x80 && (isalnum(str[i]) || strchr("~!@$%^&*_-+=<>.?/", str[i])))) + str[i] = '_'; + if(used_names.count(str) == 0){ + used_names.insert(str); + name_map.insert({id, str}); + return str; + } + for (int idx = 0 ; ; idx++){ + std::string suffixed = str + "_" + std::to_string(idx); + if (used_names.count(suffixed) == 0) { + used_names.insert(suffixed); + if(name_map.count(id) == 0) + name_map.insert({id, suffixed}); + return suffixed; + } + } + } + std::string operator[](IdString id) { + if(name_map.count(id) > 0) + return name_map[id]; + else + return insert(id); + } +}; + +struct SmtlibWriter { + std::ostream &f; + SmtlibWriter(std::ostream &out) : f(out) {} + void printf(const char *fmt, ...) + { + va_list va; + va_start(va, fmt); + f << vstringf(fmt, va); + va_end(va); + } + template + SmtlibWriter & operator <<(T &&arg) { + f << std::forward(arg); + return *this; + } +}; + +struct Arg { + int n; + explicit Arg(int n_) : n(n_) {} + bool operator ==(Arg const &other) const { return n == other.n; } +}; + +class SExpr { + enum class Type { + None, + List, + Atom, + Arg, + }; + Type type = Type::None; + union { + std::string atom_; + vector list_; + Arg arg_; + }; + void set_none() { + switch(type) { + case Type::None: break; + case Type::Atom: atom_.~basic_string(); break; + case Type::List: list_.~vector(); break; + case Type::Arg: arg_.~Arg(); break; + } + type = Type::None; + } +public: + SExpr() {} + SExpr(const char *atom) : type(Type::Atom), atom_(atom) {} + SExpr(std::string atom) : type(Type::Atom), atom_(atom) {} + SExpr(int n) : type(Type::Atom), atom_(std::to_string(n)) {} + SExpr(RTLIL::Const const & n) : type(Type::Atom) { + new(&atom_) std::string("#b"); + atom_.reserve(n.size() + 2); + for (size_t i = n.size(); i > 0; i--) + if(n[i-1] == State::S1) + atom_ += "1"; + else + atom_ += "0"; + } + SExpr(vector &&list) : type(Type::List), list_(list) {} + SExpr(std::initializer_list list) : type(Type::List), list_(list) {} + SExpr(Arg arg) : type(Type::Arg), arg_(arg) {} + SExpr(SExpr const &other) { *this = other; } + SExpr(SExpr &&other) { *this = std::move(other); } + SExpr &operator =(SExpr const &other) { + set_none(); + switch(other.type) { + case Type::None: break; + case Type::Atom: new(&atom_) std::string(other.atom_); break; + case Type::List: new(&list_) vector(other.list_); break; + case Type::Arg: new(&arg_) Arg(other.arg_); break; + } + type = other.type; + return *this; + } + SExpr &operator =(SExpr &&other) { + set_none(); + switch(other.type) { + case Type::None: break; + case Type::Atom: new(&atom_) std::string(std::move(other.atom_)); break; + case Type::List: new(&list_) vector(std::move(other.list_)); break; + case Type::Arg: new(&arg_) Arg(std::move(other.arg_)); break; + } + type = other.type; + return *this; + } + ~SExpr() { set_none(); } + bool operator==(SExpr const &other) const { + if(type != other.type) return false; + switch(type) { + case Type::None: return true; + case Type::Atom: return atom_ == other.atom_; + case Type::List: return list_ == other.list_; + case Type::Arg: return arg_ == other.arg_; + } + } + bool is_none() const { return type == Type::None; } + bool is_atom() const { return type == Type::Atom; } + bool is_list() const { return type == Type::List; } + bool is_arg() const { return type == Type::Arg; } + std::string const &atom() const { log_assert(is_atom()); return atom_; } + std::vector const &list() const { log_assert(is_list()); return list_; } + Arg const &arg() const { log_assert(is_arg()); return arg_; } + unsigned int hash() const { + unsigned int inner; + switch(type) { + case Type::None: inner = 0; break; + case Type::Atom: inner = mkhash(atom_); break; + case Type::List: inner = mkhash(list_); break; + case Type::Arg: inner = arg_.n; break; + } + return mkhash((unsigned int) type, inner); + } + SExpr subst_args(std::vector const & args) const { + switch(type) { + case Type::None: + case Type::Atom: + return *this; + case Type::List: { + vector ret; + for(auto & a : list_) + ret.emplace_back(a.subst_args(args)); + return SExpr(std::move(ret)); + } + case Type::Arg: + if(arg_.n >= 1 && (size_t)arg_.n <= args.size()) + return args[arg_.n - 1]; + else + return *this; + } + } +}; + +std::ostream& operator << (std::ostream &os, SExpr const &s) { + if(s.is_atom()) + os << s.atom(); + else if(s.is_list()){ + os << "("; + bool first = true; + for(auto &el: s.list()) { + if(!first) os << " "; + else first = false; + os << el; + } + os << ")"; + }else if(s.is_arg()) + os << "#" << s.arg().n; + else if(s.is_none()) + os << ""; + else + os << "???"; + return os; +} + +struct SmtlibStruct { + SmtlibScope &scope; + std::string name; + idict members; + vector widths; + vector accessors; + SmtlibStruct(std::string name, SmtlibScope &scope) : scope(scope), name(name) { + } + std::string insert(IdString field, int width) { + if(members.at(field, -1) == -1){ + members(field); + scope.insert(field); + widths.push_back(width); + accessors.push_back(scope.insert(std::string("\\") + name + "_" + RTLIL::unescape_id(field))); + } + return scope[field]; + } + void print(SmtlibWriter &f) { + f.printf("(declare-datatype %s ((%s\n", name.c_str(), name.c_str()); + for (size_t i = 0; i < members.size(); i++) + f.printf(" (%s (_ BitVec %d))\n", accessors[i].c_str(), widths[i]); + f.printf(")))\n"); + } + void print_value(SmtlibWriter &f, dict values, int indentation) { + std::string spaces(indentation, ' '); + f << spaces << "(" << name << "\n"; + for (size_t i = 0; i < members.size(); i++) + f << spaces << " " << values[members[i]] << " ; " << RTLIL::unescape_id(members[i]) << "\n"; + f << spaces << ")\n"; + } + SExpr access(SExpr record, IdString member) { + int i = members.at(member); + return SExpr{accessors[i], record}; + } + std::string operator[](IdString field) { + return scope[field]; + } +}; + +struct Node { + SExpr expr; + int width; + + Node(SExpr &&expr, int width) : expr(std::move(expr)), width(width) {} + + bool operator==(Node const &other) const { + return expr == other.expr && width == other.width; + } + + unsigned int hash() const { + return mkhash(expr.hash(), width); + } +}; + +typedef ComputeGraph SmtlibComputeGraph; + +struct SmtlibModule { + std::string name; + SmtlibScope global_scope; + SmtlibStruct input_struct; + SmtlibStruct output_struct; + SmtlibStruct state_struct; + SmtlibComputeGraph compute_graph; + + SmtlibModule(std::string const &name) : + name(name), + input_struct(name + "_inputs", global_scope), + output_struct(name + "_outputs", global_scope), + state_struct(name + "_state", global_scope) + { } + void calculate_compute_graph(RTLIL::Module *module); + void write(std::ostream &stream); +}; + +class SmtlibComputeGraphFactory { + SmtlibModule &module; + SmtlibComputeGraph &graph; + using T = SmtlibComputeGraph::Ref; + static bool is_single_output(IdString type) + { + auto it = yosys_celltypes.cell_types.find(type); + return it != yosys_celltypes.cell_types.end() && it->second.outputs.size() <= 1; + } + T node(SExpr &&expr, int width, std::initializer_list args) { + return graph.add(Node(std::move(expr), width), 0, args); + } + T shift(const char *name, T a, T b, int y_width, int b_width, bool a_signed = false) { + int width = max(y_width, b_width); + T a_ = y_width < width ? extend(a, y_width, width, a_signed) : a; + T b_ = b_width < width ? extend(b, b_width, width, false) : b; + T y_ = node(SExpr {name, Arg(1), Arg(2)}, width, {a_, b_}); + if(y_width < width) + return slice(y_, width, 0, y_width); + else + return y_; + } + SExpr from_bool(SExpr &&arg) { + return SExpr{"ite", std::move(arg), RTLIL::Const(State::S1), RTLIL::Const(State::S0)}; + } + SExpr to_bool(SExpr &&arg) { + return SExpr{"=", std::move(arg), RTLIL::Const(State::S1)}; + } + SExpr extract(SExpr &&arg, int offset, int out_width) { + return SExpr {SExpr {"_", "extract", offset + out_width - 1, offset}, std::move(arg)}; + } +public: + SmtlibComputeGraphFactory(SmtlibModule &mod) : module(mod), graph(mod.compute_graph) {} + T slice(T a, int in_width, int offset, int out_width) { + assert(offset + out_width <= in_width); + return node(extract(Arg(1), offset, out_width), out_width, {a}); + } + T extend(T a, int in_width, int out_width, bool is_signed) { + assert(in_width < out_width); + if(is_signed) + return node(SExpr {SExpr {"_", "sign_extend", out_width - in_width}, Arg(1)}, out_width, {a}); + else + return node(SExpr {SExpr {"_", "zero_extend", out_width - in_width}, Arg(1)}, out_width, {a}); + } + T concat(T a, int a_width, T b, int b_width) { + return node(SExpr {"concat", Arg(1), Arg(2)}, a_width + b_width, {a, b}); + } + T add(T a, T b, int width) { return node(SExpr {"bvadd", Arg(1), Arg(2)}, width, {a, b}); } + T sub(T a, T b, int width) { return node(SExpr {"bvsub", Arg(1), Arg(2)}, width, {a, b}); } + T bitwise_and(T a, T b, int width) { return node(SExpr {"bvand", Arg(1), Arg(2)}, width, {a, b}); } + T bitwise_or(T a, T b, int width) { return node(SExpr {"bvor", Arg(1), Arg(2)}, width, {a, b}); } + T bitwise_xor(T a, T b, int width) { return node(SExpr {"bvxor", Arg(1), Arg(2)}, width, {a, b}); } + T bitwise_not(T a, int width) { return node(SExpr {"bvnot", Arg(1)}, width, {a}); } + T neg(T a, int width) { return node(SExpr {"bvneg", Arg(1)}, width, {a}); } + T mux(T a, T b, T s, int width) { return node(SExpr {"ite", to_bool(Arg(1)), Arg(2), Arg(3)}, width, {s, a, b}); } + T pmux(T a, T b, T s, int width, int s_width) { + T rv = a; + for(int i = 0; i < s_width; i++) + rv = node(SExpr {"ite", to_bool(extract(Arg(1), i, 1)), extract(Arg(2), width * i, width), Arg(3)}, width, {s, b, rv}); + return rv; + } + T reduce_and(T a, int width) { return node(from_bool(SExpr {"=", Arg(1), RTLIL::Const(State::S1, width)}), 1, {a}); } + T reduce_or(T a, int width) { return node(from_bool(SExpr {"distinct", Arg(1), RTLIL::Const(State::S0, width)}), 1, {a}); } + T reduce_xor(T a, int) { return node(SExpr {"reduce_xor", Arg(1)}, 1, {a}); } + T eq(T a, T b, int) { return node(from_bool(SExpr {"=", Arg(1), Arg(2)}), 1, {a, b}); } + T ne(T a, T b, int) { return node(from_bool(SExpr {"distinct", Arg(1), Arg(2)}), 1, {a, b}); } + T gt(T a, T b, int) { return node(from_bool(SExpr {"bvsgt", Arg(1), Arg(2)}), 1, {a, b}); } + T ge(T a, T b, int) { return node(from_bool(SExpr {"bvsge", Arg(1), Arg(2)}), 1, {a, b}); } + T ugt(T a, T b, int) { return node(from_bool(SExpr {"bvugt", Arg(1), Arg(2)}), 1, {a, b}); } + T uge(T a, T b, int) { return node(from_bool(SExpr {"bvuge", Arg(1), Arg(2)}), 1, {a, b}); } + T logical_shift_left(T a, T b, int y_width, int b_width) { return shift("bvshl", a, b, y_width, b_width); } + T logical_shift_right(T a, T b, int y_width, int b_width) { return shift("bvlshl", a, b, y_width, b_width); } + T arithmetic_shift_right(T a, T b, int y_width, int b_width) { return shift("bvashr", a, b, y_width, b_width, true); } + + T constant(RTLIL::Const value) { return node(SExpr(value), value.size(), {}); } + T input(IdString name, int width) { + module.input_struct.insert(name, width); + return node(module.input_struct.access("inputs", name), width, {}); + } + T state(IdString name, int width) { + module.state_struct.insert(name, width); + return node(module.state_struct.access("current_state", name), width, {}); + } + T cell_output(T cell, IdString type, IdString name, int width) { + if (is_single_output(type)) + return cell; + else + return node(SExpr {"cell_output", Arg(1), RTLIL::unescape_id(name)}, width, {cell}); + } + T multiple(vector args, int width) { + vector expr; + expr.reserve(args.size() + 1); + expr.push_back("multiple"); + for(size_t i = 1; i <= args.size(); i++) + expr.push_back(Arg(i)); + return graph.add(Node(SExpr(std::move(expr)), width), 0, args); + } + T undriven(int width) { + return constant(RTLIL::Const(State::S0, width)); + //return node(SExpr {"undriven"}, width, {}); + } + T create_pending(int width) { + return node(SExpr(), width, {}); + } + void update_pending(T pending, T node) { + assert(pending.function().expr.is_none()); + pending.set_function(Node(Arg(1), pending.function().width)); + pending.append_arg(node); + } + void declare_output(T node, IdString name, int width) { + module.output_struct.insert(name, width); + node.assign_key(name); + } + void declare_state(T node, IdString name, int width) { + module.state_struct.insert(name, width); + node.assign_key(name); + } + void suggest_name(T node, IdString name) { + node.sparse_attr() = name; + } +}; + +void SmtlibModule::calculate_compute_graph(RTLIL::Module *module) +{ + SmtlibComputeGraphFactory factory(*this); + ComputeGraphConstruction construction(factory); + construction.add_module(module); + construction.process_queue(); + + // Perform topo sort and detect SCCs + SmtlibComputeGraph::SccAdaptor compute_graph_scc(compute_graph); + + bool scc = false; + std::vector perm; + topo_sorted_sccs(compute_graph_scc, [&](int *begin, int *end) { + perm.insert(perm.end(), begin, end); + if (end > begin + 1) + { + log_warning("SCC:"); + for (int *i = begin; i != end; ++i) + log(" %d", *i); + log("\n"); + scc = true; + } + }, /* sources_first */ true); + compute_graph.permute(perm); + if(scc) log_error("combinational loops, aborting\n"); + + // Forward $$buf + std::vector alias; + perm.clear(); + + for (int i = 0; i < compute_graph.size(); ++i) + { + auto node = compute_graph[i]; + if (node.function().expr == Arg(1) && node.arg(0).index() < i) + { + int target_index = alias[node.arg(0).index()]; + auto target_node = compute_graph[perm[target_index]]; + if(!target_node.has_sparse_attr() && node.has_sparse_attr()) + target_node.sparse_attr() = node.sparse_attr(); + alias.push_back(target_index); + } + else + { + alias.push_back(GetSize(perm)); + perm.push_back(i); + } + } + compute_graph.permute(perm, alias); +} + +void SmtlibModule::write(std::ostream &stream) +{ + SmtlibWriter f(stream); + + idict node_names; + SmtlibScope locals; + int paren_count = 0; + + input_struct.print(f); + output_struct.print(f); + state_struct.print(f); + + f << "(declare-datatypes ((Pair 2)) ((par (X Y) ((pair (first X) (second Y))))))\n"; + f.printf("(define-fun %s_step ((inputs %s_inputs) (current_state %s_state)) (Pair %s_outputs %s_state)\n", + name.c_str(), name.c_str(), name.c_str(), name.c_str(), name.c_str()); + + for (int i = 0; i < compute_graph.size(); ++i) + { + auto ref = compute_graph[i]; + std::string name; + if(ref.has_sparse_attr()) + name = locals.insert(ref.sparse_attr()); + else + name = locals.insert("\\n" + std::to_string(i)); + node_names(name); + vector args; + args.reserve(ref.size()); + for (int i = 0, end = ref.size(); i != end; ++i) + args.push_back(node_names[ref.arg(i).index()]); + f << " (let " << SExpr{{SExpr{name, ref.function().expr.subst_args(args)}}} << "\n"; + paren_count++; + } + dict outputs; + for(auto &a: compute_graph.keys()) + outputs.insert({a.first, node_names[a.second]}); + f.printf(" (pair \n"); + output_struct.print_value(f, outputs, 6); + state_struct.print_value(f, outputs, 6); + f.printf(" )\n"); + while(paren_count > 0){ + int n = min(paren_count, 80); + f << " " << std::string(n, ')') << "\n"; + paren_count -= n; + } + f.printf(")\n"); +} + +struct FunctionalSmtlibBackend : public Backend +{ + FunctionalSmtlibBackend() : Backend("functional_smtlib", "convert design to SMTLIB using the functional backend") {} + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + } + + void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override + { + log_header(design, "Executing Functional SMTLIB backend.\n"); + + size_t argidx = 1; + extra_args(f, filename, args, argidx, design); + + for (auto module : design->selected_modules()) { + log("Dumping module `%s'.\n", module->name.c_str()); + SmtlibModule smt(RTLIL::unescape_id(module->name)); + smt.calculate_compute_graph(module); + smt.write(*f); + } + } +} FunctionalSmtlibBackend; + +PRIVATE_NAMESPACE_END diff --git a/kernel/graphtools.h b/kernel/graphtools.h index 1a59ef1b537..adf4764c201 100644 --- a/kernel/graphtools.h +++ b/kernel/graphtools.h @@ -32,7 +32,7 @@ class CellSimplifier { Factory &factory; T reduce_shift_width(T b, int b_width, int y_width, int &reduced_b_width) { log_assert(y_width > 0); - int new_width = sizeof(int) * 8 - __builtin_clz(y_width); + int new_width = ceil_log2(y_width + 1); if (b_width <= new_width) { reduced_b_width = b_width; return b; @@ -221,7 +221,7 @@ class ComputeGraphConstruction { for (auto wire : module->wires()) { if (wire->port_output) { T node = enqueue(DriveChunk(DriveChunkWire(wire, 0, wire->width))); - factory.declare_output(node, wire->name); + factory.declare_output(node, wire->name, wire->width); } } } @@ -273,7 +273,7 @@ class ComputeGraphConstruction { for (auto const &conn : cell->connections()) { if (driver_map.celltypes.cell_input(cell->type, conn.first)) { T node = enqueue(DriveChunkPort(cell, conn)); - factory.declare_state(node, cell->name); + factory.declare_state(node, cell->name, port_chunk.width); } } } From 720429b1fd9eef2f87ec4b2d623964ca5b066b0c Mon Sep 17 00:00:00 2001 From: Roland Coeurjoly Date: Wed, 12 Jun 2024 08:32:24 +0200 Subject: [PATCH 12/92] Add test_cell tests for C++ functional backend --- backends/functional/cxx.cc | 93 +++++++++---- backends/functional/cxx_runtime/sim.h | 1 + backends/functional/smtlib.cc | 1 + tests/functional/.gitignore | 4 + .../single_cells/rtlil/test_cell_add_00000.il | 17 +++ .../single_cells/rtlil/test_cell_alu_00000.il | 25 ++++ .../single_cells/rtlil/test_cell_and_00000.il | 17 +++ .../rtlil/test_cell_bmux_00000.il | 14 ++ .../rtlil/test_cell_demux_00000.il | 14 ++ .../single_cells/rtlil/test_cell_div_00000.il | 17 +++ .../rtlil/test_cell_divfloor_00000.il | 17 +++ .../single_cells/rtlil/test_cell_eq_00000.il | 17 +++ .../single_cells/rtlil/test_cell_fa_00000.il | 17 +++ .../single_cells/rtlil/test_cell_ge_00000.il | 17 +++ .../single_cells/rtlil/test_cell_gt_00000.il | 17 +++ .../single_cells/rtlil/test_cell_lcu_00000.il | 15 +++ .../single_cells/rtlil/test_cell_le_00000.il | 17 +++ .../rtlil/test_cell_logic_and_00000.il | 17 +++ .../rtlil/test_cell_logic_not_00000.il | 13 ++ .../rtlil/test_cell_logic_or_00000.il | 17 +++ .../single_cells/rtlil/test_cell_lt_00000.il | 17 +++ .../single_cells/rtlil/test_cell_lut_00000.il | 12 ++ .../rtlil/test_cell_macc_00000.il | 17 +++ .../single_cells/rtlil/test_cell_mod_00000.il | 17 +++ .../rtlil/test_cell_modfloor_00000.il | 17 +++ .../single_cells/rtlil/test_cell_mul_00000.il | 17 +++ .../single_cells/rtlil/test_cell_mux_00000.il | 15 +++ .../single_cells/rtlil/test_cell_ne_00000.il | 17 +++ .../single_cells/rtlil/test_cell_neg_00000.il | 13 ++ .../single_cells/rtlil/test_cell_not_00000.il | 13 ++ .../single_cells/rtlil/test_cell_or_00000.il | 17 +++ .../single_cells/rtlil/test_cell_pos_00000.il | 13 ++ .../rtlil/test_cell_reduce_and_00000.il | 13 ++ .../rtlil/test_cell_reduce_bool_00000.il | 13 ++ .../rtlil/test_cell_reduce_or_00000.il | 13 ++ .../rtlil/test_cell_reduce_xnor_00000.il | 13 ++ .../rtlil/test_cell_reduce_xor_00000.il | 13 ++ .../rtlil/test_cell_shift_00000.il | 17 +++ .../rtlil/test_cell_shiftx_00000.il | 17 +++ .../single_cells/rtlil/test_cell_shl_00000.il | 17 +++ .../single_cells/rtlil/test_cell_shr_00000.il | 17 +++ .../single_cells/rtlil/test_cell_sop_00000.il | 13 ++ .../rtlil/test_cell_sshl_00000.il | 17 +++ .../rtlil/test_cell_sshr_00000.il | 17 +++ .../single_cells/rtlil/test_cell_sub_00000.il | 17 +++ .../rtlil/test_cell_xnor_00000.il | 17 +++ .../single_cells/rtlil/test_cell_xor_00000.il | 17 +++ tests/functional/single_cells/run-test.sh | 83 ++++++++++++ tests/functional/single_cells/vcd_harness.cc | 123 ++++++++++++++++++ 49 files changed, 963 insertions(+), 26 deletions(-) create mode 100644 tests/functional/.gitignore create mode 100644 tests/functional/single_cells/rtlil/test_cell_add_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_alu_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_and_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_bmux_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_demux_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_div_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_divfloor_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_eq_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_fa_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_ge_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_gt_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_lcu_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_le_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_logic_and_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_logic_not_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_logic_or_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_lt_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_lut_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_macc_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_mod_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_modfloor_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_mul_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_mux_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_ne_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_neg_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_not_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_or_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_pos_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_reduce_and_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_reduce_bool_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_reduce_or_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_reduce_xnor_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_reduce_xor_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_shift_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_shiftx_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_shl_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_shr_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_sop_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_sshl_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_sshr_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_sub_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_xnor_00000.il create mode 100644 tests/functional/single_cells/rtlil/test_cell_xor_00000.il create mode 100755 tests/functional/single_cells/run-test.sh create mode 100644 tests/functional/single_cells/vcd_harness.cc diff --git a/backends/functional/cxx.cc b/backends/functional/cxx.cc index c0444e7fe5a..03f33092cdc 100644 --- a/backends/functional/cxx.cc +++ b/backends/functional/cxx.cc @@ -17,6 +17,7 @@ * */ +#include #include "kernel/yosys.h" #include "kernel/drivertools.h" #include "kernel/topo_scc.h" @@ -96,31 +97,70 @@ struct CxxWriter { }; struct CxxStruct { - std::string name; - dict types; - CxxScope scope; - CxxStruct(std::string name) : name(name) { - scope.reserve("out"); - scope.reserve("dump"); - } - void insert(IdString name, std::string type) { - scope.insert(name); - types.insert({name, type}); - } - void print(CxxWriter &f) { - f.printf("struct %s {\n", name.c_str()); - for (auto p : types) { - f.printf("\t%s %s;\n", p.second.c_str(), scope[p.first].c_str()); - } - f.printf("\n\ttemplate void dump(T &out) {\n"); - for (auto p : types) { - f.printf("\t\tout(\"%s\", %s);\n", RTLIL::unescape_id(p.first).c_str(), scope[p.first].c_str()); - } - f.printf("\t}\n};\n\n"); - } - std::string operator[](IdString field) { - return scope[field]; - } + std::string name; + dict types; + CxxScope scope; + bool generate_methods; + int count; + CxxStruct(std::string name, bool generate_methods = false, int count = 0) + : name(name), generate_methods(generate_methods), count(count) { + scope.reserve("out"); + scope.reserve("dump"); + } + void insert(IdString name, std::string type) { + scope.insert(name); + types.insert({name, type}); + } + void print(CxxWriter &f) { + f.printf("struct %s {\n", name.c_str()); + for (auto p : types) { + f.printf("\t%s %s;\n", p.second.c_str(), scope[p.first].c_str()); + } + f.printf("\n\ttemplate void dump(T &out) const {\n"); + for (auto p : types) { + f.printf("\t\tout(\"%s\", %s);\n", RTLIL::unescape_id(p.first).c_str(), scope[p.first].c_str()); + } + f.printf("\t}\n\n"); + + if (generate_methods) { + // Add size method + f.printf("\tint size() const {\n"); + f.printf("\t\treturn %d;\n", count); + f.printf("\t}\n\n"); + + // Add get_input method + f.printf("\tstd::variant<%s> get_input(const int index) {\n", generate_variant_types().c_str()); + f.printf("\t\tswitch (index) {\n"); + int idx = 0; + for (auto p : types) { + f.printf("\t\t\tcase %d: return std::ref(%s);\n", idx, scope[p.first].c_str()); + idx++; + } + f.printf("\t\t\tdefault: throw std::out_of_range(\"Invalid input index\");\n"); + f.printf("\t\t}\n"); + f.printf("\t}\n"); + } + + f.printf("};\n\n"); + }; + std::string operator[](IdString field) { + return scope[field]; + } + private: + std::string generate_variant_types() const { + std::set unique_types; + for (const auto& p : types) { + unique_types.insert("std::reference_wrapper<" + p.second + ">"); + } + std::ostringstream oss; + for (auto it = unique_types.begin(); it != unique_types.end(); ++it) { + if (it != unique_types.begin()) { + oss << ", "; + } + oss << *it; + } + return oss.str(); + } }; struct CxxFunction { @@ -302,7 +342,8 @@ struct FunctionalCxxBackend : public Backend state[ref.function().parameters.begin()->first] = ref.function().width; } f.printf("#include \"sim.h\"\n"); - CxxStruct input_struct(name + "_Inputs"); + f.printf("#include \n"); + CxxStruct input_struct(name + "_Inputs", true, inputs.size()); for (auto const &input : inputs) input_struct.insert(input.first, "Signal<" + std::to_string(input.second) + ">"); CxxStruct output_struct(name + "_Outputs"); diff --git a/backends/functional/cxx_runtime/sim.h b/backends/functional/cxx_runtime/sim.h index 4e7d9b60142..1e5a282639c 100644 --- a/backends/functional/cxx_runtime/sim.h +++ b/backends/functional/cxx_runtime/sim.h @@ -20,6 +20,7 @@ #ifndef SIM_H #define SIM_H +#include #include template diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index f6af797919b..cd11111b9d8 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -17,6 +17,7 @@ * */ +#include #include "kernel/yosys.h" #include "kernel/drivertools.h" #include "kernel/topo_scc.h" diff --git a/tests/functional/.gitignore b/tests/functional/.gitignore new file mode 100644 index 00000000000..543226b49dc --- /dev/null +++ b/tests/functional/.gitignore @@ -0,0 +1,4 @@ +my_module_cxxrtl.cc +my_module_functional_cxx.cc +vcd_harness +*.vcd \ No newline at end of file diff --git a/tests/functional/single_cells/rtlil/test_cell_add_00000.il b/tests/functional/single_cells/rtlil/test_cell_add_00000.il new file mode 100644 index 00000000000..531dabe41fa --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_add_00000.il @@ -0,0 +1,17 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 5 input 1 \A + wire width 4 input 2 \B + wire width 6 output 3 \Y + cell $add \UUT + parameter \A_SIGNED 1 + parameter \A_WIDTH 5 + parameter \B_SIGNED 1 + parameter \B_WIDTH 4 + parameter \Y_WIDTH 6 + connect \A \A + connect \B \B + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_alu_00000.il b/tests/functional/single_cells/rtlil/test_cell_alu_00000.il new file mode 100644 index 00000000000..ebdd3b7343b --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_alu_00000.il @@ -0,0 +1,25 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 8 input 1 \A + wire width 7 input 2 \B + wire input 3 \BI + wire input 4 \CI + wire width 6 output 5 \CO + wire width 6 output 6 \X + wire width 6 output 7 \Y + cell $alu \UUT + parameter \A_SIGNED 0 + parameter \A_WIDTH 8 + parameter \B_SIGNED 0 + parameter \B_WIDTH 7 + parameter \Y_WIDTH 6 + connect \A \A + connect \B \B + connect \BI \BI + connect \CI \CI + connect \CO \CO + connect \X \X + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_and_00000.il b/tests/functional/single_cells/rtlil/test_cell_and_00000.il new file mode 100644 index 00000000000..2578f60b774 --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_and_00000.il @@ -0,0 +1,17 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 2 input 1 \A + wire width 3 input 2 \B + wire width 2 output 3 \Y + cell $and \UUT + parameter \A_SIGNED 1 + parameter \A_WIDTH 2 + parameter \B_SIGNED 1 + parameter \B_WIDTH 3 + parameter \Y_WIDTH 2 + connect \A \A + connect \B \B + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_bmux_00000.il b/tests/functional/single_cells/rtlil/test_cell_bmux_00000.il new file mode 100644 index 00000000000..1b613bd698c --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_bmux_00000.il @@ -0,0 +1,14 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 8 input 1 \A + wire input 2 \S + wire width 4 output 3 \Y + cell $bmux \UUT + parameter \S_WIDTH 1 + parameter \WIDTH 4 + connect \A \A + connect \S \S + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_demux_00000.il b/tests/functional/single_cells/rtlil/test_cell_demux_00000.il new file mode 100644 index 00000000000..dbe2dee62ac --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_demux_00000.il @@ -0,0 +1,14 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 6 input 1 \A + wire width 5 input 2 \S + wire width 192 output 3 \Y + cell $demux \UUT + parameter \S_WIDTH 5 + parameter \WIDTH 6 + connect \A \A + connect \S \S + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_div_00000.il b/tests/functional/single_cells/rtlil/test_cell_div_00000.il new file mode 100644 index 00000000000..6f278a7a079 --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_div_00000.il @@ -0,0 +1,17 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 4 input 1 \A + wire width 6 input 2 \B + wire output 3 \Y + cell $div \UUT + parameter \A_SIGNED 0 + parameter \A_WIDTH 4 + parameter \B_SIGNED 0 + parameter \B_WIDTH 6 + parameter \Y_WIDTH 1 + connect \A \A + connect \B \B + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_divfloor_00000.il b/tests/functional/single_cells/rtlil/test_cell_divfloor_00000.il new file mode 100644 index 00000000000..67fce36b331 --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_divfloor_00000.il @@ -0,0 +1,17 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 4 input 1 \A + wire width 4 input 2 \B + wire width 6 output 3 \Y + cell $divfloor \UUT + parameter \A_SIGNED 0 + parameter \A_WIDTH 4 + parameter \B_SIGNED 0 + parameter \B_WIDTH 4 + parameter \Y_WIDTH 6 + connect \A \A + connect \B \B + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_eq_00000.il b/tests/functional/single_cells/rtlil/test_cell_eq_00000.il new file mode 100644 index 00000000000..06fef02b5b2 --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_eq_00000.il @@ -0,0 +1,17 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 5 input 1 \A + wire input 2 \B + wire width 4 output 3 \Y + cell $eq \UUT + parameter \A_SIGNED 0 + parameter \A_WIDTH 5 + parameter \B_SIGNED 0 + parameter \B_WIDTH 1 + parameter \Y_WIDTH 4 + connect \A \A + connect \B \B + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_fa_00000.il b/tests/functional/single_cells/rtlil/test_cell_fa_00000.il new file mode 100644 index 00000000000..97f0eb0297d --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_fa_00000.il @@ -0,0 +1,17 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire input 1 \A + wire input 2 \B + wire input 3 \C + wire output 4 \X + wire output 5 \Y + cell $fa \UUT + parameter \WIDTH 1 + connect \A \A + connect \B \B + connect \C \C + connect \X \X + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_ge_00000.il b/tests/functional/single_cells/rtlil/test_cell_ge_00000.il new file mode 100644 index 00000000000..0ac72e9433d --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_ge_00000.il @@ -0,0 +1,17 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 3 input 1 \A + wire width 7 input 2 \B + wire width 6 output 3 \Y + cell $ge \UUT + parameter \A_SIGNED 1 + parameter \A_WIDTH 3 + parameter \B_SIGNED 1 + parameter \B_WIDTH 7 + parameter \Y_WIDTH 6 + connect \A \A + connect \B \B + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_gt_00000.il b/tests/functional/single_cells/rtlil/test_cell_gt_00000.il new file mode 100644 index 00000000000..d4498d36f9e --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_gt_00000.il @@ -0,0 +1,17 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 7 input 1 \A + wire width 3 input 2 \B + wire width 4 output 3 \Y + cell $gt \UUT + parameter \A_SIGNED 1 + parameter \A_WIDTH 7 + parameter \B_SIGNED 1 + parameter \B_WIDTH 3 + parameter \Y_WIDTH 4 + connect \A \A + connect \B \B + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_lcu_00000.il b/tests/functional/single_cells/rtlil/test_cell_lcu_00000.il new file mode 100644 index 00000000000..e1bda0b433c --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_lcu_00000.il @@ -0,0 +1,15 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire input 1 \CI + wire width 2 output 2 \CO + wire width 2 input 3 \G + wire width 2 input 4 \P + cell $lcu \UUT + parameter \WIDTH 2 + connect \CI \CI + connect \CO \CO + connect \G \G + connect \P \P + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_le_00000.il b/tests/functional/single_cells/rtlil/test_cell_le_00000.il new file mode 100644 index 00000000000..609d99ce8a4 --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_le_00000.il @@ -0,0 +1,17 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 5 input 1 \A + wire width 4 input 2 \B + wire width 6 output 3 \Y + cell $le \UUT + parameter \A_SIGNED 1 + parameter \A_WIDTH 5 + parameter \B_SIGNED 1 + parameter \B_WIDTH 4 + parameter \Y_WIDTH 6 + connect \A \A + connect \B \B + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_logic_and_00000.il b/tests/functional/single_cells/rtlil/test_cell_logic_and_00000.il new file mode 100644 index 00000000000..2eae6e3fcb1 --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_logic_and_00000.il @@ -0,0 +1,17 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 2 input 1 \A + wire width 7 input 2 \B + wire output 3 \Y + cell $logic_and \UUT + parameter \A_SIGNED 0 + parameter \A_WIDTH 2 + parameter \B_SIGNED 0 + parameter \B_WIDTH 7 + parameter \Y_WIDTH 1 + connect \A \A + connect \B \B + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_logic_not_00000.il b/tests/functional/single_cells/rtlil/test_cell_logic_not_00000.il new file mode 100644 index 00000000000..c7898491ca5 --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_logic_not_00000.il @@ -0,0 +1,13 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 3 input 1 \A + wire width 8 output 2 \Y + cell $logic_not \UUT + parameter \A_SIGNED 1 + parameter \A_WIDTH 3 + parameter \Y_WIDTH 8 + connect \A \A + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_logic_or_00000.il b/tests/functional/single_cells/rtlil/test_cell_logic_or_00000.il new file mode 100644 index 00000000000..7a1ef6cbda0 --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_logic_or_00000.il @@ -0,0 +1,17 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 8 input 1 \A + wire width 7 input 2 \B + wire width 2 output 3 \Y + cell $logic_or \UUT + parameter \A_SIGNED 0 + parameter \A_WIDTH 8 + parameter \B_SIGNED 0 + parameter \B_WIDTH 7 + parameter \Y_WIDTH 2 + connect \A \A + connect \B \B + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_lt_00000.il b/tests/functional/single_cells/rtlil/test_cell_lt_00000.il new file mode 100644 index 00000000000..fd3ee1f8bad --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_lt_00000.il @@ -0,0 +1,17 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 8 input 1 \A + wire width 5 input 2 \B + wire width 6 output 3 \Y + cell $lt \UUT + parameter \A_SIGNED 0 + parameter \A_WIDTH 8 + parameter \B_SIGNED 0 + parameter \B_WIDTH 5 + parameter \Y_WIDTH 6 + connect \A \A + connect \B \B + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_lut_00000.il b/tests/functional/single_cells/rtlil/test_cell_lut_00000.il new file mode 100644 index 00000000000..7c4882bb61a --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_lut_00000.il @@ -0,0 +1,12 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 2 input 1 \A + wire output 2 \Y + cell $lut \UUT + parameter \LUT 4'1111 + parameter \WIDTH 2 + connect \A \A + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_macc_00000.il b/tests/functional/single_cells/rtlil/test_cell_macc_00000.il new file mode 100644 index 00000000000..66b51634e97 --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_macc_00000.il @@ -0,0 +1,17 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 3 input 1 \A + wire width 0 input 2 \B + wire width 2 output 3 \Y + cell $macc \UUT + parameter \A_WIDTH 3 + parameter \B_WIDTH 0 + parameter \CONFIG 10'0110000010 + parameter \CONFIG_WIDTH 10 + parameter \Y_WIDTH 2 + connect \A \A + connect \B { } + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_mod_00000.il b/tests/functional/single_cells/rtlil/test_cell_mod_00000.il new file mode 100644 index 00000000000..a484918f05c --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_mod_00000.il @@ -0,0 +1,17 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 6 input 1 \A + wire width 8 input 2 \B + wire width 2 output 3 \Y + cell $mod \UUT + parameter \A_SIGNED 0 + parameter \A_WIDTH 6 + parameter \B_SIGNED 0 + parameter \B_WIDTH 8 + parameter \Y_WIDTH 2 + connect \A \A + connect \B \B + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_modfloor_00000.il b/tests/functional/single_cells/rtlil/test_cell_modfloor_00000.il new file mode 100644 index 00000000000..6cd00aeea5d --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_modfloor_00000.il @@ -0,0 +1,17 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 5 input 1 \A + wire width 7 input 2 \B + wire width 4 output 3 \Y + cell $modfloor \UUT + parameter \A_SIGNED 0 + parameter \A_WIDTH 5 + parameter \B_SIGNED 0 + parameter \B_WIDTH 7 + parameter \Y_WIDTH 4 + connect \A \A + connect \B \B + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_mul_00000.il b/tests/functional/single_cells/rtlil/test_cell_mul_00000.il new file mode 100644 index 00000000000..c77aaffb87e --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_mul_00000.il @@ -0,0 +1,17 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 6 input 1 \A + wire width 2 input 2 \B + wire width 5 output 3 \Y + cell $mul \UUT + parameter \A_SIGNED 0 + parameter \A_WIDTH 6 + parameter \B_SIGNED 0 + parameter \B_WIDTH 2 + parameter \Y_WIDTH 5 + connect \A \A + connect \B \B + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_mux_00000.il b/tests/functional/single_cells/rtlil/test_cell_mux_00000.il new file mode 100644 index 00000000000..3fc52ac7716 --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_mux_00000.il @@ -0,0 +1,15 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 4 input 1 \A + wire width 4 input 2 \B + wire input 3 \S + wire width 4 output 4 \Y + cell $mux \UUT + parameter \WIDTH 4 + connect \A \A + connect \B \B + connect \S \S + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_ne_00000.il b/tests/functional/single_cells/rtlil/test_cell_ne_00000.il new file mode 100644 index 00000000000..47ba31397ed --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_ne_00000.il @@ -0,0 +1,17 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 7 input 1 \A + wire width 5 input 2 \B + wire width 4 output 3 \Y + cell $ne \UUT + parameter \A_SIGNED 0 + parameter \A_WIDTH 7 + parameter \B_SIGNED 0 + parameter \B_WIDTH 5 + parameter \Y_WIDTH 4 + connect \A \A + connect \B \B + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_neg_00000.il b/tests/functional/single_cells/rtlil/test_cell_neg_00000.il new file mode 100644 index 00000000000..ee4273508d1 --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_neg_00000.il @@ -0,0 +1,13 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 2 input 1 \A + wire width 5 output 2 \Y + cell $neg \UUT + parameter \A_SIGNED 0 + parameter \A_WIDTH 2 + parameter \Y_WIDTH 5 + connect \A \A + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_not_00000.il b/tests/functional/single_cells/rtlil/test_cell_not_00000.il new file mode 100644 index 00000000000..9281ded0dfa --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_not_00000.il @@ -0,0 +1,13 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 7 input 1 \A + wire width 7 output 2 \Y + cell $not \UUT + parameter \A_SIGNED 1 + parameter \A_WIDTH 7 + parameter \Y_WIDTH 7 + connect \A \A + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_or_00000.il b/tests/functional/single_cells/rtlil/test_cell_or_00000.il new file mode 100644 index 00000000000..100ea42cd38 --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_or_00000.il @@ -0,0 +1,17 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 7 input 1 \A + wire input 2 \B + wire width 2 output 3 \Y + cell $or \UUT + parameter \A_SIGNED 1 + parameter \A_WIDTH 7 + parameter \B_SIGNED 1 + parameter \B_WIDTH 1 + parameter \Y_WIDTH 2 + connect \A \A + connect \B \B + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_pos_00000.il b/tests/functional/single_cells/rtlil/test_cell_pos_00000.il new file mode 100644 index 00000000000..182f0acd38f --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_pos_00000.il @@ -0,0 +1,13 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire input 1 \A + wire width 3 output 2 \Y + cell $pos \UUT + parameter \A_SIGNED 1 + parameter \A_WIDTH 1 + parameter \Y_WIDTH 3 + connect \A \A + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_reduce_and_00000.il b/tests/functional/single_cells/rtlil/test_cell_reduce_and_00000.il new file mode 100644 index 00000000000..079ed33ade5 --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_reduce_and_00000.il @@ -0,0 +1,13 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 4 input 1 \A + wire width 5 output 2 \Y + cell $reduce_and \UUT + parameter \A_SIGNED 0 + parameter \A_WIDTH 4 + parameter \Y_WIDTH 5 + connect \A \A + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_reduce_bool_00000.il b/tests/functional/single_cells/rtlil/test_cell_reduce_bool_00000.il new file mode 100644 index 00000000000..a417179c818 --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_reduce_bool_00000.il @@ -0,0 +1,13 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire input 1 \A + wire width 2 output 2 \Y + cell $reduce_bool \UUT + parameter \A_SIGNED 1 + parameter \A_WIDTH 1 + parameter \Y_WIDTH 2 + connect \A \A + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_reduce_or_00000.il b/tests/functional/single_cells/rtlil/test_cell_reduce_or_00000.il new file mode 100644 index 00000000000..881c6dfe3a1 --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_reduce_or_00000.il @@ -0,0 +1,13 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 2 input 1 \A + wire width 7 output 2 \Y + cell $reduce_or \UUT + parameter \A_SIGNED 0 + parameter \A_WIDTH 2 + parameter \Y_WIDTH 7 + connect \A \A + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_reduce_xnor_00000.il b/tests/functional/single_cells/rtlil/test_cell_reduce_xnor_00000.il new file mode 100644 index 00000000000..c06562729ea --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_reduce_xnor_00000.il @@ -0,0 +1,13 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 4 input 1 \A + wire width 4 output 2 \Y + cell $reduce_xnor \UUT + parameter \A_SIGNED 0 + parameter \A_WIDTH 4 + parameter \Y_WIDTH 4 + connect \A \A + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_reduce_xor_00000.il b/tests/functional/single_cells/rtlil/test_cell_reduce_xor_00000.il new file mode 100644 index 00000000000..428d2ea87a2 --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_reduce_xor_00000.il @@ -0,0 +1,13 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 4 input 1 \A + wire output 2 \Y + cell $reduce_xor \UUT + parameter \A_SIGNED 0 + parameter \A_WIDTH 4 + parameter \Y_WIDTH 1 + connect \A \A + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_shift_00000.il b/tests/functional/single_cells/rtlil/test_cell_shift_00000.il new file mode 100644 index 00000000000..f82a2a79df7 --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_shift_00000.il @@ -0,0 +1,17 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire input 1 \A + wire width 6 input 2 \B + wire width 4 output 3 \Y + cell $shift \UUT + parameter \A_SIGNED 1 + parameter \A_WIDTH 1 + parameter \B_SIGNED 1 + parameter \B_WIDTH 6 + parameter \Y_WIDTH 4 + connect \A \A + connect \B \B + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_shiftx_00000.il b/tests/functional/single_cells/rtlil/test_cell_shiftx_00000.il new file mode 100644 index 00000000000..b89ac5bef91 --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_shiftx_00000.il @@ -0,0 +1,17 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire input 1 \A + wire width 5 input 2 \B + wire width 3 output 3 \Y + cell $shiftx \UUT + parameter \A_SIGNED 0 + parameter \A_WIDTH 1 + parameter \B_SIGNED 0 + parameter \B_WIDTH 5 + parameter \Y_WIDTH 3 + connect \A \A + connect \B \B + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_shl_00000.il b/tests/functional/single_cells/rtlil/test_cell_shl_00000.il new file mode 100644 index 00000000000..1cfee2a52b5 --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_shl_00000.il @@ -0,0 +1,17 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 8 input 1 \A + wire width 2 input 2 \B + wire width 3 output 3 \Y + cell $shl \UUT + parameter \A_SIGNED 0 + parameter \A_WIDTH 8 + parameter \B_SIGNED 0 + parameter \B_WIDTH 2 + parameter \Y_WIDTH 3 + connect \A \A + connect \B \B + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_shr_00000.il b/tests/functional/single_cells/rtlil/test_cell_shr_00000.il new file mode 100644 index 00000000000..35dd379099c --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_shr_00000.il @@ -0,0 +1,17 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 7 input 1 \A + wire width 6 input 2 \B + wire width 4 output 3 \Y + cell $shr \UUT + parameter \A_SIGNED 1 + parameter \A_WIDTH 7 + parameter \B_SIGNED 0 + parameter \B_WIDTH 6 + parameter \Y_WIDTH 4 + connect \A \A + connect \B \B + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_sop_00000.il b/tests/functional/single_cells/rtlil/test_cell_sop_00000.il new file mode 100644 index 00000000000..df0f8f20abe --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_sop_00000.il @@ -0,0 +1,13 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 8 input 1 \A + wire output 2 \Y + cell $sop \UUT + parameter \DEPTH 8 + parameter \TABLE 128'10010000100100000101101010001001101000101010010100010000010100000101010100000001001010010110101010101010101000100100011001000110 + parameter \WIDTH 8 + connect \A \A + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_sshl_00000.il b/tests/functional/single_cells/rtlil/test_cell_sshl_00000.il new file mode 100644 index 00000000000..798288754ce --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_sshl_00000.il @@ -0,0 +1,17 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 5 input 1 \A + wire width 3 input 2 \B + wire width 6 output 3 \Y + cell $sshl \UUT + parameter \A_SIGNED 0 + parameter \A_WIDTH 5 + parameter \B_SIGNED 0 + parameter \B_WIDTH 3 + parameter \Y_WIDTH 6 + connect \A \A + connect \B \B + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_sshr_00000.il b/tests/functional/single_cells/rtlil/test_cell_sshr_00000.il new file mode 100644 index 00000000000..3f59a693f24 --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_sshr_00000.il @@ -0,0 +1,17 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 3 input 1 \A + wire width 2 input 2 \B + wire width 2 output 3 \Y + cell $sshr \UUT + parameter \A_SIGNED 1 + parameter \A_WIDTH 3 + parameter \B_SIGNED 0 + parameter \B_WIDTH 2 + parameter \Y_WIDTH 2 + connect \A \A + connect \B \B + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_sub_00000.il b/tests/functional/single_cells/rtlil/test_cell_sub_00000.il new file mode 100644 index 00000000000..cb9ec2f7904 --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_sub_00000.il @@ -0,0 +1,17 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 6 input 1 \A + wire width 6 input 2 \B + wire width 6 output 3 \Y + cell $sub \UUT + parameter \A_SIGNED 0 + parameter \A_WIDTH 6 + parameter \B_SIGNED 0 + parameter \B_WIDTH 6 + parameter \Y_WIDTH 6 + connect \A \A + connect \B \B + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_xnor_00000.il b/tests/functional/single_cells/rtlil/test_cell_xnor_00000.il new file mode 100644 index 00000000000..85bd7d239df --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_xnor_00000.il @@ -0,0 +1,17 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 7 input 1 \A + wire width 8 input 2 \B + wire width 7 output 3 \Y + cell $xnor \UUT + parameter \A_SIGNED 1 + parameter \A_WIDTH 7 + parameter \B_SIGNED 1 + parameter \B_WIDTH 8 + parameter \Y_WIDTH 7 + connect \A \A + connect \B \B + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/rtlil/test_cell_xor_00000.il b/tests/functional/single_cells/rtlil/test_cell_xor_00000.il new file mode 100644 index 00000000000..2359698b55b --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_xor_00000.il @@ -0,0 +1,17 @@ +# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) +autoidx 1 +module \gold + wire width 6 input 1 \A + wire width 2 input 2 \B + wire width 8 output 3 \Y + cell $xor \UUT + parameter \A_SIGNED 0 + parameter \A_WIDTH 6 + parameter \B_SIGNED 0 + parameter \B_WIDTH 2 + parameter \Y_WIDTH 8 + connect \A \A + connect \B \B + connect \Y \Y + end +end diff --git a/tests/functional/single_cells/run-test.sh b/tests/functional/single_cells/run-test.sh new file mode 100755 index 00000000000..67cdf4a11c4 --- /dev/null +++ b/tests/functional/single_cells/run-test.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +# Initialize an array to store the names of failing RTLIL files and their failure types +declare -A failing_files +# Initialize an array to store the names of successful RTLIL files +declare -A successful_files + +# Function to run the test on a given RTLIL file +run_test() { + # Define the common variable for the relative path + BASE_PATH="../../../" + + local rtlil_file=$1 + + # Extract the base name without extension + local base_name=$(basename "$rtlil_file" .v) + + # Run yosys to process each RTLIL file + if ${BASE_PATH}yosys -p "read_rtlil $rtlil_file; write_functional_cxx my_module_functional_cxx.cc"; then + echo "Yosys processed $rtlil_file successfully." + + # Compile the generated C++ files with vcd_harness.cpp + if ${CXX:-g++} -g -fprofile-arcs -ftest-coverage vcd_harness.cc -I ${BASE_PATH}backends/functional/cxx_runtime/ -std=c++17 -o vcd_harness; then + echo "Compilation successful." + # Generate VCD files with base_name + if ./vcd_harness ${base_name}_functional_cxx.vcd; then + + # Run yosys to process each RTLIL file + if ${BASE_PATH}yosys -p "read_rtlil $rtlil_file; sim -r ${base_name}_functional_cxx.vcd -scope gold -vcd ${base_name}_yosys_sim.vcd -timescale 1us -sim-gold"; then + echo "Yosys sim $rtlil_file successfully." + successful_files["$rtlil_file"]="Success" + else + ${BASE_PATH}yosys -p "read_rtlil $rtlil_file; sim -vcd ${base_name}_yosys_sim.vcd -r ${base_name}_functional_cxx.vcd -scope gold -timescale 1us" + echo "Yosys simulation of $rtlil_file failed. There is a discrepancy with functional cxx" + failing_files["$rtlil_file"]="Yosys sim failure" + fi + + else + echo "Failed to generate VCD files for $rtlil_file." + failing_files["$rtlil_file"]="VCD generation failure" + fi + else + echo "Failed to compile harness for $rtlil_file." + failing_files["$rtlil_file"]="Compilation failure" + fi + else + echo "Yosys failed to process $rtlil_file." + failing_files["$rtlil_file"]="Yosys failure" + fi +} + +# Main function to run all tests +run_all_tests() { + # Loop through all RTLIL files in the rtlil directory + for rtlil_file in rtlil/*.il; do + run_test "$rtlil_file" + done + + # Check if the array of failing files is empty + if [ ${#failing_files[@]} -eq 0 ]; then + echo "All files passed." + echo "The following files passed:" + for file in "${!successful_files[@]}"; do + echo "$file" + done + return 0 + else + echo "The following files failed:" + for file in "${!failing_files[@]}"; do + echo "$file: ${failing_files[$file]}" + done + echo "The following files passed:" + for file in "${!successful_files[@]}"; do + echo "$file" + done + return 1 + fi +} + +# If the script is being sourced, do not execute the tests +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + run_all_tests +fi diff --git a/tests/functional/single_cells/vcd_harness.cc b/tests/functional/single_cells/vcd_harness.cc new file mode 100644 index 00000000000..10ab10e428e --- /dev/null +++ b/tests/functional/single_cells/vcd_harness.cc @@ -0,0 +1,123 @@ +#include +#include +#include +#include + +#include "my_module_functional_cxx.cc" + +struct DumpHeader { + std::ofstream &ofs; + DumpHeader(std::ofstream &ofs) : ofs(ofs) {} + template + void operator()(const char *name, Signal value) { + ofs << "$var wire " << n << " " << name[0] << " " << name << " $end\n"; + } +}; + +struct Dump { + std::ofstream &ofs; + Dump(std::ofstream &ofs) : ofs(ofs) {} + template + void operator()(const char *name, Signal value) { + // Bit + if (n == 1) { + ofs << (value[0] ? '1' : '0'); + ofs << name[0] << "\n"; + return; + } + // vector (multi-bit) signals + ofs << "b"; + for (size_t i = n; i-- > 0;) + ofs << (value[i] ? '1' : '0'); + ofs << " " << name[0] << "\n"; + } +}; + +// Function to set all values in a signal to false +template +void set_all_false(Signal& signal) { + std::fill(signal.begin(), signal.end(), false); +} + +template +void set_all_random(Signal& signal) { + std::random_device rd; // Random device for seeding + std::mt19937 gen(rd()); // Mersenne Twister engine + std::bernoulli_distribution dist(0.5); // 50-50 distribution + + for (auto& value : signal) { + value = dist(gen); // Set each value to a random boolean + } +} + +int main(int argc, char **argv) +{ + if (argc != 2) { + std::cerr << "Usage: " << argv[0] << " \n"; + return 1; + } + + const std::string functional_vcd_filename = argv[1]; + + constexpr int steps = 10; + constexpr int number_timescale = 1; + const std::string units_timescale = "us"; + gold_Inputs inputs; + gold_Outputs outputs; + gold_State state; + gold_State next_state; + + std::ofstream vcd_file(functional_vcd_filename); + + vcd_file << "$timescale " << number_timescale << " " << units_timescale << " $end\n"; + vcd_file << "$scope module gold $end\n"; + { + DumpHeader d(vcd_file); + inputs.dump(d); + outputs.dump(d); + state.dump(d); + } + vcd_file << "$enddefinitions $end\n$dumpvars\n"; + vcd_file << "#0\n"; + // Set all signals to false + for (int i = 0; i < inputs.size(); ++i) { + auto input_variant = inputs.get_input(i); + std::visit([](auto& signal_ref) { + set_all_false(signal_ref.get()); + }, input_variant); + } + + gold(inputs, outputs, state, next_state); + { + Dump d(vcd_file); + inputs.dump(d); + outputs.dump(d); + state.dump(d); + } + + for (int step = 0; step < steps; ++step) { + // Functional backend cxx + vcd_file << "#" << (step + 1) << "\n"; + // Set all signals to random + for (int i = 0; i < inputs.size(); ++i) { + auto input_variant = inputs.get_input(i); + std::visit([](auto& signal_ref) { + set_all_random(signal_ref.get()); + }, input_variant); + } + + gold(inputs, outputs, state, next_state); + { + Dump d(vcd_file); + inputs.dump(d); + outputs.dump(d); + state.dump(d); + } + + state = next_state; + } + + vcd_file.close(); + + return 0; +} From 76371d177fa759c000778a0969b2333fbc4dc594 Mon Sep 17 00:00:00 2001 From: Roland Coeurjoly Date: Wed, 12 Jun 2024 09:23:44 +0200 Subject: [PATCH 13/92] Change assert to log_assert --- backends/functional/cxx.cc | 9 ++++----- backends/functional/cxx_runtime/sim.h | 11 +++++------ backends/functional/smtlib.cc | 7 +++---- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/backends/functional/cxx.cc b/backends/functional/cxx.cc index 03f33092cdc..b42e20ca718 100644 --- a/backends/functional/cxx.cc +++ b/backends/functional/cxx.cc @@ -17,7 +17,6 @@ * */ -#include #include "kernel/yosys.h" #include "kernel/drivertools.h" #include "kernel/topo_scc.h" @@ -38,7 +37,7 @@ const char *reserved_keywords[] = { "int","long","mutable","namespace","new","noexcept","not","not_eq", "nullptr","operator","or","or_eq","private","protected","public", "reflexpr","register","reinterpret_cast","requires","return","short", - "signed","sizeof","static","static_assert","static_cast","struct", + "signed","sizeof","static","static_log_assert","static_cast","struct", "switch","synchronized","template","this","thread_local","throw", "true","try","typedef","typeid","typename","union","unsigned", "using","virtual","void","volatile","wchar_t","while","xor","xor_eq", @@ -193,11 +192,11 @@ class CxxComputeGraphFactory { public: CxxComputeGraphFactory(CxxComputeGraph &g) : graph(g) {} T slice(T a, int in_width, int offset, int out_width) { - assert(offset + out_width <= in_width); + log_assert(offset + out_width <= in_width); return graph.add(CxxFunction(ID($$slice), out_width, {{ID(offset), offset}}), 0, std::array{a}); } T extend(T a, int in_width, int out_width, bool is_signed) { - assert(in_width < out_width); + log_assert(in_width < out_width); if(is_signed) return graph.add(CxxFunction(ID($sign_extend), out_width, {{ID(WIDTH), out_width}}), 0, std::array{a}); else @@ -250,7 +249,7 @@ class CxxComputeGraphFactory { return graph.add(CxxFunction(ID($$pending), width), 0); } void update_pending(T pending, T node) { - assert(pending.function().name == ID($$pending)); + log_assert(pending.function().name == ID($$pending)); pending.set_function(CxxFunction(ID($$buf), pending.function().width)); pending.append_arg(node); } diff --git a/backends/functional/cxx_runtime/sim.h b/backends/functional/cxx_runtime/sim.h index 1e5a282639c..75d045ccf35 100644 --- a/backends/functional/cxx_runtime/sim.h +++ b/backends/functional/cxx_runtime/sim.h @@ -20,7 +20,6 @@ #ifndef SIM_H #define SIM_H -#include #include template @@ -250,7 +249,7 @@ Signal $shl(Signal const& a, Signal const &b) { if(nb >= sizeof(int) * 8 - 1) for(size_t i = sizeof(int) * 8 - 1; i < nb; i++) - assert(!b[i]); + log_assert(!b[i]); size_t amount = as_int(b); Signal ret = $const(0); if(amount < n){ @@ -267,7 +266,7 @@ Signal $shr(Signal const& a, Signal const &b) { if(nb >= sizeof(int) * 8 - 1) for(size_t i = sizeof(int) * 8 - 1; i < nb; i++) - assert(!b[i]); + log_assert(!b[i]); size_t amount = as_int(b); Signal ret; for (size_t i = 0; i < n; i++) { @@ -284,7 +283,7 @@ Signal $asr(Signal const& a, Signal const &b) { if(nb >= sizeof(int) * 8 - 1) for(size_t i = sizeof(int) * 8 - 1; i < nb; i++) - assert(!b[i]); + log_assert(!b[i]); size_t amount = as_int(b); Signal ret; for (size_t i = 0; i < n; i++) { @@ -345,7 +344,7 @@ Signal concat(Signal const& a, Signal const& b) template Signal $zero_extend(Signal const& a) { - assert(n >= m); + log_assert(n >= m); Signal ret; std::copy(a.begin(), a.end(), ret.begin()); for(size_t i = m; i < n; i++) @@ -356,7 +355,7 @@ Signal $zero_extend(Signal const& a) template Signal $sign_extend(Signal const& a) { - assert(n >= m); + log_assert(n >= m); Signal ret; std::copy(a.begin(), a.end(), ret.begin()); for(size_t i = m; i < n; i++) diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index cd11111b9d8..7a283cccf26 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -17,7 +17,6 @@ * */ -#include #include "kernel/yosys.h" #include "kernel/drivertools.h" #include "kernel/topo_scc.h" @@ -328,11 +327,11 @@ class SmtlibComputeGraphFactory { public: SmtlibComputeGraphFactory(SmtlibModule &mod) : module(mod), graph(mod.compute_graph) {} T slice(T a, int in_width, int offset, int out_width) { - assert(offset + out_width <= in_width); + log_assert(offset + out_width <= in_width); return node(extract(Arg(1), offset, out_width), out_width, {a}); } T extend(T a, int in_width, int out_width, bool is_signed) { - assert(in_width < out_width); + log_assert(in_width < out_width); if(is_signed) return node(SExpr {SExpr {"_", "sign_extend", out_width - in_width}, Arg(1)}, out_width, {a}); else @@ -399,7 +398,7 @@ class SmtlibComputeGraphFactory { return node(SExpr(), width, {}); } void update_pending(T pending, T node) { - assert(pending.function().expr.is_none()); + log_assert(pending.function().expr.is_none()); pending.set_function(Node(Arg(1), pending.function().width)); pending.append_arg(node); } From 7b29d177ac511cd68a4c51001e6ad3522268b08e Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Wed, 12 Jun 2024 11:27:10 +0100 Subject: [PATCH 14/92] add support for memories to c++ and smtlib functional backends --- backends/functional/cxx.cc | 110 ++++++++++++++++++-------- backends/functional/cxx_runtime/sim.h | 19 +++++ backends/functional/smtlib.cc | 72 ++++++++++++++--- kernel/drivertools.h | 7 ++ kernel/graphtools.h | 68 ++++++++++++++-- 5 files changed, 226 insertions(+), 50 deletions(-) diff --git a/backends/functional/cxx.cc b/backends/functional/cxx.cc index b42e20ca718..08d9ba7914b 100644 --- a/backends/functional/cxx.cc +++ b/backends/functional/cxx.cc @@ -83,6 +83,41 @@ struct CxxScope { } }; +struct CxxType { + bool _is_memory; + int _width; + int _addr_width; +public: + CxxType() : _is_memory(false), _width(0), _addr_width(0) { } + CxxType(int width) : _is_memory(false), _width(width), _addr_width(0) { } + CxxType(int addr_width, int data_width) : _is_memory(true), _width(data_width), _addr_width(addr_width) { } + static CxxType signal(int width) { return CxxType(width); } + static CxxType memory(int addr_width, int data_width) { return CxxType(addr_width, data_width); } + bool is_signal() const { return !_is_memory; } + bool is_memory() const { return _is_memory; } + int width() const { log_assert(is_signal()); return _width; } + int addr_width() const { log_assert(is_memory()); return _addr_width; } + int data_width() const { log_assert(is_memory()); return _width; } + std::string to_string() const { + if(_is_memory) { + return stringf("Memory<%d, %d>", addr_width(), data_width()); + } else { + return stringf("Signal<%d>", width()); + } + } + bool operator ==(CxxType const& other) const { + if(_is_memory != other._is_memory) return false; + if(_is_memory && _addr_width != other._addr_width) return false; + return _width == other._width; + } + unsigned int hash() const { + if(_is_memory) + return mkhash(1, mkhash(_width, _addr_width)); + else + return mkhash(0, _width); + } +}; + struct CxxWriter { std::ostream &f; CxxWriter(std::ostream &out) : f(out) {} @@ -97,7 +132,7 @@ struct CxxWriter { struct CxxStruct { std::string name; - dict types; + dict types; CxxScope scope; bool generate_methods; int count; @@ -106,14 +141,14 @@ struct CxxStruct { scope.reserve("out"); scope.reserve("dump"); } - void insert(IdString name, std::string type) { + void insert(IdString name, CxxType type) { scope.insert(name); types.insert({name, type}); } void print(CxxWriter &f) { f.printf("struct %s {\n", name.c_str()); for (auto p : types) { - f.printf("\t%s %s;\n", p.second.c_str(), scope[p.first].c_str()); + f.printf("\t%s %s;\n", p.second.to_string().c_str(), scope[p.first].c_str()); } f.printf("\n\ttemplate void dump(T &out) const {\n"); for (auto p : types) { @@ -149,7 +184,7 @@ struct CxxStruct { std::string generate_variant_types() const { std::set unique_types; for (const auto& p : types) { - unique_types.insert("std::reference_wrapper<" + p.second + ">"); + unique_types.insert("std::reference_wrapper<" + p.second.to_string() + ">"); } std::ostringstream oss; for (auto it = unique_types.begin(); it != unique_types.end(); ++it) { @@ -164,18 +199,18 @@ struct CxxStruct { struct CxxFunction { IdString name; - int width; + CxxType type; dict parameters; - CxxFunction(IdString name, int width) : name(name), width(width) {} - CxxFunction(IdString name, int width, dict parameters) : name(name), width(width), parameters(parameters) {} + CxxFunction(IdString name, CxxType type) : name(name), type(type) {} + CxxFunction(IdString name, CxxType type, dict parameters) : name(name), type(type), parameters(parameters) {} bool operator==(CxxFunction const &other) const { - return name == other.name && parameters == other.parameters && width == other.width; + return name == other.name && parameters == other.parameters && type == other.type; } unsigned int hash() const { - return mkhash(name.hash(), parameters.hash()); + return mkhash(name.hash(), mkhash(type.hash(), parameters.hash())); } }; @@ -232,6 +267,9 @@ class CxxComputeGraphFactory { } T input(IdString name, int width) { return graph.add(CxxFunction(ID($$input), width, {{name, {}}}), 0); } T state(IdString name, int width) { return graph.add(CxxFunction(ID($$state), width, {{name, {}}}), 0); } + T state_memory(IdString name, int addr_width, int data_width) { + return graph.add(CxxFunction(ID($$state), CxxType::memory(addr_width, data_width), {{name, {}}}), 0); + } T cell_output(T cell, IdString type, IdString name, int width) { if (is_single_output(type)) return cell; @@ -245,12 +283,19 @@ class CxxComputeGraphFactory { return graph.add(CxxFunction(ID($$undriven), width), 0); } + T memory_read(T mem, T addr, int addr_width, int data_width) { + return graph.add(CxxFunction(ID($memory_read), data_width), 0, std::array{mem, addr}); + } + T memory_write(T mem, T addr, T data, int addr_width, int data_width) { + return graph.add(CxxFunction(ID($memory_write), CxxType::memory(addr_width, data_width)), 0, std::array{mem, addr, data}); + } + T create_pending(int width) { return graph.add(CxxFunction(ID($$pending), width), 0); } void update_pending(T pending, T node) { log_assert(pending.function().name == ID($$pending)); - pending.set_function(CxxFunction(ID($$buf), pending.function().width)); + pending.set_function(CxxFunction(ID($$buf), pending.function().type)); pending.append_arg(node); } void declare_output(T node, IdString name, int) { @@ -259,6 +304,9 @@ class CxxComputeGraphFactory { void declare_state(T node, IdString name, int) { node.assign_key(name); } + void declare_state_memory(T node, IdString name, int, int) { + node.assign_key(name); + } void suggest_name(T node, IdString name) { node.sparse_attr() = name; } @@ -312,8 +360,10 @@ struct FunctionalCxxBackend : public Backend { int target_index = alias[node.arg(0).index()]; auto target_node = compute_graph[perm[target_index]]; - if(!target_node.has_sparse_attr() && node.has_sparse_attr()) - target_node.sparse_attr() = node.sparse_attr(); + if(!target_node.has_sparse_attr() && node.has_sparse_attr()){ + IdString id = node.sparse_attr(); + target_node.sparse_attr() = id; + } alias.push_back(target_index); } else @@ -328,7 +378,7 @@ struct FunctionalCxxBackend : public Backend void printCxx(std::ostream &stream, std::string, std::string const & name, CxxComputeGraph &compute_graph) { - dict inputs, state; + dict inputs, state; CxxWriter f(stream); // Dump the compute graph @@ -336,22 +386,22 @@ struct FunctionalCxxBackend : public Backend { auto ref = compute_graph[i]; if(ref.function().name == ID($$input)) - inputs[ref.function().parameters.begin()->first] = ref.function().width; + inputs[ref.function().parameters.begin()->first] = ref.function().type; if(ref.function().name == ID($$state)) - state[ref.function().parameters.begin()->first] = ref.function().width; + state[ref.function().parameters.begin()->first] = ref.function().type; } f.printf("#include \"sim.h\"\n"); f.printf("#include \n"); CxxStruct input_struct(name + "_Inputs", true, inputs.size()); for (auto const &input : inputs) - input_struct.insert(input.first, "Signal<" + std::to_string(input.second) + ">"); + input_struct.insert(input.first, input.second); CxxStruct output_struct(name + "_Outputs"); for (auto const &key : compute_graph.keys()) if(state.count(key.first) == 0) - output_struct.insert(key.first, "Signal<" + std::to_string(compute_graph[key.second].function().width) + ">"); + output_struct.insert(key.first, compute_graph[key.second].function().type); CxxStruct state_struct(name + "_State"); for (auto const &state_var : state) - state_struct.insert(state_var.first, "Signal<" + std::to_string(state_var.second) + ">"); + state_struct.insert(state_var.first, state_var.second); idict node_names; CxxScope locals; @@ -368,7 +418,7 @@ struct FunctionalCxxBackend : public Backend for (int i = 0; i < compute_graph.size(); ++i) { auto ref = compute_graph[i]; - int width = ref.function().width; + auto type = ref.function().type; std::string name; if(ref.has_sparse_attr()) name = locals.insert(ref.sparse_attr()); @@ -376,19 +426,19 @@ struct FunctionalCxxBackend : public Backend name = locals.insert("\\n" + std::to_string(i)); node_names(name); if(ref.function().name == ID($$input)) - f.printf("\tSignal<%d> %s = input.%s;\n", width, name.c_str(), input_struct[ref.function().parameters.begin()->first].c_str()); + f.printf("\t%s %s = input.%s;\n", type.to_string().c_str(), name.c_str(), input_struct[ref.function().parameters.begin()->first].c_str()); else if(ref.function().name == ID($$state)) - f.printf("\tSignal<%d> %s = current_state.%s;\n", width, name.c_str(), state_struct[ref.function().parameters.begin()->first].c_str()); + f.printf("\t%s %s = current_state.%s;\n", type.to_string().c_str(), name.c_str(), state_struct[ref.function().parameters.begin()->first].c_str()); else if(ref.function().name == ID($$buf)) - f.printf("\tSignal<%d> %s = %s;\n", width, name.c_str(), node_names[ref.arg(0).index()].c_str()); + f.printf("\t%s %s = %s;\n", type.to_string().c_str(), name.c_str(), node_names[ref.arg(0).index()].c_str()); else if(ref.function().name == ID($$cell_output)) - f.printf("\tSignal<%d> %s = %s.%s;\n", width, name.c_str(), node_names[ref.arg(0).index()].c_str(), RTLIL::unescape_id(ref.function().parameters.begin()->first).c_str()); + f.printf("\t%s %s = %s.%s;\n", type.to_string().c_str(), name.c_str(), node_names[ref.arg(0).index()].c_str(), RTLIL::unescape_id(ref.function().parameters.begin()->first).c_str()); else if(ref.function().name == ID($$const)){ auto c = ref.function().parameters.begin()->second; if(c.size() <= 32){ - f.printf("\tSignal<%d> %s = $const<%d>(%#x);\n", width, name.c_str(), width, (uint32_t) c.as_int()); + f.printf("\t%s %s = $const<%d>(%#x);\n", type.to_string().c_str(), name.c_str(), type.width(), (uint32_t) c.as_int()); }else{ - f.printf("\tSignal<%d> %s = $const<%d>({%#x", width, name.c_str(), width, (uint32_t) c.as_int()); + f.printf("\t%s %s = $const<%d>({%#x", type.to_string().c_str(), name.c_str(), type.width(), (uint32_t) c.as_int()); while(c.size() > 32){ c = c.extract(32, c.size() - 32); f.printf(", %#x", c.as_int()); @@ -396,9 +446,9 @@ struct FunctionalCxxBackend : public Backend f.printf("});\n"); } }else if(ref.function().name == ID($$undriven)) - f.printf("\tSignal<%d> %s; //undriven\n", width, name.c_str()); + f.printf("\t%s %s; //undriven\n", type.to_string().c_str(), name.c_str()); else if(ref.function().name == ID($$slice)) - f.printf("\tSignal<%d> %s = slice<%d>(%s, %d);\n", width, name.c_str(), width, node_names[ref.arg(0).index()].c_str(), ref.function().parameters.at(ID(offset)).as_int()); + f.printf("\t%s %s = slice<%d>(%s, %d);\n", type.to_string().c_str(), name.c_str(), type.width(), node_names[ref.arg(0).index()].c_str(), ref.function().parameters.at(ID(offset)).as_int()); else if(ref.function().name == ID($$concat)){ f.printf("\tauto %s = concat(", name.c_str()); for (int i = 0, end = ref.size(); i != end; ++i){ @@ -409,11 +459,7 @@ struct FunctionalCxxBackend : public Backend f.printf(");\n"); }else{ f.printf("\t"); - if(ref.function().width > 0) - f.printf("Signal<%d>", ref.function().width); - else - f.printf("%s_Outputs", log_id(ref.function().name)); - f.printf(" %s = %s", name.c_str(), log_id(ref.function().name)); + f.printf("%s %s = %s", type.to_string().c_str(), name.c_str(), log_id(ref.function().name)); if(ref.function().parameters.count(ID(WIDTH))){ f.printf("<%d>", ref.function().parameters.at(ID(WIDTH)).as_int()); } diff --git a/backends/functional/cxx_runtime/sim.h b/backends/functional/cxx_runtime/sim.h index 75d045ccf35..310927f5b7b 100644 --- a/backends/functional/cxx_runtime/sim.h +++ b/backends/functional/cxx_runtime/sim.h @@ -363,4 +363,23 @@ Signal $sign_extend(Signal const& a) return ret; } +template +struct Memory { + std::array, 1< contents; +}; + +template +Signal $memory_read(Memory memory, Signal addr) +{ + return memory.contents[as_int(addr)]; +} + +template +Memory $memory_write(Memory memory, Signal addr, Signal data) +{ + Memory ret = memory; + ret.contents[as_int(addr)] = data; + return ret; +} + #endif diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index 7a283cccf26..a90927ab6f5 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -219,19 +219,54 @@ std::ostream& operator << (std::ostream &os, SExpr const &s) { return os; } +struct SmtlibType { + bool _is_memory; + int _width; + int _addr_width; +public: + SmtlibType() : _is_memory(false), _width(0), _addr_width(0) { } + SmtlibType(int width) : _is_memory(false), _width(width), _addr_width(0) { } + SmtlibType(int addr_width, int data_width) : _is_memory(true), _width(data_width), _addr_width(addr_width) { } + static SmtlibType signal(int width) { return SmtlibType(width); } + static SmtlibType memory(int addr_width, int data_width) { return SmtlibType(addr_width, data_width); } + bool is_signal() const { return !_is_memory; } + bool is_memory() const { return _is_memory; } + int width() const { log_assert(is_signal()); return _width; } + int addr_width() const { log_assert(is_memory()); return _addr_width; } + int data_width() const { log_assert(is_memory()); return _width; } + SExpr to_sexpr() const { + if(_is_memory) { + return SExpr{ "Array", SExpr{ "_", "BitVec", addr_width() }, SExpr{ "_", "BitVec", data_width() }}; + } else { + return SExpr{ "_", "BitVec", width() }; + } + } + bool operator ==(SmtlibType const& other) const { + if(_is_memory != other._is_memory) return false; + if(_is_memory && _addr_width != other._addr_width) return false; + return _width == other._width; + } + unsigned int hash() const { + if(_is_memory) + return mkhash(1, mkhash(_width, _addr_width)); + else + return mkhash(0, _width); + } +}; + struct SmtlibStruct { SmtlibScope &scope; std::string name; idict members; - vector widths; + vector types; vector accessors; SmtlibStruct(std::string name, SmtlibScope &scope) : scope(scope), name(name) { } - std::string insert(IdString field, int width) { + std::string insert(IdString field, SmtlibType type) { if(members.at(field, -1) == -1){ members(field); scope.insert(field); - widths.push_back(width); + types.push_back(type); accessors.push_back(scope.insert(std::string("\\") + name + "_" + RTLIL::unescape_id(field))); } return scope[field]; @@ -239,7 +274,7 @@ struct SmtlibStruct { void print(SmtlibWriter &f) { f.printf("(declare-datatype %s ((%s\n", name.c_str(), name.c_str()); for (size_t i = 0; i < members.size(); i++) - f.printf(" (%s (_ BitVec %d))\n", accessors[i].c_str(), widths[i]); + f << " " << SExpr{accessors[i], types[i].to_sexpr()} << "\n"; f.printf(")))\n"); } void print_value(SmtlibWriter &f, dict values, int indentation) { @@ -260,16 +295,16 @@ struct SmtlibStruct { struct Node { SExpr expr; - int width; + SmtlibType type; - Node(SExpr &&expr, int width) : expr(std::move(expr)), width(width) {} + Node(SExpr &&expr, SmtlibType type) : expr(std::move(expr)), type(type) {} bool operator==(Node const &other) const { - return expr == other.expr && width == other.width; + return expr == other.expr && type == other.type; } unsigned int hash() const { - return mkhash(expr.hash(), width); + return mkhash(expr.hash(), type.hash()); } }; @@ -302,8 +337,8 @@ class SmtlibComputeGraphFactory { auto it = yosys_celltypes.cell_types.find(type); return it != yosys_celltypes.cell_types.end() && it->second.outputs.size() <= 1; } - T node(SExpr &&expr, int width, std::initializer_list args) { - return graph.add(Node(std::move(expr), width), 0, args); + T node(SExpr &&expr, SmtlibType type, std::initializer_list args) { + return graph.add(Node(std::move(expr), type), 0, args); } T shift(const char *name, T a, T b, int y_width, int b_width, bool a_signed = false) { int width = max(y_width, b_width); @@ -367,6 +402,13 @@ class SmtlibComputeGraphFactory { T logical_shift_right(T a, T b, int y_width, int b_width) { return shift("bvlshl", a, b, y_width, b_width); } T arithmetic_shift_right(T a, T b, int y_width, int b_width) { return shift("bvashr", a, b, y_width, b_width, true); } + T memory_read(T mem, T addr, int addr_width, int data_width) { + return node(SExpr {"select", Arg(1), Arg(2)}, data_width, {mem, addr}); + } + T memory_write(T mem, T addr, T data, int addr_width, int data_width) { + return node(SExpr {"store", Arg(1), Arg(2), Arg(3)}, SmtlibType::memory(addr_width, data_width), {mem, addr, data}); + } + T constant(RTLIL::Const value) { return node(SExpr(value), value.size(), {}); } T input(IdString name, int width) { module.input_struct.insert(name, width); @@ -376,6 +418,10 @@ class SmtlibComputeGraphFactory { module.state_struct.insert(name, width); return node(module.state_struct.access("current_state", name), width, {}); } + T state_memory(IdString name, int addr_width, int data_width) { + module.state_struct.insert(name, SmtlibType::memory(addr_width, data_width)); + return node(module.state_struct.access("current_state", name), SmtlibType::memory(addr_width, data_width), {}); + } T cell_output(T cell, IdString type, IdString name, int width) { if (is_single_output(type)) return cell; @@ -399,7 +445,7 @@ class SmtlibComputeGraphFactory { } void update_pending(T pending, T node) { log_assert(pending.function().expr.is_none()); - pending.set_function(Node(Arg(1), pending.function().width)); + pending.set_function(Node(Arg(1), pending.function().type)); pending.append_arg(node); } void declare_output(T node, IdString name, int width) { @@ -410,6 +456,10 @@ class SmtlibComputeGraphFactory { module.state_struct.insert(name, width); node.assign_key(name); } + void declare_state_memory(T node, IdString name, int addr_width, int data_width) { + module.state_struct.insert(name, SmtlibType::memory(addr_width, data_width)); + node.assign_key(name); + } void suggest_name(T node, IdString name) { node.sparse_attr() = name; } diff --git a/kernel/drivertools.h b/kernel/drivertools.h index 1cb835df2cc..48c846b5f79 100644 --- a/kernel/drivertools.h +++ b/kernel/drivertools.h @@ -1064,6 +1064,13 @@ struct DriveSpec append(bit); } + DriveSpec(SigSpec const &sig) + { + // TODO: converting one chunk at a time would be faster + for (auto const &bit : sig.bits()) + append(bit); + } + std::vector const &chunks() const { pack(); return chunks_; } std::vector const &bits() const { unpack(); return bits_; } diff --git a/kernel/graphtools.h b/kernel/graphtools.h index adf4764c201..98046c6be0b 100644 --- a/kernel/graphtools.h +++ b/kernel/graphtools.h @@ -23,6 +23,7 @@ #include "kernel/yosys.h" #include "kernel/drivertools.h" #include "kernel/functional.h" +#include "kernel/mem.h" USING_YOSYS_NAMESPACE YOSYS_NAMESPACE_BEGIN @@ -71,6 +72,11 @@ class CellSimplifier { T reduced_b = reduce_shift_width(b, b_width, y_width, reduced_b_width); return factory.arithmetic_shift_right(a, reduced_b, y_width, reduced_b_width); } + T bitwise_mux(T a, T b, T s, int width) { + T aa = factory.bitwise_and(a, factory.bitwise_not(s, width), width); + T bb = factory.bitwise_and(b, s, width); + return factory.bitwise_or(aa, bb, width); + } CellSimplifier(Factory &f) : factory(f) {} T handle(IdString cellType, dict parameters, dict inputs) { @@ -104,7 +110,7 @@ class CellSimplifier { T b = extend(inputs.at(ID(B)), b_width, width, is_signed); if(cellType.in({ID($eq), ID($eqx)})) return extend(factory.eq(a, b, width), 1, y_width, false); - if(cellType.in({ID($ne), ID($nex)})) + else if(cellType.in({ID($ne), ID($nex)})) return extend(factory.ne(a, b, width), 1, y_width, false); else if(cellType == ID($lt)) return extend(is_signed ? factory.gt(b, a, width) : factory.ugt(b, a, width), 1, y_width, false); @@ -197,6 +203,8 @@ class ComputeGraphConstruction { DriverMap driver_map; Factory& factory; CellSimplifier simplifier; + vector memories_vector; + dict memories; T enqueue(DriveSpec const &spec) { @@ -224,6 +232,45 @@ class ComputeGraphConstruction { factory.declare_output(node, wire->name, wire->width); } } + memories_vector = Mem::get_all_memories(module); + for (auto &mem : memories_vector) { + if (mem.cell != nullptr) + memories[mem.cell] = &mem; + } + } + T concatenate_read_results(Mem *mem, vector results) + { + if(results.size() == 0) + return factory.undriven(0); + T node = results[0]; + int size = results[0].size(); + for(size_t i = 1; i < results.size(); i++) { + node = factory.concat(node, size, results[i], results[i].size()); + size += results[i].size(); + } + return node; + } + T handle_memory(Mem *mem) + { + vector read_results; + int addr_width = ceil_log2(mem->size); + int data_width = mem->width; + T node = factory.state_memory(mem->cell->name, addr_width, data_width); + for (auto &rd : mem->rd_ports) { + log_assert(!rd.clk_enable); + T addr = enqueue(driver_map(DriveSpec(rd.addr))); + read_results.push_back(factory.memory_read(node, addr, addr_width, data_width)); + } + for (auto &wr : mem->wr_ports) { + T en = enqueue(driver_map(DriveSpec(wr.en))); + T addr = enqueue(driver_map(DriveSpec(wr.addr))); + T new_data = enqueue(driver_map(DriveSpec(wr.data))); + T old_data = factory.memory_read(node, addr, addr_width, data_width); + T wr_data = simplifier.bitwise_mux(old_data, new_data, en, data_width); + node = factory.memory_write(node, addr, wr_data, addr_width, data_width); + } + factory.declare_state_memory(node, mem->cell->name, addr_width, data_width); + return concatenate_read_results(mem, read_results); } void process_queue() { @@ -306,13 +353,20 @@ class ComputeGraphConstruction { factory.update_pending(pending, node); } else if (chunk.is_marker()) { Cell *cell = cells[chunk.marker().marker]; - dict connections; - for(auto const &conn : cell->connections()) { - if(driver_map.celltypes.cell_input(cell->type, conn.first)) - connections.insert({ conn.first, enqueue(DriveChunkPort(cell, conn)) }); + if (cell->is_mem_cell()) { + Mem *mem = memories.at(cell, nullptr); + log_assert(mem != nullptr); + T node = handle_memory(mem); + factory.update_pending(pending, node); + } else { + dict connections; + for(auto const &conn : cell->connections()) { + if(driver_map.celltypes.cell_input(cell->type, conn.first)) + connections.insert({ conn.first, enqueue(DriveChunkPort(cell, conn)) }); + } + T node = simplifier.handle(cell->type, cell->parameters, connections); + factory.update_pending(pending, node); } - T node = simplifier.handle(cell->type, cell->parameters, connections); - factory.update_pending(pending, node); } else if (chunk.is_none()) { T node = factory.undriven(chunk.size()); factory.update_pending(pending, node); From 3552a8a2b2cfe9ab487bb44377e48d9f4fc4fd84 Mon Sep 17 00:00:00 2001 From: Roland Coeurjoly Date: Wed, 12 Jun 2024 09:41:42 +0200 Subject: [PATCH 15/92] sim.h cannot use log_assert because does not include yosys headers --- backends/functional/cxx_runtime/sim.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/backends/functional/cxx_runtime/sim.h b/backends/functional/cxx_runtime/sim.h index 310927f5b7b..3a398a13a5a 100644 --- a/backends/functional/cxx_runtime/sim.h +++ b/backends/functional/cxx_runtime/sim.h @@ -21,6 +21,7 @@ #define SIM_H #include +#include template using Signal = std::array; @@ -249,7 +250,7 @@ Signal $shl(Signal const& a, Signal const &b) { if(nb >= sizeof(int) * 8 - 1) for(size_t i = sizeof(int) * 8 - 1; i < nb; i++) - log_assert(!b[i]); + assert(!b[i]); size_t amount = as_int(b); Signal ret = $const(0); if(amount < n){ @@ -266,7 +267,7 @@ Signal $shr(Signal const& a, Signal const &b) { if(nb >= sizeof(int) * 8 - 1) for(size_t i = sizeof(int) * 8 - 1; i < nb; i++) - log_assert(!b[i]); + assert(!b[i]); size_t amount = as_int(b); Signal ret; for (size_t i = 0; i < n; i++) { @@ -283,7 +284,7 @@ Signal $asr(Signal const& a, Signal const &b) { if(nb >= sizeof(int) * 8 - 1) for(size_t i = sizeof(int) * 8 - 1; i < nb; i++) - log_assert(!b[i]); + assert(!b[i]); size_t amount = as_int(b); Signal ret; for (size_t i = 0; i < n; i++) { @@ -344,7 +345,7 @@ Signal concat(Signal const& a, Signal const& b) template Signal $zero_extend(Signal const& a) { - log_assert(n >= m); + assert(n >= m); Signal ret; std::copy(a.begin(), a.end(), ret.begin()); for(size_t i = m; i < n; i++) @@ -355,7 +356,7 @@ Signal $zero_extend(Signal const& a) template Signal $sign_extend(Signal const& a) { - log_assert(n >= m); + assert(n >= m); Signal ret; std::copy(a.begin(), a.end(), ret.begin()); for(size_t i = m; i < n; i++) From dbf2bc3b1dfe0a316e33ada2abb99fa65c8e0616 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Wed, 12 Jun 2024 12:27:43 +0100 Subject: [PATCH 16/92] need unsigned comparison when checking shift widths for overflow in functional backend --- kernel/graphtools.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/graphtools.h b/kernel/graphtools.h index 98046c6be0b..4fb7aacf444 100644 --- a/kernel/graphtools.h +++ b/kernel/graphtools.h @@ -40,7 +40,7 @@ class CellSimplifier { } else { reduced_b_width = new_width; T lower_b = factory.slice(b, b_width, 0, new_width); - T overflow = factory.gt(b, factory.constant(RTLIL::Const(y_width, b_width)), b_width); + T overflow = factory.ugt(b, factory.constant(RTLIL::Const(y_width, b_width)), b_width); return factory.mux(lower_b, factory.constant(RTLIL::Const(y_width, new_width)), overflow, new_width); } } From 248d5f72d41c7194b54f493a692f9f312e198979 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Thu, 20 Jun 2024 16:25:21 +0100 Subject: [PATCH 17/92] add support for std::variant to hashlib --- kernel/hashlib.h | 19 +++++++++++++++++++ kernel/yosys_common.h | 1 + 2 files changed, 20 insertions(+) diff --git a/kernel/hashlib.h b/kernel/hashlib.h index 6b880f3a666..d32db6832fa 100644 --- a/kernel/hashlib.h +++ b/kernel/hashlib.h @@ -186,6 +186,25 @@ inline unsigned int mkhash(const T &v) { return hash_ops().hash(v); } +template<> struct hash_ops { + static inline bool cmp(std::monostate a, std::monostate b) { + return a == b; + } + static inline unsigned int hash(std::monostate) { + return mkhash_init; + } +}; + +template struct hash_ops> { + static inline bool cmp(std::variant a, std::variant b) { + return a == b; + } + static inline unsigned int hash(std::variant a) { + unsigned int h = std::visit([](const auto &v) { return mkhash(v); }, a); + return mkhash(a.index(), h); + } +}; + inline int hashtable_size(int min_size) { // Primes as generated by https://oeis.org/A175953 diff --git a/kernel/yosys_common.h b/kernel/yosys_common.h index 56d2356b102..e71e94a78c5 100644 --- a/kernel/yosys_common.h +++ b/kernel/yosys_common.h @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include From 6f9e21219b8536cd957c49b49f442d4372038590 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Thu, 20 Jun 2024 16:40:02 +0100 Subject: [PATCH 18/92] add new generic compute graph and rewrite c++ functional backend to use it --- Makefile | 2 +- backends/functional/Makefile.inc | 3 +- backends/functional/cxx.cc | 392 +++++++---------------- backends/functional/test_generic.cc | 56 ++++ kernel/functional.h | 22 +- kernel/{graphtools.h => functionalir.cc} | 74 ++++- kernel/functionalir.h | 381 ++++++++++++++++++++++ 7 files changed, 628 insertions(+), 302 deletions(-) create mode 100644 backends/functional/test_generic.cc rename kernel/{graphtools.h => functionalir.cc} (88%) create mode 100644 kernel/functionalir.h diff --git a/Makefile b/Makefile index 812a69271a7..1feeeab95d6 100644 --- a/Makefile +++ b/Makefile @@ -639,7 +639,7 @@ $(eval $(call add_include_file,backends/rtlil/rtlil_backend.h)) OBJS += kernel/driver.o kernel/register.o kernel/rtlil.o kernel/log.o kernel/calc.o kernel/yosys.o OBJS += kernel/binding.o OBJS += kernel/cellaigs.o kernel/celledges.o kernel/cost.o kernel/satgen.o kernel/scopeinfo.o kernel/qcsat.o kernel/mem.o kernel/ffmerge.o kernel/ff.o kernel/yw.o kernel/json.o kernel/fmt.o -OBJS += kernel/drivertools.o +OBJS += kernel/drivertools.o kernel/functionalir.o ifeq ($(ENABLE_ZLIB),1) OBJS += kernel/fstdata.o endif diff --git a/backends/functional/Makefile.inc b/backends/functional/Makefile.inc index c712d2aefe5..f4b968ef371 100644 --- a/backends/functional/Makefile.inc +++ b/backends/functional/Makefile.inc @@ -1,2 +1,3 @@ OBJS += backends/functional/cxx.o -OBJS += backends/functional/smtlib.o +#OBJS += backends/functional/smtlib.o +OBJS += backends/functional/test_generic.o diff --git a/backends/functional/cxx.cc b/backends/functional/cxx.cc index 08d9ba7914b..789ab7949ba 100644 --- a/backends/functional/cxx.cc +++ b/backends/functional/cxx.cc @@ -18,10 +18,7 @@ */ #include "kernel/yosys.h" -#include "kernel/drivertools.h" -#include "kernel/topo_scc.h" -#include "kernel/functional.h" -#include "kernel/graphtools.h" +#include "kernel/functionalir.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN @@ -84,38 +81,17 @@ struct CxxScope { }; struct CxxType { - bool _is_memory; - int _width; - int _addr_width; -public: - CxxType() : _is_memory(false), _width(0), _addr_width(0) { } - CxxType(int width) : _is_memory(false), _width(width), _addr_width(0) { } - CxxType(int addr_width, int data_width) : _is_memory(true), _width(data_width), _addr_width(addr_width) { } - static CxxType signal(int width) { return CxxType(width); } - static CxxType memory(int addr_width, int data_width) { return CxxType(addr_width, data_width); } - bool is_signal() const { return !_is_memory; } - bool is_memory() const { return _is_memory; } - int width() const { log_assert(is_signal()); return _width; } - int addr_width() const { log_assert(is_memory()); return _addr_width; } - int data_width() const { log_assert(is_memory()); return _width; } + FunctionalIR::Sort sort; + CxxType(FunctionalIR::Sort sort) : sort(sort) {} std::string to_string() const { - if(_is_memory) { - return stringf("Memory<%d, %d>", addr_width(), data_width()); + if(sort.is_memory()) { + return stringf("Memory<%d, %d>", sort.addr_width(), sort.data_width()); + } else if(sort.is_signal()) { + return stringf("Signal<%d>", sort.width()); } else { - return stringf("Signal<%d>", width()); + log_error("unknown sort"); } } - bool operator ==(CxxType const& other) const { - if(_is_memory != other._is_memory) return false; - if(_is_memory && _addr_width != other._addr_width) return false; - return _width == other._width; - } - unsigned int hash() const { - if(_is_memory) - return mkhash(1, mkhash(_width, _addr_width)); - else - return mkhash(0, _width); - } }; struct CxxWriter { @@ -135,9 +111,8 @@ struct CxxStruct { dict types; CxxScope scope; bool generate_methods; - int count; - CxxStruct(std::string name, bool generate_methods = false, int count = 0) - : name(name), generate_methods(generate_methods), count(count) { + CxxStruct(std::string name, bool generate_methods = false) + : name(name), generate_methods(generate_methods) { scope.reserve("out"); scope.reserve("dump"); } @@ -159,7 +134,7 @@ struct CxxStruct { if (generate_methods) { // Add size method f.printf("\tint size() const {\n"); - f.printf("\t\treturn %d;\n", count); + f.printf("\t\treturn %d;\n", types.size()); f.printf("\t}\n\n"); // Add get_input method @@ -197,119 +172,87 @@ struct CxxStruct { } }; -struct CxxFunction { - IdString name; - CxxType type; - dict parameters; - - CxxFunction(IdString name, CxxType type) : name(name), type(type) {} - CxxFunction(IdString name, CxxType type, dict parameters) : name(name), type(type), parameters(parameters) {} - - bool operator==(CxxFunction const &other) const { - return name == other.name && parameters == other.parameters && type == other.type; - } - - unsigned int hash() const { - return mkhash(name.hash(), mkhash(type.hash(), parameters.hash())); - } -}; - -typedef ComputeGraph CxxComputeGraph; - -class CxxComputeGraphFactory { - CxxComputeGraph &graph; - using T = CxxComputeGraph::Ref; - static bool is_single_output(IdString type) - { - auto it = yosys_celltypes.cell_types.find(type); - return it != yosys_celltypes.cell_types.end() && it->second.outputs.size() <= 1; - } +struct CxxTemplate { + vector> _v; public: - CxxComputeGraphFactory(CxxComputeGraph &g) : graph(g) {} - T slice(T a, int in_width, int offset, int out_width) { - log_assert(offset + out_width <= in_width); - return graph.add(CxxFunction(ID($$slice), out_width, {{ID(offset), offset}}), 0, std::array{a}); - } - T extend(T a, int in_width, int out_width, bool is_signed) { - log_assert(in_width < out_width); - if(is_signed) - return graph.add(CxxFunction(ID($sign_extend), out_width, {{ID(WIDTH), out_width}}), 0, std::array{a}); - else - return graph.add(CxxFunction(ID($zero_extend), out_width, {{ID(WIDTH), out_width}}), 0, std::array{a}); - } - T concat(T a, int a_width, T b, int b_width) { - return graph.add(CxxFunction(ID($$concat), a_width + b_width), 0, std::array{a, b}); - } - T add(T a, T b, int width) { return graph.add(CxxFunction(ID($add), width), 0, std::array{a, b}); } - T sub(T a, T b, int width) { return graph.add(CxxFunction(ID($sub), width), 0, std::array{a, b}); } - T bitwise_and(T a, T b, int width) { return graph.add(CxxFunction(ID($and), width), 0, std::array{a, b}); } - T bitwise_or(T a, T b, int width) { return graph.add(CxxFunction(ID($or), width), 0, std::array{a, b}); } - T bitwise_xor(T a, T b, int width) { return graph.add(CxxFunction(ID($xor), width), 0, std::array{a, b}); } - T bitwise_not(T a, int width) { return graph.add(CxxFunction(ID($not), width), 0, std::array{a}); } - T neg(T a, int width) { return graph.add(CxxFunction(ID($neg), width), 0, std::array{a}); } - T mux(T a, T b, T s, int width) { return graph.add(CxxFunction(ID($mux), width), 0, std::array{a, b, s}); } - T pmux(T a, T b, T s, int width, int) { return graph.add(CxxFunction(ID($pmux), width), 0, std::array{a, b, s}); } - T reduce_and(T a, int) { return graph.add(CxxFunction(ID($reduce_and), 1), 0, std::array{a}); } - T reduce_or(T a, int) { return graph.add(CxxFunction(ID($reduce_or), 1), 0, std::array{a}); } - T reduce_xor(T a, int) { return graph.add(CxxFunction(ID($reduce_xor), 1), 0, std::array{a}); } - T eq(T a, T b, int) { return graph.add(CxxFunction(ID($eq), 1), 0, std::array{a, b}); } - T ne(T a, T b, int) { return graph.add(CxxFunction(ID($ne), 1), 0, std::array{a, b}); } - T gt(T a, T b, int) { return graph.add(CxxFunction(ID($gt), 1), 0, std::array{a, b}); } - T ge(T a, T b, int) { return graph.add(CxxFunction(ID($ge), 1), 0, std::array{a, b}); } - T ugt(T a, T b, int) { return graph.add(CxxFunction(ID($ugt), 1), 0, std::array{a, b}); } - T uge(T a, T b, int) { return graph.add(CxxFunction(ID($uge), 1), 0, std::array{a, b}); } - T logical_shift_left(T a, T b, int y_width, int) { return graph.add(CxxFunction(ID($shl), y_width, {{ID(WIDTH), y_width}}), 0, std::array{a, b}); } - T logical_shift_right(T a, T b, int y_width, int) { return graph.add(CxxFunction(ID($shr), y_width, {{ID(WIDTH), y_width}}), 0, std::array{a, b}); } - T arithmetic_shift_right(T a, T b, int y_width, int) { return graph.add(CxxFunction(ID($asr), y_width, {{ID(WIDTH), y_width}}), 0, std::array{a, b}); } - - T constant(RTLIL::Const value) { - return graph.add(CxxFunction(ID($$const), value.size(), {{ID(value), value}}), 0); - } - T input(IdString name, int width) { return graph.add(CxxFunction(ID($$input), width, {{name, {}}}), 0); } - T state(IdString name, int width) { return graph.add(CxxFunction(ID($$state), width, {{name, {}}}), 0); } - T state_memory(IdString name, int addr_width, int data_width) { - return graph.add(CxxFunction(ID($$state), CxxType::memory(addr_width, data_width), {{name, {}}}), 0); - } - T cell_output(T cell, IdString type, IdString name, int width) { - if (is_single_output(type)) - return cell; - else - return graph.add(CxxFunction(ID($$cell_output), width, {{name, {}}}), 0, std::array{cell}); - } - T multiple(vector args, int width) { - return graph.add(CxxFunction(ID($$multiple), width), 0, args); - } - T undriven(int width) { - return graph.add(CxxFunction(ID($$undriven), width), 0); - } - - T memory_read(T mem, T addr, int addr_width, int data_width) { - return graph.add(CxxFunction(ID($memory_read), data_width), 0, std::array{mem, addr}); + CxxTemplate(std::string fmt) { + std::string buf; + for(auto it = fmt.begin(); it != fmt.end(); it++){ + if(*it == '%'){ + it++; + log_assert(it != fmt.end()); + if(*it == '%') + buf += *it; + else { + log_assert(*it >= '0' && *it <= '9'); + _v.emplace_back(std::move(buf)); + _v.emplace_back((int)(*it - '0')); + } + }else + buf += *it; + } + if(!buf.empty()) + _v.emplace_back(std::move(buf)); } - T memory_write(T mem, T addr, T data, int addr_width, int data_width) { - return graph.add(CxxFunction(ID($memory_write), CxxType::memory(addr_width, data_width)), 0, std::array{mem, addr, data}); + template static std::string format(CxxTemplate fmt, Args&&... args) { + vector strs = {args...}; + std::string result; + for(auto &v : fmt._v){ + if(std::string *s = std::get_if(&v)) + result += *s; + else if(int *i = std::get_if(&v)) + result += strs[*i]; + else + log_error("missing case"); + } + return result; } +}; - T create_pending(int width) { - return graph.add(CxxFunction(ID($$pending), width), 0); - } - void update_pending(T pending, T node) { - log_assert(pending.function().name == ID($$pending)); - pending.set_function(CxxFunction(ID($$buf), pending.function().type)); - pending.append_arg(node); - } - void declare_output(T node, IdString name, int) { - node.assign_key(name); - } - void declare_state(T node, IdString name, int) { - node.assign_key(name); - } - void declare_state_memory(T node, IdString name, int, int) { - node.assign_key(name); - } - void suggest_name(T node, IdString name) { - node.sparse_attr() = name; +template struct CxxPrintVisitor { + using Node = FunctionalIR::Node; + NodeNames np; + CxxStruct &input_struct; + CxxStruct &state_struct; + CxxPrintVisitor(NodeNames np, CxxStruct &input_struct, CxxStruct &state_struct) : np(np), input_struct(input_struct), state_struct(state_struct) { } + template std::string arg_to_string(T n) { return std::to_string(n); } + template<> std::string arg_to_string(std::string n) { return n; } + template<> std::string arg_to_string(Node n) { return np(n); } + template std::string format(std::string fmt, Args&&... args) { + return CxxTemplate::format(fmt, arg_to_string(args)...); } + std::string buf(Node, Node n) { return np(n); } + std::string slice(Node, Node a, int, int offset, int out_width) { return format("slice<%2>(%0, %1)", a, offset, out_width); } + std::string zero_extend(Node, Node a, int, int out_width) { return format("$zero_extend<%1>(%0)", a, out_width); } + std::string sign_extend(Node, Node a, int, int out_width) { return format("$sign_extend<%1>(%0)", a, out_width); } + std::string concat(Node, Node a, int, Node b, int) { return format("concat(%0, %1)", a, b); } + std::string add(Node, Node a, Node b, int) { return format("$add(%0, %1)", a, b); } + std::string sub(Node, Node a, Node b, int) { return format("$sub(%0, %1)", a, b); } + std::string bitwise_and(Node, Node a, Node b, int) { return format("$and(%0, %1)", a, b); } + std::string bitwise_or(Node, Node a, Node b, int) { return format("$or(%0, %1)", a, b); } + std::string bitwise_xor(Node, Node a, Node b, int) { return format("$xor(%0, %1)", a, b); } + std::string bitwise_not(Node, Node a, int) { return format("$not(%0)", a); } + std::string unary_minus(Node, Node a, int) { return format("$neg(%0)", a); } + std::string reduce_and(Node, Node a, int) { return format("$reduce_and(%0)", a); } + std::string reduce_or(Node, Node a, int) { return format("$reduce_or(%0)", a); } + std::string reduce_xor(Node, Node a, int) { return format("$reduce_xor(%0)", a); } + std::string equal(Node, Node a, Node b, int) { return format("$eq(%0, %1)", a, b); } + std::string not_equal(Node, Node a, Node b, int) { return format("$ne(%0, %1)", a, b); } + std::string signed_greater_than(Node, Node a, Node b, int) { return format("$gt(%0, %1)", a, b); } + std::string signed_greater_equal(Node, Node a, Node b, int) { return format("$ge(%0, %1)", a, b); } + std::string unsigned_greater_than(Node, Node a, Node b, int) { return format("$ugt(%0, %1)", a, b); } + std::string unsigned_greater_equal(Node, Node a, Node b, int) { return format("$uge(%0, %1)", a, b); } + std::string logical_shift_left(Node, Node a, Node b, int, int) { return format("$shl<%2>(%0, %1)", a, b, a.width()); } + std::string logical_shift_right(Node, Node a, Node b, int, int) { return format("$shr<%2>(%0, %1)", a, b, a.width()); } + std::string arithmetic_shift_right(Node, Node a, Node b, int, int) { return format("$asr<%2>(%0, %1)", a, b, a.width()); } + std::string mux(Node, Node a, Node b, Node s, int) { return format("$mux(%0, %1, %2)", a, b, s); } + std::string pmux(Node, Node a, Node b, Node s, int, int) { return format("$pmux(%0, %1, %2)", a, b, s); } + std::string constant(Node, RTLIL::Const value) { return format("$const<%0>(%1)", value.size(), value.as_int()); } + std::string input(Node, IdString name) { return format("input.%0", input_struct[name]); } + std::string state(Node, IdString name) { return format("current_state.%0", state_struct[name]); } + std::string memory_read(Node, Node mem, Node addr, int, int) { return format("$memory_read(%0, %1)", mem, addr); } + std::string memory_write(Node, Node mem, Node addr, Node data, int, int) { return format("$memory_write(%0, %1, %2)", mem, addr, data); } + std::string undriven(Node, int width) { return format("$const<%0>(0)", width); } }; struct FunctionalCxxBackend : public Backend @@ -322,88 +265,24 @@ struct FunctionalCxxBackend : public Backend log("\n"); } - CxxComputeGraph calculate_compute_graph(RTLIL::Module *module) - { - CxxComputeGraph compute_graph; - CxxComputeGraphFactory factory(compute_graph); - ComputeGraphConstruction construction(factory); - construction.add_module(module); - construction.process_queue(); - - // Perform topo sort and detect SCCs - CxxComputeGraph::SccAdaptor compute_graph_scc(compute_graph); - - bool scc = false; - std::vector perm; - topo_sorted_sccs(compute_graph_scc, [&](int *begin, int *end) { - perm.insert(perm.end(), begin, end); - if (end > begin + 1) - { - log_warning("SCC:"); - for (int *i = begin; i != end; ++i) - log(" %d(%s)(%s)", *i, compute_graph[*i].function().name.c_str(), compute_graph[*i].has_sparse_attr() ? compute_graph[*i].sparse_attr().c_str() : ""); - log("\n"); - scc = true; - } - }, /* sources_first */ true); - compute_graph.permute(perm); - if(scc) log_error("combinational loops, aborting\n"); - - // Forward $$buf - std::vector alias; - perm.clear(); - - for (int i = 0; i < compute_graph.size(); ++i) - { - auto node = compute_graph[i]; - if (node.function().name == ID($$buf) && node.arg(0).index() < i) - { - int target_index = alias[node.arg(0).index()]; - auto target_node = compute_graph[perm[target_index]]; - if(!target_node.has_sparse_attr() && node.has_sparse_attr()){ - IdString id = node.sparse_attr(); - target_node.sparse_attr() = id; - } - alias.push_back(target_index); - } - else - { - alias.push_back(GetSize(perm)); - perm.push_back(i); - } - } - compute_graph.permute(perm, alias); - return compute_graph; - } - - void printCxx(std::ostream &stream, std::string, std::string const & name, CxxComputeGraph &compute_graph) + void printCxx(std::ostream &stream, std::string, std::string const & name, Module *module) { - dict inputs, state; + auto ir = FunctionalIR::from_module(module); CxxWriter f(stream); - // Dump the compute graph - for (int i = 0; i < compute_graph.size(); ++i) - { - auto ref = compute_graph[i]; - if(ref.function().name == ID($$input)) - inputs[ref.function().parameters.begin()->first] = ref.function().type; - if(ref.function().name == ID($$state)) - state[ref.function().parameters.begin()->first] = ref.function().type; - } f.printf("#include \"sim.h\"\n"); f.printf("#include \n"); - CxxStruct input_struct(name + "_Inputs", true, inputs.size()); - for (auto const &input : inputs) - input_struct.insert(input.first, input.second); + CxxStruct input_struct(name + "_Inputs", true); + for (auto [name, sort] : ir.inputs()) + input_struct.insert(name, sort); CxxStruct output_struct(name + "_Outputs"); - for (auto const &key : compute_graph.keys()) - if(state.count(key.first) == 0) - output_struct.insert(key.first, compute_graph[key.second].function().type); + for (auto [name, sort] : ir.outputs()) + output_struct.insert(name, sort); CxxStruct state_struct(name + "_State"); - for (auto const &state_var : state) - state_struct.insert(state_var.first, state_var.second); + for (auto [name, sort] : ir.state()) + state_struct.insert(name, sort); - idict node_names; + dict node_names; CxxScope locals; input_struct.print(f); @@ -415,73 +294,17 @@ struct FunctionalCxxBackend : public Backend locals.reserve("output"); locals.reserve("current_state"); locals.reserve("next_state"); - for (int i = 0; i < compute_graph.size(); ++i) - { - auto ref = compute_graph[i]; - auto type = ref.function().type; - std::string name; - if(ref.has_sparse_attr()) - name = locals.insert(ref.sparse_attr()); - else - name = locals.insert("\\n" + std::to_string(i)); - node_names(name); - if(ref.function().name == ID($$input)) - f.printf("\t%s %s = input.%s;\n", type.to_string().c_str(), name.c_str(), input_struct[ref.function().parameters.begin()->first].c_str()); - else if(ref.function().name == ID($$state)) - f.printf("\t%s %s = current_state.%s;\n", type.to_string().c_str(), name.c_str(), state_struct[ref.function().parameters.begin()->first].c_str()); - else if(ref.function().name == ID($$buf)) - f.printf("\t%s %s = %s;\n", type.to_string().c_str(), name.c_str(), node_names[ref.arg(0).index()].c_str()); - else if(ref.function().name == ID($$cell_output)) - f.printf("\t%s %s = %s.%s;\n", type.to_string().c_str(), name.c_str(), node_names[ref.arg(0).index()].c_str(), RTLIL::unescape_id(ref.function().parameters.begin()->first).c_str()); - else if(ref.function().name == ID($$const)){ - auto c = ref.function().parameters.begin()->second; - if(c.size() <= 32){ - f.printf("\t%s %s = $const<%d>(%#x);\n", type.to_string().c_str(), name.c_str(), type.width(), (uint32_t) c.as_int()); - }else{ - f.printf("\t%s %s = $const<%d>({%#x", type.to_string().c_str(), name.c_str(), type.width(), (uint32_t) c.as_int()); - while(c.size() > 32){ - c = c.extract(32, c.size() - 32); - f.printf(", %#x", c.as_int()); - } - f.printf("});\n"); - } - }else if(ref.function().name == ID($$undriven)) - f.printf("\t%s %s; //undriven\n", type.to_string().c_str(), name.c_str()); - else if(ref.function().name == ID($$slice)) - f.printf("\t%s %s = slice<%d>(%s, %d);\n", type.to_string().c_str(), name.c_str(), type.width(), node_names[ref.arg(0).index()].c_str(), ref.function().parameters.at(ID(offset)).as_int()); - else if(ref.function().name == ID($$concat)){ - f.printf("\tauto %s = concat(", name.c_str()); - for (int i = 0, end = ref.size(); i != end; ++i){ - if(i > 0) - f.printf(", "); - f.printf("%s", node_names[ref.arg(i).index()].c_str()); - } - f.printf(");\n"); - }else{ - f.printf("\t"); - f.printf("%s %s = %s", type.to_string().c_str(), name.c_str(), log_id(ref.function().name)); - if(ref.function().parameters.count(ID(WIDTH))){ - f.printf("<%d>", ref.function().parameters.at(ID(WIDTH)).as_int()); - } - f.printf("("); - for (int i = 0, end = ref.size(); i != end; ++i) - f.printf("%s%s", i>0?", ":"", node_names[ref.arg(i).index()].c_str()); - f.printf("); //"); - for (auto const ¶m : ref.function().parameters) - { - if (param.second.empty()) - f.printf("[%s]", log_id(param.first)); - else - f.printf("[%s=%s]", log_id(param.first), log_const(param.second)); - } - f.printf("\n"); - } - } - - for (auto const &key : compute_graph.keys()) + auto node_to_string = [&](FunctionalIR::Node n) { return node_names.at(n.id()); }; + for (auto node : ir) { - f.printf("\t%s.%s = %s;\n", state.count(key.first) > 0 ? "next_state" : "output", state_struct[key.first].c_str(), node_names[key.second].c_str()); + std::string name = locals.insert(node.name()); + node_names.emplace(node.id(), name); + f.printf("\t%s %s = %s;\n", CxxType(node.sort()).to_string().c_str(), name.c_str(), node.visit(CxxPrintVisitor(node_to_string, input_struct, state_struct)).c_str()); } + for (auto [name, sort] : ir.state()) + f.printf("\tnext_state.%s = %s;\n", state_struct[name].c_str(), node_to_string(ir.get_state_next_node(name)).c_str()); + for (auto [name, sort] : ir.outputs()) + f.printf("\toutput.%s = %s;\n", output_struct[name].c_str(), node_to_string(ir.get_output_node(name)).c_str()); f.printf("}\n"); } @@ -494,8 +317,7 @@ struct FunctionalCxxBackend : public Backend for (auto module : design->selected_modules()) { log("Dumping module `%s'.\n", module->name.c_str()); - auto compute_graph = calculate_compute_graph(module); - printCxx(*f, filename, RTLIL::unescape_id(module->name), compute_graph); + printCxx(*f, filename, RTLIL::unescape_id(module->name), module); } } } FunctionalCxxBackend; diff --git a/backends/functional/test_generic.cc b/backends/functional/test_generic.cc new file mode 100644 index 00000000000..172ac7fd6f5 --- /dev/null +++ b/backends/functional/test_generic.cc @@ -0,0 +1,56 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Emily Schmidt + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/yosys.h" +#include "kernel/functionalir.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct FunctionalTestGeneric : public Pass +{ + FunctionalTestGeneric() : Pass("test_generic", "test the generic compute graph") {} + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + } + + void execute(std::vector args, RTLIL::Design *design) override + { + log_header(design, "Executing Test Generic.\n"); + + size_t argidx = 1; + extra_args(args, argidx, design); + + for (auto module : design->selected_modules()) { + log("Dumping module `%s'.\n", module->name.c_str()); + auto fir = FunctionalIR::from_module(module); + for(auto node : fir) + std::cout << RTLIL::unescape_id(node.name()) << " = " << node.to_string([](auto n) { return RTLIL::unescape_id(n.name()); }) << "\n"; + for(auto [name, sort] : fir.outputs()) + std::cout << RTLIL::unescape_id(name) << " = " << RTLIL::unescape_id(fir.get_output_node(name).name()) << "\n"; + for(auto [name, sort] : fir.state()) + std::cout << RTLIL::unescape_id(name) << " = " << RTLIL::unescape_id(fir.get_state_next_node(name).name()) << "\n"; + } + } +} FunctionalCxxBackend; + +PRIVATE_NAMESPACE_END diff --git a/kernel/functional.h b/kernel/functional.h index e5ee8824099..09a8826433c 100644 --- a/kernel/functional.h +++ b/kernel/functional.h @@ -123,6 +123,8 @@ struct ComputeGraph Node &deref() const { this->check(); return this->graph_->nodes[this->index_]; } public: + Ref(BaseRef ref) : Ref(ref.graph_, ref.index_) {} + void set_function(Fn const &function) const { deref().fn_index = this->graph_->functions(function); @@ -224,7 +226,7 @@ struct ComputeGraph } template - Ref add(Fn const &function, Attr const &attr, T const &args) + Ref add(Fn const &function, Attr const &attr, T &&args) { Ref added = add(function, attr); for (auto arg : args) @@ -233,7 +235,23 @@ struct ComputeGraph } template - Ref add(Fn const &function, Attr &&attr, T const &args) + Ref add(Fn const &function, Attr &&attr, T &&args) + { + Ref added = add(function, std::move(attr)); + for (auto arg : args) + added.append_arg(arg); + return added; + } + + Ref add(Fn const &function, Attr const &attr, std::initializer_list args) + { + Ref added = add(function, attr); + for (auto arg : args) + added.append_arg(arg); + return added; + } + + Ref add(Fn const &function, Attr &&attr, std::initializer_list args) { Ref added = add(function, std::move(attr)); for (auto arg : args) diff --git a/kernel/graphtools.h b/kernel/functionalir.cc similarity index 88% rename from kernel/graphtools.h rename to kernel/functionalir.cc index 4fb7aacf444..27765ac26c5 100644 --- a/kernel/graphtools.h +++ b/kernel/functionalir.cc @@ -17,15 +17,8 @@ * */ -#ifndef GRAPHTOOLS_H -#define GRAPHTOOLS_H +#include "kernel/functionalir.h" -#include "kernel/yosys.h" -#include "kernel/drivertools.h" -#include "kernel/functional.h" -#include "kernel/mem.h" - -USING_YOSYS_NAMESPACE YOSYS_NAMESPACE_BEGIN template @@ -196,7 +189,7 @@ class CellSimplifier { }; template -class ComputeGraphConstruction { +class FunctionalIRConstruction { std::deque queue; dict graph_nodes; idict cells; @@ -218,7 +211,7 @@ class ComputeGraphConstruction { return it->second; } public: - ComputeGraphConstruction(Factory &f) : factory(f), simplifier(f) {} + FunctionalIRConstruction(Factory &f) : factory(f), simplifier(f) {} void add_module(Module *module) { driver_map.add(module); @@ -238,8 +231,9 @@ class ComputeGraphConstruction { memories[mem.cell] = &mem; } } - T concatenate_read_results(Mem *mem, vector results) + T concatenate_read_results(Mem *, vector results) { + /* TODO: write code to check that this is ok to do */ if(results.size() == 0) return factory.undriven(0); T node = results[0]; @@ -381,6 +375,60 @@ class ComputeGraphConstruction { } }; -YOSYS_NAMESPACE_END +FunctionalIR FunctionalIR::from_module(Module *module) { + FunctionalIR ir; + auto factory = ir.factory(); + FunctionalIRConstruction ctor(factory); + ctor.add_module(module); + ctor.process_queue(); + ir.topological_sort(); + ir.forward_buf(); + return ir; +} + +void FunctionalIR::topological_sort() { + Graph::SccAdaptor compute_graph_scc(_graph); + bool scc = false; + std::vector perm; + topo_sorted_sccs(compute_graph_scc, [&](int *begin, int *end) { + perm.insert(perm.end(), begin, end); + if (end > begin + 1) + { + log_warning("SCC:"); + for (int *i = begin; i != end; ++i) + log(" %d", *i); + log("\n"); + scc = true; + } + }, /* sources_first */ true); + _graph.permute(perm); + if(scc) log_error("combinational loops, aborting\n"); +} -#endif +void FunctionalIR::forward_buf() { + std::vector perm, alias; + perm.clear(); + + for (int i = 0; i < _graph.size(); ++i) + { + auto node = _graph[i]; + if (node.function().fn() == Fn::buf && node.arg(0).index() < i) + { + int target_index = alias[node.arg(0).index()]; + auto target_node = _graph[perm[target_index]]; + if(!target_node.has_sparse_attr() && node.has_sparse_attr()){ + IdString id = node.sparse_attr(); + target_node.sparse_attr() = id; + } + alias.push_back(target_index); + } + else + { + alias.push_back(GetSize(perm)); + perm.push_back(i); + } + } + _graph.permute(perm, alias); +} + +YOSYS_NAMESPACE_END diff --git a/kernel/functionalir.h b/kernel/functionalir.h new file mode 100644 index 00000000000..7eca9d87f83 --- /dev/null +++ b/kernel/functionalir.h @@ -0,0 +1,381 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Emily Schmidt + * + * 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 FUNCTIONALIR_H +#define FUNCTIONALIR_H + +#include "kernel/yosys.h" +#include "kernel/functional.h" +#include "kernel/drivertools.h" +#include "kernel/mem.h" +#include "kernel/topo_scc.h" + +USING_YOSYS_NAMESPACE +YOSYS_NAMESPACE_BEGIN + +class FunctionalIR { + enum class Fn { + invalid, + buf, + slice, + zero_extend, + sign_extend, + concat, + add, + sub, + bitwise_and, + bitwise_or, + bitwise_xor, + bitwise_not, + reduce_and, + reduce_or, + reduce_xor, + unary_minus, + equal, + not_equal, + signed_greater_than, + signed_greater_equal, + unsigned_greater_than, + unsigned_greater_equal, + logical_shift_left, + logical_shift_right, + arithmetic_shift_right, + mux, + pmux, + constant, + input, + state, + multiple, + undriven, + memory_read, + memory_write + }; +public: + class Sort { + std::variant> _v; + public: + explicit Sort(int width) : _v(width) { } + Sort(int addr_width, int data_width) : _v(std::make_pair(addr_width, data_width)) { } + bool is_signal() const { return _v.index() == 0; } + bool is_memory() const { return _v.index() == 1; } + int width() const { return std::get<0>(_v); } + int addr_width() const { return std::get<1>(_v).first; } + int data_width() const { return std::get<1>(_v).second; } + bool operator==(Sort const& other) const { return _v == other._v; } + unsigned int hash() const { return mkhash(_v); } + }; +private: + class NodeData { + Fn _fn; + std::variant< + std::monostate, + RTLIL::Const, + IdString, + int + > _extra; + public: + NodeData() : _fn(Fn::invalid) {} + NodeData(Fn fn) : _fn(fn) {} + template NodeData(Fn fn, T &&extra) : _fn(fn), _extra(std::forward(extra)) {} + Fn fn() const { return _fn; } + const RTLIL::Const &as_const() const { return std::get(_extra); } + IdString as_idstring() const { return std::get(_extra); } + int as_int() const { return std::get(_extra); } + int hash() const { + return mkhash((unsigned int) _fn, mkhash(_extra)); + } + bool operator==(NodeData const &other) const { + return _fn == other._fn && _extra == other._extra; + } + }; + struct Attr { + Sort sort; + }; + using Graph = ComputeGraph>; + Graph _graph; + dict _inputs; + dict _outputs; + dict _state; + void add_input(IdString name, Sort sort) { + auto [it, found] = _inputs.emplace(name, std::move(sort)); + if(found) + log_assert(it->second == sort); + } + void add_state(IdString name, Sort sort) { + auto [it, found] = _state.emplace(name, std::move(sort)); + if(found) + log_assert(it->second == sort); + } + void add_output(IdString name, Sort sort) { + auto [it, found] = _outputs.emplace(name, std::move(sort)); + if(found) + log_assert(it->second == sort); + } +public: + class Factory; + class Node { + friend class Factory; + friend class FunctionalIR; + Graph::Ref _ref; + Node(Graph::Ref ref) : _ref(ref) { } + operator Graph::Ref() { return _ref; } + template struct PrintVisitor { + NodePrinter np; + PrintVisitor(NodePrinter np) : np(np) { } + std::string buf(Node, Node n) { return "buf(" + np(n) + ")"; } + std::string slice(Node, Node a, int, int offset, int out_width) { return "slice(" + np(a) + ", " + std::to_string(offset) + ", " + std::to_string(out_width) + ")"; } + std::string zero_extend(Node, Node a, int, int out_width) { return "zero_extend(" + np(a) + ", " + std::to_string(out_width) + ")"; } + std::string sign_extend(Node, Node a, int, int out_width) { return "sign_extend(" + np(a) + ", " + std::to_string(out_width) + ")"; } + std::string concat(Node, Node a, int, Node b, int) { return "concat(" + np(a) + ", " + np(b) + ")"; } + std::string add(Node, Node a, Node b, int) { return "add(" + np(a) + ", " + np(b) + ")"; } + std::string sub(Node, Node a, Node b, int) { return "sub(" + np(a) + ", " + np(b) + ")"; } + std::string bitwise_and(Node, Node a, Node b, int) { return "bitwise_and(" + np(a) + ", " + np(b) + ")"; } + std::string bitwise_or(Node, Node a, Node b, int) { return "bitwise_or(" + np(a) + ", " + np(b) + ")"; } + std::string bitwise_xor(Node, Node a, Node b, int) { return "bitwise_xor(" + np(a) + ", " + np(b) + ")"; } + std::string bitwise_not(Node, Node a, int) { return "bitwise_not(" + np(a) + ")"; } + std::string unary_minus(Node, Node a, int) { return "unary_minus(" + np(a) + ")"; } + std::string reduce_and(Node, Node a, int) { return "reduce_and(" + np(a) + ")"; } + std::string reduce_or(Node, Node a, int) { return "reduce_or(" + np(a) + ")"; } + std::string reduce_xor(Node, Node a, int) { return "reduce_xor(" + np(a) + ")"; } + std::string equal(Node, Node a, Node b, int) { return "equal(" + np(a) + ", " + np(b) + ")"; } + std::string not_equal(Node, Node a, Node b, int) { return "not_equal(" + np(a) + ", " + np(b) + ")"; } + std::string signed_greater_than(Node, Node a, Node b, int) { return "signed_greater_than(" + np(a) + ", " + np(b) + ")"; } + std::string signed_greater_equal(Node, Node a, Node b, int) { return "signed_greater_equal(" + np(a) + ", " + np(b) + ")"; } + std::string unsigned_greater_than(Node, Node a, Node b, int) { return "unsigned_greater_than(" + np(a) + ", " + np(b) + ")"; } + std::string unsigned_greater_equal(Node, Node a, Node b, int) { return "unsigned_greater_equal(" + np(a) + ", " + np(b) + ")"; } + std::string logical_shift_left(Node, Node a, Node b, int, int) { return "logical_shift_left(" + np(a) + ", " + np(b) + ")"; } + std::string logical_shift_right(Node, Node a, Node b, int, int) { return "logical_shift_right(" + np(a) + ", " + np(b) + ")"; } + std::string arithmetic_shift_right(Node, Node a, Node b, int, int) { return "arithmetic_shift_right(" + np(a) + ", " + np(b) + ")"; } + std::string mux(Node, Node a, Node b, Node s, int) { return "mux(" + np(a) + ", " + np(b) + ", " + np(s) + ")"; } + std::string pmux(Node, Node a, Node b, Node s, int, int) { return "pmux(" + np(a) + ", " + np(b) + ", " + np(s) + ")"; } + std::string constant(Node, RTLIL::Const value) { return "constant(" + value.as_string() + ")"; } + std::string input(Node, IdString name) { return "input(" + name.str() + ")"; } + std::string state(Node, IdString name) { return "state(" + name.str() + ")"; } + std::string memory_read(Node, Node mem, Node addr, int, int) { return "memory_read(" + np(mem) + ", " + np(addr) + ")"; } + std::string memory_write(Node, Node mem, Node addr, Node data, int, int) { return "memory_write(" + np(mem) + ", " + np(addr) + ", " + np(data) + ")"; } + std::string undriven(Node, int width) { return "undriven(" + std::to_string(width) + ")"; } + }; + public: + int id() const { return _ref.index(); } + IdString name() const { + if(_ref.has_sparse_attr()) + return _ref.sparse_attr(); + else + return std::string("\\n") + std::to_string(id()); + } + Sort sort() const { return _ref.attr().sort; } + int width() const { return sort().width(); } + Node arg(int n) const { return Node(_ref.arg(n)); } + template auto visit(Visitor v) const + { + switch(_ref.function().fn()) { + case Fn::invalid: log_error("invalid node in visit"); break; + case Fn::buf: return v.buf(*this, arg(0)); break; + case Fn::slice: return v.slice(*this, arg(0), arg(0).width(), _ref.function().as_int(), sort().width()); break; + case Fn::zero_extend: return v.zero_extend(*this, arg(0), arg(0).width(), width()); break; + case Fn::sign_extend: return v.sign_extend(*this, arg(0), arg(0).width(), width()); break; + case Fn::concat: return v.concat(*this, arg(0), arg(0).width(), arg(1), arg(1).width()); break; + case Fn::add: return v.add(*this, arg(0), arg(1), sort().width()); break; + case Fn::sub: return v.sub(*this, arg(0), arg(1), sort().width()); break; + case Fn::bitwise_and: return v.bitwise_and(*this, arg(0), arg(1), sort().width()); break; + case Fn::bitwise_or: return v.bitwise_or(*this, arg(0), arg(1), sort().width()); break; + case Fn::bitwise_xor: return v.bitwise_xor(*this, arg(0), arg(1), sort().width()); break; + case Fn::bitwise_not: return v.bitwise_not(*this, arg(0), sort().width()); break; + case Fn::unary_minus: return v.bitwise_not(*this, arg(0), sort().width()); break; + case Fn::reduce_and: return v.reduce_and(*this, arg(0), arg(0).width()); break; + case Fn::reduce_or: return v.reduce_or(*this, arg(0), arg(0).width()); break; + case Fn::reduce_xor: return v.reduce_xor(*this, arg(0), arg(0).width()); break; + case Fn::equal: return v.equal(*this, arg(0), arg(1), arg(0).width()); break; + case Fn::not_equal: return v.not_equal(*this, arg(0), arg(1), arg(0).width()); break; + case Fn::signed_greater_than: return v.signed_greater_than(*this, arg(0), arg(1), arg(0).width()); break; + case Fn::signed_greater_equal: return v.signed_greater_equal(*this, arg(0), arg(1), arg(0).width()); break; + case Fn::unsigned_greater_than: return v.unsigned_greater_than(*this, arg(0), arg(1), arg(0).width()); break; + case Fn::unsigned_greater_equal: return v.unsigned_greater_equal(*this, arg(0), arg(1), arg(0).width()); break; + case Fn::logical_shift_left: return v.logical_shift_left(*this, arg(0), arg(1), arg(0).width(), arg(1).width()); break; + case Fn::logical_shift_right: return v.logical_shift_right(*this, arg(0), arg(1), arg(0).width(), arg(1).width()); break; + case Fn::arithmetic_shift_right: return v.arithmetic_shift_right(*this, arg(0), arg(1), arg(0).width(), arg(1).width()); break; + case Fn::mux: return v.mux(*this, arg(0), arg(1), arg(2), arg(0).width()); break; + case Fn::pmux: return v.pmux(*this, arg(0), arg(1), arg(2), arg(0).width(), arg(2).width()); break; + case Fn::constant: return v.constant(*this, _ref.function().as_const()); break; + case Fn::input: return v.input(*this, _ref.function().as_idstring()); break; + case Fn::state: return v.state(*this, _ref.function().as_idstring()); break; + case Fn::memory_read: return v.memory_read(*this, arg(0), arg(1), arg(1).width(), width()); break; + case Fn::memory_write: return v.memory_write(*this, arg(0), arg(1), arg(2), arg(1).width(), arg(2).width()); break; + case Fn::multiple: log_error("multiple in visit"); break; + case Fn::undriven: return v.undriven(*this, width()); break; + } + } + template std::string to_string(NodePrinter np) + { + return visit(PrintVisitor(np)); + } + /* TODO: delete */ int size() const { return sort().width(); } + }; + class Factory { + FunctionalIR &_ir; + friend class FunctionalIR; + explicit Factory(FunctionalIR &ir) : _ir(ir) {} + Node add(NodeData &&fn, Sort &&sort, std::initializer_list args) { + Graph::Ref ref = _ir._graph.add(std::move(fn), {std::move(sort)}); + for (auto arg : args) + ref.append_arg(Graph::Ref(arg)); + return ref; + } + void check_basic_binary(Node const &a, Node const &b) { log_assert(a.sort().is_signal() && a.sort() == b.sort()); } + void check_shift(Node const &a, Node const &b) { log_assert(a.sort().is_signal() && b.sort().is_signal()); } + void check_unary(Node const &a) { log_assert(a.sort().is_signal()); } + public: + Node slice(Node a, int, int offset, int out_width) { + log_assert(a.sort().is_signal() && offset + out_width <= a.sort().width()); + return add(NodeData(Fn::slice, offset), Sort(out_width), {a}); + } + Node extend(Node a, int, int out_width, bool is_signed) { + log_assert(a.sort().is_signal() && a.sort().width() < out_width); + if(is_signed) + return add(Fn::sign_extend, Sort(out_width), {a}); + else + return add(Fn::zero_extend, Sort(out_width), {a}); + } + Node concat(Node a, int, Node b, int) { + log_assert(a.sort().is_signal() && b.sort().is_signal()); + return add(Fn::concat, Sort(a.sort().width() + b.sort().width()), {a, b}); + } + Node add(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::add, a.sort(), {a, b}); } + Node sub(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::sub, a.sort(), {a, b}); } + Node bitwise_and(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::bitwise_and, a.sort(), {a, b}); } + Node bitwise_or(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::bitwise_or, a.sort(), {a, b}); } + Node bitwise_xor(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::bitwise_xor, a.sort(), {a, b}); } + Node bitwise_not(Node a, int) { check_unary(a); return add(Fn::bitwise_not, a.sort(), {a}); } + Node unary_minus(Node a, int) { check_unary(a); return add(Fn::unary_minus, a.sort(), {a}); } + Node reduce_and(Node a, int) { check_unary(a); return add(Fn::reduce_and, Sort(1), {a}); } + Node reduce_or(Node a, int) { check_unary(a); return add(Fn::reduce_or, Sort(1), {a}); } + Node reduce_xor(Node a, int) { check_unary(a); return add(Fn::reduce_xor, Sort(1), {a}); } + Node equal(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::equal, Sort(1), {a, b}); } + Node not_equal(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::not_equal, Sort(1), {a, b}); } + Node signed_greater_than(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::signed_greater_than, Sort(1), {a, b}); } + Node signed_greater_equal(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::signed_greater_equal, Sort(1), {a, b}); } + Node unsigned_greater_than(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::unsigned_greater_than, Sort(1), {a, b}); } + Node unsigned_greater_equal(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::unsigned_greater_equal, Sort(1), {a, b}); } + Node logical_shift_left(Node a, Node b, int, int) { check_shift(a, b); return add(Fn::logical_shift_left, a.sort(), {a, b}); } + Node logical_shift_right(Node a, Node b, int, int) { check_shift(a, b); return add(Fn::logical_shift_right, a.sort(), {a, b}); } + Node arithmetic_shift_right(Node a, Node b, int, int) { check_shift(a, b); return add(Fn::arithmetic_shift_right, a.sort(), {a, b}); } + Node mux(Node a, Node b, Node s, int) { + log_assert(a.sort().is_signal() && a.sort() == b.sort() && s.sort() == Sort(1)); + return add(Fn::mux, a.sort(), {a, b, s}); + } + Node pmux(Node a, Node b, Node s, int, int) { + log_assert(a.sort().is_signal() && b.sort().is_signal() && s.sort().is_signal() && a.sort().width() * s.sort().width() == b.sort().width()); + return add(Fn::pmux, a.sort(), {a, b, s}); + } + Node memory_read(Node mem, Node addr, int, int) { + log_assert(mem.sort().is_memory() && addr.sort().is_signal() && mem.sort().addr_width() == addr.sort().width()); + return add(Fn::memory_read, Sort(mem.sort().data_width()), {mem, addr}); + } + Node memory_write(Node mem, Node addr, Node data, int, int) { + log_assert(mem.sort().is_memory() && addr.sort().is_signal() && data.sort().is_signal() && + mem.sort().addr_width() == addr.sort().width() && mem.sort().data_width() == data.sort().width()); + return add(Fn::memory_write, mem.sort(), {mem, addr, data}); + } + Node constant(RTLIL::Const value) { + return add(NodeData(Fn::constant, std::move(value)), Sort(value.size()), {}); + } + Node create_pending(int width) { + return add(Fn::buf, Sort(width), {}); + } + void update_pending(Node node, Node value) { + log_assert(node._ref.function() == Fn::buf && node._ref.size() == 0 && node.sort() == value.sort()); + node._ref.append_arg(value._ref); + } + Node input(IdString name, int width) { + _ir.add_input(name, Sort(width)); + return add(NodeData(Fn::input, name), Sort(width), {}); + } + Node state(IdString name, int width) { + _ir.add_state(name, Sort(width)); + return add(NodeData(Fn::state, name), Sort(width), {}); + } + Node state_memory(IdString name, int addr_width, int data_width) { + _ir.add_state(name, Sort(addr_width, data_width)); + return add(NodeData(Fn::state, name), Sort(addr_width, data_width), {}); + } + Node cell_output(Node node, IdString, IdString, int) { + return node; + } + Node multiple(vector args, int width) { + auto node = add(Fn::multiple, Sort(width), {}); + for(const auto &arg : args) + node._ref.append_arg(arg._ref); + return node; + } + Node undriven(int width) { + return add(Fn::undriven, Sort(width), {}); + } + void declare_output(Node node, IdString name, int width) { + _ir.add_output(name, Sort(width)); + node._ref.assign_key({name, false}); + } + void declare_state(Node node, IdString name, int width) { + _ir.add_state(name, Sort(width)); + node._ref.assign_key({name, true}); + } + void declare_state_memory(Node node, IdString name, int addr_width, int data_width) { + _ir.add_state(name, Sort(addr_width, data_width)); + node._ref.assign_key({name, true}); + } + void suggest_name(Node node, IdString name) { + node._ref.sparse_attr() = name; + } + + /* TODO delete this later*/ + Node eq(Node a, Node b, int) { return equal(a, b, 0); } + Node ne(Node a, Node b, int) { return not_equal(a, b, 0); } + Node gt(Node a, Node b, int) { return signed_greater_than(a, b, 0); } + Node ge(Node a, Node b, int) { return signed_greater_equal(a, b, 0); } + Node ugt(Node a, Node b, int) { return unsigned_greater_than(a, b, 0); } + Node uge(Node a, Node b, int) { return unsigned_greater_equal(a, b, 0); } + Node neg(Node a, int) { return unary_minus(a, 0); } + }; + static FunctionalIR from_module(Module *module); + Factory factory() { return Factory(*this); } + int size() const { return _graph.size(); } + Node operator[](int i) { return _graph[i]; } + void topological_sort(); + void forward_buf(); + dict inputs() const { return _inputs; } + dict outputs() const { return _outputs; } + dict state() const { return _state; } + Node get_output_node(IdString name) { return _graph({name, false}); } + Node get_state_next_node(IdString name) { return _graph({name, true}); } + class Iterator { + friend class FunctionalIR; + FunctionalIR &_ir; + int _index; + Iterator(FunctionalIR &ir, int index) : _ir(ir), _index(index) {} + public: + Node operator*() { return _ir._graph[_index]; } + Iterator &operator++() { _index++; return *this; } + bool operator!=(Iterator const &other) const { return _index != other._index; } + }; + Iterator begin() { return Iterator(*this, 0); } + Iterator end() { return Iterator(*this, _graph.size()); } +}; + +YOSYS_NAMESPACE_END + +#endif From 4e370f4426bf53144b322911d0a68c4724d2dfea Mon Sep 17 00:00:00 2001 From: Roland Coeurjoly Date: Fri, 21 Jun 2024 12:59:34 +0200 Subject: [PATCH 19/92] Initial functional SMT backend using functional IR --- backends/functional/Makefile.inc | 2 +- backends/functional/cxx.cc | 4 +- backends/functional/smtlib.cc | 835 +++++++++++-------------------- 3 files changed, 291 insertions(+), 550 deletions(-) diff --git a/backends/functional/Makefile.inc b/backends/functional/Makefile.inc index f4b968ef371..13c962c99ff 100644 --- a/backends/functional/Makefile.inc +++ b/backends/functional/Makefile.inc @@ -1,3 +1,3 @@ OBJS += backends/functional/cxx.o -#OBJS += backends/functional/smtlib.o +OBJS += backends/functional/smtlib.o OBJS += backends/functional/test_generic.o diff --git a/backends/functional/cxx.cc b/backends/functional/cxx.cc index 789ab7949ba..9571385ccb3 100644 --- a/backends/functional/cxx.cc +++ b/backends/functional/cxx.cc @@ -216,8 +216,8 @@ template struct CxxPrintVisitor { CxxStruct &state_struct; CxxPrintVisitor(NodeNames np, CxxStruct &input_struct, CxxStruct &state_struct) : np(np), input_struct(input_struct), state_struct(state_struct) { } template std::string arg_to_string(T n) { return std::to_string(n); } - template<> std::string arg_to_string(std::string n) { return n; } - template<> std::string arg_to_string(Node n) { return np(n); } + std::string arg_to_string(std::string n) { return n; } + std::string arg_to_string(Node n) { return np(n); } template std::string format(std::string fmt, Args&&... args) { return CxxTemplate::format(fmt, arg_to_string(args)...); } diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index a90927ab6f5..a24ab84017f 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -18,574 +18,315 @@ */ #include "kernel/yosys.h" -#include "kernel/drivertools.h" -#include "kernel/topo_scc.h" -#include "kernel/functional.h" -#include "kernel/graphtools.h" +#include "kernel/functionalir.h" +#include +#include +#include +#include +#include USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN -struct SmtlibScope { - pool used_names; - dict name_map; - - SmtlibScope() { - /*for(const char **p = reserved_keywords; *p != nullptr; p++) - reserve(*p);*/ - } - void reserve(std::string name) { - used_names.insert(name); - } - std::string insert(IdString id) { - std::string str = RTLIL::unescape_id(id); - for(size_t i = 0; i < str.size(); i++) - if(!((unsigned char)str[i] < 0x80 && (isalnum(str[i]) || strchr("~!@$%^&*_-+=<>.?/", str[i])))) - str[i] = '_'; - if(used_names.count(str) == 0){ - used_names.insert(str); - name_map.insert({id, str}); - return str; - } - for (int idx = 0 ; ; idx++){ - std::string suffixed = str + "_" + std::to_string(idx); - if (used_names.count(suffixed) == 0) { - used_names.insert(suffixed); - if(name_map.count(id) == 0) - name_map.insert({id, suffixed}); - return suffixed; - } - } - } - std::string operator[](IdString id) { - if(name_map.count(id) > 0) - return name_map[id]; - else - return insert(id); - } -}; +struct SmtScope { + pool used_names; + dict name_map; + + SmtScope() {} + + void reserve(const std::string& name) { + used_names.insert(name); + } + + std::string insert(IdString id) { + std::string name = RTLIL::unescape_id(id); + if (used_names.count(name) == 0) { + used_names.insert(name); + name_map[id] = name; + return name; + } + for (int idx = 0;; ++idx) { + std::string new_name = name + "_" + std::to_string(idx); + if (used_names.count(new_name) == 0) { + used_names.insert(new_name); + name_map[id] = new_name; + return new_name; + } + } + } -struct SmtlibWriter { - std::ostream &f; - SmtlibWriter(std::ostream &out) : f(out) {} - void printf(const char *fmt, ...) - { - va_list va; - va_start(va, fmt); - f << vstringf(fmt, va); - va_end(va); - } - template - SmtlibWriter & operator <<(T &&arg) { - f << std::forward(arg); - return *this; - } + std::string operator[](IdString id) { + if (name_map.count(id)) { + return name_map[id]; + } else { + return insert(id); + } + } }; -struct Arg { - int n; - explicit Arg(int n_) : n(n_) {} - bool operator ==(Arg const &other) const { return n == other.n; } +struct SmtWriter { + std::ostream& stream; + + SmtWriter(std::ostream& out) : stream(out) {} + + void print(const char* fmt, ...) { + va_list args; + va_start(args, fmt); + stream << vstringf(fmt, args); + va_end(args); + } }; -class SExpr { - enum class Type { - None, - List, - Atom, - Arg, - }; - Type type = Type::None; - union { - std::string atom_; - vector list_; - Arg arg_; - }; - void set_none() { - switch(type) { - case Type::None: break; - case Type::Atom: atom_.~basic_string(); break; - case Type::List: list_.~vector(); break; - case Type::Arg: arg_.~Arg(); break; - } - type = Type::None; - } -public: - SExpr() {} - SExpr(const char *atom) : type(Type::Atom), atom_(atom) {} - SExpr(std::string atom) : type(Type::Atom), atom_(atom) {} - SExpr(int n) : type(Type::Atom), atom_(std::to_string(n)) {} - SExpr(RTLIL::Const const & n) : type(Type::Atom) { - new(&atom_) std::string("#b"); - atom_.reserve(n.size() + 2); - for (size_t i = n.size(); i > 0; i--) - if(n[i-1] == State::S1) - atom_ += "1"; - else - atom_ += "0"; - } - SExpr(vector &&list) : type(Type::List), list_(list) {} - SExpr(std::initializer_list list) : type(Type::List), list_(list) {} - SExpr(Arg arg) : type(Type::Arg), arg_(arg) {} - SExpr(SExpr const &other) { *this = other; } - SExpr(SExpr &&other) { *this = std::move(other); } - SExpr &operator =(SExpr const &other) { - set_none(); - switch(other.type) { - case Type::None: break; - case Type::Atom: new(&atom_) std::string(other.atom_); break; - case Type::List: new(&list_) vector(other.list_); break; - case Type::Arg: new(&arg_) Arg(other.arg_); break; - } - type = other.type; - return *this; - } - SExpr &operator =(SExpr &&other) { - set_none(); - switch(other.type) { - case Type::None: break; - case Type::Atom: new(&atom_) std::string(std::move(other.atom_)); break; - case Type::List: new(&list_) vector(std::move(other.list_)); break; - case Type::Arg: new(&arg_) Arg(std::move(other.arg_)); break; - } - type = other.type; - return *this; +template +struct SmtPrintVisitor { + using Node = FunctionalIR::Node; + NodeNames np; + SmtScope &scope; + + SmtPrintVisitor(NodeNames np, SmtScope &scope) + : np(np), scope(scope) {} + + template + std::string arg_to_string(T n) { return std::to_string(n); } + + std::string arg_to_string(std::string n) { return n; } + + std::string arg_to_string(Node n) { return np(n); } + + template + std::string format(std::string fmt, Args&&... args) { + std::vector arg_strings = { arg_to_string(std::forward(args))... }; + for (size_t i = 0; i < arg_strings.size(); ++i) { + std::string placeholder = "%" + std::to_string(i); + size_t pos = 0; + while ((pos = fmt.find(placeholder, pos)) != std::string::npos) { + fmt.replace(pos, placeholder.length(), arg_strings[i]); + pos += arg_strings[i].length(); + } } - ~SExpr() { set_none(); } - bool operator==(SExpr const &other) const { - if(type != other.type) return false; - switch(type) { - case Type::None: return true; - case Type::Atom: return atom_ == other.atom_; - case Type::List: return list_ == other.list_; - case Type::Arg: return arg_ == other.arg_; - } - } - bool is_none() const { return type == Type::None; } - bool is_atom() const { return type == Type::Atom; } - bool is_list() const { return type == Type::List; } - bool is_arg() const { return type == Type::Arg; } - std::string const &atom() const { log_assert(is_atom()); return atom_; } - std::vector const &list() const { log_assert(is_list()); return list_; } - Arg const &arg() const { log_assert(is_arg()); return arg_; } - unsigned int hash() const { - unsigned int inner; - switch(type) { - case Type::None: inner = 0; break; - case Type::Atom: inner = mkhash(atom_); break; - case Type::List: inner = mkhash(list_); break; - case Type::Arg: inner = arg_.n; break; - } - return mkhash((unsigned int) type, inner); - } - SExpr subst_args(std::vector const & args) const { - switch(type) { - case Type::None: - case Type::Atom: - return *this; - case Type::List: { - vector ret; - for(auto & a : list_) - ret.emplace_back(a.subst_args(args)); - return SExpr(std::move(ret)); - } - case Type::Arg: - if(arg_.n >= 1 && (size_t)arg_.n <= args.size()) - return args[arg_.n - 1]; - else - return *this; - } - } + return fmt; + } + std::string buf(Node, Node n) { return np(n); } + + std::string slice(Node, Node a, int, int offset, int out_width) { + return format("(_ extract %1 %2 %0)", np(a), offset, offset + out_width - 1); + } + + std::string zero_extend(Node, Node a, int, int out_width) { + return format("((_ zero_extend %1) %0)", np(a), out_width); + } + + std::string sign_extend(Node, Node a, int, int out_width) { + return format("((_ sign_extend %1) %0)", np(a), out_width); + } + + std::string concat(Node, Node a, int, Node b, int) { + return format("(concat %0 %1)", np(a), np(b)); + } + + std::string add(Node, Node a, Node b, int) { + return format("(bvadd %0 %1)", np(a), np(b)); + } + + std::string sub(Node, Node a, Node b, int) { + return format("(bvsub %0 %1)", np(a), np(b)); + } + + std::string bitwise_and(Node, Node a, Node b, int) { + return format("(bvand %0 %1)", np(a), np(b)); + } + + std::string bitwise_or(Node, Node a, Node b, int) { + return format("(bvor %0 %1)", np(a), np(b)); + } + + std::string bitwise_xor(Node, Node a, Node b, int) { + return format("(bvxor %0 %1)", np(a), np(b)); + } + + std::string bitwise_not(Node, Node a, int) { + return format("(bvnot %0)", np(a)); + } + + std::string unary_minus(Node, Node a, int) { + return format("(bvneg %0)", np(a)); + } + + std::string reduce_and(Node, Node a, int) { + return format("(= %0 #b1)", np(a)); + } + + std::string reduce_or(Node, Node a, int) { + return format("(distinct %0 #b0)", np(a)); + } + + std::string reduce_xor(Node, Node a, int) { + return format("(bvxor_reduce %0)", np(a)); + } + + std::string equal(Node, Node a, Node b, int) { + return format("(= %0 %1)", np(a), np(b)); + } + + std::string not_equal(Node, Node a, Node b, int) { + return format("(distinct %0 %1)", np(a), np(b)); + } + + std::string signed_greater_than(Node, Node a, Node b, int) { + return format("(bvsgt %0 %1)", np(a), np(b)); + } + + std::string signed_greater_equal(Node, Node a, Node b, int) { + return format("(bvsge %0 %1)", np(a), np(b)); + } + + std::string unsigned_greater_than(Node, Node a, Node b, int) { + return format("(bvugt %0 %1)", np(a), np(b)); + } + + std::string unsigned_greater_equal(Node, Node a, Node b, int) { + return format("(bvuge %0 %1)", np(a), np(b)); + } + + std::string logical_shift_left(Node, Node a, Node b, int, int) { + return format("(bvshl %0 %1)", np(a), np(b)); + } + + std::string logical_shift_right(Node, Node a, Node b, int, int) { + return format("(bvlshr %0 %1)", np(a), np(b)); + } + + std::string arithmetic_shift_right(Node, Node a, Node b, int, int) { + return format("(bvashr %0 %1)", np(a), np(b)); + } + + std::string mux(Node, Node a, Node b, Node s, int) { + return format("(ite %2 %0 %1)", np(a), np(b), np(s)); + } + + std::string pmux(Node, Node a, Node b, Node s, int, int) { + // Assume s is a bit vector, combine a and b based on the selection bits + return format("(pmux %0 %1 %2)", np(a), np(b), np(s)); + } + + std::string constant(Node, RTLIL::Const value) { + return format("#b%1", value.as_string()); + } + + std::string input(Node, IdString name) { + return format("%0", scope[name]); + } + + std::string state(Node, IdString name) { + return format("%0", scope[name]); + } + + std::string memory_read(Node, Node mem, Node addr, int, int) { + return format("(select %0 %1)", np(mem), np(addr)); + } + + std::string memory_write(Node, Node mem, Node addr, Node data, int, int) { + return format("(store %0 %1 %2)", np(mem), np(addr), np(data)); + } + + std::string undriven(Node, int width) { + return format("#b%0", std::string(width, '0')); + } }; +struct SmtModule { + std::string name; + SmtScope scope; + FunctionalIR ir; + + SmtModule(const std::string& module_name, FunctionalIR ir) + : name(module_name), ir(std::move(ir)) {} + + void write(std::ostream& out) { + SmtWriter writer(out); + + writer.print("(declare-fun %s () Bool)\n", name.c_str()); + + writer.print("(declare-datatypes () ((Inputs (mk_inputs"); + for (const auto& input : ir.inputs()) { + std::string input_name = scope[input.first]; + writer.print(" (%s (_ BitVec %d))", input_name.c_str(), input.second.width()); + } + writer.print("))))\n"); -std::ostream& operator << (std::ostream &os, SExpr const &s) { - if(s.is_atom()) - os << s.atom(); - else if(s.is_list()){ - os << "("; - bool first = true; - for(auto &el: s.list()) { - if(!first) os << " "; - else first = false; - os << el; - } - os << ")"; - }else if(s.is_arg()) - os << "#" << s.arg().n; - else if(s.is_none()) - os << ""; - else - os << "???"; - return os; -} - -struct SmtlibType { - bool _is_memory; - int _width; - int _addr_width; -public: - SmtlibType() : _is_memory(false), _width(0), _addr_width(0) { } - SmtlibType(int width) : _is_memory(false), _width(width), _addr_width(0) { } - SmtlibType(int addr_width, int data_width) : _is_memory(true), _width(data_width), _addr_width(addr_width) { } - static SmtlibType signal(int width) { return SmtlibType(width); } - static SmtlibType memory(int addr_width, int data_width) { return SmtlibType(addr_width, data_width); } - bool is_signal() const { return !_is_memory; } - bool is_memory() const { return _is_memory; } - int width() const { log_assert(is_signal()); return _width; } - int addr_width() const { log_assert(is_memory()); return _addr_width; } - int data_width() const { log_assert(is_memory()); return _width; } - SExpr to_sexpr() const { - if(_is_memory) { - return SExpr{ "Array", SExpr{ "_", "BitVec", addr_width() }, SExpr{ "_", "BitVec", data_width() }}; - } else { - return SExpr{ "_", "BitVec", width() }; - } - } - bool operator ==(SmtlibType const& other) const { - if(_is_memory != other._is_memory) return false; - if(_is_memory && _addr_width != other._addr_width) return false; - return _width == other._width; - } - unsigned int hash() const { - if(_is_memory) - return mkhash(1, mkhash(_width, _addr_width)); - else - return mkhash(0, _width); - } -}; + writer.print("(declare-datatypes () ((Outputs (mk_outputs"); + for (const auto& output : ir.outputs()) { + std::string output_name = scope[output.first]; + writer.print(" (%s (_ BitVec %d))", output_name.c_str(), output.second.width()); + } + writer.print("))))\n"); -struct SmtlibStruct { - SmtlibScope &scope; - std::string name; - idict members; - vector types; - vector accessors; - SmtlibStruct(std::string name, SmtlibScope &scope) : scope(scope), name(name) { - } - std::string insert(IdString field, SmtlibType type) { - if(members.at(field, -1) == -1){ - members(field); - scope.insert(field); - types.push_back(type); - accessors.push_back(scope.insert(std::string("\\") + name + "_" + RTLIL::unescape_id(field))); - } - return scope[field]; - } - void print(SmtlibWriter &f) { - f.printf("(declare-datatype %s ((%s\n", name.c_str(), name.c_str()); - for (size_t i = 0; i < members.size(); i++) - f << " " << SExpr{accessors[i], types[i].to_sexpr()} << "\n"; - f.printf(")))\n"); - } - void print_value(SmtlibWriter &f, dict values, int indentation) { - std::string spaces(indentation, ' '); - f << spaces << "(" << name << "\n"; - for (size_t i = 0; i < members.size(); i++) - f << spaces << " " << values[members[i]] << " ; " << RTLIL::unescape_id(members[i]) << "\n"; - f << spaces << ")\n"; - } - SExpr access(SExpr record, IdString member) { - int i = members.at(member); - return SExpr{accessors[i], record}; - } - std::string operator[](IdString field) { - return scope[field]; - } -}; + writer.print("(declare-fun state () (_ BitVec 1))\n"); -struct Node { - SExpr expr; - SmtlibType type; + writer.print("(define-fun %s_step ((state (_ BitVec 1)) (inputs Inputs)) Outputs\n", name.c_str()); - Node(SExpr &&expr, SmtlibType type) : expr(std::move(expr)), type(type) {} + writer.print(" (let (\n"); + for (const auto& input : ir.inputs()) { + std::string input_name = scope[input.first]; + writer.print(" (%s (%s inputs))\n", input_name.c_str(), input_name.c_str()); + } + writer.print(" )\n"); + + auto node_to_string = [&](FunctionalIR::Node n) { return scope[n.name()]; }; + SmtPrintVisitor visitor(node_to_string, scope); + writer.print(" (let (\n"); + for (auto it = ir.begin(); it != ir.end(); ++it) { + const FunctionalIR::Node& node = *it; + + if (ir.inputs().count(node.name()) > 0) { + continue; + } + + std::string node_name = scope[node.name()]; + std::string node_expr = node.visit(visitor); + writer.print(" (%s %s)\n", node_name.c_str(), node_expr.c_str()); + } + writer.print(" )\n"); - bool operator==(Node const &other) const { - return expr == other.expr && type == other.type; - } + writer.print(" (let (\n"); + for (const auto& output : ir.outputs()) { + std::string output_name = scope[output.first]; + // writer.print(" (%s %s)\n", output_name.c_str(), scope[output_name].c_str()); + } + writer.print(" )\n"); - unsigned int hash() const { - return mkhash(expr.hash(), type.hash()); - } + writer.print(" (mk_outputs\n"); + for (const auto& output : ir.outputs()) { + std::string output_name = scope[output.first]; + writer.print(" %s\n", output_name.c_str()); + } + writer.print(" )\n"); + writer.print(" )\n"); + writer.print(" )\n"); + writer.print(" )\n"); + writer.print(")\n"); + } }; -typedef ComputeGraph SmtlibComputeGraph; - -struct SmtlibModule { - std::string name; - SmtlibScope global_scope; - SmtlibStruct input_struct; - SmtlibStruct output_struct; - SmtlibStruct state_struct; - SmtlibComputeGraph compute_graph; - - SmtlibModule(std::string const &name) : - name(name), - input_struct(name + "_inputs", global_scope), - output_struct(name + "_outputs", global_scope), - state_struct(name + "_state", global_scope) - { } - void calculate_compute_graph(RTLIL::Module *module); - void write(std::ostream &stream); -}; +struct FunctionalSmtBackend : public Backend { + FunctionalSmtBackend() : Backend("functional_smt2", "Generate SMT-LIB from Functional IR") {} -class SmtlibComputeGraphFactory { - SmtlibModule &module; - SmtlibComputeGraph &graph; - using T = SmtlibComputeGraph::Ref; - static bool is_single_output(IdString type) - { - auto it = yosys_celltypes.cell_types.find(type); - return it != yosys_celltypes.cell_types.end() && it->second.outputs.size() <= 1; - } - T node(SExpr &&expr, SmtlibType type, std::initializer_list args) { - return graph.add(Node(std::move(expr), type), 0, args); - } - T shift(const char *name, T a, T b, int y_width, int b_width, bool a_signed = false) { - int width = max(y_width, b_width); - T a_ = y_width < width ? extend(a, y_width, width, a_signed) : a; - T b_ = b_width < width ? extend(b, b_width, width, false) : b; - T y_ = node(SExpr {name, Arg(1), Arg(2)}, width, {a_, b_}); - if(y_width < width) - return slice(y_, width, 0, y_width); - else - return y_; - } - SExpr from_bool(SExpr &&arg) { - return SExpr{"ite", std::move(arg), RTLIL::Const(State::S1), RTLIL::Const(State::S0)}; - } - SExpr to_bool(SExpr &&arg) { - return SExpr{"=", std::move(arg), RTLIL::Const(State::S1)}; - } - SExpr extract(SExpr &&arg, int offset, int out_width) { - return SExpr {SExpr {"_", "extract", offset + out_width - 1, offset}, std::move(arg)}; - } -public: - SmtlibComputeGraphFactory(SmtlibModule &mod) : module(mod), graph(mod.compute_graph) {} - T slice(T a, int in_width, int offset, int out_width) { - log_assert(offset + out_width <= in_width); - return node(extract(Arg(1), offset, out_width), out_width, {a}); - } - T extend(T a, int in_width, int out_width, bool is_signed) { - log_assert(in_width < out_width); - if(is_signed) - return node(SExpr {SExpr {"_", "sign_extend", out_width - in_width}, Arg(1)}, out_width, {a}); - else - return node(SExpr {SExpr {"_", "zero_extend", out_width - in_width}, Arg(1)}, out_width, {a}); - } - T concat(T a, int a_width, T b, int b_width) { - return node(SExpr {"concat", Arg(1), Arg(2)}, a_width + b_width, {a, b}); - } - T add(T a, T b, int width) { return node(SExpr {"bvadd", Arg(1), Arg(2)}, width, {a, b}); } - T sub(T a, T b, int width) { return node(SExpr {"bvsub", Arg(1), Arg(2)}, width, {a, b}); } - T bitwise_and(T a, T b, int width) { return node(SExpr {"bvand", Arg(1), Arg(2)}, width, {a, b}); } - T bitwise_or(T a, T b, int width) { return node(SExpr {"bvor", Arg(1), Arg(2)}, width, {a, b}); } - T bitwise_xor(T a, T b, int width) { return node(SExpr {"bvxor", Arg(1), Arg(2)}, width, {a, b}); } - T bitwise_not(T a, int width) { return node(SExpr {"bvnot", Arg(1)}, width, {a}); } - T neg(T a, int width) { return node(SExpr {"bvneg", Arg(1)}, width, {a}); } - T mux(T a, T b, T s, int width) { return node(SExpr {"ite", to_bool(Arg(1)), Arg(2), Arg(3)}, width, {s, a, b}); } - T pmux(T a, T b, T s, int width, int s_width) { - T rv = a; - for(int i = 0; i < s_width; i++) - rv = node(SExpr {"ite", to_bool(extract(Arg(1), i, 1)), extract(Arg(2), width * i, width), Arg(3)}, width, {s, b, rv}); - return rv; - } - T reduce_and(T a, int width) { return node(from_bool(SExpr {"=", Arg(1), RTLIL::Const(State::S1, width)}), 1, {a}); } - T reduce_or(T a, int width) { return node(from_bool(SExpr {"distinct", Arg(1), RTLIL::Const(State::S0, width)}), 1, {a}); } - T reduce_xor(T a, int) { return node(SExpr {"reduce_xor", Arg(1)}, 1, {a}); } - T eq(T a, T b, int) { return node(from_bool(SExpr {"=", Arg(1), Arg(2)}), 1, {a, b}); } - T ne(T a, T b, int) { return node(from_bool(SExpr {"distinct", Arg(1), Arg(2)}), 1, {a, b}); } - T gt(T a, T b, int) { return node(from_bool(SExpr {"bvsgt", Arg(1), Arg(2)}), 1, {a, b}); } - T ge(T a, T b, int) { return node(from_bool(SExpr {"bvsge", Arg(1), Arg(2)}), 1, {a, b}); } - T ugt(T a, T b, int) { return node(from_bool(SExpr {"bvugt", Arg(1), Arg(2)}), 1, {a, b}); } - T uge(T a, T b, int) { return node(from_bool(SExpr {"bvuge", Arg(1), Arg(2)}), 1, {a, b}); } - T logical_shift_left(T a, T b, int y_width, int b_width) { return shift("bvshl", a, b, y_width, b_width); } - T logical_shift_right(T a, T b, int y_width, int b_width) { return shift("bvlshl", a, b, y_width, b_width); } - T arithmetic_shift_right(T a, T b, int y_width, int b_width) { return shift("bvashr", a, b, y_width, b_width, true); } - - T memory_read(T mem, T addr, int addr_width, int data_width) { - return node(SExpr {"select", Arg(1), Arg(2)}, data_width, {mem, addr}); - } - T memory_write(T mem, T addr, T data, int addr_width, int data_width) { - return node(SExpr {"store", Arg(1), Arg(2), Arg(3)}, SmtlibType::memory(addr_width, data_width), {mem, addr, data}); - } - - T constant(RTLIL::Const value) { return node(SExpr(value), value.size(), {}); } - T input(IdString name, int width) { - module.input_struct.insert(name, width); - return node(module.input_struct.access("inputs", name), width, {}); - } - T state(IdString name, int width) { - module.state_struct.insert(name, width); - return node(module.state_struct.access("current_state", name), width, {}); - } - T state_memory(IdString name, int addr_width, int data_width) { - module.state_struct.insert(name, SmtlibType::memory(addr_width, data_width)); - return node(module.state_struct.access("current_state", name), SmtlibType::memory(addr_width, data_width), {}); - } - T cell_output(T cell, IdString type, IdString name, int width) { - if (is_single_output(type)) - return cell; - else - return node(SExpr {"cell_output", Arg(1), RTLIL::unescape_id(name)}, width, {cell}); - } - T multiple(vector args, int width) { - vector expr; - expr.reserve(args.size() + 1); - expr.push_back("multiple"); - for(size_t i = 1; i <= args.size(); i++) - expr.push_back(Arg(i)); - return graph.add(Node(SExpr(std::move(expr)), width), 0, args); - } - T undriven(int width) { - return constant(RTLIL::Const(State::S0, width)); - //return node(SExpr {"undriven"}, width, {}); - } - T create_pending(int width) { - return node(SExpr(), width, {}); - } - void update_pending(T pending, T node) { - log_assert(pending.function().expr.is_none()); - pending.set_function(Node(Arg(1), pending.function().type)); - pending.append_arg(node); - } - void declare_output(T node, IdString name, int width) { - module.output_struct.insert(name, width); - node.assign_key(name); - } - void declare_state(T node, IdString name, int width) { - module.state_struct.insert(name, width); - node.assign_key(name); - } - void declare_state_memory(T node, IdString name, int addr_width, int data_width) { - module.state_struct.insert(name, SmtlibType::memory(addr_width, data_width)); - node.assign_key(name); - } - void suggest_name(T node, IdString name) { - node.sparse_attr() = name; - } -}; + void help() override { + log("\nFunctional SMT Backend.\n\n"); + } -void SmtlibModule::calculate_compute_graph(RTLIL::Module *module) -{ - SmtlibComputeGraphFactory factory(*this); - ComputeGraphConstruction construction(factory); - construction.add_module(module); - construction.process_queue(); - - // Perform topo sort and detect SCCs - SmtlibComputeGraph::SccAdaptor compute_graph_scc(compute_graph); - - bool scc = false; - std::vector perm; - topo_sorted_sccs(compute_graph_scc, [&](int *begin, int *end) { - perm.insert(perm.end(), begin, end); - if (end > begin + 1) - { - log_warning("SCC:"); - for (int *i = begin; i != end; ++i) - log(" %d", *i); - log("\n"); - scc = true; - } - }, /* sources_first */ true); - compute_graph.permute(perm); - if(scc) log_error("combinational loops, aborting\n"); - - // Forward $$buf - std::vector alias; - perm.clear(); - - for (int i = 0; i < compute_graph.size(); ++i) - { - auto node = compute_graph[i]; - if (node.function().expr == Arg(1) && node.arg(0).index() < i) - { - int target_index = alias[node.arg(0).index()]; - auto target_node = compute_graph[perm[target_index]]; - if(!target_node.has_sparse_attr() && node.has_sparse_attr()) - target_node.sparse_attr() = node.sparse_attr(); - alias.push_back(target_index); - } - else - { - alias.push_back(GetSize(perm)); - perm.push_back(i); - } - } - compute_graph.permute(perm, alias); -} - -void SmtlibModule::write(std::ostream &stream) -{ - SmtlibWriter f(stream); - - idict node_names; - SmtlibScope locals; - int paren_count = 0; - - input_struct.print(f); - output_struct.print(f); - state_struct.print(f); - - f << "(declare-datatypes ((Pair 2)) ((par (X Y) ((pair (first X) (second Y))))))\n"; - f.printf("(define-fun %s_step ((inputs %s_inputs) (current_state %s_state)) (Pair %s_outputs %s_state)\n", - name.c_str(), name.c_str(), name.c_str(), name.c_str(), name.c_str()); - - for (int i = 0; i < compute_graph.size(); ++i) - { - auto ref = compute_graph[i]; - std::string name; - if(ref.has_sparse_attr()) - name = locals.insert(ref.sparse_attr()); - else - name = locals.insert("\\n" + std::to_string(i)); - node_names(name); - vector args; - args.reserve(ref.size()); - for (int i = 0, end = ref.size(); i != end; ++i) - args.push_back(node_names[ref.arg(i).index()]); - f << " (let " << SExpr{{SExpr{name, ref.function().expr.subst_args(args)}}} << "\n"; - paren_count++; - } - dict outputs; - for(auto &a: compute_graph.keys()) - outputs.insert({a.first, node_names[a.second]}); - f.printf(" (pair \n"); - output_struct.print_value(f, outputs, 6); - state_struct.print_value(f, outputs, 6); - f.printf(" )\n"); - while(paren_count > 0){ - int n = min(paren_count, 80); - f << " " << std::string(n, ')') << "\n"; - paren_count -= n; - } - f.printf(")\n"); -} - -struct FunctionalSmtlibBackend : public Backend -{ - FunctionalSmtlibBackend() : Backend("functional_smtlib", "convert design to SMTLIB using the functional backend") {} - - void help() override - { - // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| - log("\n"); - } + void execute(std::ostream*& f, std::string filename, std::vector args, RTLIL::Design* design) override { + log_header(design, "Executing Functional SMT Backend.\n"); + + size_t argidx = 1; + extra_args(f, filename, args, argidx, design); - void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override - { - log_header(design, "Executing Functional SMTLIB backend.\n"); - - size_t argidx = 1; - extra_args(f, filename, args, argidx, design); - - for (auto module : design->selected_modules()) { - log("Dumping module `%s'.\n", module->name.c_str()); - SmtlibModule smt(RTLIL::unescape_id(module->name)); - smt.calculate_compute_graph(module); - smt.write(*f); - } - } -} FunctionalSmtlibBackend; + for (auto module : design->selected_modules()) { + log("Processing module `%s`.\n", module->name.c_str()); + auto ir = FunctionalIR::from_module(module); + SmtModule smt(RTLIL::unescape_id(module->name), ir); + smt.write(*f); + } + } +} FunctionalSmtBackend; PRIVATE_NAMESPACE_END From 54225b5c4252778dc39ad0ef912a6ebaa7d99853 Mon Sep 17 00:00:00 2001 From: Roland Coeurjoly Date: Fri, 21 Jun 2024 12:59:45 +0200 Subject: [PATCH 20/92] Add test for SMT backend. Tests if SMT is valid and compares simulation with yosys sim --- tests/functional/single_cells/run-test_smt.sh | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100755 tests/functional/single_cells/run-test_smt.sh diff --git a/tests/functional/single_cells/run-test_smt.sh b/tests/functional/single_cells/run-test_smt.sh new file mode 100755 index 00000000000..42e1a7c4b65 --- /dev/null +++ b/tests/functional/single_cells/run-test_smt.sh @@ -0,0 +1,91 @@ +#!/bin/bash + +# Initialize an array to store the names of failing RTLIL files and their failure types +declare -A failing_files +# Initialize an array to store the names of successful RTLIL files +declare -A successful_files + +# Function to run the test on a given RTLIL file +run_test() { + # Define the common variable for the relative path + BASE_PATH="../../../" + + local rtlil_file=$1 + + # Extract the base name without extension + local base_name=$(basename "$rtlil_file" .il) + + # Run yosys to process each RTLIL file + if ${BASE_PATH}yosys -p "read_rtlil $rtlil_file; write_functional_smt2 ${base_name}.smt2"; then + echo "Yosys processed $rtlil_file successfully." + # Check that the smt file generated is valid + # Execute the Python script to create a VCD file from the SMT2 file + if z3 "${base_name}.smt2"; then + echo "SMT file ${base_name}.smt2 is valid ." + + if python3 using_smtio.py "${base_name}.smt2"; then + echo "Python script generated VCD file for $rtlil_file successfully." + + # Check if VCD file was generated successfully + if [ -f "${base_name}.smt2.vcd" ]; then + echo "VCD file ${base_name}.vcd generated successfully by Python." + + # Run yosys simulation to generate a reference VCD file + if ${BASE_PATH}yosys -p "read_rtlil $rtlil_file; sim -vcd ${base_name}_yosys.vcd -r ${base_name}.smt2.vcd -scope gold -timescale 1us"; then + echo "Yosys simulation for $rtlil_file completed successfully." + successful_files["$rtlil_file"]="Success" + else + echo "Yosys simulation failed for $rtlil_file." + failing_files["$rtlil_file"]="Yosys simulation failure" + fi + else + + echo "Failed to generate VCD file (${base_name}.vcd) for $rtlil_file. " + failing_files["$rtlil_file"]="VCD generation failure" + fi + else + echo "Failed to run Python script for $rtlil_file." + failing_files["$rtlil_file"]="Python script failure" + fi + else + echo "SMT file for $rtlil_file is invalid" + failing_files["$rtlil_file"]="Invalid SMT" + fi + else + echo "Yosys failed to process $rtlil_file." + failing_files["$rtlil_file"]="Yosys failure" + fi +} + +# Main function to run all tests +run_all_tests() { + # Loop through all RTLIL files in the rtlil directory + for rtlil_file in rtlil/*.il; do + run_test "$rtlil_file" + done + + # Check if the array of failing files is empty + if [ ${#failing_files[@]} -eq 0 ]; then + echo "All files passed." + echo "The following files passed:" + for file in "${!successful_files[@]}"; do + echo "$file" + done + return 0 + else + echo "The following files failed:" + for file in "${!failing_files[@]}"]; do + echo "$file: ${failing_files[$file]}" +done +echo "The following files passed:" +for file in "${!successful_files[@]}"]; do + echo "$file" + done + return 1 + fi + } + + # If the script is being sourced, do not execute the tests + if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + run_all_tests + fi From c6e112686ca93403847a144ded8eefceef674c42 Mon Sep 17 00:00:00 2001 From: Roland Coeurjoly Date: Sun, 23 Jun 2024 22:13:31 +0200 Subject: [PATCH 21/92] Remove unused includes --- backends/functional/smtlib.cc | 5 ----- 1 file changed, 5 deletions(-) diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index a24ab84017f..65541d8d8e9 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -19,11 +19,6 @@ #include "kernel/yosys.h" #include "kernel/functionalir.h" -#include -#include -#include -#include -#include USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN From 547c5466ecc74d593793ba30dde20fc941170a89 Mon Sep 17 00:00:00 2001 From: Roland Coeurjoly Date: Mon, 24 Jun 2024 02:40:32 +0200 Subject: [PATCH 22/92] Ignore smt2 files, generated by the execution of the tests --- tests/functional/.gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/functional/.gitignore b/tests/functional/.gitignore index 543226b49dc..8b20fbedf55 100644 --- a/tests/functional/.gitignore +++ b/tests/functional/.gitignore @@ -1,4 +1,5 @@ my_module_cxxrtl.cc my_module_functional_cxx.cc vcd_harness -*.vcd \ No newline at end of file +*.vcd +*.smt2 \ No newline at end of file From 71aaa1c80dcf1ac8c5a9836025d4859a60fe9bd7 Mon Sep 17 00:00:00 2001 From: Roland Coeurjoly Date: Mon, 24 Jun 2024 03:05:44 +0200 Subject: [PATCH 23/92] Consolidate tests scripts into one --- tests/functional/single_cells/run-test.sh | 116 +++++++++++++----- tests/functional/single_cells/run-test_smt.sh | 91 -------------- 2 files changed, 87 insertions(+), 120 deletions(-) delete mode 100755 tests/functional/single_cells/run-test_smt.sh diff --git a/tests/functional/single_cells/run-test.sh b/tests/functional/single_cells/run-test.sh index 67cdf4a11c4..fd1b178e45a 100755 --- a/tests/functional/single_cells/run-test.sh +++ b/tests/functional/single_cells/run-test.sh @@ -1,80 +1,138 @@ #!/bin/bash -# Initialize an array to store the names of failing RTLIL files and their failure types -declare -A failing_files -# Initialize an array to store the names of successful RTLIL files -declare -A successful_files +declare -A cxx_failing_files +declare -A smt_failing_files +declare -A cxx_successful_files +declare -A smt_successful_files -# Function to run the test on a given RTLIL file -run_test() { - # Define the common variable for the relative path +run_cxx_test() { BASE_PATH="../../../" local rtlil_file=$1 - # Extract the base name without extension local base_name=$(basename "$rtlil_file" .v) - # Run yosys to process each RTLIL file if ${BASE_PATH}yosys -p "read_rtlil $rtlil_file; write_functional_cxx my_module_functional_cxx.cc"; then echo "Yosys processed $rtlil_file successfully." - # Compile the generated C++ files with vcd_harness.cpp if ${CXX:-g++} -g -fprofile-arcs -ftest-coverage vcd_harness.cc -I ${BASE_PATH}backends/functional/cxx_runtime/ -std=c++17 -o vcd_harness; then echo "Compilation successful." - # Generate VCD files with base_name if ./vcd_harness ${base_name}_functional_cxx.vcd; then - # Run yosys to process each RTLIL file if ${BASE_PATH}yosys -p "read_rtlil $rtlil_file; sim -r ${base_name}_functional_cxx.vcd -scope gold -vcd ${base_name}_yosys_sim.vcd -timescale 1us -sim-gold"; then echo "Yosys sim $rtlil_file successfully." - successful_files["$rtlil_file"]="Success" + cxx_successful_files["$rtlil_file"]="Success" else ${BASE_PATH}yosys -p "read_rtlil $rtlil_file; sim -vcd ${base_name}_yosys_sim.vcd -r ${base_name}_functional_cxx.vcd -scope gold -timescale 1us" echo "Yosys simulation of $rtlil_file failed. There is a discrepancy with functional cxx" - failing_files["$rtlil_file"]="Yosys sim failure" + cxx_failing_files["$rtlil_file"]="Yosys sim failure" fi else echo "Failed to generate VCD files for $rtlil_file." - failing_files["$rtlil_file"]="VCD generation failure" + cxx_failing_files["$rtlil_file"]="VCD generation failure" fi else echo "Failed to compile harness for $rtlil_file." - failing_files["$rtlil_file"]="Compilation failure" + cxx_failing_files["$rtlil_file"]="Compilation failure" fi else echo "Yosys failed to process $rtlil_file." - failing_files["$rtlil_file"]="Yosys failure" + cxx_failing_files["$rtlil_file"]="Yosys failure" fi } -# Main function to run all tests +run_smt_test() { + BASE_PATH="../../../" + + local rtlil_file=$1 + + local base_name=$(basename "$rtlil_file" .il) + + if ${BASE_PATH}yosys -p "read_rtlil $rtlil_file; write_functional_smt2 ${base_name}.smt2"; then + echo "Yosys processed $rtlil_file successfully." + # TODO: which SMT solver should be run? + if z3 "${base_name}.smt2"; then + echo "SMT file ${base_name}.smt2 is valid ." + + if python3 using_smtio.py "${base_name}.smt2"; then + echo "Python script generated VCD file for $rtlil_file successfully." + + if [ -f "${base_name}.smt2.vcd" ]; then + echo "VCD file ${base_name}.vcd generated successfully by Python." + + if ${BASE_PATH}yosys -p "read_rtlil $rtlil_file; sim -vcd ${base_name}_yosys.vcd -r ${base_name}.smt2.vcd -scope gold -timescale 1us"; then + echo "Yosys simulation for $rtlil_file completed successfully." + smt_successful_files["$rtlil_file"]="Success" + else + echo "Yosys simulation failed for $rtlil_file." + smt_failing_files["$rtlil_file"]="Yosys simulation failure" + fi + else + + echo "Failed to generate VCD file (${base_name}.vcd) for $rtlil_file. " + smt_failing_files["$rtlil_file"]="VCD generation failure" + fi + else + echo "Failed to run Python script for $rtlil_file." + smt_failing_files["$rtlil_file"]="Python script failure" + fi + else + echo "SMT file for $rtlil_file is invalid" + smt_failing_files["$rtlil_file"]="Invalid SMT" + fi + else + echo "Yosys failed to process $rtlil_file." + smt_failing_files["$rtlil_file"]="Yosys failure" + fi +} + + run_all_tests() { - # Loop through all RTLIL files in the rtlil directory + return_code=0 for rtlil_file in rtlil/*.il; do - run_test "$rtlil_file" + run_cxx_test "$rtlil_file" + run_smt_test "$rtlil_file" done - - # Check if the array of failing files is empty - if [ ${#failing_files[@]} -eq 0 ]; then + + echo "C++ tests results:" + if [ ${#cxx_failing_files[@]} -eq 0 ]; then + echo "All files passed." + echo "The following files passed:" + for file in "${!cxx_successful_files[@]}"; do + echo "$file" + done + else + echo "The following files failed:" + for file in "${!cxx_failing_files[@]}"; do + echo "$file: ${cxx_failing_files[$file]}" + done + echo "The following files passed:" + for file in "${!cxx_successful_files[@]}"; do + echo "$file" + done + return_code=1 + fi + + echo "SMT tests results:" + if [ ${#smt_failing_files[@]} -eq 0 ]; then echo "All files passed." echo "The following files passed:" - for file in "${!successful_files[@]}"; do + for file in "${!smt_successful_files[@]}"; do echo "$file" done - return 0 else echo "The following files failed:" - for file in "${!failing_files[@]}"; do - echo "$file: ${failing_files[$file]}" + for file in "${!smt_failing_files[@]}"; do + echo "$file: ${smt_failing_files[$file]}" done echo "The following files passed:" - for file in "${!successful_files[@]}"; do + for file in "${!smt_successful_files[@]}"; do echo "$file" done - return 1 + return_code=1 fi + return $return_code } # If the script is being sourced, do not execute the tests diff --git a/tests/functional/single_cells/run-test_smt.sh b/tests/functional/single_cells/run-test_smt.sh deleted file mode 100755 index 42e1a7c4b65..00000000000 --- a/tests/functional/single_cells/run-test_smt.sh +++ /dev/null @@ -1,91 +0,0 @@ -#!/bin/bash - -# Initialize an array to store the names of failing RTLIL files and their failure types -declare -A failing_files -# Initialize an array to store the names of successful RTLIL files -declare -A successful_files - -# Function to run the test on a given RTLIL file -run_test() { - # Define the common variable for the relative path - BASE_PATH="../../../" - - local rtlil_file=$1 - - # Extract the base name without extension - local base_name=$(basename "$rtlil_file" .il) - - # Run yosys to process each RTLIL file - if ${BASE_PATH}yosys -p "read_rtlil $rtlil_file; write_functional_smt2 ${base_name}.smt2"; then - echo "Yosys processed $rtlil_file successfully." - # Check that the smt file generated is valid - # Execute the Python script to create a VCD file from the SMT2 file - if z3 "${base_name}.smt2"; then - echo "SMT file ${base_name}.smt2 is valid ." - - if python3 using_smtio.py "${base_name}.smt2"; then - echo "Python script generated VCD file for $rtlil_file successfully." - - # Check if VCD file was generated successfully - if [ -f "${base_name}.smt2.vcd" ]; then - echo "VCD file ${base_name}.vcd generated successfully by Python." - - # Run yosys simulation to generate a reference VCD file - if ${BASE_PATH}yosys -p "read_rtlil $rtlil_file; sim -vcd ${base_name}_yosys.vcd -r ${base_name}.smt2.vcd -scope gold -timescale 1us"; then - echo "Yosys simulation for $rtlil_file completed successfully." - successful_files["$rtlil_file"]="Success" - else - echo "Yosys simulation failed for $rtlil_file." - failing_files["$rtlil_file"]="Yosys simulation failure" - fi - else - - echo "Failed to generate VCD file (${base_name}.vcd) for $rtlil_file. " - failing_files["$rtlil_file"]="VCD generation failure" - fi - else - echo "Failed to run Python script for $rtlil_file." - failing_files["$rtlil_file"]="Python script failure" - fi - else - echo "SMT file for $rtlil_file is invalid" - failing_files["$rtlil_file"]="Invalid SMT" - fi - else - echo "Yosys failed to process $rtlil_file." - failing_files["$rtlil_file"]="Yosys failure" - fi -} - -# Main function to run all tests -run_all_tests() { - # Loop through all RTLIL files in the rtlil directory - for rtlil_file in rtlil/*.il; do - run_test "$rtlil_file" - done - - # Check if the array of failing files is empty - if [ ${#failing_files[@]} -eq 0 ]; then - echo "All files passed." - echo "The following files passed:" - for file in "${!successful_files[@]}"; do - echo "$file" - done - return 0 - else - echo "The following files failed:" - for file in "${!failing_files[@]}"]; do - echo "$file: ${failing_files[$file]}" -done -echo "The following files passed:" -for file in "${!successful_files[@]}"]; do - echo "$file" - done - return 1 - fi - } - - # If the script is being sourced, do not execute the tests - if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then - run_all_tests - fi From b98210d8ac312497bd3523f5b5cc81f602487226 Mon Sep 17 00:00:00 2001 From: Roland Coeurjoly Date: Wed, 26 Jun 2024 19:13:53 +0200 Subject: [PATCH 24/92] Valid SMT is emitted, improved test script --- backends/functional/smtlib.cc | 59 +++++++++++++------- tests/functional/single_cells/run-test.sh | 67 ++++++++++++++++------- 2 files changed, 86 insertions(+), 40 deletions(-) diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index 65541d8d8e9..671b3a76312 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -108,11 +108,11 @@ struct SmtPrintVisitor { } std::string zero_extend(Node, Node a, int, int out_width) { - return format("((_ zero_extend %1) %0)", np(a), out_width); + return format("((_ zero_extend %1) %0)", np(a), out_width - a.width()); } std::string sign_extend(Node, Node a, int, int out_width) { - return format("((_ sign_extend %1) %0)", np(a), out_width); + return format("((_ sign_extend %1) %0)", np(a), out_width - a.width()); } std::string concat(Node, Node a, int, Node b, int) { @@ -228,6 +228,7 @@ struct SmtPrintVisitor { return format("#b%0", std::string(width, '0')); } }; + struct SmtModule { std::string name; SmtScope scope; @@ -236,11 +237,24 @@ struct SmtModule { SmtModule(const std::string& module_name, FunctionalIR ir) : name(module_name), ir(std::move(ir)) {} + std::string replaceCharacters(const std::string& input) { + std::string result = input; + std::replace(result.begin(), result.end(), '$', '_'); // Replace $ with _ + + // Since \ is an escape character, we use a loop to replace it + size_t pos = 0; + while ((pos = result.find('\\', pos)) != std::string::npos) { + result.replace(pos, 1, "_"); + pos += 1; // Move past the replaced character + } + + return result; + } + void write(std::ostream& out) { SmtWriter writer(out); - + writer.print("(declare-fun %s () Bool)\n", name.c_str()); - writer.print("(declare-datatypes () ((Inputs (mk_inputs"); for (const auto& input : ir.inputs()) { std::string input_name = scope[input.first]; @@ -258,7 +272,6 @@ struct SmtModule { writer.print("(declare-fun state () (_ BitVec 1))\n"); writer.print("(define-fun %s_step ((state (_ BitVec 1)) (inputs Inputs)) Outputs\n", name.c_str()); - writer.print(" (let (\n"); for (const auto& input : ir.inputs()) { std::string input_name = scope[input.first]; @@ -268,35 +281,41 @@ struct SmtModule { auto node_to_string = [&](FunctionalIR::Node n) { return scope[n.name()]; }; SmtPrintVisitor visitor(node_to_string, scope); - writer.print(" (let (\n"); + + std::string nested_lets; for (auto it = ir.begin(); it != ir.end(); ++it) { const FunctionalIR::Node& node = *it; - if (ir.inputs().count(node.name()) > 0) { - continue; - } + if (ir.inputs().count(node.name()) > 0) continue; - std::string node_name = scope[node.name()]; + std::string node_name = replaceCharacters(scope[node.name()]); std::string node_expr = node.visit(visitor); - writer.print(" (%s %s)\n", node_name.c_str(), node_expr.c_str()); + + nested_lets += "(let (\n (" + node_name + " " + node_expr + "))\n"; } - writer.print(" )\n"); - writer.print(" (let (\n"); + nested_lets += " (let (\n"; for (const auto& output : ir.outputs()) { std::string output_name = scope[output.first]; - // writer.print(" (%s %s)\n", output_name.c_str(), scope[output_name].c_str()); + const std::string output_assignment = ir.get_output_node(output.first).name().c_str(); + nested_lets += " (" + output_name + " " + replaceCharacters(output_assignment).substr(1) + ")\n"; } - writer.print(" )\n"); + nested_lets += " )\n"; + nested_lets += " (mk_outputs\n"; - writer.print(" (mk_outputs\n"); for (const auto& output : ir.outputs()) { std::string output_name = scope[output.first]; - writer.print(" %s\n", output_name.c_str()); + nested_lets += " " + output_name + "\n"; } - writer.print(" )\n"); - writer.print(" )\n"); - writer.print(" )\n"); + nested_lets += " )\n"; + nested_lets += " )\n"; + + // Close the nested lets + for (size_t i = 0; i < ir.size() - ir.inputs().size(); ++i) { + nested_lets += " )\n"; + } + + writer.print("%s", nested_lets.c_str()); writer.print(" )\n"); writer.print(")\n"); } diff --git a/tests/functional/single_cells/run-test.sh b/tests/functional/single_cells/run-test.sh index fd1b178e45a..473c7faea51 100755 --- a/tests/functional/single_cells/run-test.sh +++ b/tests/functional/single_cells/run-test.sh @@ -54,29 +54,29 @@ run_smt_test() { # TODO: which SMT solver should be run? if z3 "${base_name}.smt2"; then echo "SMT file ${base_name}.smt2 is valid ." + smt_successful_files["$rtlil_file"]="Success" + # if python3 using_smtio.py "${base_name}.smt2"; then + # echo "Python script generated VCD file for $rtlil_file successfully." - if python3 using_smtio.py "${base_name}.smt2"; then - echo "Python script generated VCD file for $rtlil_file successfully." + # if [ -f "${base_name}.smt2.vcd" ]; then + # echo "VCD file ${base_name}.vcd generated successfully by Python." - if [ -f "${base_name}.smt2.vcd" ]; then - echo "VCD file ${base_name}.vcd generated successfully by Python." - - if ${BASE_PATH}yosys -p "read_rtlil $rtlil_file; sim -vcd ${base_name}_yosys.vcd -r ${base_name}.smt2.vcd -scope gold -timescale 1us"; then - echo "Yosys simulation for $rtlil_file completed successfully." - smt_successful_files["$rtlil_file"]="Success" - else - echo "Yosys simulation failed for $rtlil_file." - smt_failing_files["$rtlil_file"]="Yosys simulation failure" - fi - else + # if ${BASE_PATH}yosys -p "read_rtlil $rtlil_file; sim -vcd ${base_name}_yosys.vcd -r ${base_name}.smt2.vcd -scope gold -timescale 1us"; then + # echo "Yosys simulation for $rtlil_file completed successfully." + # smt_successful_files["$rtlil_file"]="Success" + # else + # echo "Yosys simulation failed for $rtlil_file." + # smt_failing_files["$rtlil_file"]="Yosys simulation failure" + # fi + # else - echo "Failed to generate VCD file (${base_name}.vcd) for $rtlil_file. " - smt_failing_files["$rtlil_file"]="VCD generation failure" - fi - else - echo "Failed to run Python script for $rtlil_file." - smt_failing_files["$rtlil_file"]="Python script failure" - fi + # echo "Failed to generate VCD file (${base_name}.vcd) for $rtlil_file. " + # smt_failing_files["$rtlil_file"]="VCD generation failure" + # fi + # else + # echo "Failed to run Python script for $rtlil_file." + # smt_failing_files["$rtlil_file"]="Python script failure" + # fi else echo "SMT file for $rtlil_file is invalid" smt_failing_files["$rtlil_file"]="Invalid SMT" @@ -135,6 +135,33 @@ run_all_tests() { return $return_code } +run_smt_tests() { + return_code=0 + for rtlil_file in rtlil/*.il; do + run_smt_test "$rtlil_file" + done + + echo "SMT tests results:" + if [ ${#smt_failing_files[@]} -eq 0 ]; then + echo "All files passed." + echo "The following files passed:" + for file in "${!smt_successful_files[@]}"; do + echo "$file" + done + else + echo "The following files failed:" + for file in "${!smt_failing_files[@]}"; do + echo "$file: ${smt_failing_files[$file]}" + done + echo "The following files passed:" + for file in "${!smt_successful_files[@]}"; do + echo "$file" + done + return_code=1 + fi + return $return_code +} + # If the script is being sourced, do not execute the tests if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then run_all_tests From 94ddbc957727b6a71064a608a869405d49eb9dcb Mon Sep 17 00:00:00 2001 From: Roland Coeurjoly Date: Wed, 26 Jun 2024 19:45:54 +0200 Subject: [PATCH 25/92] Fix reduce_or --- backends/functional/smtlib.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index 671b3a76312..2ece98d8999 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -152,7 +152,10 @@ struct SmtPrintVisitor { } std::string reduce_or(Node, Node a, int) { - return format("(distinct %0 #b0)", np(a)); + std::stringstream ss; + // We use ite to set the result to bit vector, to ensure appropriate type + ss << "(ite (= " << np(a) << " #b" << std::string(a.width(), '0') << ") #b0 #b1)"; + return ss.str(); } std::string reduce_xor(Node, Node a, int) { From 4109fcedcff1161b2c9a67d6651fdf416cb22e47 Mon Sep 17 00:00:00 2001 From: Roland Coeurjoly Date: Wed, 26 Jun 2024 20:56:29 +0200 Subject: [PATCH 26/92] clang-format smtlib.cc --- backends/functional/smtlib.cc | 554 +++++++++++++++------------------- 1 file changed, 250 insertions(+), 304 deletions(-) diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index 2ece98d8999..49dd5cf0d21 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -17,333 +17,279 @@ * */ -#include "kernel/yosys.h" #include "kernel/functionalir.h" +#include "kernel/yosys.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN struct SmtScope { - pool used_names; - dict name_map; - - SmtScope() {} - - void reserve(const std::string& name) { - used_names.insert(name); - } - - std::string insert(IdString id) { - std::string name = RTLIL::unescape_id(id); - if (used_names.count(name) == 0) { - used_names.insert(name); - name_map[id] = name; - return name; - } - for (int idx = 0;; ++idx) { - std::string new_name = name + "_" + std::to_string(idx); - if (used_names.count(new_name) == 0) { - used_names.insert(new_name); - name_map[id] = new_name; - return new_name; - } - } - } - - std::string operator[](IdString id) { - if (name_map.count(id)) { - return name_map[id]; - } else { - return insert(id); - } - } + pool used_names; + dict name_map; + + SmtScope() {} + + void reserve(const std::string &name) { used_names.insert(name); } + + std::string insert(IdString id) + { + std::string name = RTLIL::unescape_id(id); + if (used_names.count(name) == 0) { + used_names.insert(name); + name_map[id] = name; + return name; + } + for (int idx = 0;; ++idx) { + std::string new_name = name + "_" + std::to_string(idx); + if (used_names.count(new_name) == 0) { + used_names.insert(new_name); + name_map[id] = new_name; + return new_name; + } + } + } + + std::string operator[](IdString id) + { + if (name_map.count(id)) { + return name_map[id]; + } else { + return insert(id); + } + } }; struct SmtWriter { - std::ostream& stream; + std::ostream &stream; - SmtWriter(std::ostream& out) : stream(out) {} + SmtWriter(std::ostream &out) : stream(out) {} - void print(const char* fmt, ...) { - va_list args; - va_start(args, fmt); - stream << vstringf(fmt, args); - va_end(args); - } + void print(const char *fmt, ...) + { + va_list args; + va_start(args, fmt); + stream << vstringf(fmt, args); + va_end(args); + } }; -template -struct SmtPrintVisitor { - using Node = FunctionalIR::Node; - NodeNames np; - SmtScope &scope; - - SmtPrintVisitor(NodeNames np, SmtScope &scope) - : np(np), scope(scope) {} - - template - std::string arg_to_string(T n) { return std::to_string(n); } - - std::string arg_to_string(std::string n) { return n; } - - std::string arg_to_string(Node n) { return np(n); } - - template - std::string format(std::string fmt, Args&&... args) { - std::vector arg_strings = { arg_to_string(std::forward(args))... }; - for (size_t i = 0; i < arg_strings.size(); ++i) { - std::string placeholder = "%" + std::to_string(i); - size_t pos = 0; - while ((pos = fmt.find(placeholder, pos)) != std::string::npos) { - fmt.replace(pos, placeholder.length(), arg_strings[i]); - pos += arg_strings[i].length(); - } - } - return fmt; - } - std::string buf(Node, Node n) { return np(n); } - - std::string slice(Node, Node a, int, int offset, int out_width) { - return format("(_ extract %1 %2 %0)", np(a), offset, offset + out_width - 1); - } - - std::string zero_extend(Node, Node a, int, int out_width) { - return format("((_ zero_extend %1) %0)", np(a), out_width - a.width()); - } - - std::string sign_extend(Node, Node a, int, int out_width) { - return format("((_ sign_extend %1) %0)", np(a), out_width - a.width()); - } - - std::string concat(Node, Node a, int, Node b, int) { - return format("(concat %0 %1)", np(a), np(b)); - } - - std::string add(Node, Node a, Node b, int) { - return format("(bvadd %0 %1)", np(a), np(b)); - } - - std::string sub(Node, Node a, Node b, int) { - return format("(bvsub %0 %1)", np(a), np(b)); - } - - std::string bitwise_and(Node, Node a, Node b, int) { - return format("(bvand %0 %1)", np(a), np(b)); - } - - std::string bitwise_or(Node, Node a, Node b, int) { - return format("(bvor %0 %1)", np(a), np(b)); - } - - std::string bitwise_xor(Node, Node a, Node b, int) { - return format("(bvxor %0 %1)", np(a), np(b)); - } - - std::string bitwise_not(Node, Node a, int) { - return format("(bvnot %0)", np(a)); - } - - std::string unary_minus(Node, Node a, int) { - return format("(bvneg %0)", np(a)); - } - - std::string reduce_and(Node, Node a, int) { - return format("(= %0 #b1)", np(a)); - } - - std::string reduce_or(Node, Node a, int) { - std::stringstream ss; - // We use ite to set the result to bit vector, to ensure appropriate type - ss << "(ite (= " << np(a) << " #b" << std::string(a.width(), '0') << ") #b0 #b1)"; - return ss.str(); - } - - std::string reduce_xor(Node, Node a, int) { - return format("(bvxor_reduce %0)", np(a)); - } - - std::string equal(Node, Node a, Node b, int) { - return format("(= %0 %1)", np(a), np(b)); - } - - std::string not_equal(Node, Node a, Node b, int) { - return format("(distinct %0 %1)", np(a), np(b)); - } - - std::string signed_greater_than(Node, Node a, Node b, int) { - return format("(bvsgt %0 %1)", np(a), np(b)); - } - - std::string signed_greater_equal(Node, Node a, Node b, int) { - return format("(bvsge %0 %1)", np(a), np(b)); - } - - std::string unsigned_greater_than(Node, Node a, Node b, int) { - return format("(bvugt %0 %1)", np(a), np(b)); - } - - std::string unsigned_greater_equal(Node, Node a, Node b, int) { - return format("(bvuge %0 %1)", np(a), np(b)); - } - - std::string logical_shift_left(Node, Node a, Node b, int, int) { - return format("(bvshl %0 %1)", np(a), np(b)); - } - - std::string logical_shift_right(Node, Node a, Node b, int, int) { - return format("(bvlshr %0 %1)", np(a), np(b)); - } - - std::string arithmetic_shift_right(Node, Node a, Node b, int, int) { - return format("(bvashr %0 %1)", np(a), np(b)); - } - - std::string mux(Node, Node a, Node b, Node s, int) { - return format("(ite %2 %0 %1)", np(a), np(b), np(s)); - } - - std::string pmux(Node, Node a, Node b, Node s, int, int) { - // Assume s is a bit vector, combine a and b based on the selection bits - return format("(pmux %0 %1 %2)", np(a), np(b), np(s)); - } - - std::string constant(Node, RTLIL::Const value) { - return format("#b%1", value.as_string()); - } - - std::string input(Node, IdString name) { - return format("%0", scope[name]); - } - - std::string state(Node, IdString name) { - return format("%0", scope[name]); - } - - std::string memory_read(Node, Node mem, Node addr, int, int) { - return format("(select %0 %1)", np(mem), np(addr)); - } - - std::string memory_write(Node, Node mem, Node addr, Node data, int, int) { - return format("(store %0 %1 %2)", np(mem), np(addr), np(data)); - } - - std::string undriven(Node, int width) { - return format("#b%0", std::string(width, '0')); - } +template struct SmtPrintVisitor { + using Node = FunctionalIR::Node; + NodeNames np; + SmtScope &scope; + + SmtPrintVisitor(NodeNames np, SmtScope &scope) : np(np), scope(scope) {} + + template std::string arg_to_string(T n) { return std::to_string(n); } + + std::string arg_to_string(std::string n) { return n; } + + std::string arg_to_string(Node n) { return np(n); } + + template std::string format(std::string fmt, Args &&...args) + { + std::vector arg_strings = {arg_to_string(std::forward(args))...}; + for (size_t i = 0; i < arg_strings.size(); ++i) { + std::string placeholder = "%" + std::to_string(i); + size_t pos = 0; + while ((pos = fmt.find(placeholder, pos)) != std::string::npos) { + fmt.replace(pos, placeholder.length(), arg_strings[i]); + pos += arg_strings[i].length(); + } + } + return fmt; + } + std::string buf(Node, Node n) { return np(n); } + + std::string slice(Node, Node a, int, int offset, int out_width) + { + return format("(_ extract %1 %2 %0)", np(a), offset, offset + out_width - 1); + } + + std::string zero_extend(Node, Node a, int, int out_width) { return format("((_ zero_extend %1) %0)", np(a), out_width - a.width()); } + + std::string sign_extend(Node, Node a, int, int out_width) { return format("((_ sign_extend %1) %0)", np(a), out_width - a.width()); } + + std::string concat(Node, Node a, int, Node b, int) { return format("(concat %0 %1)", np(a), np(b)); } + + std::string add(Node, Node a, Node b, int) { return format("(bvadd %0 %1)", np(a), np(b)); } + + std::string sub(Node, Node a, Node b, int) { return format("(bvsub %0 %1)", np(a), np(b)); } + + std::string bitwise_and(Node, Node a, Node b, int) { return format("(bvand %0 %1)", np(a), np(b)); } + + std::string bitwise_or(Node, Node a, Node b, int) { return format("(bvor %0 %1)", np(a), np(b)); } + + std::string bitwise_xor(Node, Node a, Node b, int) { return format("(bvxor %0 %1)", np(a), np(b)); } + + std::string bitwise_not(Node, Node a, int) { return format("(bvnot %0)", np(a)); } + + std::string unary_minus(Node, Node a, int) { return format("(bvneg %0)", np(a)); } + + std::string reduce_and(Node, Node a, int) { return format("(= %0 #b1)", np(a)); } + + std::string reduce_or(Node, Node a, int) + { + std::stringstream ss; + // We use ite to set the result to bit vector, to ensure appropriate type + ss << "(ite (= " << np(a) << " #b" << std::string(a.width(), '0') << ") #b0 #b1)"; + return ss.str(); + } + + std::string reduce_xor(Node, Node a, int) { return format("(bvxor_reduce %0)", np(a)); } + + std::string equal(Node, Node a, Node b, int) { return format("(= %0 %1)", np(a), np(b)); } + + std::string not_equal(Node, Node a, Node b, int) { return format("(distinct %0 %1)", np(a), np(b)); } + + std::string signed_greater_than(Node, Node a, Node b, int) { return format("(bvsgt %0 %1)", np(a), np(b)); } + + std::string signed_greater_equal(Node, Node a, Node b, int) { return format("(bvsge %0 %1)", np(a), np(b)); } + + std::string unsigned_greater_than(Node, Node a, Node b, int) { return format("(bvugt %0 %1)", np(a), np(b)); } + + std::string unsigned_greater_equal(Node, Node a, Node b, int) { return format("(bvuge %0 %1)", np(a), np(b)); } + + std::string logical_shift_left(Node, Node a, Node b, int, int) { return format("(bvshl %0 %1)", np(a), np(b)); } + + std::string logical_shift_right(Node, Node a, Node b, int, int) { return format("(bvlshr %0 %1)", np(a), np(b)); } + + std::string arithmetic_shift_right(Node, Node a, Node b, int, int) { return format("(bvashr %0 %1)", np(a), np(b)); } + + std::string mux(Node, Node a, Node b, Node s, int) { return format("(ite %2 %0 %1)", np(a), np(b), np(s)); } + + std::string pmux(Node, Node a, Node b, Node s, int, int) + { + // Assume s is a bit vector, combine a and b based on the selection bits + return format("(pmux %0 %1 %2)", np(a), np(b), np(s)); + } + + std::string constant(Node, RTLIL::Const value) { return format("#b%1", value.as_string()); } + + std::string input(Node, IdString name) { return format("%0", scope[name]); } + + std::string state(Node, IdString name) { return format("%0", scope[name]); } + + std::string memory_read(Node, Node mem, Node addr, int, int) { return format("(select %0 %1)", np(mem), np(addr)); } + + std::string memory_write(Node, Node mem, Node addr, Node data, int, int) { return format("(store %0 %1 %2)", np(mem), np(addr), np(data)); } + + std::string undriven(Node, int width) { return format("#b%0", std::string(width, '0')); } }; struct SmtModule { - std::string name; - SmtScope scope; - FunctionalIR ir; - - SmtModule(const std::string& module_name, FunctionalIR ir) - : name(module_name), ir(std::move(ir)) {} - - std::string replaceCharacters(const std::string& input) { - std::string result = input; - std::replace(result.begin(), result.end(), '$', '_'); // Replace $ with _ - - // Since \ is an escape character, we use a loop to replace it - size_t pos = 0; - while ((pos = result.find('\\', pos)) != std::string::npos) { - result.replace(pos, 1, "_"); - pos += 1; // Move past the replaced character - } - - return result; - } - - void write(std::ostream& out) { - SmtWriter writer(out); - - writer.print("(declare-fun %s () Bool)\n", name.c_str()); - writer.print("(declare-datatypes () ((Inputs (mk_inputs"); - for (const auto& input : ir.inputs()) { - std::string input_name = scope[input.first]; - writer.print(" (%s (_ BitVec %d))", input_name.c_str(), input.second.width()); - } - writer.print("))))\n"); - - writer.print("(declare-datatypes () ((Outputs (mk_outputs"); - for (const auto& output : ir.outputs()) { - std::string output_name = scope[output.first]; - writer.print(" (%s (_ BitVec %d))", output_name.c_str(), output.second.width()); - } - writer.print("))))\n"); - - writer.print("(declare-fun state () (_ BitVec 1))\n"); - - writer.print("(define-fun %s_step ((state (_ BitVec 1)) (inputs Inputs)) Outputs\n", name.c_str()); - writer.print(" (let (\n"); - for (const auto& input : ir.inputs()) { - std::string input_name = scope[input.first]; - writer.print(" (%s (%s inputs))\n", input_name.c_str(), input_name.c_str()); - } - writer.print(" )\n"); - - auto node_to_string = [&](FunctionalIR::Node n) { return scope[n.name()]; }; - SmtPrintVisitor visitor(node_to_string, scope); - - std::string nested_lets; - for (auto it = ir.begin(); it != ir.end(); ++it) { - const FunctionalIR::Node& node = *it; - - if (ir.inputs().count(node.name()) > 0) continue; - - std::string node_name = replaceCharacters(scope[node.name()]); - std::string node_expr = node.visit(visitor); - - nested_lets += "(let (\n (" + node_name + " " + node_expr + "))\n"; - } - - nested_lets += " (let (\n"; - for (const auto& output : ir.outputs()) { - std::string output_name = scope[output.first]; - const std::string output_assignment = ir.get_output_node(output.first).name().c_str(); - nested_lets += " (" + output_name + " " + replaceCharacters(output_assignment).substr(1) + ")\n"; - } - nested_lets += " )\n"; - nested_lets += " (mk_outputs\n"; - - for (const auto& output : ir.outputs()) { - std::string output_name = scope[output.first]; - nested_lets += " " + output_name + "\n"; - } - nested_lets += " )\n"; - nested_lets += " )\n"; - - // Close the nested lets - for (size_t i = 0; i < ir.size() - ir.inputs().size(); ++i) { - nested_lets += " )\n"; - } - - writer.print("%s", nested_lets.c_str()); - writer.print(" )\n"); - writer.print(")\n"); - } + std::string name; + SmtScope scope; + FunctionalIR ir; + + SmtModule(const std::string &module_name, FunctionalIR ir) : name(module_name), ir(std::move(ir)) {} + + std::string replaceCharacters(const std::string &input) + { + std::string result = input; + std::replace(result.begin(), result.end(), '$', '_'); // Replace $ with _ + + // Since \ is an escape character, we use a loop to replace it + size_t pos = 0; + while ((pos = result.find('\\', pos)) != std::string::npos) { + result.replace(pos, 1, "_"); + pos += 1; // Move past the replaced character + } + + return result; + } + + void write(std::ostream &out) + { + SmtWriter writer(out); + + writer.print("(declare-fun %s () Bool)\n", name.c_str()); + writer.print("(declare-datatypes () ((Inputs (mk_inputs"); + for (const auto &input : ir.inputs()) { + std::string input_name = scope[input.first]; + writer.print(" (%s (_ BitVec %d))", input_name.c_str(), input.second.width()); + } + writer.print("))))\n"); + + writer.print("(declare-datatypes () ((Outputs (mk_outputs"); + for (const auto &output : ir.outputs()) { + std::string output_name = scope[output.first]; + writer.print(" (%s (_ BitVec %d))", output_name.c_str(), output.second.width()); + } + writer.print("))))\n"); + + writer.print("(declare-fun state () (_ BitVec 1))\n"); + + writer.print("(define-fun %s_step ((state (_ BitVec 1)) (inputs Inputs)) Outputs", name.c_str()); + writer.print(" (let ("); + for (const auto &input : ir.inputs()) { + std::string input_name = scope[input.first]; + writer.print(" (%s (%s inputs))", input_name.c_str(), input_name.c_str()); + } + writer.print(" )"); + + auto node_to_string = [&](FunctionalIR::Node n) { return scope[n.name()]; }; + SmtPrintVisitor visitor(node_to_string, scope); + + std::string nested_lets; + for (auto it = ir.begin(); it != ir.end(); ++it) { + const FunctionalIR::Node &node = *it; + + if (ir.inputs().count(node.name()) > 0) + continue; + + std::string node_name = replaceCharacters(scope[node.name()]); + std::string node_expr = node.visit(visitor); + + nested_lets += "(let ( (" + node_name + " " + node_expr + "))"; + } + + nested_lets += " (let ("; + for (const auto &output : ir.outputs()) { + std::string output_name = scope[output.first]; + const std::string output_assignment = ir.get_output_node(output.first).name().c_str(); + nested_lets += " (" + output_name + " " + replaceCharacters(output_assignment).substr(1) + ")"; + } + nested_lets += " )"; + nested_lets += " (mk_outputs"; + + for (const auto &output : ir.outputs()) { + std::string output_name = scope[output.first]; + nested_lets += " " + output_name; + } + nested_lets += " )"; + nested_lets += " )"; + + // Close the nested lets + for (size_t i = 0; i < ir.size() - ir.inputs().size(); ++i) { + nested_lets += " )"; + } + + writer.print("%s", nested_lets.c_str()); + writer.print(" )"); + writer.print(")\n"); + } }; struct FunctionalSmtBackend : public Backend { - FunctionalSmtBackend() : Backend("functional_smt2", "Generate SMT-LIB from Functional IR") {} + FunctionalSmtBackend() : Backend("functional_smt2", "Generate SMT-LIB from Functional IR") {} - void help() override { - log("\nFunctional SMT Backend.\n\n"); - } + void help() override { log("\nFunctional SMT Backend.\n\n"); } - void execute(std::ostream*& f, std::string filename, std::vector args, RTLIL::Design* design) override { - log_header(design, "Executing Functional SMT Backend.\n"); + void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override + { + log_header(design, "Executing Functional SMT Backend.\n"); - size_t argidx = 1; - extra_args(f, filename, args, argidx, design); + size_t argidx = 1; + extra_args(f, filename, args, argidx, design); - for (auto module : design->selected_modules()) { - log("Processing module `%s`.\n", module->name.c_str()); - auto ir = FunctionalIR::from_module(module); - SmtModule smt(RTLIL::unescape_id(module->name), ir); - smt.write(*f); - } - } + for (auto module : design->selected_modules()) { + log("Processing module `%s`.\n", module->name.c_str()); + auto ir = FunctionalIR::from_module(module); + SmtModule smt(RTLIL::unescape_id(module->name), ir); + smt.write(*f); + } + } } FunctionalSmtBackend; PRIVATE_NAMESPACE_END From 39bf4f04f73ef0f91fc793d277b64047a82e4ea9 Mon Sep 17 00:00:00 2001 From: Roland Coeurjoly Date: Thu, 27 Jun 2024 01:46:10 +0200 Subject: [PATCH 27/92] Create VCD file from SMT file --- backends/functional/smtio.py | 1331 +++++++++++++++++ tests/functional/single_cells/run-test.sh | 39 +- .../single_cells/vcd_harness_smt.py | 199 +++ 3 files changed, 1549 insertions(+), 20 deletions(-) create mode 100644 backends/functional/smtio.py create mode 100644 tests/functional/single_cells/vcd_harness_smt.py diff --git a/backends/functional/smtio.py b/backends/functional/smtio.py new file mode 100644 index 00000000000..e32f43c60a0 --- /dev/null +++ b/backends/functional/smtio.py @@ -0,0 +1,1331 @@ +# +# yosys -- Yosys Open SYnthesis Suite +# +# Copyright (C) 2012 Claire Xenia Wolf +# +# 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. +# + +import sys, re, os, signal, json +import subprocess +if os.name == "posix": + import resource +from copy import copy +from select import select +from time import time +from queue import Queue, Empty +from threading import Thread + + +# This is needed so that the recursive SMT2 S-expression parser +# does not run out of stack frames when parsing large expressions +if os.name == "posix": + smtio_reclimit = 64 * 1024 + if sys.getrecursionlimit() < smtio_reclimit: + sys.setrecursionlimit(smtio_reclimit) + + current_rlimit_stack = resource.getrlimit(resource.RLIMIT_STACK) + if current_rlimit_stack[0] != resource.RLIM_INFINITY: + smtio_stacksize = 128 * 1024 * 1024 + if os.uname().sysname == "Darwin": + # MacOS has rather conservative stack limits + smtio_stacksize = 8 * 1024 * 1024 + if current_rlimit_stack[1] != resource.RLIM_INFINITY: + smtio_stacksize = min(smtio_stacksize, current_rlimit_stack[1]) + if current_rlimit_stack[0] < smtio_stacksize: + try: + resource.setrlimit(resource.RLIMIT_STACK, (smtio_stacksize, current_rlimit_stack[1])) + except ValueError: + # couldn't get more stack, just run with what we have + pass + + +# currently running solvers (so we can kill them) +running_solvers = dict() +forced_shutdown = False +solvers_index = 0 + +def force_shutdown(signum, frame): + global forced_shutdown + if not forced_shutdown: + forced_shutdown = True + if signum is not None: + print("<%s>" % signal.Signals(signum).name) + for p in running_solvers.values(): + # os.killpg(os.getpgid(p.pid), signal.SIGTERM) + os.kill(p.pid, signal.SIGTERM) + sys.exit(1) + +if os.name == "posix": + signal.signal(signal.SIGHUP, force_shutdown) +signal.signal(signal.SIGINT, force_shutdown) +signal.signal(signal.SIGTERM, force_shutdown) + +def except_hook(exctype, value, traceback): + if not forced_shutdown: + sys.__excepthook__(exctype, value, traceback) + force_shutdown(None, None) + +sys.excepthook = except_hook + + +def recursion_helper(iteration, *request): + stack = [iteration(*request)] + + while stack: + top = stack.pop() + try: + request = next(top) + except StopIteration: + continue + + stack.append(top) + stack.append(iteration(*request)) + + +hex_dict = { + "0": "0000", "1": "0001", "2": "0010", "3": "0011", + "4": "0100", "5": "0101", "6": "0110", "7": "0111", + "8": "1000", "9": "1001", "A": "1010", "B": "1011", + "C": "1100", "D": "1101", "E": "1110", "F": "1111", + "a": "1010", "b": "1011", "c": "1100", "d": "1101", + "e": "1110", "f": "1111" +} + + +class SmtModInfo: + def __init__(self): + self.inputs = set() + self.outputs = set() + self.registers = set() + self.memories = dict() + self.wires = set() + self.wsize = dict() + self.clocks = dict() + self.cells = dict() + self.asserts = dict() + self.assumes = dict() + self.covers = dict() + self.maximize = set() + self.minimize = set() + self.anyconsts = dict() + self.anyseqs = dict() + self.allconsts = dict() + self.allseqs = dict() + self.asize = dict() + self.witness = [] + + +class SmtIo: + def __init__(self, opts=None): + global solvers_index + + self.logic = None + self.logic_qf = True + self.logic_ax = True + self.logic_uf = True + self.logic_bv = True + self.logic_dt = False + self.forall = False + self.timeout = 0 + self.produce_models = True + self.recheck = False + self.smt2cache = [list()] + self.smt2_options = dict() + self.smt2_assumptions = dict() + self.p = None + self.p_index = solvers_index + solvers_index += 1 + + if opts is not None: + self.logic = opts.logic + self.solver = opts.solver + self.solver_opts = opts.solver_opts + self.debug_print = opts.debug_print + self.debug_file = opts.debug_file + self.dummy_file = opts.dummy_file + self.timeinfo = opts.timeinfo + self.timeout = opts.timeout + self.unroll = opts.unroll + self.noincr = opts.noincr + self.info_stmts = opts.info_stmts + self.nocomments = opts.nocomments + + else: + self.solver = "yices" + self.solver_opts = list() + self.debug_print = False + self.debug_file = None + self.dummy_file = None + self.timeinfo = os.name != "nt" + self.timeout = 0 + self.unroll = False + self.noincr = False + self.info_stmts = list() + self.nocomments = False + + self.start_time = time() + + self.modinfo = dict() + self.curmod = None + self.topmod = None + self.setup_done = False + + def __del__(self): + if self.p is not None and not forced_shutdown: + os.killpg(os.getpgid(self.p.pid), signal.SIGTERM) + if running_solvers is not None: + del running_solvers[self.p_index] + + def setup(self): + assert not self.setup_done + + if self.forall: + self.unroll = False + + if self.solver == "yices": + if self.forall: + self.noincr = True + + if self.noincr: + self.popen_vargs = ['yices-smt2'] + self.solver_opts + else: + self.popen_vargs = ['yices-smt2', '--incremental'] + self.solver_opts + if self.timeout != 0: + self.popen_vargs.append('-t') + self.popen_vargs.append('%d' % self.timeout); + + if self.solver == "z3": + self.popen_vargs = ['z3', '-smt2', '-in'] + self.solver_opts + if self.timeout != 0: + self.popen_vargs.append('-T:%d' % self.timeout); + + if self.solver in ["cvc4", "cvc5"]: + self.recheck = True + if self.noincr: + self.popen_vargs = [self.solver, '--lang', 'smt2.6' if self.logic_dt else 'smt2'] + self.solver_opts + else: + self.popen_vargs = [self.solver, '--incremental', '--lang', 'smt2.6' if self.logic_dt else 'smt2'] + self.solver_opts + if self.timeout != 0: + self.popen_vargs.append('--tlimit=%d000' % self.timeout); + + if self.solver == "mathsat": + self.popen_vargs = ['mathsat'] + self.solver_opts + if self.timeout != 0: + print('timeout option is not supported for mathsat.') + sys.exit(1) + + if self.solver in ["boolector", "bitwuzla"]: + if self.noincr: + self.popen_vargs = [self.solver, '--smt2'] + self.solver_opts + else: + self.popen_vargs = [self.solver, '--smt2', '-i'] + self.solver_opts + self.unroll = True + if self.timeout != 0: + print('timeout option is not supported for %s.' % self.solver) + sys.exit(1) + + if self.solver == "abc": + if len(self.solver_opts) > 0: + self.popen_vargs = ['yosys-abc', '-S', '; '.join(self.solver_opts)] + else: + self.popen_vargs = ['yosys-abc', '-S', '%blast; &sweep -C 5000; &syn4; &cec -s -m -C 2000'] + self.logic_ax = False + self.unroll = True + self.noincr = True + if self.timeout != 0: + print('timeout option is not supported for abc.') + sys.exit(1) + + if self.solver == "dummy": + assert self.dummy_file is not None + self.dummy_fd = open(self.dummy_file, "r") + else: + if self.dummy_file is not None: + self.dummy_fd = open(self.dummy_file, "w") + if not self.noincr: + self.p_open() + + if self.unroll: + assert not self.forall + self.logic_uf = False + self.unroll_idcnt = 0 + self.unroll_buffer = "" + self.unroll_level = 0 + self.unroll_sorts = set() + self.unroll_objs = set() + self.unroll_decls = dict() + self.unroll_cache = dict() + self.unroll_stack = list() + + if self.logic is None: + self.logic = "" + if self.logic_qf: self.logic += "QF_" + if self.logic_ax: self.logic += "A" + if self.logic_uf: self.logic += "UF" + if self.logic_bv: self.logic += "BV" + if self.logic_dt: self.logic = "ALL" + if self.solver == "yices" and self.forall: self.logic = "BV" + + self.setup_done = True + + for stmt in self.info_stmts: + self.write(stmt) + + if self.produce_models: + self.write("(set-option :produce-models true)") + + #See the SMT-LIB Standard, Section 4.1.7 + modestart_options = [":global-declarations", ":interactive-mode", ":produce-assertions", ":produce-assignments", ":produce-models", ":produce-proofs", ":produce-unsat-assumptions", ":produce-unsat-cores", ":random-seed"] + for key, val in self.smt2_options.items(): + if key in modestart_options: + self.write("(set-option {} {})".format(key, val)) + + self.write("(set-logic %s)" % self.logic) + + if self.forall and self.solver == "yices": + self.write("(set-option :yices-ef-max-iters 1000000000)") + + for key, val in self.smt2_options.items(): + if key not in modestart_options: + self.write("(set-option {} {})".format(key, val)) + + def timestamp(self): + secs = int(time() - self.start_time) + return "## %3d:%02d:%02d " % (secs // (60*60), (secs // 60) % 60, secs % 60) + + def replace_in_stmt(self, stmt, pat, repl): + if stmt == pat: + return repl + + if isinstance(stmt, list): + return [self.replace_in_stmt(s, pat, repl) for s in stmt] + + return stmt + + def unroll_stmt(self, stmt): + result = [] + recursion_helper(self._unroll_stmt_into, stmt, result) + return result.pop() + + def _unroll_stmt_into(self, stmt, output, depth=128): + if not isinstance(stmt, list): + output.append(stmt) + return + + new_stmt = [] + for s in stmt: + if depth: + yield from self._unroll_stmt_into(s, new_stmt, depth - 1) + else: + yield s, new_stmt + stmt = new_stmt + + if len(stmt) >= 2 and not isinstance(stmt[0], list) and stmt[0] in self.unroll_decls: + assert stmt[1] in self.unroll_objs + + key = tuple(stmt) + if key not in self.unroll_cache: + decl = copy(self.unroll_decls[key[0]]) + + self.unroll_cache[key] = "|UNROLL#%d|" % self.unroll_idcnt + decl[1] = self.unroll_cache[key] + self.unroll_idcnt += 1 + + if decl[0] == "declare-fun": + if isinstance(decl[3], list) or decl[3] not in self.unroll_sorts: + self.unroll_objs.add(decl[1]) + decl[2] = list() + else: + self.unroll_objs.add(decl[1]) + decl = list() + + elif decl[0] == "define-fun": + arg_index = 1 + for arg_name, arg_sort in decl[2]: + decl[4] = self.replace_in_stmt(decl[4], arg_name, key[arg_index]) + arg_index += 1 + decl[2] = list() + + if len(decl) > 0: + tmp = [] + if depth: + yield from self._unroll_stmt_into(decl, tmp, depth - 1) + else: + yield decl, tmp + + decl = tmp.pop() + self.write(self.unparse(decl), unroll=False) + + output.append(self.unroll_cache[key]) + return + + output.append(stmt) + + def p_thread_main(self): + while True: + data = self.p.stdout.readline().decode("utf-8") + if data == "": break + self.p_queue.put(data) + self.p_queue.put("") + self.p_running = False + + def p_open(self): + assert self.p is None + try: + self.p = subprocess.Popen(self.popen_vargs, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + except FileNotFoundError: + print("%s SMT Solver '%s' not found in path." % (self.timestamp(), self.popen_vargs[0]), flush=True) + sys.exit(1) + running_solvers[self.p_index] = self.p + self.p_running = True + self.p_next = None + self.p_queue = Queue() + self.p_thread = Thread(target=self.p_thread_main) + self.p_thread.start() + + def p_write(self, data, flush): + assert self.p is not None + self.p.stdin.write(bytes(data, "utf-8")) + if flush: self.p.stdin.flush() + + def p_read(self): + assert self.p is not None + if self.p_next is not None: + data = self.p_next + self.p_next = None + return data + if not self.p_running: + return "" + return self.p_queue.get() + + def p_poll(self, timeout=0.1): + assert self.p is not None + assert self.p_running + if self.p_next is not None: + return False + try: + self.p_next = self.p_queue.get(True, timeout) + return False + except Empty: + return True + + def p_close(self): + assert self.p is not None + self.p.stdin.close() + self.p_thread.join() + assert not self.p_running + del running_solvers[self.p_index] + self.p = None + self.p_next = None + self.p_queue = None + self.p_thread = None + + def write(self, stmt, unroll=True): + if stmt.startswith(";"): + self.info(stmt) + if not self.setup_done: + self.info_stmts.append(stmt) + return + elif not self.setup_done: + self.setup() + + stmt = stmt.strip() + + if self.nocomments or self.unroll: + stmt = re.sub(r" *;.*", "", stmt) + if stmt == "": return + + recheck = None + + if self.solver != "dummy": + if self.noincr: + # Don't close the solver yet, if we're just unrolling definitions + # required for a (get-...) statement + if self.p is not None and not stmt.startswith("(get-") and unroll: + self.p_close() + + if unroll and self.unroll: + s = re.sub(r"\|[^|]*\|", "", stmt) + self.unroll_level += s.count("(") - s.count(")") + if self.unroll_level > 0: + self.unroll_buffer += stmt + self.unroll_buffer += " " + return + else: + stmt = self.unroll_buffer + stmt + self.unroll_buffer = "" + + s = self.parse(stmt) + + if self.recheck and s and s[0].startswith("get-"): + recheck = self.unroll_idcnt + + if self.debug_print: + print("-> %s" % s) + + if len(s) == 3 and s[0] == "declare-sort" and s[2] == "0": + self.unroll_sorts.add(s[1]) + return + + elif len(s) == 4 and s[0] == "declare-fun" and s[2] == [] and s[3] in self.unroll_sorts: + self.unroll_objs.add(s[1]) + return + + elif len(s) >= 4 and s[0] == "declare-fun": + for arg_sort in s[2]: + if arg_sort in self.unroll_sorts: + self.unroll_decls[s[1]] = s + return + + elif len(s) >= 4 and s[0] == "define-fun": + for arg_name, arg_sort in s[2]: + if arg_sort in self.unroll_sorts: + self.unroll_decls[s[1]] = s + return + + stmt = self.unparse(self.unroll_stmt(s)) + + if recheck is not None and recheck != self.unroll_idcnt: + self.check_sat(["sat"]) + + if stmt == "(push 1)": + self.unroll_stack.append(( + copy(self.unroll_sorts), + copy(self.unroll_objs), + copy(self.unroll_decls), + copy(self.unroll_cache), + )) + + if stmt == "(pop 1)": + self.unroll_sorts, self.unroll_objs, self.unroll_decls, self.unroll_cache = self.unroll_stack.pop() + + if self.debug_print: + print("> %s" % stmt) + + if self.debug_file: + print(stmt, file=self.debug_file) + self.debug_file.flush() + + if self.solver != "dummy": + if self.noincr: + if stmt == "(push 1)": + self.smt2cache.append(list()) + elif stmt == "(pop 1)": + self.smt2cache.pop() + else: + if self.p is not None: + self.p_write(stmt + "\n", True) + self.smt2cache[-1].append(stmt) + else: + self.p_write(stmt + "\n", True) + + def info(self, stmt): + if not stmt.startswith("; yosys-smt2-"): + return + + fields = stmt.split() + + if fields[1] == "yosys-smt2-solver-option": + self.smt2_options[fields[2]] = fields[3] + + if fields[1] == "yosys-smt2-nomem": + if self.logic is None: + self.logic_ax = False + + if fields[1] == "yosys-smt2-nobv": + if self.logic is None: + self.logic_bv = False + + if fields[1] == "yosys-smt2-stdt": + if self.logic is None: + self.logic_dt = True + + if fields[1] == "yosys-smt2-forall": + if self.logic is None: + self.logic_qf = False + self.forall = True + + if fields[1] == "yosys-smt2-module": + self.curmod = fields[2] + self.modinfo[self.curmod] = SmtModInfo() + + if fields[1] == "yosys-smt2-cell": + self.modinfo[self.curmod].cells[fields[3]] = fields[2] + + if fields[1] == "yosys-smt2-topmod": + self.topmod = fields[2] + + if fields[1] == "yosys-smt2-input": + self.modinfo[self.curmod].inputs.add(fields[2]) + self.modinfo[self.curmod].wsize[fields[2]] = int(fields[3]) + + if fields[1] == "yosys-smt2-output": + self.modinfo[self.curmod].outputs.add(fields[2]) + self.modinfo[self.curmod].wsize[fields[2]] = int(fields[3]) + + if fields[1] == "yosys-smt2-register": + self.modinfo[self.curmod].registers.add(fields[2]) + self.modinfo[self.curmod].wsize[fields[2]] = int(fields[3]) + + if fields[1] == "yosys-smt2-memory": + self.modinfo[self.curmod].memories[fields[2]] = (int(fields[3]), int(fields[4]), int(fields[5]), int(fields[6]), fields[7] == "async") + + if fields[1] == "yosys-smt2-wire": + self.modinfo[self.curmod].wires.add(fields[2]) + self.modinfo[self.curmod].wsize[fields[2]] = int(fields[3]) + + if fields[1] == "yosys-smt2-clock": + for edge in fields[3:]: + if fields[2] not in self.modinfo[self.curmod].clocks: + self.modinfo[self.curmod].clocks[fields[2]] = edge + elif self.modinfo[self.curmod].clocks[fields[2]] != edge: + self.modinfo[self.curmod].clocks[fields[2]] = "event" + + if fields[1] == "yosys-smt2-assert": + if len(fields) > 4: + self.modinfo[self.curmod].asserts["%s_a %s" % (self.curmod, fields[2])] = f'{fields[4]} ({fields[3]})' + else: + self.modinfo[self.curmod].asserts["%s_a %s" % (self.curmod, fields[2])] = fields[3] + + if fields[1] == "yosys-smt2-cover": + if len(fields) > 4: + self.modinfo[self.curmod].covers["%s_c %s" % (self.curmod, fields[2])] = f'{fields[4]} ({fields[3]})' + else: + self.modinfo[self.curmod].covers["%s_c %s" % (self.curmod, fields[2])] = fields[3] + + if fields[1] == "yosys-smt2-assume": + if len(fields) > 4: + self.modinfo[self.curmod].assumes["%s_u %s" % (self.curmod, fields[2])] = f'{fields[4]} ({fields[3]})' + else: + self.modinfo[self.curmod].assumes["%s_u %s" % (self.curmod, fields[2])] = fields[3] + + if fields[1] == "yosys-smt2-maximize": + self.modinfo[self.curmod].maximize.add(fields[2]) + + if fields[1] == "yosys-smt2-minimize": + self.modinfo[self.curmod].minimize.add(fields[2]) + + if fields[1] == "yosys-smt2-anyconst": + self.modinfo[self.curmod].anyconsts[fields[2]] = (fields[4], None if len(fields) <= 5 else fields[5]) + self.modinfo[self.curmod].asize[fields[2]] = int(fields[3]) + + if fields[1] == "yosys-smt2-anyseq": + self.modinfo[self.curmod].anyseqs[fields[2]] = (fields[4], None if len(fields) <= 5 else fields[5]) + self.modinfo[self.curmod].asize[fields[2]] = int(fields[3]) + + if fields[1] == "yosys-smt2-allconst": + self.modinfo[self.curmod].allconsts[fields[2]] = (fields[4], None if len(fields) <= 5 else fields[5]) + self.modinfo[self.curmod].asize[fields[2]] = int(fields[3]) + + if fields[1] == "yosys-smt2-allseq": + self.modinfo[self.curmod].allseqs[fields[2]] = (fields[4], None if len(fields) <= 5 else fields[5]) + self.modinfo[self.curmod].asize[fields[2]] = int(fields[3]) + + if fields[1] == "yosys-smt2-witness": + data = json.loads(stmt.split(None, 2)[2]) + if data.get("type") in ["cell", "mem", "posedge", "negedge", "input", "reg", "init", "seq", "blackbox"]: + self.modinfo[self.curmod].witness.append(data) + + def hiernets(self, top, regs_only=False): + def hiernets_worker(nets, mod, cursor): + for netname in sorted(self.modinfo[mod].wsize.keys()): + if not regs_only or netname in self.modinfo[mod].registers: + nets.append(cursor + [netname]) + for cellname, celltype in sorted(self.modinfo[mod].cells.items()): + hiernets_worker(nets, celltype, cursor + [cellname]) + + nets = list() + hiernets_worker(nets, top, []) + return nets + + def hieranyconsts(self, top): + def worker(results, mod, cursor): + for name, value in sorted(self.modinfo[mod].anyconsts.items()): + width = self.modinfo[mod].asize[name] + results.append((cursor, name, value[0], value[1], width)) + for cellname, celltype in sorted(self.modinfo[mod].cells.items()): + worker(results, celltype, cursor + [cellname]) + + results = list() + worker(results, top, []) + return results + + def hieranyseqs(self, top): + def worker(results, mod, cursor): + for name, value in sorted(self.modinfo[mod].anyseqs.items()): + width = self.modinfo[mod].asize[name] + results.append((cursor, name, value[0], value[1], width)) + for cellname, celltype in sorted(self.modinfo[mod].cells.items()): + worker(results, celltype, cursor + [cellname]) + + results = list() + worker(results, top, []) + return results + + def hierallconsts(self, top): + def worker(results, mod, cursor): + for name, value in sorted(self.modinfo[mod].allconsts.items()): + width = self.modinfo[mod].asize[name] + results.append((cursor, name, value[0], value[1], width)) + for cellname, celltype in sorted(self.modinfo[mod].cells.items()): + worker(results, celltype, cursor + [cellname]) + + results = list() + worker(results, top, []) + return results + + def hierallseqs(self, top): + def worker(results, mod, cursor): + for name, value in sorted(self.modinfo[mod].allseqs.items()): + width = self.modinfo[mod].asize[name] + results.append((cursor, name, value[0], value[1], width)) + for cellname, celltype in sorted(self.modinfo[mod].cells.items()): + worker(results, celltype, cursor + [cellname]) + + results = list() + worker(results, top, []) + return results + + def hiermems(self, top): + def hiermems_worker(mems, mod, cursor): + for memname in sorted(self.modinfo[mod].memories.keys()): + mems.append(cursor + [memname]) + for cellname, celltype in sorted(self.modinfo[mod].cells.items()): + hiermems_worker(mems, celltype, cursor + [cellname]) + + mems = list() + hiermems_worker(mems, top, []) + return mems + + def hierwitness(self, top, allregs=False, blackbox=True): + init_witnesses = [] + seq_witnesses = [] + clk_witnesses = [] + mem_witnesses = [] + + def absolute(path, cursor, witness): + return { + **witness, + "path": path + tuple(witness["path"]), + "smtpath": cursor + [witness["smtname"]], + } + + for witness in self.modinfo[top].witness: + if witness["type"] == "input": + seq_witnesses.append(absolute((), [], witness)) + if witness["type"] in ("posedge", "negedge"): + clk_witnesses.append(absolute((), [], witness)) + + init_types = ["init"] + if allregs: + init_types.append("reg") + + seq_types = ["seq"] + if blackbox: + seq_types.append("blackbox") + + def worker(mod, path, cursor): + cell_paths = {} + for witness in self.modinfo[mod].witness: + if witness["type"] in init_types: + init_witnesses.append(absolute(path, cursor, witness)) + if witness["type"] in seq_types: + seq_witnesses.append(absolute(path, cursor, witness)) + if witness["type"] == "mem": + if allregs and not witness["rom"]: + width, size = witness["width"], witness["size"] + witness = {**witness, "uninitialized": [{"width": width * size, "offset": 0}]} + if not witness["uninitialized"]: + continue + + mem_witnesses.append(absolute(path, cursor, witness)) + if witness["type"] == "cell": + cell_paths[witness["smtname"]] = tuple(witness["path"]) + + for cellname, celltype in sorted(self.modinfo[mod].cells.items()): + worker(celltype, path + cell_paths.get(cellname, ("?" + cellname,)), cursor + [cellname]) + + worker(top, (), []) + return init_witnesses, seq_witnesses, clk_witnesses, mem_witnesses + + def read(self): + stmt = [] + count_brackets = 0 + + while True: + if self.solver == "dummy": + line = self.dummy_fd.readline().strip() + else: + line = self.p_read().strip() + if self.dummy_file is not None: + self.dummy_fd.write(line + "\n") + + count_brackets += line.count("(") + count_brackets -= line.count(")") + stmt.append(line) + + if self.debug_print: + print("< %s" % line) + if count_brackets == 0: + break + if self.solver != "dummy" and self.p.poll(): + print("%s Solver terminated unexpectedly: %s" % (self.timestamp(), "".join(stmt)), flush=True) + sys.exit(1) + + stmt = "".join(stmt) + if stmt.startswith("(error"): + print("%s Solver Error: %s" % (self.timestamp(), stmt), flush=True) + if self.solver != "dummy": + self.p_close() + sys.exit(1) + + return stmt + + def check_sat(self, expected=["sat", "unsat", "unknown", "timeout", "interrupted"]): + if self.smt2_assumptions: + assume_exprs = " ".join(self.smt2_assumptions.values()) + check_stmt = f"(check-sat-assuming ({assume_exprs}))" + else: + check_stmt = "(check-sat)" + if self.debug_print: + print(f"> {check_stmt}") + if self.debug_file and not self.nocomments: + print("; running check-sat..", file=self.debug_file) + self.debug_file.flush() + + if self.solver != "dummy": + if self.noincr: + if self.p is not None: + self.p_close() + self.p_open() + for cache_ctx in self.smt2cache: + for cache_stmt in cache_ctx: + self.p_write(cache_stmt + "\n", False) + + self.p_write(f"{check_stmt}\n", True) + + if self.timeinfo: + i = 0 + s = r"/-\|" + + count = 0 + num_bs = 0 + while self.p_poll(): + count += 1 + + if count < 25: + continue + + if count % 10 == 0 or count == 25: + secs = count // 10 + + if secs < 60: + m = "(%d seconds)" % secs + elif secs < 60*60: + m = "(%d seconds -- %d:%02d)" % (secs, secs // 60, secs % 60) + else: + m = "(%d seconds -- %d:%02d:%02d)" % (secs, secs // (60*60), (secs // 60) % 60, secs % 60) + + print("%s %s %c" % ("\b \b" * num_bs, m, s[i]), end="", file=sys.stderr) + num_bs = len(m) + 3 + + else: + print("\b" + s[i], end="", file=sys.stderr) + + sys.stderr.flush() + i = (i + 1) % len(s) + + if num_bs != 0: + print("\b \b" * num_bs, end="", file=sys.stderr) + sys.stderr.flush() + + else: + count = 0 + while self.p_poll(60): + count += 1 + msg = None + + if count == 1: + msg = "1 minute" + + elif count in [5, 10, 15, 30]: + msg = "%d minutes" % count + + elif count == 60: + msg = "1 hour" + + elif count % 60 == 0: + msg = "%d hours" % (count // 60) + + if msg is not None: + print("%s waiting for solver (%s)" % (self.timestamp(), msg), flush=True) + + if self.forall: + result = self.read() + while result not in ["sat", "unsat", "unknown", "timeout", "interrupted", ""]: + print("%s %s: %s" % (self.timestamp(), self.solver, result)) + result = self.read() + else: + result = self.read() + + if self.debug_file: + print("(set-info :status %s)" % result, file=self.debug_file) + print(check_stmt, file=self.debug_file) + self.debug_file.flush() + + if result not in expected: + if result == "": + print("%s Unexpected EOF response from solver." % (self.timestamp()), flush=True) + else: + print("%s Unexpected response from solver: %s" % (self.timestamp(), result), flush=True) + if self.solver != "dummy": + self.p_close() + sys.exit(1) + + return result + + def parse(self, stmt): + def worker(stmt, cursor=0): + while stmt[cursor] in [" ", "\t", "\r", "\n"]: + cursor += 1 + + if stmt[cursor] == '(': + expr = [] + cursor += 1 + while stmt[cursor] != ')': + el, cursor = worker(stmt, cursor) + expr.append(el) + return expr, cursor+1 + + if stmt[cursor] == '|': + expr = "|" + cursor += 1 + while stmt[cursor] != '|': + expr += stmt[cursor] + cursor += 1 + expr += "|" + return expr, cursor+1 + + expr = "" + while stmt[cursor] not in ["(", ")", "|", " ", "\t", "\r", "\n"]: + expr += stmt[cursor] + cursor += 1 + return expr, cursor + return worker(stmt)[0] + + def unparse(self, stmt): + if isinstance(stmt, list): + return "(" + " ".join([self.unparse(s) for s in stmt]) + ")" + return stmt + + def bv2hex(self, v): + h = "" + v = self.bv2bin(v) + while len(v) > 0: + d = 0 + if len(v) > 0 and v[-1] == "1": d += 1 + if len(v) > 1 and v[-2] == "1": d += 2 + if len(v) > 2 and v[-3] == "1": d += 4 + if len(v) > 3 and v[-4] == "1": d += 8 + h = hex(d)[2:] + h + if len(v) < 4: break + v = v[:-4] + return h + + def bv2bin(self, v): + if type(v) is list and len(v) == 3 and v[0] == "_" and v[1].startswith("bv"): + x, n = int(v[1][2:]), int(v[2]) + return "".join("1" if (x & (1 << i)) else "0" for i in range(n-1, -1, -1)) + if v == "true": return "1" + if v == "false": return "0" + if v.startswith("#b"): + return v[2:] + if v.startswith("#x"): + return "".join(hex_dict.get(x) for x in v[2:]) + assert False + + def bv2int(self, v): + return int(self.bv2bin(v), 2) + + def get_raw_unsat_assumptions(self): + self.write("(get-unsat-assumptions)") + exprs = set(self.unparse(part) for part in self.parse(self.read())) + unsat_assumptions = [] + for key, value in self.smt2_assumptions.items(): + # normalize expression + value = self.unparse(self.parse(value)) + if value in exprs: + exprs.remove(value) + unsat_assumptions.append(key) + return unsat_assumptions + + def get_unsat_assumptions(self, minimize=False): + if not minimize: + return self.get_raw_unsat_assumptions() + required_assumptions = {} + + while True: + candidate_assumptions = {} + for key in self.get_raw_unsat_assumptions(): + if key not in required_assumptions: + candidate_assumptions[key] = self.smt2_assumptions[key] + + while candidate_assumptions: + + candidate_key, candidate_assume = candidate_assumptions.popitem() + + self.smt2_assumptions = {} + for key, assume in candidate_assumptions.items(): + self.smt2_assumptions[key] = assume + for key, assume in required_assumptions.items(): + self.smt2_assumptions[key] = assume + result = self.check_sat() + + if result == 'unsat': + candidate_assumptions = None + else: + required_assumptions[candidate_key] = candidate_assume + + if candidate_assumptions is not None: + return list(required_assumptions) + + def get(self, expr): + self.write("(get-value (%s))" % (expr)) + return self.parse(self.read())[0][1] + + def get_list(self, expr_list): + if len(expr_list) == 0: + return [] + self.write("(get-value (%s))" % " ".join(expr_list)) + return [n[1] for n in self.parse(self.read()) if n] + + def get_path(self, mod, path): + assert mod in self.modinfo + path = path.replace("\\", "/").split(".") + + for i in range(len(path)-1): + first = ".".join(path[0:i+1]) + second = ".".join(path[i+1:]) + + if first in self.modinfo[mod].cells: + nextmod = self.modinfo[mod].cells[first] + return [first] + self.get_path(nextmod, second) + + return [".".join(path)] + + def net_expr(self, mod, base, path): + if len(path) == 0: + return base + + if len(path) == 1: + assert mod in self.modinfo + if path[0] == "": + return base + if isinstance(path[0], int): + return "(|%s#%d| %s)" % (mod, path[0], base) + if path[0] in self.modinfo[mod].cells: + return "(|%s_h %s| %s)" % (mod, path[0], base) + if path[0] in self.modinfo[mod].wsize: + return "(|%s_n %s| %s)" % (mod, path[0], base) + if path[0] in self.modinfo[mod].memories: + return "(|%s_m %s| %s)" % (mod, path[0], base) + assert 0 + + assert mod in self.modinfo + assert path[0] in self.modinfo[mod].cells + + nextmod = self.modinfo[mod].cells[path[0]] + nextbase = "(|%s_h %s| %s)" % (mod, path[0], base) + return self.net_expr(nextmod, nextbase, path[1:]) + + def witness_net_expr(self, mod, base, witness): + net = self.net_expr(mod, base, witness["smtpath"]) + is_bool = self.net_width(mod, witness["smtpath"]) == 1 + if is_bool: + assert witness["width"] == 1 + assert witness["smtoffset"] == 0 + return net + return "((_ extract %d %d) %s)" % (witness["smtoffset"] + witness["width"] - 1, witness["smtoffset"], net) + + def net_width(self, mod, net_path): + for i in range(len(net_path)-1): + assert mod in self.modinfo + assert net_path[i] in self.modinfo[mod].cells + mod = self.modinfo[mod].cells[net_path[i]] + + assert mod in self.modinfo + if isinstance(net_path[-1], int): + return None + assert net_path[-1] in self.modinfo[mod].wsize + return self.modinfo[mod].wsize[net_path[-1]] + + def net_clock(self, mod, net_path): + for i in range(len(net_path)-1): + assert mod in self.modinfo + assert net_path[i] in self.modinfo[mod].cells + mod = self.modinfo[mod].cells[net_path[i]] + + assert mod in self.modinfo + if net_path[-1] not in self.modinfo[mod].clocks: + return None + return self.modinfo[mod].clocks[net_path[-1]] + + def net_exists(self, mod, net_path): + for i in range(len(net_path)-1): + if mod not in self.modinfo: return False + if net_path[i] not in self.modinfo[mod].cells: return False + mod = self.modinfo[mod].cells[net_path[i]] + + if mod not in self.modinfo: return False + if net_path[-1] not in self.modinfo[mod].wsize: return False + return True + + def mem_exists(self, mod, mem_path): + for i in range(len(mem_path)-1): + if mod not in self.modinfo: return False + if mem_path[i] not in self.modinfo[mod].cells: return False + mod = self.modinfo[mod].cells[mem_path[i]] + + if mod not in self.modinfo: return False + if mem_path[-1] not in self.modinfo[mod].memories: return False + return True + + def mem_expr(self, mod, base, path, port=None, infomode=False): + if len(path) == 1: + assert mod in self.modinfo + assert path[0] in self.modinfo[mod].memories + if infomode: + return self.modinfo[mod].memories[path[0]] + return "(|%s_m%s %s| %s)" % (mod, "" if port is None else ":%s" % port, path[0], base) + + assert mod in self.modinfo + assert path[0] in self.modinfo[mod].cells + + nextmod = self.modinfo[mod].cells[path[0]] + nextbase = "(|%s_h %s| %s)" % (mod, path[0], base) + return self.mem_expr(nextmod, nextbase, path[1:], port=port, infomode=infomode) + + def mem_info(self, mod, path): + return self.mem_expr(mod, "", path, infomode=True) + + def get_net(self, mod_name, net_path, state_name): + return self.get(self.net_expr(mod_name, state_name, net_path)) + + def get_net_list(self, mod_name, net_path_list, state_name): + return self.get_list([self.net_expr(mod_name, state_name, n) for n in net_path_list]) + + def get_net_hex(self, mod_name, net_path, state_name): + return self.bv2hex(self.get_net(mod_name, net_path, state_name)) + + def get_net_hex_list(self, mod_name, net_path_list, state_name): + return [self.bv2hex(v) for v in self.get_net_list(mod_name, net_path_list, state_name)] + + def get_net_bin(self, mod_name, net_path, state_name): + return self.bv2bin(self.get_net(mod_name, net_path, state_name)) + + def get_net_bin_list(self, mod_name, net_path_list, state_name): + return [self.bv2bin(v) for v in self.get_net_list(mod_name, net_path_list, state_name)] + + def wait(self): + if self.p is not None: + self.p.wait() + self.p_close() + + +class SmtOpts: + def __init__(self): + self.shortopts = "s:S:v" + self.longopts = ["unroll", "noincr", "noprogress", "timeout=", "dump-smt2=", "logic=", "dummy=", "info=", "nocomments"] + self.solver = "yices" + self.solver_opts = list() + self.debug_print = False + self.debug_file = None + self.dummy_file = None + self.unroll = False + self.noincr = False + self.timeinfo = os.name != "nt" + self.timeout = 0 + self.logic = None + self.info_stmts = list() + self.nocomments = False + + def handle(self, o, a): + if o == "-s": + self.solver = a + elif o == "-S": + self.solver_opts.append(a) + elif o == "--timeout": + self.timeout = int(a) + elif o == "-v": + self.debug_print = True + elif o == "--unroll": + self.unroll = True + elif o == "--noincr": + self.noincr = True + elif o == "--noprogress": + self.timeinfo = False + elif o == "--dump-smt2": + self.debug_file = open(a, "w") + elif o == "--logic": + self.logic = a + elif o == "--dummy": + self.dummy_file = a + elif o == "--info": + self.info_stmts.append(a) + elif o == "--nocomments": + self.nocomments = True + else: + return False + return True + + def helpmsg(self): + return """ + -s + set SMT solver: z3, yices, boolector, bitwuzla, cvc4, mathsat, dummy + default: yices + + -S + pass as command line argument to the solver + + --timeout + set the solver timeout to the specified value (in seconds). + + --logic + use the specified SMT2 logic (e.g. QF_AUFBV) + + --dummy + if solver is "dummy", read solver output from that file + otherwise: write solver output to that file + + -v + enable debug output + + --unroll + unroll uninterpreted functions + + --noincr + don't use incremental solving, instead restart solver for + each (check-sat). This also avoids (push) and (pop). + + --noprogress + disable timer display during solving + (this option is set implicitly on Windows) + + --dump-smt2 + write smt2 statements to file + + --info + include the specified smt2 info statement in the smt2 output + + --nocomments + strip all comments from the generated smt2 code +""" + + +class MkVcd: + def __init__(self, f): + self.f = f + self.t = -1 + self.nets = dict() + self.clocks = dict() + + def add_net(self, path, width): + path = tuple(path) + assert self.t == -1 + key = "n%d" % len(self.nets) + self.nets[path] = (key, width) + + def add_clock(self, path, edge): + path = tuple(path) + assert self.t == -1 + key = "n%d" % len(self.nets) + self.nets[path] = (key, 1) + self.clocks[path] = (key, edge) + + def set_net(self, path, bits): + path = tuple(path) + assert self.t >= 0 + assert path in self.nets + if path not in self.clocks: + print("b%s %s" % (bits, self.nets[path][0]), file=self.f) + + def escape_name(self, name): + name = re.sub(r"\[([0-9a-zA-Z_]*[a-zA-Z_][0-9a-zA-Z_]*)\]", r"<\1>", name) + if re.match(r"[\[\]]", name) and name[0] != "\\": + name = "\\" + name + return name + + def set_time(self, t): + assert t >= self.t + if t != self.t: + if self.t == -1: + print("$version Generated by Yosys-SMTBMC $end", file=self.f) + print("$timescale 1ns $end", file=self.f) + print("$var integer 32 t smt_step $end", file=self.f) + print("$var event 1 ! smt_clock $end", file=self.f) + + def vcdescape(n): + if n.startswith("$") or ":" in n: + return "\\" + n + return n + + scope = [] + for path in sorted(self.nets): + key, width = self.nets[path] + + uipath = list(path) + if "." in uipath[-1] and not uipath[-1].startswith("$"): + uipath = uipath[0:-1] + uipath[-1].split(".") + for i in range(len(uipath)): + uipath[i] = re.sub(r"\[([^\]]*)\]", r"<\1>", uipath[i]) + + while uipath[:len(scope)] != scope: + print("$upscope $end", file=self.f) + scope = scope[:-1] + + while uipath[:-1] != scope: + scopename = uipath[len(scope)] + print("$scope module %s $end" % vcdescape(scopename), file=self.f) + scope.append(uipath[len(scope)]) + + if path in self.clocks and self.clocks[path][1] == "event": + print("$var event 1 %s %s $end" % (key, vcdescape(uipath[-1])), file=self.f) + else: + print("$var wire %d %s %s $end" % (width, key, vcdescape(uipath[-1])), file=self.f) + + for i in range(len(scope)): + print("$upscope $end", file=self.f) + + print("$enddefinitions $end", file=self.f) + + self.t = t + assert self.t >= 0 + + if self.t > 0: + print("#%d" % (10 * self.t - 5), file=self.f) + for path in sorted(self.clocks.keys()): + if self.clocks[path][1] == "posedge": + print("b0 %s" % self.nets[path][0], file=self.f) + elif self.clocks[path][1] == "negedge": + print("b1 %s" % self.nets[path][0], file=self.f) + + print("#%d" % (10 * self.t), file=self.f) + print("1!", file=self.f) + print("b%s t" % format(self.t, "032b"), file=self.f) + + for path in sorted(self.clocks.keys()): + if self.clocks[path][1] == "negedge": + print("b0 %s" % self.nets[path][0], file=self.f) + else: + print("b1 %s" % self.nets[path][0], file=self.f) diff --git a/tests/functional/single_cells/run-test.sh b/tests/functional/single_cells/run-test.sh index 473c7faea51..67358639bf2 100755 --- a/tests/functional/single_cells/run-test.sh +++ b/tests/functional/single_cells/run-test.sh @@ -55,28 +55,27 @@ run_smt_test() { if z3 "${base_name}.smt2"; then echo "SMT file ${base_name}.smt2 is valid ." smt_successful_files["$rtlil_file"]="Success" - # if python3 using_smtio.py "${base_name}.smt2"; then - # echo "Python script generated VCD file for $rtlil_file successfully." + if python3 vcd_harness_smt.py "${base_name}.smt2"; then + echo "Python script generated VCD file for $rtlil_file successfully." - # if [ -f "${base_name}.smt2.vcd" ]; then - # echo "VCD file ${base_name}.vcd generated successfully by Python." + if [ -f "${base_name}.smt2.vcd" ]; then + echo "VCD file ${base_name}.vcd generated successfully by Python." - # if ${BASE_PATH}yosys -p "read_rtlil $rtlil_file; sim -vcd ${base_name}_yosys.vcd -r ${base_name}.smt2.vcd -scope gold -timescale 1us"; then - # echo "Yosys simulation for $rtlil_file completed successfully." - # smt_successful_files["$rtlil_file"]="Success" - # else - # echo "Yosys simulation failed for $rtlil_file." - # smt_failing_files["$rtlil_file"]="Yosys simulation failure" - # fi - # else - - # echo "Failed to generate VCD file (${base_name}.vcd) for $rtlil_file. " - # smt_failing_files["$rtlil_file"]="VCD generation failure" - # fi - # else - # echo "Failed to run Python script for $rtlil_file." - # smt_failing_files["$rtlil_file"]="Python script failure" - # fi + if ${BASE_PATH}yosys -p "read_rtlil $rtlil_file; sim -vcd ${base_name}_yosys.vcd -r ${base_name}.smt2.vcd -scope gold -timescale 1us"; then + echo "Yosys simulation for $rtlil_file completed successfully." + smt_successful_files["$rtlil_file"]="Success" + else + echo "Yosys simulation failed for $rtlil_file." + smt_failing_files["$rtlil_file"]="Yosys simulation failure" + fi + else + echo "Failed to generate VCD file (${base_name}.vcd) for $rtlil_file. " + smt_failing_files["$rtlil_file"]="VCD generation failure" + fi + else + echo "Failed to run Python script for $rtlil_file." + smt_failing_files["$rtlil_file"]="Python script failure" + fi else echo "SMT file for $rtlil_file is invalid" smt_failing_files["$rtlil_file"]="Invalid SMT" diff --git a/tests/functional/single_cells/vcd_harness_smt.py b/tests/functional/single_cells/vcd_harness_smt.py new file mode 100644 index 00000000000..a44bf4570c9 --- /dev/null +++ b/tests/functional/single_cells/vcd_harness_smt.py @@ -0,0 +1,199 @@ +import sys +import argparse +import random +import os + +# Parse command line arguments +parser = argparse.ArgumentParser(description='Process SMT file for simulation.') +parser.add_argument('smt_file', help='Path to the SMT file to simulate.') +args = parser.parse_args() + +# Get the SMT file path from the command line argument +smt_file_path = args.smt_file +current_file_path = os.path.abspath(__file__) +# Navigate to the directory you need relative to the current script +# Assuming 'backends/smt2' is located three directories up from the current script +desired_path = os.path.join(os.path.dirname(current_file_path), '..', '..', '..', 'backends', 'functional') + +# Normalize the path to resolve any '..' +normalized_path = os.path.normpath(desired_path) + +# Append the directory to the Python path +sys.path.append(normalized_path) + +# Import the module +import smtio + +# Assuming the SmtIo class and other dependencies are available in your environment +# You may need to include the entire script provided above in your script or have it available as a module + +# Step 1: Initialize an SmtIo object + +so = smtio.SmtOpts() +so.solver = "z3" +so.logic = "BV" +so.debug_print = True +# so.debug_file = "my_debug" +smt_io = smtio.SmtIo(opts=so) +# List to store the parsed results +parsed_results = [] + +# Load and execute the SMT file +with open(smt_file_path, 'r') as smt_file: + for line in smt_file: + smt_io.write(line.strip()) +smt_io.check_sat() +# Read and parse the SMT file line by line +with open(smt_file_path, 'r') as smt_file: + for line in smt_file: + # Strip leading/trailing whitespace + stripped_line = line.strip() + + # Ignore empty lines + if not stripped_line: + continue + # comments + if stripped_line.startswith(';'): + smt_io.info(stripped_line) + + # Parse the line using SmtIo's parse method + parsed_line = smt_io.parse(stripped_line) + + # Append the parsed result to the list + parsed_results.append(parsed_line) + +# Display the parsed results +for result in parsed_results: + print(result) + +inputs = {} +outputs = {} + +for lst in parsed_results: + if lst[0] == 'declare-datatypes': + for datatype_group in lst[2]: # Navigate into the nested lists + datatype_name = datatype_group[0] + declarations = datatype_group[1][1:] # Skip the first item (e.g., 'mk_inputs') + if datatype_name == 'Inputs': + for declaration in declarations: + input_name = declaration[0] + bitvec_size = declaration[1][2] + inputs[input_name] = int(bitvec_size) + elif datatype_name == 'Outputs': + for declaration in declarations: + output_name = declaration[0] + bitvec_size = declaration[1][2] + outputs[output_name] = int(bitvec_size) + +print(inputs) +print(outputs) +def set_step(inputs, step): + # This function assumes 'inputs' is a dictionary like {"A": 5, "B": 4} + # and 'input_values' is a dictionary like {"A": 5, "B": 13} specifying the concrete values for each input. + + # Construct input definitions + mk_inputs_parts = [] + for input_name, width in inputs.items(): + value = random.getrandbits(width) # Generate a random number up to the maximum value for the bit size + binary_string = format(value, '0{}b'.format(width)) # Convert value to binary with leading zeros + mk_inputs_parts.append(f"#b{binary_string}") + + # Join all parts for the mk_inputs constructor + mk_inputs_call = "mk_inputs " + " ".join(mk_inputs_parts) + define_inputs = f"(define-const test_inputs_step_n{step} Inputs ({mk_inputs_call}))\n" + + # Create the output definition by calling the gold_step function + define_output = f"(define-const test_outputs_step_n{step} Outputs (gold_step #b0 test_inputs_step_n{step}))\n" + smt_commands = [] + smt_commands.append(define_inputs) + smt_commands.append(define_output) + return smt_commands + +num_steps = 10 +smt_commands = [] +# Loop to generate SMT commands for each step +for step in range(num_steps): + for step_command in set_step(inputs, step): + smt_commands.append(step_command) + +# Print or write the SMT commands to a file +smt_file_content = ''.join(smt_commands) +print(smt_file_content) # Print to console + +# Optionally, write to a file +with open('dynamic_commands.smt2', 'w') as smt_file: + smt_file.write(smt_file_content) + +# Execute the SMT commands +for command in smt_commands: + print(command) + smt_io.write(command.strip()) + +# Check for satisfiability +# smt_io.setup() +result = smt_io.check_sat() +print(f'SAT result: {result}') + +value = smt_io.get(f'(Y test_outputs_step_n0)') +print(f" Y: {value}") + +# Store signal values +signals = {name: [] for name in list(inputs.keys()) + list(outputs.keys())} +# Retrieve and print values for each state +def hex_to_bin(value): + if value.startswith('x'): + hex_value = value[1:] # Remove the 'x' prefix + bin_value = bin(int(hex_value, 16))[2:] # Convert to binary and remove the '0b' prefix + return f'b{bin_value.zfill(len(hex_value) * 4)}' # Add 'b' prefix and pad with zeros + return value +for step in range(num_steps): + print(f"Values for step {step + 1}:") + for input_name, width in inputs.items(): + value = smt_io.get(f'({input_name} test_inputs_step_n{step})') + value = hex_to_bin(value[1:]) + print(f" {input_name}: {value}") + signals[input_name].append((step, value)) + for output_name, width in outputs.items(): + value = smt_io.get(f'({output_name} test_outputs_step_n{step})') + value = hex_to_bin(value[1:]) + print(f" {output_name}: {value}") + signals[output_name].append((step, value)) + +smt_io.p_close() + +def write_vcd(filename, signals, timescale='1 ns', date='today'): + with open(filename, 'w') as f: + # Write the header + f.write(f"$date\n {date}\n$end\n") + f.write(f"$timescale {timescale} $end\n") + + # Declare signals + f.write("$scope module gold $end\n") + for signal_name, changes in signals.items(): + signal_size = len(changes[0][1]) + f.write(f"$var wire {signal_size - 1} {signal_name} {signal_name} $end\n") + f.write("$upscope $end\n") + f.write("$enddefinitions $end\n") + + # Collect all unique timestamps + timestamps = sorted(set(time for changes in signals.values() for time, _ in changes)) + + # Write initial values + f.write("#0\n") + for signal_name, changes in signals.items(): + for time, value in changes: + if time == 0: + f.write(f"{value} {signal_name}\n") + + # Write value changes + for time in timestamps: + if time != 0: + f.write(f"#{time}\n") + for signal_name, changes in signals.items(): + for change_time, value in changes: + if change_time == time: + f.write(f"b{value} {signal_name}\n") + + +# Write the VCD file +write_vcd(smt_file_path + '.vcd', signals) From eb2bb8c45b462203b64e77c943d0e866eae3e85b Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Thu, 27 Jun 2024 13:16:57 +0100 Subject: [PATCH 28/92] tidy up generic functional backend, add generic scope class, tidy up c++ functional backend --- backends/functional/cxx.cc | 178 ++++++++++++------------------------- kernel/functionalir.cc | 44 +++++---- kernel/functionalir.h | 82 ++++++++++++----- 3 files changed, 149 insertions(+), 155 deletions(-) diff --git a/backends/functional/cxx.cc b/backends/functional/cxx.cc index 9571385ccb3..5f63a06153b 100644 --- a/backends/functional/cxx.cc +++ b/backends/functional/cxx.cc @@ -23,6 +23,7 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN +const char illegal_characters[] = "!\"#%&'()*+,-./:;<=>?@[]\\^`{|}~ "; const char *reserved_keywords[] = { "alignas","alignof","and","and_eq","asm","atomic_cancel","atomic_commit", "atomic_noexcept","auto","bitand","bitor","bool","break","case", @@ -41,45 +42,6 @@ const char *reserved_keywords[] = { nullptr }; -struct CxxScope { - pool used_names; - dict name_map; - - CxxScope() { - for(const char **p = reserved_keywords; *p != nullptr; p++) - reserve(*p); - } - void reserve(std::string name) { - used_names.insert(name); - } - std::string insert(IdString id) { - std::string str = RTLIL::unescape_id(id); - for(size_t i = 0; i < str.size(); i++) - if(strchr("!\"#%&'()*+,-./:;<=>?@[]\\^`{|}~ ", str[i])) - str[i] = '_'; - if(used_names.count(str) == 0){ - used_names.insert(str); - name_map.insert({id, str}); - return str; - } - for (int idx = 0 ; ; idx++){ - std::string suffixed = str + "_" + std::to_string(idx); - if (used_names.count(suffixed) == 0) { - used_names.insert(suffixed); - if(name_map.count(id) == 0) - name_map.insert({id, suffixed}); - return suffixed; - } - } - } - std::string operator[](IdString id) { - if(name_map.count(id) > 0) - return name_map[id]; - else - return insert(id); - } -}; - struct CxxType { FunctionalIR::Sort sort; CxxType(FunctionalIR::Sort sort) : sort(sort) {} @@ -109,66 +71,30 @@ struct CxxWriter { struct CxxStruct { std::string name; dict types; - CxxScope scope; - bool generate_methods; - CxxStruct(std::string name, bool generate_methods = false) - : name(name), generate_methods(generate_methods) { + FunctionalTools::Scope scope; + CxxStruct(std::string name) + : name(name), scope(illegal_characters, reserved_keywords) { scope.reserve("out"); scope.reserve("dump"); } void insert(IdString name, CxxType type) { - scope.insert(name); + scope(name); types.insert({name, type}); } void print(CxxWriter &f) { - f.printf("struct %s {\n", name.c_str()); + f.printf("\tstruct %s {\n", name.c_str()); for (auto p : types) { - f.printf("\t%s %s;\n", p.second.to_string().c_str(), scope[p.first].c_str()); + f.printf("\t\t%s %s;\n", p.second.to_string().c_str(), scope(p.first).c_str()); } - f.printf("\n\ttemplate void dump(T &out) const {\n"); + f.printf("\n\t\ttemplate void visit(T &fn) {\n"); for (auto p : types) { - f.printf("\t\tout(\"%s\", %s);\n", RTLIL::unescape_id(p.first).c_str(), scope[p.first].c_str()); - } - f.printf("\t}\n\n"); - - if (generate_methods) { - // Add size method - f.printf("\tint size() const {\n"); - f.printf("\t\treturn %d;\n", types.size()); - f.printf("\t}\n\n"); - - // Add get_input method - f.printf("\tstd::variant<%s> get_input(const int index) {\n", generate_variant_types().c_str()); - f.printf("\t\tswitch (index) {\n"); - int idx = 0; - for (auto p : types) { - f.printf("\t\t\tcase %d: return std::ref(%s);\n", idx, scope[p.first].c_str()); - idx++; - } - f.printf("\t\t\tdefault: throw std::out_of_range(\"Invalid input index\");\n"); - f.printf("\t\t}\n"); - f.printf("\t}\n"); + f.printf("\t\t\tfn(\"%s\", %s);\n", RTLIL::unescape_id(p.first).c_str(), scope(p.first).c_str()); } - - f.printf("};\n\n"); + f.printf("\t\t}\n"); + f.printf("\t};\n\n"); }; std::string operator[](IdString field) { - return scope[field]; - } - private: - std::string generate_variant_types() const { - std::set unique_types; - for (const auto& p : types) { - unique_types.insert("std::reference_wrapper<" + p.second.to_string() + ">"); - } - std::ostringstream oss; - for (auto it = unique_types.begin(); it != unique_types.end(); ++it) { - if (it != unique_types.begin()) { - oss << ", "; - } - oss << *it; - } - return oss.str(); + return scope(field); } }; @@ -255,58 +181,72 @@ template struct CxxPrintVisitor { std::string undriven(Node, int width) { return format("$const<%0>(0)", width); } }; -struct FunctionalCxxBackend : public Backend -{ - FunctionalCxxBackend() : Backend("functional_cxx", "convert design to C++ using the functional backend") {} +struct CxxModule { + FunctionalIR ir; + CxxStruct input_struct, output_struct, state_struct; + std::string module_name; - void help() override + explicit CxxModule(Module *module) : + ir(FunctionalIR::from_module(module)), + input_struct("Inputs"), + output_struct("Outputs"), + state_struct("State") { - // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| - log("\n"); - } - - void printCxx(std::ostream &stream, std::string, std::string const & name, Module *module) - { - auto ir = FunctionalIR::from_module(module); - CxxWriter f(stream); - - f.printf("#include \"sim.h\"\n"); - f.printf("#include \n"); - CxxStruct input_struct(name + "_Inputs", true); for (auto [name, sort] : ir.inputs()) input_struct.insert(name, sort); - CxxStruct output_struct(name + "_Outputs"); for (auto [name, sort] : ir.outputs()) output_struct.insert(name, sort); - CxxStruct state_struct(name + "_State"); for (auto [name, sort] : ir.state()) state_struct.insert(name, sort); - - dict node_names; - CxxScope locals; - + module_name = FunctionalTools::Scope(illegal_characters, reserved_keywords)(module->name); + } + void write_header(CxxWriter &f) { + f.printf("#include \"sim.h\"\n\n"); + } + void write_struct_def(CxxWriter &f) { + f.printf("struct %s {\n", module_name.c_str()); input_struct.print(f); output_struct.print(f); state_struct.print(f); - - f.printf("void %s(%s_Inputs const &input, %s_Outputs &output, %s_State const ¤t_state, %s_State &next_state)\n{\n", name.c_str(), name.c_str(), name.c_str(), name.c_str(), name.c_str()); + f.printf("\tstatic void eval(Inputs const &, Outputs &, State const &, State &);\n"); + f.printf("};\n\n"); + } + void write_eval_def(CxxWriter &f) { + f.printf("void %s::eval(%s::Inputs const &input, %s::Outputs &output, %s::State const ¤t_state, %s::State &next_state)\n{\n", module_name.c_str(), module_name.c_str(), module_name.c_str(), module_name.c_str(), module_name.c_str()); + FunctionalTools::Scope locals(illegal_characters, reserved_keywords); locals.reserve("input"); locals.reserve("output"); locals.reserve("current_state"); locals.reserve("next_state"); - auto node_to_string = [&](FunctionalIR::Node n) { return node_names.at(n.id()); }; + auto node_name = [&](FunctionalIR::Node n) { return locals(n.id(), n.name()); }; for (auto node : ir) - { - std::string name = locals.insert(node.name()); - node_names.emplace(node.id(), name); - f.printf("\t%s %s = %s;\n", CxxType(node.sort()).to_string().c_str(), name.c_str(), node.visit(CxxPrintVisitor(node_to_string, input_struct, state_struct)).c_str()); - } + f.printf("\t%s %s = %s;\n", CxxType(node.sort()).to_string().c_str(), node_name(node).c_str(), node.visit(CxxPrintVisitor(node_name, input_struct, state_struct)).c_str()); for (auto [name, sort] : ir.state()) - f.printf("\tnext_state.%s = %s;\n", state_struct[name].c_str(), node_to_string(ir.get_state_next_node(name)).c_str()); + f.printf("\tnext_state.%s = %s;\n", state_struct[name].c_str(), node_name(ir.get_state_next_node(name)).c_str()); for (auto [name, sort] : ir.outputs()) - f.printf("\toutput.%s = %s;\n", output_struct[name].c_str(), node_to_string(ir.get_output_node(name)).c_str()); + f.printf("\toutput.%s = %s;\n", output_struct[name].c_str(), node_name(ir.get_output_node(name)).c_str()); f.printf("}\n"); } +}; + +struct FunctionalCxxBackend : public Backend +{ + FunctionalCxxBackend() : Backend("functional_cxx", "convert design to C++ using the functional backend") {} + + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + } + + void printCxx(std::ostream &stream, std::string, Module *module) + { + CxxWriter f(stream); + CxxModule mod(module); + mod.write_header(f); + mod.write_struct_def(f); + mod.write_eval_def(f); + } void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override { @@ -317,7 +257,7 @@ struct FunctionalCxxBackend : public Backend for (auto module : design->selected_modules()) { log("Dumping module `%s'.\n", module->name.c_str()); - printCxx(*f, filename, RTLIL::unescape_id(module->name), module); + printCxx(*f, filename, module); } } } FunctionalCxxBackend; diff --git a/kernel/functionalir.cc b/kernel/functionalir.cc index 27765ac26c5..6ae66d6802b 100644 --- a/kernel/functionalir.cc +++ b/kernel/functionalir.cc @@ -33,7 +33,7 @@ class CellSimplifier { } else { reduced_b_width = new_width; T lower_b = factory.slice(b, b_width, 0, new_width); - T overflow = factory.ugt(b, factory.constant(RTLIL::Const(y_width, b_width)), b_width); + T overflow = factory.unsigned_greater_than(b, factory.constant(RTLIL::Const(y_width, b_width)), b_width); return factory.mux(lower_b, factory.constant(RTLIL::Const(y_width, new_width)), overflow, new_width); } } @@ -102,17 +102,17 @@ class CellSimplifier { T a = extend(inputs.at(ID(A)), a_width, width, is_signed); T b = extend(inputs.at(ID(B)), b_width, width, is_signed); if(cellType.in({ID($eq), ID($eqx)})) - return extend(factory.eq(a, b, width), 1, y_width, false); + return extend(factory.equal(a, b, width), 1, y_width, false); else if(cellType.in({ID($ne), ID($nex)})) - return extend(factory.ne(a, b, width), 1, y_width, false); + return extend(factory.not_equal(a, b, width), 1, y_width, false); else if(cellType == ID($lt)) - return extend(is_signed ? factory.gt(b, a, width) : factory.ugt(b, a, width), 1, y_width, false); + return extend(is_signed ? factory.signed_greater_than(b, a, width) : factory.unsigned_greater_than(b, a, width), 1, y_width, false); else if(cellType == ID($le)) - return extend(is_signed ? factory.ge(b, a, width) : factory.uge(b, a, width), 1, y_width, false); + return extend(is_signed ? factory.signed_greater_equal(b, a, width) : factory.unsigned_greater_equal(b, a, width), 1, y_width, false); else if(cellType == ID($gt)) - return extend(is_signed ? factory.gt(a, b, width) : factory.ugt(a, b, width), 1, y_width, false); + return extend(is_signed ? factory.signed_greater_than(a, b, width) : factory.unsigned_greater_than(a, b, width), 1, y_width, false); else if(cellType == ID($ge)) - return extend(is_signed ? factory.ge(a, b, width) : factory.uge(a, b, width), 1, y_width, false); + return extend(is_signed ? factory.signed_greater_equal(a, b, width) : factory.unsigned_greater_equal(a, b, width), 1, y_width, false); else log_abort(); }else if(cellType.in({ID($logic_or), ID($logic_and)})){ @@ -127,7 +127,7 @@ class CellSimplifier { return extend(inputs.at(ID(A)), a_width, y_width, a_signed); }else if(cellType == ID($neg)){ T a = extend(inputs.at(ID(A)), a_width, y_width, a_signed); - return factory.neg(a, y_width); + return factory.unary_minus(a, y_width); }else if(cellType == ID($logic_not)){ T a = reduce_or(inputs.at(ID(A)), a_width); T y = factory.bitwise_not(a, 1); @@ -161,7 +161,7 @@ class CellSimplifier { T shr = logical_shift_right(a, b, width, b_width); if(b_signed) { T sign_b = factory.slice(b, b_width, b_width - 1, 1); - T shl = logical_shift_left(a, factory.neg(b, b_width), width, b_width); + T shl = logical_shift_left(a, factory.unary_minus(b, b_width), width, b_width); T y = factory.mux(shr, shl, sign_b, width); return extend(y, width, y_width, false); } else { @@ -237,10 +237,10 @@ class FunctionalIRConstruction { if(results.size() == 0) return factory.undriven(0); T node = results[0]; - int size = results[0].size(); + int size = results[0].width(); for(size_t i = 1; i < results.size(); i++) { - node = factory.concat(node, size, results[i], results[i].size()); - size += results[i].size(); + node = factory.concat(node, size, results[i], results[i].width()); + size += results[i].width(); } return node; } @@ -405,6 +405,13 @@ void FunctionalIR::topological_sort() { if(scc) log_error("combinational loops, aborting\n"); } +IdString merge_name(IdString a, IdString b) { + if(a[0] == '$' && b[0] == '\\') + return b; + else + return a; +} + void FunctionalIR::forward_buf() { std::vector perm, alias; perm.clear(); @@ -416,10 +423,15 @@ void FunctionalIR::forward_buf() { { int target_index = alias[node.arg(0).index()]; auto target_node = _graph[perm[target_index]]; - if(!target_node.has_sparse_attr() && node.has_sparse_attr()){ - IdString id = node.sparse_attr(); - target_node.sparse_attr() = id; - } + if(node.has_sparse_attr()) { + if(target_node.has_sparse_attr()) { + IdString id = merge_name(node.sparse_attr(), target_node.sparse_attr()); + target_node.sparse_attr() = id; + } else { + IdString id = node.sparse_attr(); + target_node.sparse_attr() = id; + } + } alias.push_back(target_index); } else diff --git a/kernel/functionalir.h b/kernel/functionalir.h index 7eca9d87f83..f49a659f37b 100644 --- a/kernel/functionalir.h +++ b/kernel/functionalir.h @@ -29,6 +29,58 @@ USING_YOSYS_NAMESPACE YOSYS_NAMESPACE_BEGIN +namespace FunctionalTools { + class Scope { + const char *_illegal_characters; + pool _used_names; + dict _by_id; + dict _by_name; + std::string allocate_name(IdString suggestion) { + std::string str = RTLIL::unescape_id(suggestion); + for(size_t i = 0; i < str.size(); i++) + if(strchr(_illegal_characters, str[i])) + str[i] = '_'; + if(_used_names.count(str) == 0) { + _used_names.insert(str); + return str; + } + for (int idx = 0 ; ; idx++){ + std::string suffixed = str + "_" + std::to_string(idx); + if(_used_names.count(suffixed) == 0) { + _used_names.insert(suffixed); + return suffixed; + } + } + } + public: + Scope(const char *illegal_characters = "", const char **keywords = nullptr) { + _illegal_characters = illegal_characters; + if(keywords != nullptr) + for(const char **p = keywords; *p != nullptr; p++) + reserve(*p); + } + void reserve(std::string name) { + _used_names.insert(std::move(name)); + } + std::string operator()(int id, IdString suggestion) { + auto it = _by_id.find(id); + if(it != _by_id.end()) + return it->second; + std::string str = allocate_name(suggestion); + _by_id.insert({id, str}); + return str; + } + std::string operator()(IdString idstring) { + auto it = _by_name.find(idstring); + if(it != _by_name.end()) + return it->second; + std::string str = allocate_name(idstring); + _by_name.insert({idstring, str}); + return str; + } + }; +} + class FunctionalIR { enum class Fn { invalid, @@ -133,7 +185,7 @@ class FunctionalIR { friend class Factory; friend class FunctionalIR; Graph::Ref _ref; - Node(Graph::Ref ref) : _ref(ref) { } + explicit Node(Graph::Ref ref) : _ref(ref) { } operator Graph::Ref() { return _ref; } template struct PrintVisitor { NodePrinter np; @@ -225,7 +277,6 @@ class FunctionalIR { { return visit(PrintVisitor(np)); } - /* TODO: delete */ int size() const { return sort().width(); } }; class Factory { FunctionalIR &_ir; @@ -235,7 +286,7 @@ class FunctionalIR { Graph::Ref ref = _ir._graph.add(std::move(fn), {std::move(sort)}); for (auto arg : args) ref.append_arg(Graph::Ref(arg)); - return ref; + return Node(ref); } void check_basic_binary(Node const &a, Node const &b) { log_assert(a.sort().is_signal() && a.sort() == b.sort()); } void check_shift(Node const &a, Node const &b) { log_assert(a.sort().is_signal() && b.sort().is_signal()); } @@ -341,39 +392,30 @@ class FunctionalIR { void suggest_name(Node node, IdString name) { node._ref.sparse_attr() = name; } - - /* TODO delete this later*/ - Node eq(Node a, Node b, int) { return equal(a, b, 0); } - Node ne(Node a, Node b, int) { return not_equal(a, b, 0); } - Node gt(Node a, Node b, int) { return signed_greater_than(a, b, 0); } - Node ge(Node a, Node b, int) { return signed_greater_equal(a, b, 0); } - Node ugt(Node a, Node b, int) { return unsigned_greater_than(a, b, 0); } - Node uge(Node a, Node b, int) { return unsigned_greater_equal(a, b, 0); } - Node neg(Node a, int) { return unary_minus(a, 0); } }; static FunctionalIR from_module(Module *module); Factory factory() { return Factory(*this); } int size() const { return _graph.size(); } - Node operator[](int i) { return _graph[i]; } + Node operator[](int i) { return Node(_graph[i]); } void topological_sort(); void forward_buf(); dict inputs() const { return _inputs; } dict outputs() const { return _outputs; } dict state() const { return _state; } - Node get_output_node(IdString name) { return _graph({name, false}); } - Node get_state_next_node(IdString name) { return _graph({name, true}); } + Node get_output_node(IdString name) { return Node(_graph({name, false})); } + Node get_state_next_node(IdString name) { return Node(_graph({name, true})); } class Iterator { friend class FunctionalIR; - FunctionalIR &_ir; + FunctionalIR *_ir; int _index; - Iterator(FunctionalIR &ir, int index) : _ir(ir), _index(index) {} + Iterator(FunctionalIR *ir, int index) : _ir(ir), _index(index) {} public: - Node operator*() { return _ir._graph[_index]; } + Node operator*() { return Node(_ir->_graph[_index]); } Iterator &operator++() { _index++; return *this; } bool operator!=(Iterator const &other) const { return _index != other._index; } }; - Iterator begin() { return Iterator(*this, 0); } - Iterator end() { return Iterator(*this, _graph.size()); } + Iterator begin() { return Iterator(this, 0); } + Iterator end() { return Iterator(this, _graph.size()); } }; YOSYS_NAMESPACE_END From 21bb1cf1bc0383af88c58b0ddfa20db18ecbac27 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Thu, 27 Jun 2024 15:43:57 +0100 Subject: [PATCH 29/92] rewrite functional c++ simulation library --- backends/functional/cxx.cc | 60 +- backends/functional/cxx_runtime/sim.h | 624 +++++++++---------- tests/functional/single_cells/vcd_harness.cc | 77 ++- 3 files changed, 364 insertions(+), 397 deletions(-) diff --git a/backends/functional/cxx.cc b/backends/functional/cxx.cc index 5f63a06153b..d866dfea210 100644 --- a/backends/functional/cxx.cc +++ b/backends/functional/cxx.cc @@ -86,7 +86,7 @@ struct CxxStruct { for (auto p : types) { f.printf("\t\t%s %s;\n", p.second.to_string().c_str(), scope(p.first).c_str()); } - f.printf("\n\t\ttemplate void visit(T &fn) {\n"); + f.printf("\n\t\ttemplate void visit(T &&fn) {\n"); for (auto p : types) { f.printf("\t\t\tfn(\"%s\", %s);\n", RTLIL::unescape_id(p.first).c_str(), scope(p.first).c_str()); } @@ -148,37 +148,37 @@ template struct CxxPrintVisitor { return CxxTemplate::format(fmt, arg_to_string(args)...); } std::string buf(Node, Node n) { return np(n); } - std::string slice(Node, Node a, int, int offset, int out_width) { return format("slice<%2>(%0, %1)", a, offset, out_width); } - std::string zero_extend(Node, Node a, int, int out_width) { return format("$zero_extend<%1>(%0)", a, out_width); } - std::string sign_extend(Node, Node a, int, int out_width) { return format("$sign_extend<%1>(%0)", a, out_width); } - std::string concat(Node, Node a, int, Node b, int) { return format("concat(%0, %1)", a, b); } - std::string add(Node, Node a, Node b, int) { return format("$add(%0, %1)", a, b); } - std::string sub(Node, Node a, Node b, int) { return format("$sub(%0, %1)", a, b); } - std::string bitwise_and(Node, Node a, Node b, int) { return format("$and(%0, %1)", a, b); } - std::string bitwise_or(Node, Node a, Node b, int) { return format("$or(%0, %1)", a, b); } - std::string bitwise_xor(Node, Node a, Node b, int) { return format("$xor(%0, %1)", a, b); } - std::string bitwise_not(Node, Node a, int) { return format("$not(%0)", a); } - std::string unary_minus(Node, Node a, int) { return format("$neg(%0)", a); } - std::string reduce_and(Node, Node a, int) { return format("$reduce_and(%0)", a); } - std::string reduce_or(Node, Node a, int) { return format("$reduce_or(%0)", a); } - std::string reduce_xor(Node, Node a, int) { return format("$reduce_xor(%0)", a); } - std::string equal(Node, Node a, Node b, int) { return format("$eq(%0, %1)", a, b); } - std::string not_equal(Node, Node a, Node b, int) { return format("$ne(%0, %1)", a, b); } - std::string signed_greater_than(Node, Node a, Node b, int) { return format("$gt(%0, %1)", a, b); } - std::string signed_greater_equal(Node, Node a, Node b, int) { return format("$ge(%0, %1)", a, b); } - std::string unsigned_greater_than(Node, Node a, Node b, int) { return format("$ugt(%0, %1)", a, b); } - std::string unsigned_greater_equal(Node, Node a, Node b, int) { return format("$uge(%0, %1)", a, b); } - std::string logical_shift_left(Node, Node a, Node b, int, int) { return format("$shl<%2>(%0, %1)", a, b, a.width()); } - std::string logical_shift_right(Node, Node a, Node b, int, int) { return format("$shr<%2>(%0, %1)", a, b, a.width()); } - std::string arithmetic_shift_right(Node, Node a, Node b, int, int) { return format("$asr<%2>(%0, %1)", a, b, a.width()); } - std::string mux(Node, Node a, Node b, Node s, int) { return format("$mux(%0, %1, %2)", a, b, s); } - std::string pmux(Node, Node a, Node b, Node s, int, int) { return format("$pmux(%0, %1, %2)", a, b, s); } - std::string constant(Node, RTLIL::Const value) { return format("$const<%0>(%1)", value.size(), value.as_int()); } + std::string slice(Node, Node a, int, int offset, int out_width) { return format("%0.slice<%2>(%1)", a, offset, out_width); } + std::string zero_extend(Node, Node a, int, int out_width) { return format("%0.zero_extend<%1>()", a, out_width); } + std::string sign_extend(Node, Node a, int, int out_width) { return format("%0.sign_extend<%1>()", a, out_width); } + std::string concat(Node, Node a, int, Node b, int) { return format("%0.concat(%1)", a, b); } + std::string add(Node, Node a, Node b, int) { return format("%0 + %1", a, b); } + std::string sub(Node, Node a, Node b, int) { return format("%0 - %1", a, b); } + std::string bitwise_and(Node, Node a, Node b, int) { return format("%0 & %1", a, b); } + std::string bitwise_or(Node, Node a, Node b, int) { return format("%0 | %1", a, b); } + std::string bitwise_xor(Node, Node a, Node b, int) { return format("%0 ^ %1", a, b); } + std::string bitwise_not(Node, Node a, int) { return format("~%0", a); } + std::string unary_minus(Node, Node a, int) { return format("-%0", a); } + std::string reduce_and(Node, Node a, int) { return format("%0.all()", a); } + std::string reduce_or(Node, Node a, int) { return format("%0.any()", a); } + std::string reduce_xor(Node, Node a, int) { return format("%0.parity()", a); } + std::string equal(Node, Node a, Node b, int) { return format("%0 == %1", a, b); } + std::string not_equal(Node, Node a, Node b, int) { return format("%0 != %1", a, b); } + std::string signed_greater_than(Node, Node a, Node b, int) { return format("%0.signed_greater_than(%1)", a, b); } + std::string signed_greater_equal(Node, Node a, Node b, int) { return format("%0.signed_greater_equal(%1)", a, b); } + std::string unsigned_greater_than(Node, Node a, Node b, int) { return format("%0 > %1", a, b); } + std::string unsigned_greater_equal(Node, Node a, Node b, int) { return format("%0 >= %1", a, b); } + std::string logical_shift_left(Node, Node a, Node b, int, int) { return format("%0 << %1", a, b); } + std::string logical_shift_right(Node, Node a, Node b, int, int) { return format("%0 >> %1", a, b); } + std::string arithmetic_shift_right(Node, Node a, Node b, int, int) { return format("%0.arithmetic_shift_right(%1)", a, b); } + std::string mux(Node, Node a, Node b, Node s, int) { return format("%2.any() ? %1 : %0", a, b, s); } + std::string pmux(Node, Node a, Node b, Node s, int, int) { return format("%0.pmux(%1, %2)", a, b, s); } + std::string constant(Node, RTLIL::Const value) { return format("Signal<%0>(%1)", value.size(), value.as_int()); } std::string input(Node, IdString name) { return format("input.%0", input_struct[name]); } std::string state(Node, IdString name) { return format("current_state.%0", state_struct[name]); } - std::string memory_read(Node, Node mem, Node addr, int, int) { return format("$memory_read(%0, %1)", mem, addr); } - std::string memory_write(Node, Node mem, Node addr, Node data, int, int) { return format("$memory_write(%0, %1, %2)", mem, addr, data); } - std::string undriven(Node, int width) { return format("$const<%0>(0)", width); } + std::string memory_read(Node, Node mem, Node addr, int, int) { return format("%0.read(%1)", mem, addr); } + std::string memory_write(Node, Node mem, Node addr, Node data, int, int) { return format("%0.write(%1, %2)", mem, addr, data); } + std::string undriven(Node, int width) { return format("Signal<%0>(0)", width); } }; struct CxxModule { diff --git a/backends/functional/cxx_runtime/sim.h b/backends/functional/cxx_runtime/sim.h index 3a398a13a5a..0614fb34cf7 100644 --- a/backends/functional/cxx_runtime/sim.h +++ b/backends/functional/cxx_runtime/sim.h @@ -24,363 +24,333 @@ #include template -using Signal = std::array; +class Signal { + template friend class Signal; + std::array _bits; +public: + Signal() { } + Signal(uint32_t val) + { + for(size_t i = 0; i < n; i++) + if(i < 32) + _bits[i] = val & (1< -Signal slice(Signal const& a, size_t offset) -{ - Signal ret; + Signal(std::initializer_list vals) + { + size_t k, i; - std::copy(a.begin() + offset, a.begin() + offset + n, ret.begin()); - return ret; -} + k = 0; + for (auto val : vals) { + for(i = 0; i < 32; i++) + if(i + k < n) + _bits[i + k] = val & (1< -Signal $const(uint32_t val) -{ - size_t i; - Signal ret; - - for(i = 0; i < n; i++) - if(i < 32) - ret[i] = val & (1< + static Signal from_array(T vals) + { + size_t k, i; + Signal ret; + + k = 0; + for (auto val : vals) { + for(i = 0; i < 32; i++) + if(i + k < n) + ret._bits[i + k] = val & (1< -Signal $const(std::initializer_list vals) -{ - size_t k, i; - Signal ret; - - k = 0; - for (auto val : vals) { - for(i = 0; i < 32; i++) - if(i + k < n) - ret[i + k] = val & (1< ret; + for(size_t i = 0; i < n; i++) + if(i < 32) + ret._bits[i] = val & (1< ret; + for(size_t i = 0; i < n; i++) + ret._bits[i] = b; + return ret; + } -template -bool as_bool(Signal sig) -{ - for(int i = 0; i < n; i++) - if(sig[i]) - return true; - return false; -} + int size() const { return n; } + bool operator[](int i) const { assert(n >= 0 && i < n); return _bits[i]; } -template -uint32_t as_int(Signal sig) -{ - uint32_t ret = 0; - for(int i = 0; i < n; i++) - if(sig[i] && i < 32) - ret |= 1< + Signal slice(size_t offset) const + { + Signal ret; -template -Signal $mux(Signal const& a, Signal const &b, Signal<1> const &s) -{ - return s[0] ? b : a; -} + assert(offset + m <= n); + std::copy(_bits.begin() + offset, _bits.begin() + offset + m, ret._bits.begin()); + return ret; + } -template -Signal $not(Signal const& a) -{ - Signal ret; - for(size_t i = 0; i < n; i++) - ret[i] = !a[i]; - return ret; -} + bool any() const + { + for(int i = 0; i < n; i++) + if(_bits[i]) + return true; + return false; + } -template -Signal $neg(Signal const& a) -{ - Signal ret; - bool carry = true; - for(size_t i = 0; i < n; i++) { - int r = !a[i] + carry; - ret[i] = (r & 1) != 0; - carry = (r >> 1) != 0; - } - return ret; -} + bool all() const + { + for(int i = 0; i < n; i++) + if(!_bits[i]) + return false; + return true; + } -template -Signal<1> $reduce_or(Signal const& a) -{ - return { as_bool(a) }; -} + bool parity() const + { + bool result = false; + for(int i = 0; i < n; i++) + result ^= _bits[i]; + return result; + } -template -Signal<1> $reduce_and(Signal const& a) -{ - for(size_t i = 0; i < n; i++) - if(!a[i]) - return { false }; - return { true }; -} + bool sign() const { return _bits[n-1]; } -template -Signal<1> $reduce_bool(Signal const& a) -{ - return { as_bool(a) }; -} + template + T as_numeric() const + { + T ret = 0; + for(size_t i = 0; i < std::min(sizeof(T) * 8, n); i++) + if(_bits[i]) + ret |= ((T)1)< -Signal<1> $logic_and(Signal const& a, Signal const& b) -{ - return { as_bool(a) && as_bool(b) }; -} + template + T as_numeric_clamped() const + { + for(size_t i = sizeof(T) * 8; i < n; i++) + if(_bits[i]) + return ~0; + return as_numeric(); + } -template -Signal<1> $logic_or(Signal const& a, Signal const& b) -{ - return { as_bool(a) || as_bool(b) }; -} + uint32_t as_int() { return as_numeric(); } -template -Signal<1> $logic_not(Signal const& a) -{ - return { !as_bool(a) }; -} + Signal operator ~() const + { + Signal ret; + for(size_t i = 0; i < n; i++) + ret._bits[i] = !_bits[i]; + return ret; + } -template -Signal $add(Signal const& a, Signal const &b) -{ - Signal ret; - size_t i; - int x = 0; - for(i = 0; i < n; i++){ - x += (int)a[i] + (int)b[i]; - ret[i] = x & 1; - x >>= 1; - } - return ret; -} -template -Signal $sub(Signal const& a, Signal const &b) -{ - Signal ret; - int x = 1; - for(size_t i = 0; i < n; i++){ - x += (int)a[i] + (int)!b[i]; - ret[i] = x & 1; - x >>= 1; - } - return ret; -} + Signal operator -() const + { + Signal ret; + bool carry = true; + for(size_t i = 0; i < n; i++) { + int r = !_bits[i] + carry; + ret._bits[i] = (r & 1) != 0; + carry = (r >> 1) != 0; + } + return ret; + } -template -Signal<1> $uge(Signal const& a, Signal const &b) -{ - for(size_t i = n; i-- != 0; ) - if(a[i] != b[i]) - return { a[i] }; - return { true }; -} + Signal operator +(Signal const &b) const + { + Signal ret; + size_t i; + int x = 0; + for(i = 0; i < n; i++){ + x += (int)_bits[i] + (int)b._bits[i]; + ret._bits[i] = x & 1; + x >>= 1; + } + return ret; + } -template -Signal<1> $ugt(Signal const& a, Signal const &b) -{ - for(size_t i = n; i-- != 0; ) - if(a[i] != b[i]) - return { a[i] }; - return { false }; -} + Signal operator -(Signal const &b) const + { + Signal ret; + int x = 1; + for(size_t i = 0; i < n; i++){ + x += (int)_bits[i] + (int)!b._bits[i]; + ret._bits[i] = x & 1; + x >>= 1; + } + return ret; + } -template -Signal<1> $ge(Signal const& a, Signal const &b) -{ - if(a[n-1] != b[n-1]) - return { b[n-1] }; - return $uge(a, b); -} + bool operator ==(Signal const &b) const + { + for(size_t i = 0; i < n; i++) + if(_bits[i] != b._bits[i]) + return false; + return true; + } -template -Signal<1> $gt(Signal const& a, Signal const &b) -{ - if(a[n-1] != b[n-1]) - return { b[n-1] }; - return $ugt(a, b); -} - -template Signal<1> $ule(Signal const& a, Signal const &b) { return $uge(b, a); } -template Signal<1> $ult(Signal const& a, Signal const &b) { return $ugt(b, a); } -template Signal<1> $le(Signal const& a, Signal const &b) { return $ge(b, a); } -template Signal<1> $lt(Signal const& a, Signal const &b) { return $gt(b, a); } + bool operator >=(Signal const &b) const + { + for(size_t i = n; i-- != 0; ) + if(_bits[i] != b._bits[i]) + return _bits[i]; + return true; + } -template -Signal $and(Signal const& a, Signal const &b) -{ - Signal ret; - for(size_t i = 0; i < n; i++) - ret[i] = a[i] && b[i]; - return ret; -} + bool operator >(Signal const &b) const + { + for(size_t i = n; i-- != 0; ) + if(_bits[i] != b._bits[i]) + return _bits[i]; + return false; + } -template -Signal $or(Signal const& a, Signal const &b) -{ - Signal ret; - for(size_t i = 0; i < n; i++) - ret[i] = a[i] || b[i]; - return ret; -} + bool operator !=(Signal const &b) const { return !(*this == b); } + bool operator <=(Signal const &b) const { return b <= *this; } + bool operator <(Signal const &b) const { return b < *this; } -template -Signal $xor(Signal const& a, Signal const &b) -{ - Signal ret; - for(size_t i = 0; i < n; i++) - ret[i] = a[i] != b[i]; - return ret; -} - -template -Signal $shl(Signal const& a, Signal const &b) -{ - if(nb >= sizeof(int) * 8 - 1) - for(size_t i = sizeof(int) * 8 - 1; i < nb; i++) - assert(!b[i]); - size_t amount = as_int(b); - Signal ret = $const(0); - if(amount < n){ - if(amount + na > n) - std::copy(a.begin(), a.begin() + (n - amount), ret.begin() + amount); - else - std::copy(a.begin(), a.end(), ret.begin() + amount); - } - return ret; -} - -template -Signal $shr(Signal const& a, Signal const &b) -{ - if(nb >= sizeof(int) * 8 - 1) - for(size_t i = sizeof(int) * 8 - 1; i < nb; i++) - assert(!b[i]); - size_t amount = as_int(b); - Signal ret; - for (size_t i = 0; i < n; i++) { - if(i + amount < n) - ret[i] = a[i + amount]; - else - ret[i] = false; - } - return ret; -} - -template -Signal $asr(Signal const& a, Signal const &b) -{ - if(nb >= sizeof(int) * 8 - 1) - for(size_t i = sizeof(int) * 8 - 1; i < nb; i++) - assert(!b[i]); - size_t amount = as_int(b); - Signal ret; - for (size_t i = 0; i < n; i++) { - if(i + amount < n) - ret[i] = a[i + amount]; - else - ret[i] = a[n - 1]; - } - return ret; -} + bool signed_greater_than(Signal const &b) const + { + if(_bits[n-1] != b._bits[n-1]) + return b._bits[n-1]; + return *this > b; + } -template -Signal<1> $eq(Signal const& a, Signal const &b) -{ - for(size_t i = 0; i < n; i++) - if(a[i] != b[i]) - return { false }; - return { true }; -} + bool signed_greater_equal(Signal const &b) const + { + if(_bits[n-1] != b._bits[n-1]) + return b._bits[n-1]; + return *this >= b; + } -template -Signal<1> $ne(Signal const& a, Signal const &b) -{ - for(size_t i = 0; i < n; i++) - if(a[i] != b[i]) - return { true }; - return { false }; -} - -template -Signal $pmux(Signal const& a, Signal const &b, Signal const &s) -{ - bool found; - Signal ret; - - found = false; - ret = a; - for(size_t i = 0; i < ns; i++){ - if(s[i]){ - if(found) - return $const(0); - found = true; - ret = slice(b, n * i); + Signal operator &(Signal const &b) const + { + Signal ret; + for(size_t i = 0; i < n; i++) + ret._bits[i] = _bits[i] && b._bits[i]; + return ret; + } + + Signal operator |(Signal const &b) const + { + Signal ret; + for(size_t i = 0; i < n; i++) + ret._bits[i] = _bits[i] || b._bits[i]; + return ret; + } + + Signal operator ^(Signal const &b) const + { + Signal ret; + for(size_t i = 0; i < n; i++) + ret._bits[i] = _bits[i] != b._bits[i]; + return ret; + } + + template + Signal operator <<(Signal const &b) const + { + Signal ret = 0; + size_t amount = b.template as_numeric_clamped(); + if(amount < n) + std::copy(_bits.begin(), _bits.begin() + (n - amount), ret._bits.begin() + amount); + return ret; + } + + template + Signal operator >>(Signal const &b) const + { + Signal ret = 0; + size_t amount = b.template as_numeric_clamped(); + if(amount < n) + std::copy(_bits.begin() + amount, _bits.end(), ret._bits.begin()); + return ret; + } + + template + Signal arithmetic_shift_right(Signal const &b) const + { + Signal ret = Signal::repeat(sign()); + size_t amount = b.template as_numeric_clamped(); + if(amount < n) + std::copy(_bits.begin() + amount, _bits.end(), ret._bits.begin()); + return ret; + } + + template + Signal pmux(Signal const &b, Signal const &s) const + { + bool found; + Signal ret; + + found = false; + ret = *this; + for(size_t i = 0; i < ns; i++){ + if(s._bits[i]){ + if(found) + return 0; + found = true; + ret = b.template slice(n * i); + } } + return ret; } - return ret; -} - -template -Signal concat(Signal const& a, Signal const& b) -{ - Signal ret; - std::copy(a.begin(), a.end(), ret.begin()); - std::copy(b.begin(), b.end(), ret.begin() + n); - return ret; -} - -template -Signal $zero_extend(Signal const& a) -{ - assert(n >= m); - Signal ret; - std::copy(a.begin(), a.end(), ret.begin()); - for(size_t i = m; i < n; i++) - ret[i] = false; - return ret; -} - -template -Signal $sign_extend(Signal const& a) -{ - assert(n >= m); - Signal ret; - std::copy(a.begin(), a.end(), ret.begin()); - for(size_t i = m; i < n; i++) - ret[i] = a[m-1]; - return ret; -} -template -struct Memory { - std::array, 1< contents; -}; + template + Signal concat(Signal const& b) const + { + Signal ret; + std::copy(_bits.begin(), _bits.end(), ret._bits.begin()); + std::copy(b._bits.begin(), b._bits.end(), ret._bits.begin() + n); + return ret; + } -template -Signal $memory_read(Memory memory, Signal addr) -{ - return memory.contents[as_int(addr)]; -} + template + Signal zero_extend() const + { + assert(m >= n); + Signal ret = 0; + std::copy(_bits.begin(), _bits.end(), ret._bits.begin()); + return ret; + } + + template + Signal sign_extend() const + { + assert(m >= n); + Signal ret = Signal::repeat(sign()); + std::copy(_bits.begin(), _bits.end(), ret._bits.begin()); + return ret; + } +}; template -Memory $memory_write(Memory memory, Signal addr, Signal data) -{ - Memory ret = memory; - ret.contents[as_int(addr)] = data; - return ret; -} +class Memory { + std::array, 1< _contents; +public: + Signal read(Signal addr) const + { + return _contents[addr.template as_numeric()]; + } + Memory write(Signal addr, Signal data) const + { + Memory ret = *this; + ret._contents[addr.template as_numeric()] = data; + return ret; + } +}; #endif diff --git a/tests/functional/single_cells/vcd_harness.cc b/tests/functional/single_cells/vcd_harness.cc index 10ab10e428e..18c9ed5dfaa 100644 --- a/tests/functional/single_cells/vcd_harness.cc +++ b/tests/functional/single_cells/vcd_harness.cc @@ -33,22 +33,30 @@ struct Dump { } }; -// Function to set all values in a signal to false -template -void set_all_false(Signal& signal) { - std::fill(signal.begin(), signal.end(), false); -} - -template -void set_all_random(Signal& signal) { +template +Signal random_signal() { std::random_device rd; // Random device for seeding std::mt19937 gen(rd()); // Mersenne Twister engine - std::bernoulli_distribution dist(0.5); // 50-50 distribution + std::uniform_int_distribution dist; + std::array words; + for(auto &w : words) + w = dist(gen); + return Signal::from_array(words); +} - for (auto& value : signal) { - value = dist(gen); // Set each value to a random boolean +struct Reset { + template + void operator()(const char *, Signal &signal) { + signal = 0; } -} +}; + +struct Randomize { + template + void operator()(const char *, Signal &signal) { + signal = random_signal(); + } +}; int main(int argc, char **argv) { @@ -62,10 +70,10 @@ int main(int argc, char **argv) constexpr int steps = 10; constexpr int number_timescale = 1; const std::string units_timescale = "us"; - gold_Inputs inputs; - gold_Outputs outputs; - gold_State state; - gold_State next_state; + gold::Inputs inputs; + gold::Outputs outputs; + gold::State state; + gold::State next_state; std::ofstream vcd_file(functional_vcd_filename); @@ -73,45 +81,34 @@ int main(int argc, char **argv) vcd_file << "$scope module gold $end\n"; { DumpHeader d(vcd_file); - inputs.dump(d); - outputs.dump(d); - state.dump(d); + inputs.visit(d); + outputs.visit(d); + state.visit(d); } vcd_file << "$enddefinitions $end\n$dumpvars\n"; vcd_file << "#0\n"; // Set all signals to false - for (int i = 0; i < inputs.size(); ++i) { - auto input_variant = inputs.get_input(i); - std::visit([](auto& signal_ref) { - set_all_false(signal_ref.get()); - }, input_variant); - } + inputs.visit(Reset()); - gold(inputs, outputs, state, next_state); + gold::eval(inputs, outputs, state, next_state); { Dump d(vcd_file); - inputs.dump(d); - outputs.dump(d); - state.dump(d); + inputs.visit(d); + outputs.visit(d); + state.visit(d); } for (int step = 0; step < steps; ++step) { // Functional backend cxx vcd_file << "#" << (step + 1) << "\n"; - // Set all signals to random - for (int i = 0; i < inputs.size(); ++i) { - auto input_variant = inputs.get_input(i); - std::visit([](auto& signal_ref) { - set_all_random(signal_ref.get()); - }, input_variant); - } + inputs.visit(Randomize()); - gold(inputs, outputs, state, next_state); + gold::eval(inputs, outputs, state, next_state); { Dump d(vcd_file); - inputs.dump(d); - outputs.dump(d); - state.dump(d); + inputs.visit(d); + outputs.visit(d); + state.visit(d); } state = next_state; From e235fc704dba8726d69f3f5964aa986952488231 Mon Sep 17 00:00:00 2001 From: Roland Coeurjoly Date: Thu, 27 Jun 2024 02:09:19 +0200 Subject: [PATCH 30/92] Create std::mt19937 only once --- tests/functional/single_cells/vcd_harness.cc | 21 ++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/tests/functional/single_cells/vcd_harness.cc b/tests/functional/single_cells/vcd_harness.cc index 18c9ed5dfaa..9b3777c37fd 100644 --- a/tests/functional/single_cells/vcd_harness.cc +++ b/tests/functional/single_cells/vcd_harness.cc @@ -34,9 +34,7 @@ struct Dump { }; template -Signal random_signal() { - std::random_device rd; // Random device for seeding - std::mt19937 gen(rd()); // Mersenne Twister engine +Signal random_signal(std::mt19937 &gen) { std::uniform_int_distribution dist; std::array words; for(auto &w : words) @@ -52,9 +50,12 @@ struct Reset { }; struct Randomize { + std::mt19937 &gen; + Randomize(std::mt19937 &gen) : gen(gen) {} + template void operator()(const char *, Signal &signal) { - signal = random_signal(); + signal = random_signal(gen); } }; @@ -89,7 +90,7 @@ int main(int argc, char **argv) vcd_file << "#0\n"; // Set all signals to false inputs.visit(Reset()); - + gold::eval(inputs, outputs, state, next_state); { Dump d(vcd_file); @@ -97,11 +98,15 @@ int main(int argc, char **argv) outputs.visit(d); state.visit(d); } - + + // Initialize random number generator once + std::random_device rd; + std::mt19937 gen(rd()); + for (int step = 0; step < steps; ++step) { // Functional backend cxx vcd_file << "#" << (step + 1) << "\n"; - inputs.visit(Randomize()); + inputs.visit(Randomize(gen)); gold::eval(inputs, outputs, state, next_state); { @@ -110,7 +115,7 @@ int main(int argc, char **argv) outputs.visit(d); state.visit(d); } - + state = next_state; } From ee6bd5943684f49d933f233ddc1dbe464521921c Mon Sep 17 00:00:00 2001 From: Roland Coeurjoly Date: Thu, 27 Jun 2024 04:14:05 +0200 Subject: [PATCH 31/92] Removed unnecesary nested_lets variable, use writer.print instead --- backends/functional/smtlib.cc | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index 49dd5cf0d21..7f1ca0a61b6 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -231,7 +231,6 @@ struct SmtModule { auto node_to_string = [&](FunctionalIR::Node n) { return scope[n.name()]; }; SmtPrintVisitor visitor(node_to_string, scope); - std::string nested_lets; for (auto it = ir.begin(); it != ir.end(); ++it) { const FunctionalIR::Node &node = *it; @@ -241,31 +240,29 @@ struct SmtModule { std::string node_name = replaceCharacters(scope[node.name()]); std::string node_expr = node.visit(visitor); - nested_lets += "(let ( (" + node_name + " " + node_expr + "))"; + writer.print(" (let ( (%s %s))", node_name.c_str(), node_expr.c_str()); } - nested_lets += " (let ("; + writer.print(" (let ("); for (const auto &output : ir.outputs()) { std::string output_name = scope[output.first]; const std::string output_assignment = ir.get_output_node(output.first).name().c_str(); - nested_lets += " (" + output_name + " " + replaceCharacters(output_assignment).substr(1) + ")"; + writer.print(" (%s %s)", output_name.c_str(), replaceCharacters(output_assignment).substr(1).c_str()); } - nested_lets += " )"; - nested_lets += " (mk_outputs"; - + writer.print(" )"); + writer.print(" (mk_outputs"); for (const auto &output : ir.outputs()) { std::string output_name = scope[output.first]; - nested_lets += " " + output_name; + writer.print(" %s", output_name.c_str()); } - nested_lets += " )"; - nested_lets += " )"; + writer.print(" )"); + writer.print(" )"); // Close the nested lets for (size_t i = 0; i < ir.size() - ir.inputs().size(); ++i) { - nested_lets += " )"; + writer.print(" )"); } - writer.print("%s", nested_lets.c_str()); writer.print(" )"); writer.print(")\n"); } From 32cdf25838da8e492935e4b87829b504dfa0aec9 Mon Sep 17 00:00:00 2001 From: Roland Coeurjoly Date: Thu, 27 Jun 2024 04:42:59 +0200 Subject: [PATCH 32/92] Use FunctionalTools::Scope instead of replaceCharacters --- backends/functional/smtlib.cc | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index 7f1ca0a61b6..afddbd83440 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -23,11 +23,14 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN +const char illegal_characters[] = "$\\"; +const char *reserved_keywords[] = {}; + struct SmtScope { pool used_names; dict name_map; - - SmtScope() {} + FunctionalTools::Scope scope; + SmtScope() : scope(illegal_characters, reserved_keywords) {} void reserve(const std::string &name) { used_names.insert(name); } @@ -184,21 +187,6 @@ struct SmtModule { SmtModule(const std::string &module_name, FunctionalIR ir) : name(module_name), ir(std::move(ir)) {} - std::string replaceCharacters(const std::string &input) - { - std::string result = input; - std::replace(result.begin(), result.end(), '$', '_'); // Replace $ with _ - - // Since \ is an escape character, we use a loop to replace it - size_t pos = 0; - while ((pos = result.find('\\', pos)) != std::string::npos) { - result.replace(pos, 1, "_"); - pos += 1; // Move past the replaced character - } - - return result; - } - void write(std::ostream &out) { SmtWriter writer(out); @@ -237,7 +225,7 @@ struct SmtModule { if (ir.inputs().count(node.name()) > 0) continue; - std::string node_name = replaceCharacters(scope[node.name()]); + std::string node_name = scope[node.name()]; std::string node_expr = node.visit(visitor); writer.print(" (let ( (%s %s))", node_name.c_str(), node_expr.c_str()); @@ -247,7 +235,7 @@ struct SmtModule { for (const auto &output : ir.outputs()) { std::string output_name = scope[output.first]; const std::string output_assignment = ir.get_output_node(output.first).name().c_str(); - writer.print(" (%s %s)", output_name.c_str(), replaceCharacters(output_assignment).substr(1).c_str()); + writer.print(" (%s %s)", output_name.c_str(), output_assignment.substr(1).c_str()); } writer.print(" )"); writer.print(" (mk_outputs"); From 9700df50d6818d5df276bcfff41501fef0be0ac3 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Thu, 4 Jul 2024 10:22:51 +0100 Subject: [PATCH 33/92] add generic writer class with formatting function to FunctionalTools --- backends/functional/cxx.cc | 180 +++++++++++--------------- backends/functional/cxx_runtime/sim.h | 4 +- kernel/functionalir.cc | 51 ++++++++ kernel/functionalir.h | 128 ++++++++++-------- 4 files changed, 203 insertions(+), 160 deletions(-) diff --git a/backends/functional/cxx.cc b/backends/functional/cxx.cc index d866dfea210..21e287a96c4 100644 --- a/backends/functional/cxx.cc +++ b/backends/functional/cxx.cc @@ -56,17 +56,7 @@ struct CxxType { } }; -struct CxxWriter { - std::ostream &f; - CxxWriter(std::ostream &out) : f(out) {} - void printf(const char *fmt, ...) - { - va_list va; - va_start(va, fmt); - f << vstringf(fmt, va); - va_end(va); - } -}; +using CxxWriter = FunctionalTools::Writer; struct CxxStruct { std::string name; @@ -74,111 +64,85 @@ struct CxxStruct { FunctionalTools::Scope scope; CxxStruct(std::string name) : name(name), scope(illegal_characters, reserved_keywords) { - scope.reserve("out"); - scope.reserve("dump"); + scope.reserve("fn"); + scope.reserve("visit"); } void insert(IdString name, CxxType type) { scope(name); types.insert({name, type}); } void print(CxxWriter &f) { - f.printf("\tstruct %s {\n", name.c_str()); + f.print("\tstruct {} {{\n", name); for (auto p : types) { - f.printf("\t\t%s %s;\n", p.second.to_string().c_str(), scope(p.first).c_str()); + f.print("\t\t{} {};\n", p.second.to_string(), scope(p.first)); } - f.printf("\n\t\ttemplate void visit(T &&fn) {\n"); + f.print("\n\t\ttemplate void visit(T &&fn) {{\n"); for (auto p : types) { - f.printf("\t\t\tfn(\"%s\", %s);\n", RTLIL::unescape_id(p.first).c_str(), scope(p.first).c_str()); + f.print("\t\t\tfn(\"{}\", {});\n", RTLIL::unescape_id(p.first), scope(p.first)); } - f.printf("\t\t}\n"); - f.printf("\t};\n\n"); + f.print("\t\t}}\n"); + f.print("\t}};\n\n"); }; std::string operator[](IdString field) { return scope(field); } }; -struct CxxTemplate { - vector> _v; -public: - CxxTemplate(std::string fmt) { - std::string buf; - for(auto it = fmt.begin(); it != fmt.end(); it++){ - if(*it == '%'){ - it++; - log_assert(it != fmt.end()); - if(*it == '%') - buf += *it; - else { - log_assert(*it >= '0' && *it <= '9'); - _v.emplace_back(std::move(buf)); - _v.emplace_back((int)(*it - '0')); - } - }else - buf += *it; - } - if(!buf.empty()) - _v.emplace_back(std::move(buf)); - } - template static std::string format(CxxTemplate fmt, Args&&... args) { - vector strs = {args...}; - std::string result; - for(auto &v : fmt._v){ - if(std::string *s = std::get_if(&v)) - result += *s; - else if(int *i = std::get_if(&v)) - result += strs[*i]; - else - log_error("missing case"); - } - return result; - } -}; - -template struct CxxPrintVisitor { +template struct CxxPrintVisitor { using Node = FunctionalIR::Node; - NodeNames np; + CxxWriter &f; + NodePrinter np; CxxStruct &input_struct; CxxStruct &state_struct; - CxxPrintVisitor(NodeNames np, CxxStruct &input_struct, CxxStruct &state_struct) : np(np), input_struct(input_struct), state_struct(state_struct) { } - template std::string arg_to_string(T n) { return std::to_string(n); } - std::string arg_to_string(std::string n) { return n; } - std::string arg_to_string(Node n) { return np(n); } - template std::string format(std::string fmt, Args&&... args) { - return CxxTemplate::format(fmt, arg_to_string(args)...); + CxxPrintVisitor(CxxWriter &f, NodePrinter np, CxxStruct &input_struct, CxxStruct &state_struct) : f(f), np(np), input_struct(input_struct), state_struct(state_struct) { } + template void print(const char *fmt, Args&&... args) { + f.print_with(np, fmt, std::forward(args)...); } - std::string buf(Node, Node n) { return np(n); } - std::string slice(Node, Node a, int, int offset, int out_width) { return format("%0.slice<%2>(%1)", a, offset, out_width); } - std::string zero_extend(Node, Node a, int, int out_width) { return format("%0.zero_extend<%1>()", a, out_width); } - std::string sign_extend(Node, Node a, int, int out_width) { return format("%0.sign_extend<%1>()", a, out_width); } - std::string concat(Node, Node a, int, Node b, int) { return format("%0.concat(%1)", a, b); } - std::string add(Node, Node a, Node b, int) { return format("%0 + %1", a, b); } - std::string sub(Node, Node a, Node b, int) { return format("%0 - %1", a, b); } - std::string bitwise_and(Node, Node a, Node b, int) { return format("%0 & %1", a, b); } - std::string bitwise_or(Node, Node a, Node b, int) { return format("%0 | %1", a, b); } - std::string bitwise_xor(Node, Node a, Node b, int) { return format("%0 ^ %1", a, b); } - std::string bitwise_not(Node, Node a, int) { return format("~%0", a); } - std::string unary_minus(Node, Node a, int) { return format("-%0", a); } - std::string reduce_and(Node, Node a, int) { return format("%0.all()", a); } - std::string reduce_or(Node, Node a, int) { return format("%0.any()", a); } - std::string reduce_xor(Node, Node a, int) { return format("%0.parity()", a); } - std::string equal(Node, Node a, Node b, int) { return format("%0 == %1", a, b); } - std::string not_equal(Node, Node a, Node b, int) { return format("%0 != %1", a, b); } - std::string signed_greater_than(Node, Node a, Node b, int) { return format("%0.signed_greater_than(%1)", a, b); } - std::string signed_greater_equal(Node, Node a, Node b, int) { return format("%0.signed_greater_equal(%1)", a, b); } - std::string unsigned_greater_than(Node, Node a, Node b, int) { return format("%0 > %1", a, b); } - std::string unsigned_greater_equal(Node, Node a, Node b, int) { return format("%0 >= %1", a, b); } - std::string logical_shift_left(Node, Node a, Node b, int, int) { return format("%0 << %1", a, b); } - std::string logical_shift_right(Node, Node a, Node b, int, int) { return format("%0 >> %1", a, b); } - std::string arithmetic_shift_right(Node, Node a, Node b, int, int) { return format("%0.arithmetic_shift_right(%1)", a, b); } - std::string mux(Node, Node a, Node b, Node s, int) { return format("%2.any() ? %1 : %0", a, b, s); } - std::string pmux(Node, Node a, Node b, Node s, int, int) { return format("%0.pmux(%1, %2)", a, b, s); } - std::string constant(Node, RTLIL::Const value) { return format("Signal<%0>(%1)", value.size(), value.as_int()); } - std::string input(Node, IdString name) { return format("input.%0", input_struct[name]); } - std::string state(Node, IdString name) { return format("current_state.%0", state_struct[name]); } - std::string memory_read(Node, Node mem, Node addr, int, int) { return format("%0.read(%1)", mem, addr); } - std::string memory_write(Node, Node mem, Node addr, Node data, int, int) { return format("%0.write(%1, %2)", mem, addr, data); } - std::string undriven(Node, int width) { return format("Signal<%0>(0)", width); } + void buf(Node, Node n) { print("{}", n); } + void slice(Node, Node a, int, int offset, int out_width) { print("{0}.slice<{2}>({1})", a, offset, out_width); } + void zero_extend(Node, Node a, int, int out_width) { print("{}.zero_extend<{}>()", a, out_width); } + void sign_extend(Node, Node a, int, int out_width) { print("{}.sign_extend<{}>()", a, out_width); } + void concat(Node, Node a, int, Node b, int) { print("{}.concat({})", a, b); } + void add(Node, Node a, Node b, int) { print("{} + {}", a, b); } + void sub(Node, Node a, Node b, int) { print("{} - {}", a, b); } + void bitwise_and(Node, Node a, Node b, int) { print("{} & {}", a, b); } + void bitwise_or(Node, Node a, Node b, int) { print("{} | {}", a, b); } + void bitwise_xor(Node, Node a, Node b, int) { print("{} ^ {}", a, b); } + void bitwise_not(Node, Node a, int) { print("~{}", a); } + void unary_minus(Node, Node a, int) { print("-{}", a); } + void reduce_and(Node, Node a, int) { print("{}.all()", a); } + void reduce_or(Node, Node a, int) { print("{}.any()", a); } + void reduce_xor(Node, Node a, int) { print("{}.parity()", a); } + void equal(Node, Node a, Node b, int) { print("{} == {}", a, b); } + void not_equal(Node, Node a, Node b, int) { print("{} != {}", a, b); } + void signed_greater_than(Node, Node a, Node b, int) { print("{}.signed_greater_than({})", a, b); } + void signed_greater_equal(Node, Node a, Node b, int) { print("{}.signed_greater_equal({})", a, b); } + void unsigned_greater_than(Node, Node a, Node b, int) { print("{} > {}", a, b); } + void unsigned_greater_equal(Node, Node a, Node b, int) { print("{} >= {}", a, b); } + void logical_shift_left(Node, Node a, Node b, int, int) { print("{} << {}", a, b); } + void logical_shift_right(Node, Node a, Node b, int, int) { print("{} >> {}", a, b); } + void arithmetic_shift_right(Node, Node a, Node b, int, int) { print("{}.arithmetic_shift_right{})", a, b); } + void mux(Node, Node a, Node b, Node s, int) { print("{2}.any() ? {1} : {0}", a, b, s); } + void pmux(Node, Node a, Node b, Node s, int, int) { print("{0}.pmux({1}, {2})", a, b, s); } + void constant(Node, RTLIL::Const value) { + std::stringstream ss; + bool multiple = value.size() > 32; + ss << "Signal<" << value.size() << ">(" << std::hex << std::showbase; + if(multiple) ss << "{"; + while(value.size() > 32) { + ss << value.as_int() << ", "; + value = value.extract(32, value.size() - 32); + } + ss << value.as_int(); + if(multiple) ss << "}"; + ss << ")"; + print("{}", ss.str()); + } + void input(Node, IdString name) { print("input.{}", input_struct[name]); } + void state(Node, IdString name) { print("current_state.{}", state_struct[name]); } + void memory_read(Node, Node mem, Node addr, int, int) { print("{}.read({})", mem, addr); } + void memory_write(Node, Node mem, Node addr, Node data, int, int) { print("{}.write({}, {})", mem, addr, data); } + void undriven(Node, int width) { print("Signal<{}>(0)", width); } }; struct CxxModule { @@ -201,31 +165,35 @@ struct CxxModule { module_name = FunctionalTools::Scope(illegal_characters, reserved_keywords)(module->name); } void write_header(CxxWriter &f) { - f.printf("#include \"sim.h\"\n\n"); + f.print("#include \"sim.h\"\n\n"); } void write_struct_def(CxxWriter &f) { - f.printf("struct %s {\n", module_name.c_str()); + f.print("struct {} {{\n", module_name); input_struct.print(f); output_struct.print(f); state_struct.print(f); - f.printf("\tstatic void eval(Inputs const &, Outputs &, State const &, State &);\n"); - f.printf("};\n\n"); + f.print("\tstatic void eval(Inputs const &, Outputs &, State const &, State &);\n"); + f.print("}};\n\n"); } void write_eval_def(CxxWriter &f) { - f.printf("void %s::eval(%s::Inputs const &input, %s::Outputs &output, %s::State const ¤t_state, %s::State &next_state)\n{\n", module_name.c_str(), module_name.c_str(), module_name.c_str(), module_name.c_str(), module_name.c_str()); + f.print("void {0}::eval({0}::Inputs const &input, {0}::Outputs &output, {0}::State const ¤t_state, {0}::State &next_state)\n{{\n", module_name); FunctionalTools::Scope locals(illegal_characters, reserved_keywords); locals.reserve("input"); locals.reserve("output"); locals.reserve("current_state"); locals.reserve("next_state"); auto node_name = [&](FunctionalIR::Node n) { return locals(n.id(), n.name()); }; - for (auto node : ir) - f.printf("\t%s %s = %s;\n", CxxType(node.sort()).to_string().c_str(), node_name(node).c_str(), node.visit(CxxPrintVisitor(node_name, input_struct, state_struct)).c_str()); + CxxPrintVisitor printVisitor(f, node_name, input_struct, state_struct); + for (auto node : ir) { + f.print("\t{} {} = ", CxxType(node.sort()).to_string(), node_name(node)); + node.visit(printVisitor); + f.print(";\n"); + } for (auto [name, sort] : ir.state()) - f.printf("\tnext_state.%s = %s;\n", state_struct[name].c_str(), node_name(ir.get_state_next_node(name)).c_str()); + f.print("\tnext_state.{} = {};\n", state_struct[name], node_name(ir.get_state_next_node(name))); for (auto [name, sort] : ir.outputs()) - f.printf("\toutput.%s = %s;\n", output_struct[name].c_str(), node_name(ir.get_output_node(name)).c_str()); - f.printf("}\n"); + f.print("\toutput.{} = {};\n", output_struct[name], node_name(ir.get_output_node(name))); + f.print("}}\n"); } }; diff --git a/backends/functional/cxx_runtime/sim.h b/backends/functional/cxx_runtime/sim.h index 0614fb34cf7..1985322f1c8 100644 --- a/backends/functional/cxx_runtime/sim.h +++ b/backends/functional/cxx_runtime/sim.h @@ -143,11 +143,11 @@ class Signal { { for(size_t i = sizeof(T) * 8; i < n; i++) if(_bits[i]) - return ~0; + return ~((T)0); return as_numeric(); } - uint32_t as_int() { return as_numeric(); } + uint32_t as_int() const { return as_numeric(); } Signal operator ~() const { diff --git a/kernel/functionalir.cc b/kernel/functionalir.cc index 6ae66d6802b..3fd2d8240e7 100644 --- a/kernel/functionalir.cc +++ b/kernel/functionalir.cc @@ -443,4 +443,55 @@ void FunctionalIR::forward_buf() { _graph.permute(perm, alias); } +static std::string quote_fmt(const char *fmt) +{ + std::string r; + for(const char *p = fmt; *p != 0; p++) { + switch(*p) { + case '\n': r += "\\n"; break; + case '\t': r += "\\t"; break; + case '"': r += "\\\""; break; + case '\\': r += "\\\\"; break; + default: r += *p; break; + } + } + return r; +} + +void FunctionalTools::Writer::print_impl(const char *fmt, vector> &fns) +{ + size_t next_index = 0; + for(const char *p = fmt; *p != 0; p++) + switch(*p) { + case '{': + if(*++p == '{') { + *os << '{'; + } else { + char *pe; + size_t index = strtoul(p, &pe, 10); + if(*pe != '}') + log_error("invalid format string: expected {}, {} or {{, got \"%s\": \"%s\"\n", + quote_fmt(std::string(p - 1, pe - p + 2).c_str()).c_str(), + quote_fmt(fmt).c_str()); + if(p == pe) + index = next_index; + else + p = pe; + if(index >= fns.size()) + log_error("invalid format string: index %zu out of bounds (%zu): \"%s\"\n", index, fns.size(), quote_fmt(fmt).c_str()); + fns[index](); + next_index = index + 1; + } + break; + case '}': + p++; + if(*p != '}') + log_error("invalid format string: unescaped }: \"%s\"\n", quote_fmt(fmt).c_str()); + *os << '}'; + break; + default: + *os << *p; + } +} + YOSYS_NAMESPACE_END diff --git a/kernel/functionalir.h b/kernel/functionalir.h index f49a659f37b..2c0d0f55c2c 100644 --- a/kernel/functionalir.h +++ b/kernel/functionalir.h @@ -29,58 +29,6 @@ USING_YOSYS_NAMESPACE YOSYS_NAMESPACE_BEGIN -namespace FunctionalTools { - class Scope { - const char *_illegal_characters; - pool _used_names; - dict _by_id; - dict _by_name; - std::string allocate_name(IdString suggestion) { - std::string str = RTLIL::unescape_id(suggestion); - for(size_t i = 0; i < str.size(); i++) - if(strchr(_illegal_characters, str[i])) - str[i] = '_'; - if(_used_names.count(str) == 0) { - _used_names.insert(str); - return str; - } - for (int idx = 0 ; ; idx++){ - std::string suffixed = str + "_" + std::to_string(idx); - if(_used_names.count(suffixed) == 0) { - _used_names.insert(suffixed); - return suffixed; - } - } - } - public: - Scope(const char *illegal_characters = "", const char **keywords = nullptr) { - _illegal_characters = illegal_characters; - if(keywords != nullptr) - for(const char **p = keywords; *p != nullptr; p++) - reserve(*p); - } - void reserve(std::string name) { - _used_names.insert(std::move(name)); - } - std::string operator()(int id, IdString suggestion) { - auto it = _by_id.find(id); - if(it != _by_id.end()) - return it->second; - std::string str = allocate_name(suggestion); - _by_id.insert({id, str}); - return str; - } - std::string operator()(IdString idstring) { - auto it = _by_name.find(idstring); - if(it != _by_name.end()) - return it->second; - std::string str = allocate_name(idstring); - _by_name.insert({idstring, str}); - return str; - } - }; -} - class FunctionalIR { enum class Fn { invalid, @@ -418,6 +366,82 @@ class FunctionalIR { Iterator end() { return Iterator(this, _graph.size()); } }; +namespace FunctionalTools { + class Scope { + const char *_illegal_characters; + pool _used_names; + dict _by_id; + dict _by_name; + std::string allocate_name(IdString suggestion) { + std::string str = RTLIL::unescape_id(suggestion); + for(size_t i = 0; i < str.size(); i++) + if(strchr(_illegal_characters, str[i])) + str[i] = '_'; + if(_used_names.count(str) == 0) { + _used_names.insert(str); + return str; + } + for (int idx = 0 ; ; idx++){ + std::string suffixed = str + "_" + std::to_string(idx); + if(_used_names.count(suffixed) == 0) { + _used_names.insert(suffixed); + return suffixed; + } + } + } + public: + Scope(const char *illegal_characters = "", const char **keywords = nullptr) { + _illegal_characters = illegal_characters; + if(keywords != nullptr) + for(const char **p = keywords; *p != nullptr; p++) + reserve(*p); + } + void reserve(std::string name) { + _used_names.insert(std::move(name)); + } + std::string operator()(int id, IdString suggestion) { + auto it = _by_id.find(id); + if(it != _by_id.end()) + return it->second; + std::string str = allocate_name(suggestion); + _by_id.insert({id, str}); + return str; + } + std::string operator()(IdString idstring) { + auto it = _by_name.find(idstring); + if(it != _by_name.end()) + return it->second; + std::string str = allocate_name(idstring); + _by_name.insert({idstring, str}); + return str; + } + }; + class Writer { + std::ostream *os; + void print_impl(const char *fmt, vector>& fns); + public: + Writer(std::ostream &os) : os(&os) {} + template Writer& operator <<(T&& arg) { *os << std::forward(arg); return *this; } + template + void print(const char *fmt, Args&&... args) + { + vector> fns { [&]() { *this << args; }... }; + print_impl(fmt, fns); + } + template + void print_with(Fn fn, const char *fmt, Args&&... args) + { + vector> fns { [&]() { + if constexpr (std::is_invocable_v) + *this << fn(args); + else + *this << args; }... + }; + print_impl(fmt, fns); + } + }; +} + YOSYS_NAMESPACE_END #endif From cb5f08364c2a775c9725d94144875539edff8533 Mon Sep 17 00:00:00 2001 From: Roland Coeurjoly Date: Wed, 3 Jul 2024 17:07:50 +0200 Subject: [PATCH 34/92] =?UTF-8?q?=C2=B4SMT=20success=20only=20if=20simulat?= =?UTF-8?q?ion=20is=20equivalent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/functional/single_cells/run-test.sh | 1 - tests/functional/single_cells/vcd_harness_smt.py | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/functional/single_cells/run-test.sh b/tests/functional/single_cells/run-test.sh index 67358639bf2..11ff1b5ce0b 100755 --- a/tests/functional/single_cells/run-test.sh +++ b/tests/functional/single_cells/run-test.sh @@ -54,7 +54,6 @@ run_smt_test() { # TODO: which SMT solver should be run? if z3 "${base_name}.smt2"; then echo "SMT file ${base_name}.smt2 is valid ." - smt_successful_files["$rtlil_file"]="Success" if python3 vcd_harness_smt.py "${base_name}.smt2"; then echo "Python script generated VCD file for $rtlil_file successfully." diff --git a/tests/functional/single_cells/vcd_harness_smt.py b/tests/functional/single_cells/vcd_harness_smt.py index a44bf4570c9..5d8c735a6c1 100644 --- a/tests/functional/single_cells/vcd_harness_smt.py +++ b/tests/functional/single_cells/vcd_harness_smt.py @@ -133,6 +133,9 @@ def set_step(inputs, step): # smt_io.setup() result = smt_io.check_sat() print(f'SAT result: {result}') +if result != 'sat': + smt_io.p_close() + sys.exit(1) value = smt_io.get(f'(Y test_outputs_step_n0)') print(f" Y: {value}") @@ -197,3 +200,4 @@ def write_vcd(filename, signals, timescale='1 ns', date='today'): # Write the VCD file write_vcd(smt_file_path + '.vcd', signals) +sys.exit(0) From 73ed5146233dca64405a30d82646b286df43144a Mon Sep 17 00:00:00 2001 From: Roland Coeurjoly Date: Thu, 4 Jul 2024 12:35:00 +0200 Subject: [PATCH 35/92] Check that there are not other solutions other than the first given --- tests/functional/single_cells/vcd_harness_smt.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/functional/single_cells/vcd_harness_smt.py b/tests/functional/single_cells/vcd_harness_smt.py index 5d8c735a6c1..fd5e94f7e2f 100644 --- a/tests/functional/single_cells/vcd_harness_smt.py +++ b/tests/functional/single_cells/vcd_harness_smt.py @@ -41,7 +41,7 @@ # Load and execute the SMT file with open(smt_file_path, 'r') as smt_file: for line in smt_file: - smt_io.write(line.strip()) + smt_io.write(line.strip()) smt_io.check_sat() # Read and parse the SMT file line by line with open(smt_file_path, 'r') as smt_file: @@ -161,6 +161,17 @@ def hex_to_bin(value): value = hex_to_bin(value[1:]) print(f" {output_name}: {value}") signals[output_name].append((step, value)) + smt_io.write(f'(push 1)') + smt_io.write(f'(assert (not (= ({output_name} test_outputs_step_n{step}) #{value})))') + result = smt_io.check_sat(["unsat"]) + if result != 'unsat': + smt_io.p_close() + sys.exit(1) + smt_io.write(f'(pop 1)') + result = smt_io.check_sat(["sat"]) + if result != 'sat': + smt_io.p_close() + sys.exit(1) smt_io.p_close() From 762f8dd8228e8b6219d67fb04e87deb434e0215a Mon Sep 17 00:00:00 2001 From: Roland Coeurjoly Date: Thu, 4 Jul 2024 12:39:45 +0200 Subject: [PATCH 36/92] Add readme explaining how to create test files --- tests/functional/single_cells/rtlil/README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 tests/functional/single_cells/rtlil/README.md diff --git a/tests/functional/single_cells/rtlil/README.md b/tests/functional/single_cells/rtlil/README.md new file mode 100644 index 00000000000..4bb84565b7e --- /dev/null +++ b/tests/functional/single_cells/rtlil/README.md @@ -0,0 +1,2 @@ +# Creation of test files +yosys -p "test_cell -n 1 -w test_cell all" \ No newline at end of file From 50f487e08c9a2343b013652e021d7e3314d0bcae Mon Sep 17 00:00:00 2001 From: Roland Coeurjoly Date: Thu, 4 Jul 2024 16:04:21 +0200 Subject: [PATCH 37/92] Added $ff test --- tests/functional/single_cells/rtlil/ff.il | 83 +++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 tests/functional/single_cells/rtlil/ff.il diff --git a/tests/functional/single_cells/rtlil/ff.il b/tests/functional/single_cells/rtlil/ff.il new file mode 100644 index 00000000000..6e5a1a339d0 --- /dev/null +++ b/tests/functional/single_cells/rtlil/ff.il @@ -0,0 +1,83 @@ +# Generated by Yosys 0.40+7 (git sha1 4fd5b29f9, g++ 13.2.0 -Og -fPIC) +autoidx 19 +attribute \cells_not_processed 1 +attribute \src "dff.v:1.1-16.10" +module \gold + attribute \src "dff.v:8.5-14.8" + wire $0\q[0:0] + attribute \init 1'0 + wire $auto$clk2fflogic.cc:65:sample_control$\reset#sampled$13 + attribute \init 1'1 + wire $auto$clk2fflogic.cc:77:sample_control_edge$\clk#sampled$7 + attribute \init 1'0 + wire $auto$clk2fflogic.cc:91:sample_data$\d#sampled$5 + attribute \init 1'x + wire $auto$clk2fflogic.cc:91:sample_data$\q#sampled$3 + wire $auto$rtlil.cc:2525:Eqx$10 + wire $auto$rtlil.cc:2582:Mux$12 + wire $auto$rtlil.cc:2582:Mux$16 + wire $auto$rtlil.cc:2582:Mux$18 + attribute \src "dff.v:2.16-2.19" + wire input 1 \clk + attribute \src "dff.v:4.16-4.17" + wire input 3 \d + attribute \keep 1 + attribute \src "dff.v:5.16-5.17" + wire output 4 \q + attribute \src "dff.v:3.16-3.21" + wire input 2 \reset + cell $mux $auto$clk2fflogic.cc:113:mux$11 + parameter \WIDTH 1 + connect \A $auto$clk2fflogic.cc:91:sample_data$\q#sampled$3 + connect \B $auto$clk2fflogic.cc:91:sample_data$\d#sampled$5 + connect \S $auto$rtlil.cc:2525:Eqx$10 + connect \Y $auto$rtlil.cc:2582:Mux$12 + end + cell $mux $auto$clk2fflogic.cc:113:mux$15 + parameter \WIDTH 1 + connect \A $auto$rtlil.cc:2582:Mux$12 + connect \B 1'0 + connect \S $auto$clk2fflogic.cc:65:sample_control$\reset#sampled$13 + connect \Y $auto$rtlil.cc:2582:Mux$16 + end + cell $mux $auto$clk2fflogic.cc:113:mux$17 + parameter \WIDTH 1 + connect \A $auto$rtlil.cc:2582:Mux$16 + connect \B 1'0 + connect \S \reset + connect \Y $auto$rtlil.cc:2582:Mux$18 + end + cell $ff $auto$clk2fflogic.cc:70:sample_control$14 + parameter \WIDTH 1 + connect \D \reset + connect \Q $auto$clk2fflogic.cc:65:sample_control$\reset#sampled$13 + end + cell $ff $auto$clk2fflogic.cc:82:sample_control_edge$8 + parameter \WIDTH 1 + connect \D \clk + connect \Q $auto$clk2fflogic.cc:77:sample_control_edge$\clk#sampled$7 + end + cell $eqx $auto$clk2fflogic.cc:83:sample_control_edge$9 + parameter \A_SIGNED 0 + parameter \A_WIDTH 2 + parameter \B_SIGNED 0 + parameter \B_WIDTH 2 + parameter \Y_WIDTH 1 + connect \A { $auto$clk2fflogic.cc:77:sample_control_edge$\clk#sampled$7 \clk } + connect \B 2'01 + connect \Y $auto$rtlil.cc:2525:Eqx$10 + end + attribute \clk2fflogic 1 + cell $ff $auto$clk2fflogic.cc:98:sample_data$4 + parameter \WIDTH 1 + connect \D \q + connect \Q $auto$clk2fflogic.cc:91:sample_data$\q#sampled$3 + end + cell $ff $auto$clk2fflogic.cc:98:sample_data$6 + parameter \WIDTH 1 + connect \D \d + connect \Q $auto$clk2fflogic.cc:91:sample_data$\d#sampled$5 + end + connect $0\q[0:0] \d + connect \q $auto$rtlil.cc:2582:Mux$18 +end From 1b2986f7fb85e00a75a0c5c6d88905f78025c467 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Thu, 4 Jul 2024 16:58:07 +0100 Subject: [PATCH 38/92] add support for $mul, $div, $divfloor, $mod, $modfloor, $pow in functional backend --- backends/functional/cxx.cc | 3 + backends/functional/cxx_runtime/sim.h | 89 +++++++++++++++++++++++++-- backends/functional/smtlib.cc | 6 ++ kernel/functionalir.cc | 73 +++++++++++++++++++++- kernel/functionalir.h | 17 ++++- 5 files changed, 177 insertions(+), 11 deletions(-) diff --git a/backends/functional/cxx.cc b/backends/functional/cxx.cc index 21e287a96c4..81af54c073e 100644 --- a/backends/functional/cxx.cc +++ b/backends/functional/cxx.cc @@ -105,6 +105,9 @@ template struct CxxPrintVisitor { void concat(Node, Node a, int, Node b, int) { print("{}.concat({})", a, b); } void add(Node, Node a, Node b, int) { print("{} + {}", a, b); } void sub(Node, Node a, Node b, int) { print("{} - {}", a, b); } + void mul(Node, Node a, Node b, int) { print("{} * {}", a, b); } + void unsigned_div(Node, Node a, Node b, int) { print("{} / {}", a, b); } + void unsigned_mod(Node, Node a, Node b, int) { print("{} % {}", a, b); } void bitwise_and(Node, Node a, Node b, int) { print("{} & {}", a, b); } void bitwise_or(Node, Node a, Node b, int) { print("{} | {}", a, b); } void bitwise_xor(Node, Node a, Node b, int) { print("{} ^ {}", a, b); } diff --git a/backends/functional/cxx_runtime/sim.h b/backends/functional/cxx_runtime/sim.h index 1985322f1c8..040c6aefce8 100644 --- a/backends/functional/cxx_runtime/sim.h +++ b/backends/functional/cxx_runtime/sim.h @@ -22,6 +22,8 @@ #include #include +#include +#include template class Signal { @@ -149,6 +151,48 @@ class Signal { uint32_t as_int() const { return as_numeric(); } +private: + std::string as_string_p2(int b) const { + std::string ret; + for(int i = (n - 1) - (n - 1) % b; i >= 0; i -= b) + ret += "0123456789abcdef"[(*this >> Signal<32>(i)).as_int() & ((1< t = *this; + Signal b = 10; + do{ + ret += (char)('0' + (t % b).as_int()); + t = t / b; + }while(t.any()); + std::reverse(ret.begin(), ret.end()); + return ret; + } +public: + std::string as_string(int base = 16, bool showbase = true) const { + std::string ret; + if(showbase) { + ret += std::to_string(n); + switch(base) { + case 2: ret += "'b"; break; + case 8: ret += "'o"; break; + case 10: ret += "'d"; break; + case 16: ret += "'h"; break; + default: assert(0); + } + } + switch(base) { + case 2: return ret + as_string_p2(1); + case 8: return ret + as_string_p2(3); + case 10: return ret + as_string_b10(); + case 16: return ret + as_string_p2(4); + default: assert(0); + } + } + friend std::ostream &operator << (std::ostream &os, Signal const &s) { return os << s.as_string(); } + Signal operator ~() const { Signal ret; @@ -160,11 +204,11 @@ class Signal { Signal operator -() const { Signal ret; - bool carry = true; + int x = 1; for(size_t i = 0; i < n; i++) { - int r = !_bits[i] + carry; - ret._bits[i] = (r & 1) != 0; - carry = (r >> 1) != 0; + x += (int)!_bits[i]; + ret._bits[i] = (x & 1) != 0; + x >>= 1; } return ret; } @@ -172,9 +216,8 @@ class Signal { Signal operator +(Signal const &b) const { Signal ret; - size_t i; int x = 0; - for(i = 0; i < n; i++){ + for(size_t i = 0; i < n; i++){ x += (int)_bits[i] + (int)b._bits[i]; ret._bits[i] = x & 1; x >>= 1; @@ -194,6 +237,40 @@ class Signal { return ret; } + Signal operator *(Signal const &b) const + { + Signal ret; + int x = 0; + for(size_t i = 0; i < n; i++){ + for(size_t j = 0; j <= i; j++) + x += (int)_bits[j] & (int)b._bits[i-j]; + ret._bits[i] = x & 1; + x >>= 1; + } + return ret; + } + +private: + Signal divmod(Signal const &b, bool modulo) const + { + if(!b.any()) return 0; + Signal q = 0; + Signal r = 0; + for(size_t i = n; i-- != 0; ){ + r = r << Signal<1>(1); + r._bits[0] = _bits[i]; + if(r >= b){ + r = r - b; + q._bits[i] = true; + } + } + return modulo ? r : q; + } +public: + + Signal operator /(Signal const &b) const { return divmod(b, false); } + Signal operator %(Signal const &b) const { return divmod(b, true); } + bool operator ==(Signal const &b) const { for(size_t i = 0; i < n; i++) diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index afddbd83440..55efc83c1c2 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -119,6 +119,12 @@ template struct SmtPrintVisitor { std::string sub(Node, Node a, Node b, int) { return format("(bvsub %0 %1)", np(a), np(b)); } + std::string mul(Node, Node a, Node b, int) { return format("(bvmul %0 %1)", np(a), np(b)); } + + std::string unsigned_div(Node, Node a, Node b, int) { return format("(bvudiv %0 %1)", np(a), np(b)); } + + std::string unsigned_mod(Node, Node a, Node b, int) { return format("(bvurem %0 %1)", np(a), np(b)); } + std::string bitwise_and(Node, Node a, Node b, int) { return format("(bvand %0 %1)", np(a), np(b)); } std::string bitwise_or(Node, Node a, Node b, int) { return format("(bvor %0 %1)", np(a), np(b)); } diff --git a/kernel/functionalir.cc b/kernel/functionalir.cc index 3fd2d8240e7..6498ef63629 100644 --- a/kernel/functionalir.cc +++ b/kernel/functionalir.cc @@ -37,6 +37,15 @@ class CellSimplifier { return factory.mux(lower_b, factory.constant(RTLIL::Const(y_width, new_width)), overflow, new_width); } } + T sign(T a, int a_width) { + return factory.slice(a, a_width, a_width - 1, 1); + } + T neg_if(T a, int a_width, T s) { + return factory.mux(a, factory.unary_minus(a, a_width), s, a_width); + } + T abs(T a, int a_width) { + return neg_if(a, a_width, sign(a, a_width)); + } public: T reduce_or(T a, int width) { if (width == 1) @@ -71,6 +80,23 @@ class CellSimplifier { return factory.bitwise_or(aa, bb, width); } CellSimplifier(Factory &f) : factory(f) {} +private: + T handle_pow(T a0, int a_width, T b, int b_width, int y_width, bool is_signed) { + T a = extend(a0, a_width, y_width, is_signed); + T r = factory.constant(Const(1, y_width)); + for(int i = 0; i < b_width; i++) { + T b_bit = factory.slice(b, b_width, i, 1); + r = factory.mux(r, factory.mul(r, a, y_width), b_bit, y_width); + a = factory.mul(a, a, y_width); + } + if (is_signed) { + T a_ge_1 = factory.unsigned_greater_than(abs(a0, a_width), factory.constant(Const(1, a_width)), a_width); + T zero_result = factory.bitwise_and(a_ge_1, sign(b, b_width), 1); + r = factory.mux(r, factory.constant(Const(0, y_width)), zero_result, y_width); + } + return r; + } +public: T handle(IdString cellType, dict parameters, dict inputs) { int a_width = parameters.at(ID(A_WIDTH), Const(-1)).as_int(); @@ -78,7 +104,7 @@ class CellSimplifier { int y_width = parameters.at(ID(Y_WIDTH), Const(-1)).as_int(); bool a_signed = parameters.at(ID(A_SIGNED), Const(0)).as_bool(); bool b_signed = parameters.at(ID(B_SIGNED), Const(0)).as_bool(); - if(cellType.in({ID($add), ID($sub), ID($and), ID($or), ID($xor), ID($xnor)})){ + if(cellType.in({ID($add), ID($sub), ID($and), ID($or), ID($xor), ID($xnor), ID($mul)})){ bool is_signed = a_signed && b_signed; T a = extend(inputs.at(ID(A)), a_width, y_width, is_signed); T b = extend(inputs.at(ID(B)), b_width, y_width, is_signed); @@ -86,6 +112,8 @@ class CellSimplifier { return factory.add(a, b, y_width); else if(cellType == ID($sub)) return factory.sub(a, b, y_width); + else if(cellType == ID($mul)) + return factory.mul(a, b, y_width); else if(cellType == ID($and)) return factory.bitwise_and(a, b, y_width); else if(cellType == ID($or)) @@ -160,7 +188,7 @@ class CellSimplifier { T b = inputs.at(ID(B)); T shr = logical_shift_right(a, b, width, b_width); if(b_signed) { - T sign_b = factory.slice(b, b_width, b_width - 1, 1); + T sign_b = sign(b, b_width); T shl = logical_shift_left(a, factory.unary_minus(b, b_width), width, b_width); T y = factory.mux(shr, shl, sign_b, width); return extend(y, width, y_width, false); @@ -182,7 +210,46 @@ class CellSimplifier { int offset = parameters.at(ID(OFFSET)).as_int(); T a = inputs.at(ID(A)); return factory.slice(a, a_width, offset, y_width); - }else{ + }else if(cellType.in({ID($div), ID($mod), ID($divfloor), ID($modfloor)})) { + int width = max(a_width, b_width); + bool is_signed = a_signed && b_signed; + T a = extend(inputs.at(ID(A)), a_width, width, is_signed); + T b = extend(inputs.at(ID(B)), b_width, width, is_signed); + if(is_signed) { + if(cellType == ID($div)) { + T abs_y = factory.unsigned_div(abs(a, width), abs(b, width), width); + T out_sign = factory.not_equal(sign(a, width), sign(b, width), 1); + return neg_if(extend(abs_y, width, y_width, true), y_width, out_sign); + } else if(cellType == ID($mod)) { + T abs_y = factory.unsigned_mod(abs(a, width), abs(b, width), width); + return neg_if(extend(abs_y, width, y_width, true), y_width, sign(a, width)); + } else if(cellType == ID($divfloor)) { + T b_sign = sign(b, width); + T a1 = neg_if(a, width, b_sign); + T b1 = neg_if(b, width, b_sign); + T a1_sign = sign(a1, width); + T a2 = factory.mux(a1, factory.bitwise_not(a1, width), a1_sign, width); + T y1 = factory.unsigned_div(a2, b1, width); + T y2 = factory.mux(y1, factory.bitwise_not(y1, width), a1_sign, width); + return extend(y2, width, y_width, true); + } else if(cellType == ID($modfloor)) { + T abs_b = abs(b, width); + T abs_y = factory.unsigned_mod(abs(a, width), abs_b, width); + T flip_y = factory.bitwise_and(factory.bitwise_xor(sign(a, width), sign(b, width), 1), factory.reduce_or(abs_y, width), 1); + T y_flipped = factory.mux(abs_y, factory.sub(abs_b, abs_y, width), flip_y, width); + T y = neg_if(y_flipped, width, sign(b, b_width)); + return extend(y, width, y_width, true); + } else + log_error("unhandled cell in CellSimplifier %s\n", cellType.c_str()); + } else { + if(cellType.in({ID($mod), ID($modfloor)})) + return extend(factory.unsigned_mod(a, b, width), width, y_width, false); + else + return extend(factory.unsigned_div(a, b, width), width, y_width, false); + } + } else if(cellType == ID($pow)) { + return handle_pow(inputs.at(ID(A)), a_width, inputs.at(ID(B)), b_width, y_width, a_signed && b_signed); + } else{ log_error("unhandled cell in CellSimplifier %s\n", cellType.c_str()); } } diff --git a/kernel/functionalir.h b/kernel/functionalir.h index 2c0d0f55c2c..3e7c55ef4ec 100644 --- a/kernel/functionalir.h +++ b/kernel/functionalir.h @@ -39,6 +39,9 @@ class FunctionalIR { concat, add, sub, + mul, + unsigned_div, + unsigned_mod, bitwise_and, bitwise_or, bitwise_xor, @@ -145,6 +148,9 @@ class FunctionalIR { std::string concat(Node, Node a, int, Node b, int) { return "concat(" + np(a) + ", " + np(b) + ")"; } std::string add(Node, Node a, Node b, int) { return "add(" + np(a) + ", " + np(b) + ")"; } std::string sub(Node, Node a, Node b, int) { return "sub(" + np(a) + ", " + np(b) + ")"; } + std::string mul(Node, Node a, Node b, int) { return "mul(" + np(a) + ", " + np(b) + ")"; } + std::string unsigned_div(Node, Node a, Node b, int) { return "unsigned_div(" + np(a) + ", " + np(b) + ")"; } + std::string unsigned_mod(Node, Node a, Node b, int) { return "unsigned_mod(" + np(a) + ", " + np(b) + ")"; } std::string bitwise_and(Node, Node a, Node b, int) { return "bitwise_and(" + np(a) + ", " + np(b) + ")"; } std::string bitwise_or(Node, Node a, Node b, int) { return "bitwise_or(" + np(a) + ", " + np(b) + ")"; } std::string bitwise_xor(Node, Node a, Node b, int) { return "bitwise_xor(" + np(a) + ", " + np(b) + ")"; } @@ -193,11 +199,14 @@ class FunctionalIR { case Fn::concat: return v.concat(*this, arg(0), arg(0).width(), arg(1), arg(1).width()); break; case Fn::add: return v.add(*this, arg(0), arg(1), sort().width()); break; case Fn::sub: return v.sub(*this, arg(0), arg(1), sort().width()); break; + case Fn::mul: return v.mul(*this, arg(0), arg(1), sort().width()); break; + case Fn::unsigned_div: return v.unsigned_div(*this, arg(0), arg(1), sort().width()); break; + case Fn::unsigned_mod: return v.unsigned_mod(*this, arg(0), arg(1), sort().width()); break; case Fn::bitwise_and: return v.bitwise_and(*this, arg(0), arg(1), sort().width()); break; case Fn::bitwise_or: return v.bitwise_or(*this, arg(0), arg(1), sort().width()); break; case Fn::bitwise_xor: return v.bitwise_xor(*this, arg(0), arg(1), sort().width()); break; case Fn::bitwise_not: return v.bitwise_not(*this, arg(0), sort().width()); break; - case Fn::unary_minus: return v.bitwise_not(*this, arg(0), sort().width()); break; + case Fn::unary_minus: return v.unary_minus(*this, arg(0), sort().width()); break; case Fn::reduce_and: return v.reduce_and(*this, arg(0), arg(0).width()); break; case Fn::reduce_or: return v.reduce_or(*this, arg(0), arg(0).width()); break; case Fn::reduce_xor: return v.reduce_xor(*this, arg(0), arg(0).width()); break; @@ -257,6 +266,9 @@ class FunctionalIR { } Node add(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::add, a.sort(), {a, b}); } Node sub(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::sub, a.sort(), {a, b}); } + Node mul(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::mul, a.sort(), {a, b}); } + Node unsigned_div(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::unsigned_div, a.sort(), {a, b}); } + Node unsigned_mod(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::unsigned_mod, a.sort(), {a, b}); } Node bitwise_and(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::bitwise_and, a.sort(), {a, b}); } Node bitwise_or(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::bitwise_or, a.sort(), {a, b}); } Node bitwise_xor(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::bitwise_xor, a.sort(), {a, b}); } @@ -298,7 +310,8 @@ class FunctionalIR { return add(Fn::buf, Sort(width), {}); } void update_pending(Node node, Node value) { - log_assert(node._ref.function() == Fn::buf && node._ref.size() == 0 && node.sort() == value.sort()); + log_assert(node._ref.function() == Fn::buf && node._ref.size() == 0); + log_assert(node.sort() == value.sort()); node._ref.append_arg(value._ref); } Node input(IdString name, int width) { From 57af68af96b534d32abbc636c0bc6f225a1f09a1 Mon Sep 17 00:00:00 2001 From: Roland Coeurjoly Date: Thu, 4 Jul 2024 19:21:38 +0200 Subject: [PATCH 39/92] include algorithm, needed for std::reverse --- backends/functional/cxx_runtime/sim.h | 1 + 1 file changed, 1 insertion(+) diff --git a/backends/functional/cxx_runtime/sim.h b/backends/functional/cxx_runtime/sim.h index 040c6aefce8..950d4c5e10b 100644 --- a/backends/functional/cxx_runtime/sim.h +++ b/backends/functional/cxx_runtime/sim.h @@ -24,6 +24,7 @@ #include #include #include +#include template class Signal { From e296b884d5c1ddc610bb4ab916da4447ceb6d502 Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Fri, 5 Jul 2024 14:50:00 +0200 Subject: [PATCH 40/92] Add Makefile helpers for coverage --- Makefile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Makefile b/Makefile index 1feeeab95d6..2bbb0dc5b48 100644 --- a/Makefile +++ b/Makefile @@ -1047,6 +1047,14 @@ coverage: lcov --capture -d . --no-external -o coverage.info genhtml coverage.info --output-directory coverage_html +clean_coverage: + find . -name "*.gcda" -type f -delete + +coverage_functional: + rm -rf coverage.info coverage_html + lcov --capture -d backends/functional --no-external -o coverage.info + genhtml coverage.info --output-directory coverage_html + qtcreator: echo "$(CXXFLAGS)" | grep -o '\-D[^ ]*' | tr ' ' '\n' | sed 's/-D/#define /' | sed 's/=/ /'> qtcreator.config { for file in $(basename $(OBJS)); do \ From f0f436cbe7c870e947971e41c74232168dc77db4 Mon Sep 17 00:00:00 2001 From: Roland Coeurjoly Date: Fri, 5 Jul 2024 15:38:42 +0200 Subject: [PATCH 41/92] Fix parenthesis for arithmetic_shift_right --- backends/functional/cxx.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/functional/cxx.cc b/backends/functional/cxx.cc index 81af54c073e..1d8caaaf440 100644 --- a/backends/functional/cxx.cc +++ b/backends/functional/cxx.cc @@ -124,7 +124,7 @@ template struct CxxPrintVisitor { void unsigned_greater_equal(Node, Node a, Node b, int) { print("{} >= {}", a, b); } void logical_shift_left(Node, Node a, Node b, int, int) { print("{} << {}", a, b); } void logical_shift_right(Node, Node a, Node b, int, int) { print("{} >> {}", a, b); } - void arithmetic_shift_right(Node, Node a, Node b, int, int) { print("{}.arithmetic_shift_right{})", a, b); } + void arithmetic_shift_right(Node, Node a, Node b, int, int) { print("{}.arithmetic_shift_right({})", a, b); } void mux(Node, Node a, Node b, Node s, int) { print("{2}.any() ? {1} : {0}", a, b, s); } void pmux(Node, Node a, Node b, Node s, int, int) { print("{0}.pmux({1}, {2})", a, b, s); } void constant(Node, RTLIL::Const value) { From 5780357cd91545ddda05a08bf701ac426ac227ed Mon Sep 17 00:00:00 2001 From: Roland Coeurjoly Date: Sun, 7 Jul 2024 21:01:38 +0200 Subject: [PATCH 42/92] Emit valid SMT for stateful designs, fix some cells --- backends/functional/smtlib.cc | 581 +++++++++++------- tests/functional/.gitignore | 2 +- tests/functional/single_cells/run-test.sh | 6 + .../single_cells/vcd_harness_smt.py | 4 +- 4 files changed, 358 insertions(+), 235 deletions(-) diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index 55efc83c1c2..c496b537591 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -23,264 +23,379 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN -const char illegal_characters[] = "$\\"; +const char illegal_characters[] = "#:\\"; const char *reserved_keywords[] = {}; struct SmtScope { - pool used_names; - dict name_map; - FunctionalTools::Scope scope; - SmtScope() : scope(illegal_characters, reserved_keywords) {} - - void reserve(const std::string &name) { used_names.insert(name); } - - std::string insert(IdString id) - { - std::string name = RTLIL::unescape_id(id); - if (used_names.count(name) == 0) { - used_names.insert(name); - name_map[id] = name; - return name; - } - for (int idx = 0;; ++idx) { - std::string new_name = name + "_" + std::to_string(idx); - if (used_names.count(new_name) == 0) { - used_names.insert(new_name); - name_map[id] = new_name; - return new_name; - } - } - } - - std::string operator[](IdString id) - { - if (name_map.count(id)) { - return name_map[id]; - } else { - return insert(id); - } - } + pool used_names; + dict name_map; + FunctionalTools::Scope scope; + SmtScope() : scope(illegal_characters, reserved_keywords) {} + + void reserve(const std::string &name) { used_names.insert(name); } + + std::string insert(IdString id) + { + + std::string name = scope(id); + if (used_names.count(name) == 0) { + used_names.insert(name); + name_map[id] = name; + return name; + } + for (int idx = 0;; ++idx) { + std::string new_name = name + "_" + std::to_string(idx); + if (used_names.count(new_name) == 0) { + used_names.insert(new_name); + name_map[id] = new_name; + return new_name; + } + } + } + + std::string operator[](IdString id) + { + if (name_map.count(id)) { + return name_map[id]; + } else { + return insert(id); + } + } }; struct SmtWriter { - std::ostream &stream; + std::ostream &stream; - SmtWriter(std::ostream &out) : stream(out) {} + SmtWriter(std::ostream &out) : stream(out) {} - void print(const char *fmt, ...) - { - va_list args; - va_start(args, fmt); - stream << vstringf(fmt, args); - va_end(args); - } + void print(const char *fmt, ...) + { + va_list args; + va_start(args, fmt); + stream << vstringf(fmt, args); + va_end(args); + } }; template struct SmtPrintVisitor { - using Node = FunctionalIR::Node; - NodeNames np; - SmtScope &scope; + using Node = FunctionalIR::Node; + NodeNames np; + SmtScope &scope; - SmtPrintVisitor(NodeNames np, SmtScope &scope) : np(np), scope(scope) {} + SmtPrintVisitor(NodeNames np, SmtScope &scope) : np(np), scope(scope) {} - template std::string arg_to_string(T n) { return std::to_string(n); } + template std::string arg_to_string(T n) { return std::to_string(n); } - std::string arg_to_string(std::string n) { return n; } + std::string arg_to_string(std::string n) { return n; } - std::string arg_to_string(Node n) { return np(n); } - - template std::string format(std::string fmt, Args &&...args) - { - std::vector arg_strings = {arg_to_string(std::forward(args))...}; - for (size_t i = 0; i < arg_strings.size(); ++i) { - std::string placeholder = "%" + std::to_string(i); - size_t pos = 0; - while ((pos = fmt.find(placeholder, pos)) != std::string::npos) { - fmt.replace(pos, placeholder.length(), arg_strings[i]); - pos += arg_strings[i].length(); - } - } - return fmt; - } - std::string buf(Node, Node n) { return np(n); } - - std::string slice(Node, Node a, int, int offset, int out_width) - { - return format("(_ extract %1 %2 %0)", np(a), offset, offset + out_width - 1); - } - - std::string zero_extend(Node, Node a, int, int out_width) { return format("((_ zero_extend %1) %0)", np(a), out_width - a.width()); } - - std::string sign_extend(Node, Node a, int, int out_width) { return format("((_ sign_extend %1) %0)", np(a), out_width - a.width()); } - - std::string concat(Node, Node a, int, Node b, int) { return format("(concat %0 %1)", np(a), np(b)); } - - std::string add(Node, Node a, Node b, int) { return format("(bvadd %0 %1)", np(a), np(b)); } - - std::string sub(Node, Node a, Node b, int) { return format("(bvsub %0 %1)", np(a), np(b)); } - - std::string mul(Node, Node a, Node b, int) { return format("(bvmul %0 %1)", np(a), np(b)); } - - std::string unsigned_div(Node, Node a, Node b, int) { return format("(bvudiv %0 %1)", np(a), np(b)); } - - std::string unsigned_mod(Node, Node a, Node b, int) { return format("(bvurem %0 %1)", np(a), np(b)); } - - std::string bitwise_and(Node, Node a, Node b, int) { return format("(bvand %0 %1)", np(a), np(b)); } - - std::string bitwise_or(Node, Node a, Node b, int) { return format("(bvor %0 %1)", np(a), np(b)); } - - std::string bitwise_xor(Node, Node a, Node b, int) { return format("(bvxor %0 %1)", np(a), np(b)); } - - std::string bitwise_not(Node, Node a, int) { return format("(bvnot %0)", np(a)); } - - std::string unary_minus(Node, Node a, int) { return format("(bvneg %0)", np(a)); } - - std::string reduce_and(Node, Node a, int) { return format("(= %0 #b1)", np(a)); } - - std::string reduce_or(Node, Node a, int) - { - std::stringstream ss; - // We use ite to set the result to bit vector, to ensure appropriate type - ss << "(ite (= " << np(a) << " #b" << std::string(a.width(), '0') << ") #b0 #b1)"; - return ss.str(); - } - - std::string reduce_xor(Node, Node a, int) { return format("(bvxor_reduce %0)", np(a)); } - - std::string equal(Node, Node a, Node b, int) { return format("(= %0 %1)", np(a), np(b)); } - - std::string not_equal(Node, Node a, Node b, int) { return format("(distinct %0 %1)", np(a), np(b)); } - - std::string signed_greater_than(Node, Node a, Node b, int) { return format("(bvsgt %0 %1)", np(a), np(b)); } - - std::string signed_greater_equal(Node, Node a, Node b, int) { return format("(bvsge %0 %1)", np(a), np(b)); } - - std::string unsigned_greater_than(Node, Node a, Node b, int) { return format("(bvugt %0 %1)", np(a), np(b)); } - - std::string unsigned_greater_equal(Node, Node a, Node b, int) { return format("(bvuge %0 %1)", np(a), np(b)); } - - std::string logical_shift_left(Node, Node a, Node b, int, int) { return format("(bvshl %0 %1)", np(a), np(b)); } - - std::string logical_shift_right(Node, Node a, Node b, int, int) { return format("(bvlshr %0 %1)", np(a), np(b)); } - - std::string arithmetic_shift_right(Node, Node a, Node b, int, int) { return format("(bvashr %0 %1)", np(a), np(b)); } - - std::string mux(Node, Node a, Node b, Node s, int) { return format("(ite %2 %0 %1)", np(a), np(b), np(s)); } - - std::string pmux(Node, Node a, Node b, Node s, int, int) - { - // Assume s is a bit vector, combine a and b based on the selection bits - return format("(pmux %0 %1 %2)", np(a), np(b), np(s)); - } - - std::string constant(Node, RTLIL::Const value) { return format("#b%1", value.as_string()); } - - std::string input(Node, IdString name) { return format("%0", scope[name]); } - - std::string state(Node, IdString name) { return format("%0", scope[name]); } - - std::string memory_read(Node, Node mem, Node addr, int, int) { return format("(select %0 %1)", np(mem), np(addr)); } - - std::string memory_write(Node, Node mem, Node addr, Node data, int, int) { return format("(store %0 %1 %2)", np(mem), np(addr), np(data)); } - - std::string undriven(Node, int width) { return format("#b%0", std::string(width, '0')); } + std::string arg_to_string(Node n) { return np(n); } + + template std::string format(std::string fmt, Args &&...args) + { + std::vector arg_strings = {arg_to_string(std::forward(args))...}; + for (size_t i = 0; i < arg_strings.size(); ++i) { + std::string placeholder = "%" + std::to_string(i); + size_t pos = 0; + while ((pos = fmt.find(placeholder, pos)) != std::string::npos) { + fmt.replace(pos, placeholder.length(), arg_strings[i]); + pos += arg_strings[i].length(); + } + } + return fmt; + } + std::string buf(Node, Node n) { return np(n); } + + std::string slice(Node, Node a, int, int offset, int out_width) + { + return format("((_ extract %2 %1) %0)", np(a), offset, offset + out_width - 1); + } + + std::string zero_extend(Node, Node a, int, int out_width) { return format("((_ zero_extend %1) %0)", np(a), out_width - a.width()); } + + std::string sign_extend(Node, Node a, int, int out_width) { return format("((_ sign_extend %1) %0)", np(a), out_width - a.width()); } + + std::string concat(Node, Node a, int, Node b, int) { return format("(concat %0 %1)", np(a), np(b)); } + + std::string add(Node, Node a, Node b, int) { return format("(bvadd %0 %1)", np(a), np(b)); } + + std::string sub(Node, Node a, Node b, int) { return format("(bvsub %0 %1)", np(a), np(b)); } + + std::string mul(Node, Node a, Node b, int) { return format("(bvmul %0 %1)", np(a), np(b)); } + + std::string unsigned_div(Node, Node a, Node b, int) { return format("(bvudiv %0 %1)", np(a), np(b)); } + + std::string unsigned_mod(Node, Node a, Node b, int) { return format("(bvurem %0 %1)", np(a), np(b)); } + + std::string bitwise_and(Node, Node a, Node b, int) { return format("(bvand %0 %1)", np(a), np(b)); } + + std::string bitwise_or(Node, Node a, Node b, int) { return format("(bvor %0 %1)", np(a), np(b)); } + + std::string bitwise_xor(Node, Node a, Node b, int) { return format("(bvxor %0 %1)", np(a), np(b)); } + + std::string bitwise_not(Node, Node a, int) { return format("(bvnot %0)", np(a)); } + + std::string unary_minus(Node, Node a, int) { return format("(bvneg %0)", np(a)); } + + std::string reduce_and(Node, Node a, int) { + std::stringstream ss; + // We use ite to set the result to bit vector, to ensure appropriate type + ss << "(ite (= " << np(a) << " #b" << std::string(a.width(), '1') << ") #b1 #b0)"; + return ss.str(); + } + + std::string reduce_or(Node, Node a, int) + { + std::stringstream ss; + // We use ite to set the result to bit vector, to ensure appropriate type + ss << "(ite (= " << np(a) << " #b" << std::string(a.width(), '0') << ") #b0 #b1)"; + return ss.str(); + } + + std::string reduce_xor(Node, Node a, int) { + std::stringstream ss; + ss << "(bvxor "; + for (int i = 0; i < a.width(); ++i) { + if (i > 0) ss << " "; + ss << "((_ extract " << i << " " << i << ") " << np(a) << ")"; + } + ss << ")"; + return ss.str(); + } + + std::string equal(Node, Node a, Node b, int) { + return format("(ite (= %0 %1) #b1 #b0)", np(a), np(b)); + } + + std::string not_equal(Node, Node a, Node b, int) { + return format("(ite (distinct %0 %1) #b1 #b0)", np(a), np(b)); + } + + std::string signed_greater_than(Node, Node a, Node b, int) { + return format("(ite (bvsgt %0 %1) #b1 #b0)", np(a), np(b)); + } + + std::string signed_greater_equal(Node, Node a, Node b, int) { + return format("(ite (bvsge %0 %1) #b1 #b0)", np(a), np(b)); + } + + std::string unsigned_greater_than(Node, Node a, Node b, int) { + return format("(ite (bvugt %0 %1) #b1 #b0)", np(a), np(b)); + } + + std::string unsigned_greater_equal(Node, Node a, Node b, int) { + return format("(ite (bvuge %0 %1) #b1 #b0)", np(a), np(b)); + } + + std::string logical_shift_left(Node, Node a, Node b, int, int) { + // Get the bit-widths of a and b + int bit_width_a = a.width(); + int bit_width_b = b.width(); + + // Extend b to match the bit-width of a if necessary + std::ostringstream oss; + if (bit_width_a > bit_width_b) { + oss << "((_ zero_extend " << (bit_width_a - bit_width_b) << ") " << np(b) << ")"; + } else { + oss << np(b); // No extension needed if b's width is already sufficient + } + std::string b_extended = oss.str(); + + // Format the bvshl operation with the extended b + oss.str(""); // Clear the stringstream + oss << "(bvshl " << np(a) << " " << b_extended << ")"; + return oss.str(); + } + + std::string logical_shift_right(Node, Node a, Node b, int, int) { + // Get the bit-widths of a and b + int bit_width_a = a.width(); + int bit_width_b = b.width(); + + // Extend b to match the bit-width of a if necessary + std::ostringstream oss; + if (bit_width_a > bit_width_b) { + oss << "((_ zero_extend " << (bit_width_a - bit_width_b) << ") " << np(b) << ")"; + } else { + oss << np(b); // No extension needed if b's width is already sufficient + } + std::string b_extended = oss.str(); + + // Format the bvlshr operation with the extended b + oss.str(""); // Clear the stringstream + oss << "(bvlshr " << np(a) << " " << b_extended << ")"; + return oss.str(); + } + + std::string arithmetic_shift_right(Node, Node a, Node b, int, int) { + // Get the bit-widths of a and b + int bit_width_a = a.width(); + int bit_width_b = b.width(); + + // Extend b to match the bit-width of a if necessary + std::ostringstream oss; + if (bit_width_a > bit_width_b) { + oss << "((_ zero_extend " << (bit_width_a - bit_width_b) << ") " << np(b) << ")"; + } else { + oss << np(b); // No extension needed if b's width is already sufficient + } + std::string b_extended = oss.str(); + + // Format the bvashr operation with the extended b + oss.str(""); // Clear the stringstream + oss << "(bvashr " << np(a) << " " << b_extended << ")"; + return oss.str(); + } + + std::string mux(Node, Node a, Node b, Node s, int) { + return format("(ite (= %2 #b1) %0 %1)", np(a), np(b), np(s)); + } + + std::string pmux(Node, Node a, Node b, Node s, int, int) + { + // Assume s is a bit vector, combine a and b based on the selection bits + return format("(pmux %0 %1 %2)", np(a), np(b), np(s)); + } + + std::string constant(Node, RTLIL::Const value) { return format("#b%0", value.as_string()); } + + std::string input(Node, IdString name) { return format("%0", scope[name]); } + + std::string state(Node, IdString name) { return format("(%0 current_state)", scope[name]); } + + std::string memory_read(Node, Node mem, Node addr, int, int) { return format("(select %0 %1)", np(mem), np(addr)); } + + std::string memory_write(Node, Node mem, Node addr, Node data, int, int) { return format("(store %0 %1 %2)", np(mem), np(addr), np(data)); } + + std::string undriven(Node, int width) { return format("#b%0", std::string(width, '0')); } }; struct SmtModule { - std::string name; - SmtScope scope; - FunctionalIR ir; - - SmtModule(const std::string &module_name, FunctionalIR ir) : name(module_name), ir(std::move(ir)) {} - - void write(std::ostream &out) - { - SmtWriter writer(out); - - writer.print("(declare-fun %s () Bool)\n", name.c_str()); - writer.print("(declare-datatypes () ((Inputs (mk_inputs"); - for (const auto &input : ir.inputs()) { - std::string input_name = scope[input.first]; - writer.print(" (%s (_ BitVec %d))", input_name.c_str(), input.second.width()); - } - writer.print("))))\n"); - - writer.print("(declare-datatypes () ((Outputs (mk_outputs"); - for (const auto &output : ir.outputs()) { - std::string output_name = scope[output.first]; - writer.print(" (%s (_ BitVec %d))", output_name.c_str(), output.second.width()); - } - writer.print("))))\n"); - - writer.print("(declare-fun state () (_ BitVec 1))\n"); - - writer.print("(define-fun %s_step ((state (_ BitVec 1)) (inputs Inputs)) Outputs", name.c_str()); - writer.print(" (let ("); - for (const auto &input : ir.inputs()) { - std::string input_name = scope[input.first]; - writer.print(" (%s (%s inputs))", input_name.c_str(), input_name.c_str()); - } - writer.print(" )"); - - auto node_to_string = [&](FunctionalIR::Node n) { return scope[n.name()]; }; - SmtPrintVisitor visitor(node_to_string, scope); - - for (auto it = ir.begin(); it != ir.end(); ++it) { - const FunctionalIR::Node &node = *it; - - if (ir.inputs().count(node.name()) > 0) - continue; - - std::string node_name = scope[node.name()]; - std::string node_expr = node.visit(visitor); - - writer.print(" (let ( (%s %s))", node_name.c_str(), node_expr.c_str()); - } - - writer.print(" (let ("); - for (const auto &output : ir.outputs()) { - std::string output_name = scope[output.first]; - const std::string output_assignment = ir.get_output_node(output.first).name().c_str(); - writer.print(" (%s %s)", output_name.c_str(), output_assignment.substr(1).c_str()); - } - writer.print(" )"); - writer.print(" (mk_outputs"); - for (const auto &output : ir.outputs()) { - std::string output_name = scope[output.first]; - writer.print(" %s", output_name.c_str()); - } - writer.print(" )"); - writer.print(" )"); - - // Close the nested lets - for (size_t i = 0; i < ir.size() - ir.inputs().size(); ++i) { - writer.print(" )"); - } - - writer.print(" )"); - writer.print(")\n"); - } + std::string name; + SmtScope scope; + FunctionalIR ir; + + SmtModule(const std::string &module_name, FunctionalIR ir) : name(module_name), ir(std::move(ir)) {} + + void write(std::ostream &out) + { + const bool stateful = ir.state().size() != 0; + SmtWriter writer(out); + + writer.print("(declare-fun %s () Bool)\n\n", name.c_str()); + + writer.print("(declare-datatypes () ((Inputs (mk_inputs"); + for (const auto &input : ir.inputs()) { + std::string input_name = scope[input.first]; + writer.print(" (%s (_ BitVec %d))", input_name.c_str(), input.second.width()); + } + writer.print("))))\n\n"); + + writer.print("(declare-datatypes () ((Outputs (mk_outputs"); + for (const auto &output : ir.outputs()) { + std::string output_name = scope[output.first]; + writer.print(" (%s (_ BitVec %d))", output_name.c_str(), output.second.width()); + } + writer.print("))))\n"); + + if (stateful) { + writer.print("(declare-datatypes () ((State (mk_state"); + for (const auto &state : ir.state()) { + std::string state_name = scope[state.first]; + writer.print(" (%s (_ BitVec %d))", state_name.c_str(), state.second.width()); + } + writer.print("))))\n"); + + writer.print("(declare-datatypes () ((Pair (mk-pair (outputs Outputs) (next_state State)))))\n"); + } + + if (stateful) + writer.print("(define-fun %s_step ((current_state State) (inputs Inputs)) Pair", name.c_str()); + else + writer.print("(define-fun %s_step ((inputs Inputs)) Outputs", name.c_str()); + + writer.print(" (let ("); + for (const auto &input : ir.inputs()) { + std::string input_name = scope[input.first]; + writer.print(" (%s (%s inputs))", input_name.c_str(), input_name.c_str()); + } + writer.print(" )"); + + auto node_to_string = [&](FunctionalIR::Node n) { return scope[n.name()]; }; + SmtPrintVisitor visitor(node_to_string, scope); + + for (auto it = ir.begin(); it != ir.end(); ++it) { + const FunctionalIR::Node &node = *it; + + if (ir.inputs().count(node.name()) > 0) + continue; + + std::string node_name = scope[node.name()]; + std::string node_expr = node.visit(visitor); + + writer.print(" (let ( (%s %s))", node_name.c_str(), node_expr.c_str()); + } + + if (stateful) { + writer.print(" (let ( (next_state (mk_state "); + for (const auto &state : ir.state()) { + std::string state_name = scope[state.first]; + const std::string state_assignment = ir.get_state_next_node(state.first).name().c_str(); + writer.print(" %s", state_assignment.substr(1).c_str()); + } + writer.print(" )))"); + } + + if (stateful) { + writer.print(" (let ( (outputs (mk_outputs "); + for (const auto &output : ir.outputs()) { + std::string output_name = scope[output.first]; + writer.print(" %s", output_name.c_str()); + } + writer.print(" )))"); + + writer.print("(mk-pair outputs next_state)"); + } + else { + writer.print(" (mk_outputs "); + for (const auto &output : ir.outputs()) { + std::string output_name = scope[output.first]; + writer.print(" %s", output_name.c_str()); + } + writer.print(" )"); // Closing mk_outputs + } + if (stateful) { + writer.print(" )"); // Closing outputs let statement + writer.print(" )"); // Closing next_state let statement + } + // Close the nested lets + for (size_t i = 0; i < ir.size() - ir.inputs().size(); ++i) { + writer.print(" )"); // Closing each node + } + + writer.print(" )"); // Closing inputs let statement + writer.print(")\n"); // Closing step function + } }; struct FunctionalSmtBackend : public Backend { - FunctionalSmtBackend() : Backend("functional_smt2", "Generate SMT-LIB from Functional IR") {} + FunctionalSmtBackend() : Backend("functional_smt2", "Generate SMT-LIB from Functional IR") {} - void help() override { log("\nFunctional SMT Backend.\n\n"); } + void help() override { log("\nFunctional SMT Backend.\n\n"); } - void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override - { - log_header(design, "Executing Functional SMT Backend.\n"); + void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override + { + log_header(design, "Executing Functional SMT Backend.\n"); - size_t argidx = 1; - extra_args(f, filename, args, argidx, design); + size_t argidx = 1; + extra_args(f, filename, args, argidx, design); - for (auto module : design->selected_modules()) { - log("Processing module `%s`.\n", module->name.c_str()); - auto ir = FunctionalIR::from_module(module); - SmtModule smt(RTLIL::unescape_id(module->name), ir); - smt.write(*f); - } - } + for (auto module : design->selected_modules()) { + log("Processing module `%s`.\n", module->name.c_str()); + auto ir = FunctionalIR::from_module(module); + SmtModule smt(RTLIL::unescape_id(module->name), ir); + smt.write(*f); + } + } } FunctionalSmtBackend; PRIVATE_NAMESPACE_END diff --git a/tests/functional/.gitignore b/tests/functional/.gitignore index 8b20fbedf55..1ffe9926ef6 100644 --- a/tests/functional/.gitignore +++ b/tests/functional/.gitignore @@ -2,4 +2,4 @@ my_module_cxxrtl.cc my_module_functional_cxx.cc vcd_harness *.vcd -*.smt2 \ No newline at end of file +*.smt2 diff --git a/tests/functional/single_cells/run-test.sh b/tests/functional/single_cells/run-test.sh index 11ff1b5ce0b..cc6bcf16086 100755 --- a/tests/functional/single_cells/run-test.sh +++ b/tests/functional/single_cells/run-test.sh @@ -87,6 +87,10 @@ run_smt_test() { run_all_tests() { + declare -A cxx_failing_files + declare -A smt_failing_files + declare -A cxx_successful_files + declare -A smt_successful_files return_code=0 for rtlil_file in rtlil/*.il; do run_cxx_test "$rtlil_file" @@ -134,6 +138,8 @@ run_all_tests() { } run_smt_tests() { + declare -A smt_failing_files + declare -A smt_successful_files return_code=0 for rtlil_file in rtlil/*.il; do run_smt_test "$rtlil_file" diff --git a/tests/functional/single_cells/vcd_harness_smt.py b/tests/functional/single_cells/vcd_harness_smt.py index fd5e94f7e2f..88973eae0d1 100644 --- a/tests/functional/single_cells/vcd_harness_smt.py +++ b/tests/functional/single_cells/vcd_harness_smt.py @@ -76,6 +76,8 @@ declarations = datatype_group[1][1:] # Skip the first item (e.g., 'mk_inputs') if datatype_name == 'Inputs': for declaration in declarations: + print("My declaration") + print(declaration) input_name = declaration[0] bitvec_size = declaration[1][2] inputs[input_name] = int(bitvec_size) @@ -103,7 +105,7 @@ def set_step(inputs, step): define_inputs = f"(define-const test_inputs_step_n{step} Inputs ({mk_inputs_call}))\n" # Create the output definition by calling the gold_step function - define_output = f"(define-const test_outputs_step_n{step} Outputs (gold_step #b0 test_inputs_step_n{step}))\n" + define_output = f"(define-const test_outputs_step_n{step} Outputs (gold_step test_inputs_step_n{step}))\n" smt_commands = [] smt_commands.append(define_inputs) smt_commands.append(define_output) From fad76ce67785c519ca6ed982a7916ccebf5f5411 Mon Sep 17 00:00:00 2001 From: Roland Coeurjoly Date: Sun, 7 Jul 2024 21:12:47 +0200 Subject: [PATCH 43/92] Fix memory leak --- backends/functional/smtlib.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index c496b537591..bc1253b8837 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -24,7 +24,7 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN const char illegal_characters[] = "#:\\"; -const char *reserved_keywords[] = {}; +const char *reserved_keywords[] = {nullptr}; struct SmtScope { pool used_names; From 7cff8fa3a31e999aa16a3f4ab25894939d08bb70 Mon Sep 17 00:00:00 2001 From: Roland Coeurjoly Date: Sun, 7 Jul 2024 21:29:33 +0200 Subject: [PATCH 44/92] Fix corner case of pos cell with input and output being same width --- backends/functional/smtlib.cc | 4 +++- .../single_cells/rtlil/test_cell_pos_00001.il | 13 +++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 tests/functional/single_cells/rtlil/test_cell_pos_00001.il diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index bc1253b8837..501d195ebde 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -277,7 +277,7 @@ struct SmtModule { SmtModule(const std::string &module_name, FunctionalIR ir) : name(module_name), ir(std::move(ir)) {} void write(std::ostream &out) - { + { const bool stateful = ir.state().size() != 0; SmtWriter writer(out); @@ -371,6 +371,8 @@ struct SmtModule { for (size_t i = 0; i < ir.size() - ir.inputs().size(); ++i) { writer.print(" )"); // Closing each node } + if (ir.size() == ir.inputs().size()) + writer.print(" )"); // Corner case writer.print(" )"); // Closing inputs let statement writer.print(")\n"); // Closing step function diff --git a/tests/functional/single_cells/rtlil/test_cell_pos_00001.il b/tests/functional/single_cells/rtlil/test_cell_pos_00001.il new file mode 100644 index 00000000000..96e9c895a18 --- /dev/null +++ b/tests/functional/single_cells/rtlil/test_cell_pos_00001.il @@ -0,0 +1,13 @@ +# Generated by Yosys 0.40+7 (git sha1 d8b5b90e7, g++ 13.2.0 -Og -fPIC) +autoidx 1 +module \gold + wire width 6 input 1 \A + wire width 6 output 2 \Y + cell $pos \UUT + parameter \A_SIGNED 0 + parameter \A_WIDTH 6 + parameter \Y_WIDTH 6 + connect \A \A + connect \Y \Y + end +end From 566e57d24f45dbffec8b40a58d1ef15b45b9bb52 Mon Sep 17 00:00:00 2001 From: Roland Coeurjoly Date: Mon, 8 Jul 2024 00:07:07 +0200 Subject: [PATCH 45/92] Support $lut cells. Both C++ and SMT tests pass --- kernel/functionalir.cc | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/kernel/functionalir.cc b/kernel/functionalir.cc index 6498ef63629..c5406f41283 100644 --- a/kernel/functionalir.cc +++ b/kernel/functionalir.cc @@ -248,9 +248,31 @@ class CellSimplifier { return extend(factory.unsigned_div(a, b, width), width, y_width, false); } } else if(cellType == ID($pow)) { - return handle_pow(inputs.at(ID(A)), a_width, inputs.at(ID(B)), b_width, y_width, a_signed && b_signed); + return handle_pow(inputs.at(ID(A)), a_width, inputs.at(ID(B)), b_width, y_width, a_signed && b_signed); + } else if (cellType == ID($lut)) { + int width = parameters.at(ID(WIDTH)).as_int(); + Const lut_table = parameters.at(ID(LUT)); + T a = inputs.at(ID(A)); + + // Output initialization + T y = factory.constant(Const(0, 1)); + + // Iterate over each possible input combination + for (int i = 0; i < (1 << width); ++i) { + // Create a constant representing the value of i + T i_val = factory.constant(Const(i, width)); + // Check if the input matches this value + T match = factory.equal(a, i_val, width); + // Get the corresponding LUT value + bool lut_val = lut_table.bits[i] == State::S1; + T lut_output = factory.constant(Const(lut_val, 1)); + // Use a multiplexer to select the correct output based on the match + y = factory.mux(y, lut_output, match, 1); + } + + return y; } else{ - log_error("unhandled cell in CellSimplifier %s\n", cellType.c_str()); + log_error("unhandled cell in CellSimplifier %s\n", cellType.c_str()); } } }; From 80582ed3afb7ebbfe1372dd97f57062b68c1eb79 Mon Sep 17 00:00:00 2001 From: Roland Coeurjoly Date: Mon, 8 Jul 2024 18:29:58 +0200 Subject: [PATCH 46/92] Check the existance of a different set of outputs. No need for (push 1) nor (pop 1) --- .../single_cells/rtlil/multiple_outputs.il | 69 +++++++++++++++++++ .../single_cells/vcd_harness_smt.py | 28 ++++---- 2 files changed, 82 insertions(+), 15 deletions(-) create mode 100644 tests/functional/single_cells/rtlil/multiple_outputs.il diff --git a/tests/functional/single_cells/rtlil/multiple_outputs.il b/tests/functional/single_cells/rtlil/multiple_outputs.il new file mode 100644 index 00000000000..5e9141a20ad --- /dev/null +++ b/tests/functional/single_cells/rtlil/multiple_outputs.il @@ -0,0 +1,69 @@ +# Generated by Yosys 0.40+7 (git sha1 cc795a3f5, g++ 13.2.0 -Og -fPIC) +autoidx 5 +attribute \cells_not_processed 1 +attribute \src "multiple_outputs.v:1.1-15.10" +module \gold + attribute \src "multiple_outputs.v:11.12-11.20" + wire $and$multiple_outputs.v:11$2_Y + attribute \src "multiple_outputs.v:10.12-10.14" + wire $not$multiple_outputs.v:10$1_Y + attribute \src "multiple_outputs.v:12.12-12.20" + wire $or$multiple_outputs.v:12$3_Y + attribute \src "multiple_outputs.v:13.12-13.20" + wire $xor$multiple_outputs.v:13$4_Y + attribute \src "multiple_outputs.v:2.16-2.17" + wire input 1 \a + attribute \src "multiple_outputs.v:3.17-3.18" + wire output 2 \b + attribute \src "multiple_outputs.v:4.17-4.18" + wire output 3 \c + attribute \src "multiple_outputs.v:5.17-5.18" + wire output 4 \d + attribute \src "multiple_outputs.v:6.17-6.18" + wire output 5 \e + attribute \src "multiple_outputs.v:11.12-11.20" + cell $and $and$multiple_outputs.v:11$2 + parameter \A_SIGNED 0 + parameter \A_WIDTH 1 + parameter \B_SIGNED 0 + parameter \B_WIDTH 1 + parameter \Y_WIDTH 1 + connect \A \a + connect \B 1'1 + connect \Y $and$multiple_outputs.v:11$2_Y + end + attribute \src "multiple_outputs.v:10.12-10.14" + cell $not $not$multiple_outputs.v:10$1 + parameter \A_SIGNED 0 + parameter \A_WIDTH 1 + parameter \Y_WIDTH 1 + connect \A \a + connect \Y $not$multiple_outputs.v:10$1_Y + end + attribute \src "multiple_outputs.v:12.12-12.20" + cell $or $or$multiple_outputs.v:12$3 + parameter \A_SIGNED 0 + parameter \A_WIDTH 1 + parameter \B_SIGNED 0 + parameter \B_WIDTH 1 + parameter \Y_WIDTH 1 + connect \A \a + connect \B 1'0 + connect \Y $or$multiple_outputs.v:12$3_Y + end + attribute \src "multiple_outputs.v:13.12-13.20" + cell $xor $xor$multiple_outputs.v:13$4 + parameter \A_SIGNED 0 + parameter \A_WIDTH 1 + parameter \B_SIGNED 0 + parameter \B_WIDTH 1 + parameter \Y_WIDTH 1 + connect \A \a + connect \B 1'1 + connect \Y $xor$multiple_outputs.v:13$4_Y + end + connect \b $not$multiple_outputs.v:10$1_Y + connect \c $and$multiple_outputs.v:11$2_Y + connect \d $or$multiple_outputs.v:12$3_Y + connect \e $xor$multiple_outputs.v:13$4_Y +end diff --git a/tests/functional/single_cells/vcd_harness_smt.py b/tests/functional/single_cells/vcd_harness_smt.py index 88973eae0d1..95947748ffe 100644 --- a/tests/functional/single_cells/vcd_harness_smt.py +++ b/tests/functional/single_cells/vcd_harness_smt.py @@ -139,9 +139,6 @@ def set_step(inputs, step): smt_io.p_close() sys.exit(1) -value = smt_io.get(f'(Y test_outputs_step_n0)') -print(f" Y: {value}") - # Store signal values signals = {name: [] for name in list(inputs.keys()) + list(outputs.keys())} # Retrieve and print values for each state @@ -151,6 +148,8 @@ def hex_to_bin(value): bin_value = bin(int(hex_value, 16))[2:] # Convert to binary and remove the '0b' prefix return f'b{bin_value.zfill(len(hex_value) * 4)}' # Add 'b' prefix and pad with zeros return value + +combined_assertions = [] for step in range(num_steps): print(f"Values for step {step + 1}:") for input_name, width in inputs.items(): @@ -163,18 +162,17 @@ def hex_to_bin(value): value = hex_to_bin(value[1:]) print(f" {output_name}: {value}") signals[output_name].append((step, value)) - smt_io.write(f'(push 1)') - smt_io.write(f'(assert (not (= ({output_name} test_outputs_step_n{step}) #{value})))') - result = smt_io.check_sat(["unsat"]) - if result != 'unsat': - smt_io.p_close() - sys.exit(1) - smt_io.write(f'(pop 1)') - result = smt_io.check_sat(["sat"]) - if result != 'sat': - smt_io.p_close() - sys.exit(1) - + combined_assertions.append(f'(= ({output_name} test_outputs_step_n{step}) #{value})') +# Create a single assertion covering all timesteps +combined_condition = " ".join(combined_assertions) +smt_io.write(f'(assert (not (and {combined_condition})))') + +# Check the combined assertion +result = smt_io.check_sat(["unsat"]) +if result != 'unsat': + smt_io.p_close() + sys.exit(1) + smt_io.p_close() def write_vcd(filename, signals, timescale='1 ns', date='today'): From 9f660b1e4bf125db23d7f5c494d3c5f9fd715ee7 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Wed, 10 Jul 2024 14:28:48 +0100 Subject: [PATCH 47/92] rewrite smtlib pass to use SExpr class --- backends/functional/cxx.cc | 28 +- backends/functional/smtlib.cc | 725 +++++++++++++++++----------------- kernel/functionalir.h | 45 +-- 3 files changed, 409 insertions(+), 389 deletions(-) diff --git a/backends/functional/cxx.cc b/backends/functional/cxx.cc index 1d8caaaf440..4ab9d53a1d4 100644 --- a/backends/functional/cxx.cc +++ b/backends/functional/cxx.cc @@ -19,11 +19,11 @@ #include "kernel/yosys.h" #include "kernel/functionalir.h" +#include USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN -const char illegal_characters[] = "!\"#%&'()*+,-./:;<=>?@[]\\^`{|}~ "; const char *reserved_keywords[] = { "alignas","alignof","and","and_eq","asm","atomic_cancel","atomic_commit", "atomic_noexcept","auto","bitand","bitor","bool","break","case", @@ -42,6 +42,16 @@ const char *reserved_keywords[] = { nullptr }; +template struct CxxScope : public FunctionalTools::Scope { + CxxScope() { + for(const char **p = reserved_keywords; *p != nullptr; p++) + this->reserve(*p); + } + bool is_character_legal(char c) override { + return isascii(c) && (isalnum(c) || c == '_' || c == '$'); + } +}; + struct CxxType { FunctionalIR::Sort sort; CxxType(FunctionalIR::Sort sort) : sort(sort) {} @@ -61,30 +71,30 @@ using CxxWriter = FunctionalTools::Writer; struct CxxStruct { std::string name; dict types; - FunctionalTools::Scope scope; + CxxScope scope; CxxStruct(std::string name) - : name(name), scope(illegal_characters, reserved_keywords) { + : name(name) { scope.reserve("fn"); scope.reserve("visit"); } void insert(IdString name, CxxType type) { - scope(name); + scope(name, name); types.insert({name, type}); } void print(CxxWriter &f) { f.print("\tstruct {} {{\n", name); for (auto p : types) { - f.print("\t\t{} {};\n", p.second.to_string(), scope(p.first)); + f.print("\t\t{} {};\n", p.second.to_string(), scope(p.first, p.first)); } f.print("\n\t\ttemplate void visit(T &&fn) {{\n"); for (auto p : types) { - f.print("\t\t\tfn(\"{}\", {});\n", RTLIL::unescape_id(p.first), scope(p.first)); + f.print("\t\t\tfn(\"{}\", {});\n", RTLIL::unescape_id(p.first), scope(p.first, p.first)); } f.print("\t\t}}\n"); f.print("\t}};\n\n"); }; std::string operator[](IdString field) { - return scope(field); + return scope(field, field); } }; @@ -165,7 +175,7 @@ struct CxxModule { output_struct.insert(name, sort); for (auto [name, sort] : ir.state()) state_struct.insert(name, sort); - module_name = FunctionalTools::Scope(illegal_characters, reserved_keywords)(module->name); + module_name = CxxScope().unique_name(module->name); } void write_header(CxxWriter &f) { f.print("#include \"sim.h\"\n\n"); @@ -180,7 +190,7 @@ struct CxxModule { } void write_eval_def(CxxWriter &f) { f.print("void {0}::eval({0}::Inputs const &input, {0}::Outputs &output, {0}::State const ¤t_state, {0}::State &next_state)\n{{\n", module_name); - FunctionalTools::Scope locals(illegal_characters, reserved_keywords); + CxxScope locals; locals.reserve("input"); locals.reserve("output"); locals.reserve("current_state"); diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index 501d195ebde..c3e159eb31e 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -19,385 +19,406 @@ #include "kernel/functionalir.h" #include "kernel/yosys.h" +#include USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN -const char illegal_characters[] = "#:\\"; -const char *reserved_keywords[] = {nullptr}; - -struct SmtScope { - pool used_names; - dict name_map; - FunctionalTools::Scope scope; - SmtScope() : scope(illegal_characters, reserved_keywords) {} - - void reserve(const std::string &name) { used_names.insert(name); } - - std::string insert(IdString id) - { - - std::string name = scope(id); - if (used_names.count(name) == 0) { - used_names.insert(name); - name_map[id] = name; - return name; - } - for (int idx = 0;; ++idx) { - std::string new_name = name + "_" + std::to_string(idx); - if (used_names.count(new_name) == 0) { - used_names.insert(new_name); - name_map[id] = new_name; - return new_name; - } - } - } - - std::string operator[](IdString id) - { - if (name_map.count(id)) { - return name_map[id]; - } else { - return insert(id); - } - } +const char *reserved_keywords[] = { + "BINARY", "DECIMAL", "HEXADECIMAL", "NUMERAL", "STRING", "_", "!", "as", "let", "exists", "forall", "match", "par", + "assert", "check-sat", "check-sat-assuming", "declare-const", "declare-datatype", "declare-datatypes", + "declare-fun", "declare-sort", "define-fun", "define-fun-rec", "define-funs-rec", "define-sort", + "exit", "get-assertions", "symbol", "sort", "get-assignment", "get-info", "get-model", + "get-option", "get-proof", "get-unsat-assumptions", "get-unsat-core", "get-value", + "pop", "push", "reset", "reset-assertions", "set-info", "set-logic", "set-option", + + "pair", "Pair", "first", "second", + "inputs", "state", + nullptr }; -struct SmtWriter { - std::ostream &stream; - - SmtWriter(std::ostream &out) : stream(out) {} - - void print(const char *fmt, ...) - { - va_list args; - va_start(args, fmt); - stream << vstringf(fmt, args); - va_end(args); - } +struct SmtScope : public FunctionalTools::Scope { + SmtScope() { + for(const char **p = reserved_keywords; *p != nullptr; p++) + reserve(*p); + } + bool is_character_legal(char c) override { + return isascii(c) && (isalnum(c) || strchr("~!@$%^&*_-+=<>.?/", c)); + } }; -template struct SmtPrintVisitor { - using Node = FunctionalIR::Node; - NodeNames np; - SmtScope &scope; - - SmtPrintVisitor(NodeNames np, SmtScope &scope) : np(np), scope(scope) {} - - template std::string arg_to_string(T n) { return std::to_string(n); } - - std::string arg_to_string(std::string n) { return n; } - - std::string arg_to_string(Node n) { return np(n); } - - template std::string format(std::string fmt, Args &&...args) - { - std::vector arg_strings = {arg_to_string(std::forward(args))...}; - for (size_t i = 0; i < arg_strings.size(); ++i) { - std::string placeholder = "%" + std::to_string(i); - size_t pos = 0; - while ((pos = fmt.find(placeholder, pos)) != std::string::npos) { - fmt.replace(pos, placeholder.length(), arg_strings[i]); - pos += arg_strings[i].length(); - } - } - return fmt; - } - std::string buf(Node, Node n) { return np(n); } - - std::string slice(Node, Node a, int, int offset, int out_width) - { - return format("((_ extract %2 %1) %0)", np(a), offset, offset + out_width - 1); - } - - std::string zero_extend(Node, Node a, int, int out_width) { return format("((_ zero_extend %1) %0)", np(a), out_width - a.width()); } - - std::string sign_extend(Node, Node a, int, int out_width) { return format("((_ sign_extend %1) %0)", np(a), out_width - a.width()); } - - std::string concat(Node, Node a, int, Node b, int) { return format("(concat %0 %1)", np(a), np(b)); } - - std::string add(Node, Node a, Node b, int) { return format("(bvadd %0 %1)", np(a), np(b)); } - - std::string sub(Node, Node a, Node b, int) { return format("(bvsub %0 %1)", np(a), np(b)); } - - std::string mul(Node, Node a, Node b, int) { return format("(bvmul %0 %1)", np(a), np(b)); } - - std::string unsigned_div(Node, Node a, Node b, int) { return format("(bvudiv %0 %1)", np(a), np(b)); } - - std::string unsigned_mod(Node, Node a, Node b, int) { return format("(bvurem %0 %1)", np(a), np(b)); } - - std::string bitwise_and(Node, Node a, Node b, int) { return format("(bvand %0 %1)", np(a), np(b)); } - - std::string bitwise_or(Node, Node a, Node b, int) { return format("(bvor %0 %1)", np(a), np(b)); } - - std::string bitwise_xor(Node, Node a, Node b, int) { return format("(bvxor %0 %1)", np(a), np(b)); } - - std::string bitwise_not(Node, Node a, int) { return format("(bvnot %0)", np(a)); } - - std::string unary_minus(Node, Node a, int) { return format("(bvneg %0)", np(a)); } - - std::string reduce_and(Node, Node a, int) { - std::stringstream ss; - // We use ite to set the result to bit vector, to ensure appropriate type - ss << "(ite (= " << np(a) << " #b" << std::string(a.width(), '1') << ") #b1 #b0)"; - return ss.str(); - } - - std::string reduce_or(Node, Node a, int) - { - std::stringstream ss; - // We use ite to set the result to bit vector, to ensure appropriate type - ss << "(ite (= " << np(a) << " #b" << std::string(a.width(), '0') << ") #b0 #b1)"; - return ss.str(); - } - - std::string reduce_xor(Node, Node a, int) { - std::stringstream ss; - ss << "(bvxor "; - for (int i = 0; i < a.width(); ++i) { - if (i > 0) ss << " "; - ss << "((_ extract " << i << " " << i << ") " << np(a) << ")"; - } - ss << ")"; - return ss.str(); - } - - std::string equal(Node, Node a, Node b, int) { - return format("(ite (= %0 %1) #b1 #b0)", np(a), np(b)); - } - - std::string not_equal(Node, Node a, Node b, int) { - return format("(ite (distinct %0 %1) #b1 #b0)", np(a), np(b)); - } - - std::string signed_greater_than(Node, Node a, Node b, int) { - return format("(ite (bvsgt %0 %1) #b1 #b0)", np(a), np(b)); - } - - std::string signed_greater_equal(Node, Node a, Node b, int) { - return format("(ite (bvsge %0 %1) #b1 #b0)", np(a), np(b)); - } - - std::string unsigned_greater_than(Node, Node a, Node b, int) { - return format("(ite (bvugt %0 %1) #b1 #b0)", np(a), np(b)); - } - - std::string unsigned_greater_equal(Node, Node a, Node b, int) { - return format("(ite (bvuge %0 %1) #b1 #b0)", np(a), np(b)); - } - - std::string logical_shift_left(Node, Node a, Node b, int, int) { - // Get the bit-widths of a and b - int bit_width_a = a.width(); - int bit_width_b = b.width(); - - // Extend b to match the bit-width of a if necessary - std::ostringstream oss; - if (bit_width_a > bit_width_b) { - oss << "((_ zero_extend " << (bit_width_a - bit_width_b) << ") " << np(b) << ")"; - } else { - oss << np(b); // No extension needed if b's width is already sufficient - } - std::string b_extended = oss.str(); - - // Format the bvshl operation with the extended b - oss.str(""); // Clear the stringstream - oss << "(bvshl " << np(a) << " " << b_extended << ")"; - return oss.str(); - } - - std::string logical_shift_right(Node, Node a, Node b, int, int) { - // Get the bit-widths of a and b - int bit_width_a = a.width(); - int bit_width_b = b.width(); +class SExpr { + std::variant, std::string> _v; +public: + SExpr(std::string a) : _v(std::move(a)) {} + SExpr(const char *a) : _v(a) {} + SExpr(int n) : _v(std::to_string(n)) {} + SExpr(std::vector const &a) : _v(std::in_place_index<0>, a) {} + SExpr(std::vector &&a) : _v(std::in_place_index<0>, std::move(a)) {} + SExpr(std::initializer_list a) : _v(std::in_place_index<0>, a) {} + bool is_atom() const { return std::holds_alternative(_v); } + std::string const &atom() const { return std::get(_v); } + bool is_list() const { return std::holds_alternative>(_v); } + std::vector const &list() const { return std::get>(_v); } + friend std::ostream &operator<<(std::ostream &os, SExpr const &sexpr) { + if(sexpr.is_atom()) + os << sexpr.atom(); + else if(sexpr.is_list()){ + os << "("; + auto l = sexpr.list(); + for(size_t i = 0; i < l.size(); i++) { + if(i > 0) os << " "; + os << l[i]; + } + os << ")"; + }else + os << ""; + return os; + } + std::string to_string() const { + std::stringstream ss; + ss << *this; + return ss.str(); + } +}; - // Extend b to match the bit-width of a if necessary - std::ostringstream oss; - if (bit_width_a > bit_width_b) { - oss << "((_ zero_extend " << (bit_width_a - bit_width_b) << ") " << np(b) << ")"; - } else { - oss << np(b); // No extension needed if b's width is already sufficient +class SExprWriter { + std::ostream &os; + int _max_line_width; + int _indent = 0; + int _pos = 0; + bool _pending_nl = false; + vector _unclosed; + vector _unclosed_stack; + void nl_if_pending() { + if(_pending_nl) { + os << '\n'; + _pos = 0; + _pending_nl = false; + } + } + void puts(std::string const &s) { + if(s.empty()) return; + nl_if_pending(); + for(auto c : s) { + if(c == '\n') { + os << c; + _pos = 0; + } else { + if(_pos == 0) { + for(int i = 0; i < _indent; i++) + os << " "; + _pos = 2 * _indent; + } + os << c; + _pos++; + } + } } - std::string b_extended = oss.str(); - - // Format the bvlshr operation with the extended b - oss.str(""); // Clear the stringstream - oss << "(bvlshr " << np(a) << " " << b_extended << ")"; - return oss.str(); - } - - std::string arithmetic_shift_right(Node, Node a, Node b, int, int) { - // Get the bit-widths of a and b - int bit_width_a = a.width(); - int bit_width_b = b.width(); - - // Extend b to match the bit-width of a if necessary - std::ostringstream oss; - if (bit_width_a > bit_width_b) { - oss << "((_ zero_extend " << (bit_width_a - bit_width_b) << ") " << np(b) << ")"; - } else { - oss << np(b); // No extension needed if b's width is already sufficient + int width(SExpr const &sexpr) { + if(sexpr.is_atom()) + return sexpr.atom().size(); + else if(sexpr.is_list()) { + int w = 2; + for(auto arg : sexpr.list()) + w += width(arg); + if(sexpr.list().size() > 1) + w += sexpr.list().size() - 1; + return w; + } else + return 0; } - std::string b_extended = oss.str(); - - // Format the bvashr operation with the extended b - oss.str(""); // Clear the stringstream - oss << "(bvashr " << np(a) << " " << b_extended << ")"; - return oss.str(); - } - - std::string mux(Node, Node a, Node b, Node s, int) { - return format("(ite (= %2 #b1) %0 %1)", np(a), np(b), np(s)); - } - - std::string pmux(Node, Node a, Node b, Node s, int, int) - { - // Assume s is a bit vector, combine a and b based on the selection bits - return format("(pmux %0 %1 %2)", np(a), np(b), np(s)); - } - - std::string constant(Node, RTLIL::Const value) { return format("#b%0", value.as_string()); } - - std::string input(Node, IdString name) { return format("%0", scope[name]); } - - std::string state(Node, IdString name) { return format("(%0 current_state)", scope[name]); } - - std::string memory_read(Node, Node mem, Node addr, int, int) { return format("(select %0 %1)", np(mem), np(addr)); } - - std::string memory_write(Node, Node mem, Node addr, Node data, int, int) { return format("(store %0 %1 %2)", np(mem), np(addr), np(data)); } - - std::string undriven(Node, int width) { return format("#b%0", std::string(width, '0')); } -}; - -struct SmtModule { - std::string name; - SmtScope scope; - FunctionalIR ir; - - SmtModule(const std::string &module_name, FunctionalIR ir) : name(module_name), ir(std::move(ir)) {} - - void write(std::ostream &out) - { - const bool stateful = ir.state().size() != 0; - SmtWriter writer(out); - - writer.print("(declare-fun %s () Bool)\n\n", name.c_str()); - - writer.print("(declare-datatypes () ((Inputs (mk_inputs"); - for (const auto &input : ir.inputs()) { - std::string input_name = scope[input.first]; - writer.print(" (%s (_ BitVec %d))", input_name.c_str(), input.second.width()); + void print(SExpr const &sexpr, bool close = true, bool indent_rest = true) { + if(sexpr.is_atom()) + puts(sexpr.atom()); + else if(sexpr.is_list()) { + auto args = sexpr.list(); + puts("("); + bool vertical = args.size() > 1 && _pos + width(sexpr) > _max_line_width; + if(vertical) _indent++; + for(size_t i = 0; i < args.size(); i++) { + if(i > 0) puts(vertical ? "\n" : " "); + print(args[i]); + } + _indent += (!close && indent_rest) - vertical; + if(close) + puts(")"); + else { + _unclosed.push_back(indent_rest); + _pending_nl = true; + } + }else + log_error("shouldn't happen in SExprWriter::print"); } - writer.print("))))\n\n"); - - writer.print("(declare-datatypes () ((Outputs (mk_outputs"); - for (const auto &output : ir.outputs()) { - std::string output_name = scope[output.first]; - writer.print(" (%s (_ BitVec %d))", output_name.c_str(), output.second.width()); +public: + SExprWriter(std::ostream &os, int max_line_width = 80) + : os(os) + , _max_line_width(max_line_width) + {} + void open(SExpr const &sexpr, bool indent_rest = true) { + log_assert(sexpr.is_list()); + print(sexpr, false, indent_rest); } - writer.print("))))\n"); - - if (stateful) { - writer.print("(declare-datatypes () ((State (mk_state"); - for (const auto &state : ir.state()) { - std::string state_name = scope[state.first]; - writer.print(" (%s (_ BitVec %d))", state_name.c_str(), state.second.width()); - } - writer.print("))))\n"); - - writer.print("(declare-datatypes () ((Pair (mk-pair (outputs Outputs) (next_state State)))))\n"); + void close(size_t n = 1) { + log_assert(_unclosed.size() - (_unclosed_stack.empty() ? 0 : _unclosed_stack.back()) >= n); + while(n-- > 0) { + bool indented = _unclosed[_unclosed.size() - 1]; + _unclosed.pop_back(); + _pending_nl = _pos >= _max_line_width; + if(indented) + _indent--; + puts(")"); + _pending_nl = true; + } } - - if (stateful) - writer.print("(define-fun %s_step ((current_state State) (inputs Inputs)) Pair", name.c_str()); - else - writer.print("(define-fun %s_step ((inputs Inputs)) Outputs", name.c_str()); - - writer.print(" (let ("); - for (const auto &input : ir.inputs()) { - std::string input_name = scope[input.first]; - writer.print(" (%s (%s inputs))", input_name.c_str(), input_name.c_str()); + void push() { + _unclosed_stack.push_back(_unclosed.size()); + } + void pop() { + auto t = _unclosed_stack.back(); + log_assert(_unclosed.size() >= t); + close(_unclosed.size() - t); + _unclosed_stack.pop_back(); + } + SExprWriter &operator <<(SExpr const &sexpr) { + print(sexpr); + _pending_nl = true; + return *this; } - writer.print(" )"); - - auto node_to_string = [&](FunctionalIR::Node n) { return scope[n.name()]; }; - SmtPrintVisitor visitor(node_to_string, scope); - - for (auto it = ir.begin(); it != ir.end(); ++it) { - const FunctionalIR::Node &node = *it; - - if (ir.inputs().count(node.name()) > 0) - continue; - - std::string node_name = scope[node.name()]; - std::string node_expr = node.visit(visitor); - - writer.print(" (let ( (%s %s))", node_name.c_str(), node_expr.c_str()); + void comment(std::string const &str, bool hanging = false) { + if(hanging) { + if(_pending_nl) { + _pending_nl = false; + puts(" "); + } + } + puts("; "); + puts(str); + puts("\n"); + } + ~SExprWriter() { + while(!_unclosed_stack.empty()) + pop(); + close(_unclosed.size()); + nl_if_pending(); } +}; - if (stateful) { - writer.print(" (let ( (next_state (mk_state "); - for (const auto &state : ir.state()) { - std::string state_name = scope[state.first]; - const std::string state_assignment = ir.get_state_next_node(state.first).name().c_str(); - writer.print(" %s", state_assignment.substr(1).c_str()); - } - writer.print(" )))"); - } +struct SmtSort { + FunctionalIR::Sort sort; + SmtSort(FunctionalIR::Sort sort) : sort(sort) {} + SExpr to_sexpr() const { + if(sort.is_memory()) { + return SExpr{"Array", {"_", "BitVec", sort.addr_width()}, {"_", "BitVec", sort.data_width()}}; + } else if(sort.is_signal()) { + return SExpr{"_", "BitVec", sort.width()}; + } else { + log_error("unknown sort"); + } + } +}; - if (stateful) { - writer.print(" (let ( (outputs (mk_outputs "); - for (const auto &output : ir.outputs()) { - std::string output_name = scope[output.first]; - writer.print(" %s", output_name.c_str()); - } - writer.print(" )))"); +class SmtStruct { + struct Field { + SmtSort sort; + std::string accessor; + }; + idict field_names; + vector fields; + SmtScope &scope; +public: + std::string name; + SmtStruct(std::string name, SmtScope &scope) : scope(scope), name(name) {} + void insert(IdString field_name, SmtSort sort) { + field_names(field_name); + auto accessor = scope.unique_name("\\" + name + "_" + RTLIL::unescape_id(field_name)); + fields.emplace_back(Field{sort, accessor}); + } + void write_definition(SExprWriter &w) { + w.open(SExpr{"declare-datatype", name}); + w.open(SExpr({})); + w.open(SExpr{name}); + for(const auto &field : fields) + w << SExpr{field.accessor, field.sort.to_sexpr()}; + w.close(3); + } + template void write_value(SExprWriter &w, Fn fn) { + w.open(SExpr(std::initializer_list{name})); + for(auto field_name : field_names) { + w << fn(field_name); + w.comment(RTLIL::unescape_id(field_name), true); + } + w.close(); + } + SExpr access(SExpr record, IdString name) { + size_t i = field_names.at(name); + return SExpr{fields[i].accessor, std::move(record)}; + } +}; - writer.print("(mk-pair outputs next_state)"); - } - else { - writer.print(" (mk_outputs "); - for (const auto &output : ir.outputs()) { - std::string output_name = scope[output.first]; - writer.print(" %s", output_name.c_str()); - } - writer.print(" )"); // Closing mk_outputs - } - if (stateful) { - writer.print(" )"); // Closing outputs let statement - writer.print(" )"); // Closing next_state let statement - } - // Close the nested lets - for (size_t i = 0; i < ir.size() - ir.inputs().size(); ++i) { - writer.print(" )"); // Closing each node - } - if (ir.size() == ir.inputs().size()) - writer.print(" )"); // Corner case +struct SmtPrintVisitor { + using Node = FunctionalIR::Node; + std::function n; + SmtStruct &input_struct; + SmtStruct &state_struct; + + SmtPrintVisitor(SmtStruct &input_struct, SmtStruct &state_struct) : input_struct(input_struct), state_struct(state_struct) {} + + std::string literal(RTLIL::Const c) { + std::string s = "#b"; + for(int i = c.size(); i-- > 0; ) + s += c[i] == State::S1 ? '1' : '0'; + return s; + } + + SExpr from_bool(SExpr &&arg) { + return SExpr{"ite", std::move(arg), "#b1", "#b0"}; + } + SExpr to_bool(SExpr &&arg) { + return SExpr{"=", std::move(arg), "#b1"}; + } + SExpr extract(SExpr &&arg, int offset, int out_width = 1) { + return SExpr{{"_", "extract", offset + out_width - 1, offset}, std::move(arg)}; + } + + SExpr buf(Node, Node a) { return n(a); } + SExpr slice(Node, Node a, int, int offset, int out_width) { return extract(n(a), offset, out_width); } + SExpr zero_extend(Node, Node a, int, int out_width) { return SExpr{{"_", "zero_extend", out_width - a.width()}, n(a)}; } + SExpr sign_extend(Node, Node a, int, int out_width) { return SExpr{{"_", "sign_extend", out_width - a.width()}, n(a)}; } + SExpr concat(Node, Node a, int, Node b, int) { return SExpr{"concat", n(a), n(b)}; } + SExpr add(Node, Node a, Node b, int) { return SExpr{"bvadd", n(a), n(b)}; } + SExpr sub(Node, Node a, Node b, int) { return SExpr{"bvsub", n(a), n(b)}; } + SExpr mul(Node, Node a, Node b, int) { return SExpr{"bvmul", n(a), n(b)}; } + SExpr unsigned_div(Node, Node a, Node b, int) { return SExpr{"bvudiv", n(a), n(b)}; } + SExpr unsigned_mod(Node, Node a, Node b, int) { return SExpr{"bvurem", n(a), n(b)}; } + SExpr bitwise_and(Node, Node a, Node b, int) { return SExpr{"bvand", n(a), n(b)}; } + SExpr bitwise_or(Node, Node a, Node b, int) { return SExpr{"bvor", n(a), n(b)}; } + SExpr bitwise_xor(Node, Node a, Node b, int) { return SExpr{"bvxor", n(a), n(b)}; } + SExpr bitwise_not(Node, Node a, int) { return SExpr{"bvnot", n(a)}; } + SExpr unary_minus(Node, Node a, int) { return SExpr{"bvneg", n(a)}; } + SExpr reduce_and(Node, Node a, int) { return from_bool(SExpr{"=", n(a), literal(RTLIL::Const(State::S1, a.width()))}); } + SExpr reduce_or(Node, Node a, int) { return from_bool(SExpr{"=", n(a), literal(RTLIL::Const(State::S0, a.width()))}); } + SExpr reduce_xor(Node, Node a, int) { + vector s { "bvxor" }; + for(int i = 0; i < a.width(); i++) + s.push_back(extract(n(a), i)); + return s; + } + SExpr equal(Node, Node a, Node b, int) { return from_bool(SExpr{"=", n(a), n(b)}); } + SExpr not_equal(Node, Node a, Node b, int) { return from_bool(SExpr{"distinct", n(a), n(b)}); } + SExpr signed_greater_than(Node, Node a, Node b, int) { return from_bool(SExpr{"bvsgt", n(a), n(b)}); } + SExpr signed_greater_equal(Node, Node a, Node b, int) { return from_bool(SExpr{"bvsge", n(a), n(b)}); } + SExpr unsigned_greater_than(Node, Node a, Node b, int) { return from_bool(SExpr{"bvugt", n(a), n(b)}); } + SExpr unsigned_greater_equal(Node, Node a, Node b, int) { return from_bool(SExpr{"bvuge", n(a), n(b)}); } + + SExpr extend(SExpr &&a, int in_width, int out_width) { + if(in_width < out_width) + return SExpr{{"_", "zero_extend", out_width - in_width}, std::move(a)}; + else + return std::move(a); + } + SExpr logical_shift_left(Node, Node a, Node b, int, int) { return SExpr{"bvshl", n(a), extend(n(b), b.width(), a.width())}; } + SExpr logical_shift_right(Node, Node a, Node b, int, int) { return SExpr{"bvshr", n(a), extend(n(b), b.width(), a.width())}; } + SExpr arithmetic_shift_right(Node, Node a, Node b, int, int) { return SExpr{"bvasr", n(a), extend(n(b), b.width(), a.width())}; } + SExpr mux(Node, Node a, Node b, Node s, int) { return SExpr{"ite", to_bool(n(s)), n(a), n(b)}; } + SExpr pmux(Node, Node a, Node b, Node s, int, int) { + SExpr rv = n(a); + for(int i = 0; i < s.width(); i++) + rv = SExpr{"ite", to_bool(extract(n(s), i)), extract(n(b), a.width() * i, a.width()), rv}; + return rv; + } + SExpr constant(Node, RTLIL::Const value) { return literal(value); } + SExpr memory_read(Node, Node mem, Node addr, int, int) { return SExpr{"select", n(mem), n(addr)}; } + SExpr memory_write(Node, Node mem, Node addr, Node data, int, int) { return SExpr{"store", n(mem), n(addr), n(data)}; } + + SExpr input(Node, IdString name) { return input_struct.access("inputs", name); } + SExpr state(Node, IdString name) { return state_struct.access("state", name); } + + SExpr undriven(Node, int width) { return literal(RTLIL::Const(State::S0, width)); } +}; - writer.print(" )"); // Closing inputs let statement - writer.print(")\n"); // Closing step function - } +struct SmtModule { + FunctionalIR ir; + SmtScope scope; + std::string name; + + SmtStruct input_struct; + SmtStruct output_struct; + SmtStruct state_struct; + + SmtModule(Module *module) + : ir(FunctionalIR::from_module(module)) + , scope() + , name(scope.unique_name(module->name)) + , input_struct(scope.unique_name(module->name.str() + "_Inputs"), scope) + , output_struct(scope.unique_name(module->name.str() + "_Outputs"), scope) + , state_struct(scope.unique_name(module->name.str() + "_State"), scope) + { + for (const auto &input : ir.inputs()) + input_struct.insert(input.first, input.second); + for (const auto &output : ir.outputs()) + output_struct.insert(output.first, output.second); + for (const auto &state : ir.state()) + state_struct.insert(state.first, state.second); + } + + void write(std::ostream &out) + { + SExprWriter w(out); + + input_struct.write_definition(w); + output_struct.write_definition(w); + state_struct.write_definition(w); + + w << SExpr{"declare-datatypes", {{"Pair", 2}}, {{"par", {"X", "Y"}, {{"pair", {"first", "X"}, {"second", "Y"}}}}}}; + + w.push(); + w.open(SExpr{"define-fun", name, + {{"inputs", input_struct.name}, + {"state", state_struct.name}}, + {"Pair", output_struct.name, state_struct.name}}); + auto inlined = [&](FunctionalIR::Node n) { + return n.fn() == FunctionalIR::Fn::constant || + n.fn() == FunctionalIR::Fn::undriven; + }; + SmtPrintVisitor visitor(input_struct, state_struct); + auto node_to_sexpr = [&](FunctionalIR::Node n) -> SExpr { + if(inlined(n)) + return n.visit(visitor); + else + return scope(n.id(), n.name()); + }; + visitor.n = node_to_sexpr; + for(auto n : ir) + if(!inlined(n)) { + w.open(SExpr{"let", {{node_to_sexpr(n), n.visit(visitor)}}}, false); + w.comment(SmtSort(n.sort()).to_sexpr().to_string(), true); + } + w.open(SExpr{"pair"}); + output_struct.write_value(w, [&](IdString name) { return node_to_sexpr(ir.get_output_node(name)); }); + state_struct.write_value(w, [&](IdString name) { return node_to_sexpr(ir.get_state_next_node(name)); }); + w.pop(); + } }; struct FunctionalSmtBackend : public Backend { - FunctionalSmtBackend() : Backend("functional_smt2", "Generate SMT-LIB from Functional IR") {} + FunctionalSmtBackend() : Backend("functional_smt2", "Generate SMT-LIB from Functional IR") {} - void help() override { log("\nFunctional SMT Backend.\n\n"); } + void help() override { log("\nFunctional SMT Backend.\n\n"); } - void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override - { - log_header(design, "Executing Functional SMT Backend.\n"); + void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override + { + log_header(design, "Executing Functional SMT Backend.\n"); - size_t argidx = 1; - extra_args(f, filename, args, argidx, design); + size_t argidx = 1; + extra_args(f, filename, args, argidx, design); - for (auto module : design->selected_modules()) { - log("Processing module `%s`.\n", module->name.c_str()); - auto ir = FunctionalIR::from_module(module); - SmtModule smt(RTLIL::unescape_id(module->name), ir); - smt.write(*f); - } - } + for (auto module : design->selected_modules()) { + log("Processing module `%s`.\n", module->name.c_str()); + SmtModule smt(module); + smt.write(*f); + } + } } FunctionalSmtBackend; PRIVATE_NAMESPACE_END diff --git a/kernel/functionalir.h b/kernel/functionalir.h index 3e7c55ef4ec..99fc872a81a 100644 --- a/kernel/functionalir.h +++ b/kernel/functionalir.h @@ -30,6 +30,7 @@ USING_YOSYS_NAMESPACE YOSYS_NAMESPACE_BEGIN class FunctionalIR { +public: enum class Fn { invalid, buf, @@ -69,7 +70,6 @@ class FunctionalIR { memory_read, memory_write }; -public: class Sort { std::variant> _v; public: @@ -185,6 +185,7 @@ class FunctionalIR { else return std::string("\\n") + std::to_string(id()); } + Fn fn() const { return _ref.function().fn(); } Sort sort() const { return _ref.attr().sort; } int width() const { return sort().width(); } Node arg(int n) const { return Node(_ref.arg(n)); } @@ -380,16 +381,22 @@ class FunctionalIR { }; namespace FunctionalTools { - class Scope { - const char *_illegal_characters; + template class Scope { + protected: + char substitution_character = '_'; + virtual bool is_character_legal(char) = 0; + private: pool _used_names; - dict _by_id; - dict _by_name; - std::string allocate_name(IdString suggestion) { + dict _by_id; + public: + void reserve(std::string name) { + _used_names.insert(std::move(name)); + } + std::string unique_name(IdString suggestion) { std::string str = RTLIL::unescape_id(suggestion); for(size_t i = 0; i < str.size(); i++) - if(strchr(_illegal_characters, str[i])) - str[i] = '_'; + if(!is_character_legal(str[i])) + str[i] = substitution_character; if(_used_names.count(str) == 0) { _used_names.insert(str); return str; @@ -402,32 +409,14 @@ namespace FunctionalTools { } } } - public: - Scope(const char *illegal_characters = "", const char **keywords = nullptr) { - _illegal_characters = illegal_characters; - if(keywords != nullptr) - for(const char **p = keywords; *p != nullptr; p++) - reserve(*p); - } - void reserve(std::string name) { - _used_names.insert(std::move(name)); - } - std::string operator()(int id, IdString suggestion) { + std::string operator()(Id id, IdString suggestion) { auto it = _by_id.find(id); if(it != _by_id.end()) return it->second; - std::string str = allocate_name(suggestion); + std::string str = unique_name(suggestion); _by_id.insert({id, str}); return str; } - std::string operator()(IdString idstring) { - auto it = _by_name.find(idstring); - if(it != _by_name.end()) - return it->second; - std::string str = allocate_name(idstring); - _by_name.insert({idstring, str}); - return str; - } }; class Writer { std::ostream *os; From c659ef29f4a00de5f1d7a656ffd646655fa4e859 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Wed, 10 Jul 2024 18:08:12 +0100 Subject: [PATCH 48/92] change smtlib backend to use list() function instead of SExpr{} constructor (leads to weird constructor overloading resolution issues) --- backends/functional/smtlib.cc | 105 ++++++++++++++++++---------------- 1 file changed, 55 insertions(+), 50 deletions(-) diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index c3e159eb31e..cbff1398e8a 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -48,14 +48,14 @@ struct SmtScope : public FunctionalTools::Scope { }; class SExpr { +public: std::variant, std::string> _v; public: SExpr(std::string a) : _v(std::move(a)) {} SExpr(const char *a) : _v(a) {} SExpr(int n) : _v(std::to_string(n)) {} - SExpr(std::vector const &a) : _v(std::in_place_index<0>, a) {} - SExpr(std::vector &&a) : _v(std::in_place_index<0>, std::move(a)) {} - SExpr(std::initializer_list a) : _v(std::in_place_index<0>, a) {} + SExpr(std::vector const &l) : _v(l) {} + SExpr(std::vector &&l) : _v(std::move(l)) {} bool is_atom() const { return std::holds_alternative(_v); } std::string const &atom() const { return std::get(_v); } bool is_list() const { return std::holds_alternative>(_v); } @@ -81,6 +81,9 @@ class SExpr { return ss.str(); } }; +template SExpr list(Args&&... args) { + return SExpr(std::vector{std::forward(args)...}); +} class SExprWriter { std::ostream &os; @@ -209,9 +212,9 @@ struct SmtSort { SmtSort(FunctionalIR::Sort sort) : sort(sort) {} SExpr to_sexpr() const { if(sort.is_memory()) { - return SExpr{"Array", {"_", "BitVec", sort.addr_width()}, {"_", "BitVec", sort.data_width()}}; + return list("Array", list("_", "BitVec", sort.addr_width()), list("_", "BitVec", sort.data_width())); } else if(sort.is_signal()) { - return SExpr{"_", "BitVec", sort.width()}; + return list("_", "BitVec", sort.width()); } else { log_error("unknown sort"); } @@ -235,15 +238,15 @@ class SmtStruct { fields.emplace_back(Field{sort, accessor}); } void write_definition(SExprWriter &w) { - w.open(SExpr{"declare-datatype", name}); - w.open(SExpr({})); - w.open(SExpr{name}); + w.open(list("declare-datatype", name)); + w.open(list()); + w.open(list(name)); for(const auto &field : fields) - w << SExpr{field.accessor, field.sort.to_sexpr()}; + w << list(field.accessor, field.sort.to_sexpr()); w.close(3); } template void write_value(SExprWriter &w, Fn fn) { - w.open(SExpr(std::initializer_list{name})); + w.open(list(name)); for(auto field_name : field_names) { w << fn(field_name); w.comment(RTLIL::unescape_id(field_name), true); @@ -252,7 +255,7 @@ class SmtStruct { } SExpr access(SExpr record, IdString name) { size_t i = field_names.at(name); - return SExpr{fields[i].accessor, std::move(record)}; + return list(fields[i].accessor, std::move(record)); } }; @@ -272,64 +275,64 @@ struct SmtPrintVisitor { } SExpr from_bool(SExpr &&arg) { - return SExpr{"ite", std::move(arg), "#b1", "#b0"}; + return list("ite", std::move(arg), "#b1", "#b0"); } SExpr to_bool(SExpr &&arg) { - return SExpr{"=", std::move(arg), "#b1"}; + return list("=", std::move(arg), "#b1"); } SExpr extract(SExpr &&arg, int offset, int out_width = 1) { - return SExpr{{"_", "extract", offset + out_width - 1, offset}, std::move(arg)}; + return list(list("_", "extract", offset + out_width - 1, offset), std::move(arg)); } SExpr buf(Node, Node a) { return n(a); } SExpr slice(Node, Node a, int, int offset, int out_width) { return extract(n(a), offset, out_width); } - SExpr zero_extend(Node, Node a, int, int out_width) { return SExpr{{"_", "zero_extend", out_width - a.width()}, n(a)}; } - SExpr sign_extend(Node, Node a, int, int out_width) { return SExpr{{"_", "sign_extend", out_width - a.width()}, n(a)}; } - SExpr concat(Node, Node a, int, Node b, int) { return SExpr{"concat", n(a), n(b)}; } - SExpr add(Node, Node a, Node b, int) { return SExpr{"bvadd", n(a), n(b)}; } - SExpr sub(Node, Node a, Node b, int) { return SExpr{"bvsub", n(a), n(b)}; } - SExpr mul(Node, Node a, Node b, int) { return SExpr{"bvmul", n(a), n(b)}; } - SExpr unsigned_div(Node, Node a, Node b, int) { return SExpr{"bvudiv", n(a), n(b)}; } - SExpr unsigned_mod(Node, Node a, Node b, int) { return SExpr{"bvurem", n(a), n(b)}; } - SExpr bitwise_and(Node, Node a, Node b, int) { return SExpr{"bvand", n(a), n(b)}; } - SExpr bitwise_or(Node, Node a, Node b, int) { return SExpr{"bvor", n(a), n(b)}; } - SExpr bitwise_xor(Node, Node a, Node b, int) { return SExpr{"bvxor", n(a), n(b)}; } - SExpr bitwise_not(Node, Node a, int) { return SExpr{"bvnot", n(a)}; } - SExpr unary_minus(Node, Node a, int) { return SExpr{"bvneg", n(a)}; } - SExpr reduce_and(Node, Node a, int) { return from_bool(SExpr{"=", n(a), literal(RTLIL::Const(State::S1, a.width()))}); } - SExpr reduce_or(Node, Node a, int) { return from_bool(SExpr{"=", n(a), literal(RTLIL::Const(State::S0, a.width()))}); } + SExpr zero_extend(Node, Node a, int, int out_width) { return list(list("_", "zero_extend", out_width - a.width()), n(a)); } + SExpr sign_extend(Node, Node a, int, int out_width) { return list(list("_", "sign_extend", out_width - a.width()), n(a)); } + SExpr concat(Node, Node a, int, Node b, int) { return list("concat", n(a), n(b)); } + SExpr add(Node, Node a, Node b, int) { return list("bvadd", n(a), n(b)); } + SExpr sub(Node, Node a, Node b, int) { return list("bvsub", n(a), n(b)); } + SExpr mul(Node, Node a, Node b, int) { return list("bvmul", n(a), n(b)); } + SExpr unsigned_div(Node, Node a, Node b, int) { return list("bvudiv", n(a), n(b)); } + SExpr unsigned_mod(Node, Node a, Node b, int) { return list("bvurem", n(a), n(b)); } + SExpr bitwise_and(Node, Node a, Node b, int) { return list("bvand", n(a), n(b)); } + SExpr bitwise_or(Node, Node a, Node b, int) { return list("bvor", n(a), n(b)); } + SExpr bitwise_xor(Node, Node a, Node b, int) { return list("bvxor", n(a), n(b)); } + SExpr bitwise_not(Node, Node a, int) { return list("bvnot", n(a)); } + SExpr unary_minus(Node, Node a, int) { return list("bvneg", n(a)); } + SExpr reduce_and(Node, Node a, int) { return from_bool(list("=", n(a), literal(RTLIL::Const(State::S1, a.width())))); } + SExpr reduce_or(Node, Node a, int) { return from_bool(list("=", n(a), literal(RTLIL::Const(State::S0, a.width())))); } SExpr reduce_xor(Node, Node a, int) { vector s { "bvxor" }; for(int i = 0; i < a.width(); i++) s.push_back(extract(n(a), i)); return s; } - SExpr equal(Node, Node a, Node b, int) { return from_bool(SExpr{"=", n(a), n(b)}); } - SExpr not_equal(Node, Node a, Node b, int) { return from_bool(SExpr{"distinct", n(a), n(b)}); } - SExpr signed_greater_than(Node, Node a, Node b, int) { return from_bool(SExpr{"bvsgt", n(a), n(b)}); } - SExpr signed_greater_equal(Node, Node a, Node b, int) { return from_bool(SExpr{"bvsge", n(a), n(b)}); } - SExpr unsigned_greater_than(Node, Node a, Node b, int) { return from_bool(SExpr{"bvugt", n(a), n(b)}); } - SExpr unsigned_greater_equal(Node, Node a, Node b, int) { return from_bool(SExpr{"bvuge", n(a), n(b)}); } + SExpr equal(Node, Node a, Node b, int) { return from_bool(list("=", n(a), n(b))); } + SExpr not_equal(Node, Node a, Node b, int) { return from_bool(list("distinct", n(a), n(b))); } + SExpr signed_greater_than(Node, Node a, Node b, int) { return from_bool(list("bvsgt", n(a), n(b))); } + SExpr signed_greater_equal(Node, Node a, Node b, int) { return from_bool(list("bvsge", n(a), n(b))); } + SExpr unsigned_greater_than(Node, Node a, Node b, int) { return from_bool(list("bvugt", n(a), n(b))); } + SExpr unsigned_greater_equal(Node, Node a, Node b, int) { return from_bool(list("bvuge", n(a), n(b))); } SExpr extend(SExpr &&a, int in_width, int out_width) { if(in_width < out_width) - return SExpr{{"_", "zero_extend", out_width - in_width}, std::move(a)}; + return list(list("_", "zero_extend", out_width - in_width), std::move(a)); else return std::move(a); } - SExpr logical_shift_left(Node, Node a, Node b, int, int) { return SExpr{"bvshl", n(a), extend(n(b), b.width(), a.width())}; } - SExpr logical_shift_right(Node, Node a, Node b, int, int) { return SExpr{"bvshr", n(a), extend(n(b), b.width(), a.width())}; } - SExpr arithmetic_shift_right(Node, Node a, Node b, int, int) { return SExpr{"bvasr", n(a), extend(n(b), b.width(), a.width())}; } - SExpr mux(Node, Node a, Node b, Node s, int) { return SExpr{"ite", to_bool(n(s)), n(a), n(b)}; } + SExpr logical_shift_left(Node, Node a, Node b, int, int) { return list("bvshl", n(a), extend(n(b), b.width(), a.width())); } + SExpr logical_shift_right(Node, Node a, Node b, int, int) { return list("bvshr", n(a), extend(n(b), b.width(), a.width())); } + SExpr arithmetic_shift_right(Node, Node a, Node b, int, int) { return list("bvasr", n(a), extend(n(b), b.width(), a.width())); } + SExpr mux(Node, Node a, Node b, Node s, int) { return list("ite", to_bool(n(s)), n(a), n(b)); } SExpr pmux(Node, Node a, Node b, Node s, int, int) { SExpr rv = n(a); for(int i = 0; i < s.width(); i++) - rv = SExpr{"ite", to_bool(extract(n(s), i)), extract(n(b), a.width() * i, a.width()), rv}; + rv = list("ite", to_bool(extract(n(s), i)), extract(n(b), a.width() * i, a.width()), rv); return rv; } SExpr constant(Node, RTLIL::Const value) { return literal(value); } - SExpr memory_read(Node, Node mem, Node addr, int, int) { return SExpr{"select", n(mem), n(addr)}; } - SExpr memory_write(Node, Node mem, Node addr, Node data, int, int) { return SExpr{"store", n(mem), n(addr), n(data)}; } + SExpr memory_read(Node, Node mem, Node addr, int, int) { return list("select", n(mem), n(addr)); } + SExpr memory_write(Node, Node mem, Node addr, Node data, int, int) { return list("store", n(mem), n(addr), n(data)); } SExpr input(Node, IdString name) { return input_struct.access("inputs", name); } SExpr state(Node, IdString name) { return state_struct.access("state", name); } @@ -370,13 +373,15 @@ struct SmtModule { output_struct.write_definition(w); state_struct.write_definition(w); - w << SExpr{"declare-datatypes", {{"Pair", 2}}, {{"par", {"X", "Y"}, {{"pair", {"first", "X"}, {"second", "Y"}}}}}}; + w << list("declare-datatypes", + list(list("Pair", 2)), + list(list("par", list("X", "Y"), list(list("pair", list("first", "X"), list("second", "Y")))))); w.push(); - w.open(SExpr{"define-fun", name, - {{"inputs", input_struct.name}, - {"state", state_struct.name}}, - {"Pair", output_struct.name, state_struct.name}}); + w.open(list("define-fun", name, + list(list("inputs", input_struct.name), + list("state", state_struct.name)), + list("Pair", output_struct.name, state_struct.name))); auto inlined = [&](FunctionalIR::Node n) { return n.fn() == FunctionalIR::Fn::constant || n.fn() == FunctionalIR::Fn::undriven; @@ -391,10 +396,10 @@ struct SmtModule { visitor.n = node_to_sexpr; for(auto n : ir) if(!inlined(n)) { - w.open(SExpr{"let", {{node_to_sexpr(n), n.visit(visitor)}}}, false); + w.open(list("let", list(list(node_to_sexpr(n), n.visit(visitor)))), false); w.comment(SmtSort(n.sort()).to_sexpr().to_string(), true); } - w.open(SExpr{"pair"}); + w.open(list("pair")); output_struct.write_value(w, [&](IdString name) { return node_to_sexpr(ir.get_output_node(name)); }); state_struct.write_value(w, [&](IdString name) { return node_to_sexpr(ir.get_state_next_node(name)); }); w.pop(); From 00a65754bbf5ac5a456f6fce5812e4400ef5449c Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Thu, 11 Jul 2024 11:30:23 +0100 Subject: [PATCH 49/92] factor out SExpr/SExprWriter classes out of smtlib backend, and also tidy them up/document them --- Makefile | 3 +- backends/functional/smtlib.cc | 163 +--------------------------------- kernel/sexpr.cc | 163 ++++++++++++++++++++++++++++++++++ kernel/sexpr.h | 122 +++++++++++++++++++++++++ 4 files changed, 290 insertions(+), 161 deletions(-) create mode 100644 kernel/sexpr.cc create mode 100644 kernel/sexpr.h diff --git a/Makefile b/Makefile index 2bbb0dc5b48..68e6fda4a0c 100644 --- a/Makefile +++ b/Makefile @@ -617,6 +617,7 @@ $(eval $(call add_include_file,kernel/register.h)) $(eval $(call add_include_file,kernel/rtlil.h)) $(eval $(call add_include_file,kernel/satgen.h)) $(eval $(call add_include_file,kernel/scopeinfo.h)) +$(eval $(call add_include_file,kernel/sexpr.h)) $(eval $(call add_include_file,kernel/sigtools.h)) $(eval $(call add_include_file,kernel/timinginfo.h)) $(eval $(call add_include_file,kernel/utils.h)) @@ -638,7 +639,7 @@ $(eval $(call add_include_file,backends/rtlil/rtlil_backend.h)) OBJS += kernel/driver.o kernel/register.o kernel/rtlil.o kernel/log.o kernel/calc.o kernel/yosys.o OBJS += kernel/binding.o -OBJS += kernel/cellaigs.o kernel/celledges.o kernel/cost.o kernel/satgen.o kernel/scopeinfo.o kernel/qcsat.o kernel/mem.o kernel/ffmerge.o kernel/ff.o kernel/yw.o kernel/json.o kernel/fmt.o +OBJS += kernel/cellaigs.o kernel/celledges.o kernel/cost.o kernel/satgen.o kernel/scopeinfo.o kernel/qcsat.o kernel/mem.o kernel/ffmerge.o kernel/ff.o kernel/yw.o kernel/json.o kernel/fmt.o kernel/sexpr.o OBJS += kernel/drivertools.o kernel/functionalir.o ifeq ($(ENABLE_ZLIB),1) OBJS += kernel/fstdata.o diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index cbff1398e8a..43e6f5bf489 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -19,11 +19,14 @@ #include "kernel/functionalir.h" #include "kernel/yosys.h" +#include "kernel/sexpr.h" #include USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN +using SExprUtil::list; + const char *reserved_keywords[] = { "BINARY", "DECIMAL", "HEXADECIMAL", "NUMERAL", "STRING", "_", "!", "as", "let", "exists", "forall", "match", "par", "assert", "check-sat", "check-sat-assuming", "declare-const", "declare-datatype", "declare-datatypes", @@ -47,166 +50,6 @@ struct SmtScope : public FunctionalTools::Scope { } }; -class SExpr { -public: - std::variant, std::string> _v; -public: - SExpr(std::string a) : _v(std::move(a)) {} - SExpr(const char *a) : _v(a) {} - SExpr(int n) : _v(std::to_string(n)) {} - SExpr(std::vector const &l) : _v(l) {} - SExpr(std::vector &&l) : _v(std::move(l)) {} - bool is_atom() const { return std::holds_alternative(_v); } - std::string const &atom() const { return std::get(_v); } - bool is_list() const { return std::holds_alternative>(_v); } - std::vector const &list() const { return std::get>(_v); } - friend std::ostream &operator<<(std::ostream &os, SExpr const &sexpr) { - if(sexpr.is_atom()) - os << sexpr.atom(); - else if(sexpr.is_list()){ - os << "("; - auto l = sexpr.list(); - for(size_t i = 0; i < l.size(); i++) { - if(i > 0) os << " "; - os << l[i]; - } - os << ")"; - }else - os << ""; - return os; - } - std::string to_string() const { - std::stringstream ss; - ss << *this; - return ss.str(); - } -}; -template SExpr list(Args&&... args) { - return SExpr(std::vector{std::forward(args)...}); -} - -class SExprWriter { - std::ostream &os; - int _max_line_width; - int _indent = 0; - int _pos = 0; - bool _pending_nl = false; - vector _unclosed; - vector _unclosed_stack; - void nl_if_pending() { - if(_pending_nl) { - os << '\n'; - _pos = 0; - _pending_nl = false; - } - } - void puts(std::string const &s) { - if(s.empty()) return; - nl_if_pending(); - for(auto c : s) { - if(c == '\n') { - os << c; - _pos = 0; - } else { - if(_pos == 0) { - for(int i = 0; i < _indent; i++) - os << " "; - _pos = 2 * _indent; - } - os << c; - _pos++; - } - } - } - int width(SExpr const &sexpr) { - if(sexpr.is_atom()) - return sexpr.atom().size(); - else if(sexpr.is_list()) { - int w = 2; - for(auto arg : sexpr.list()) - w += width(arg); - if(sexpr.list().size() > 1) - w += sexpr.list().size() - 1; - return w; - } else - return 0; - } - void print(SExpr const &sexpr, bool close = true, bool indent_rest = true) { - if(sexpr.is_atom()) - puts(sexpr.atom()); - else if(sexpr.is_list()) { - auto args = sexpr.list(); - puts("("); - bool vertical = args.size() > 1 && _pos + width(sexpr) > _max_line_width; - if(vertical) _indent++; - for(size_t i = 0; i < args.size(); i++) { - if(i > 0) puts(vertical ? "\n" : " "); - print(args[i]); - } - _indent += (!close && indent_rest) - vertical; - if(close) - puts(")"); - else { - _unclosed.push_back(indent_rest); - _pending_nl = true; - } - }else - log_error("shouldn't happen in SExprWriter::print"); - } -public: - SExprWriter(std::ostream &os, int max_line_width = 80) - : os(os) - , _max_line_width(max_line_width) - {} - void open(SExpr const &sexpr, bool indent_rest = true) { - log_assert(sexpr.is_list()); - print(sexpr, false, indent_rest); - } - void close(size_t n = 1) { - log_assert(_unclosed.size() - (_unclosed_stack.empty() ? 0 : _unclosed_stack.back()) >= n); - while(n-- > 0) { - bool indented = _unclosed[_unclosed.size() - 1]; - _unclosed.pop_back(); - _pending_nl = _pos >= _max_line_width; - if(indented) - _indent--; - puts(")"); - _pending_nl = true; - } - } - void push() { - _unclosed_stack.push_back(_unclosed.size()); - } - void pop() { - auto t = _unclosed_stack.back(); - log_assert(_unclosed.size() >= t); - close(_unclosed.size() - t); - _unclosed_stack.pop_back(); - } - SExprWriter &operator <<(SExpr const &sexpr) { - print(sexpr); - _pending_nl = true; - return *this; - } - void comment(std::string const &str, bool hanging = false) { - if(hanging) { - if(_pending_nl) { - _pending_nl = false; - puts(" "); - } - } - puts("; "); - puts(str); - puts("\n"); - } - ~SExprWriter() { - while(!_unclosed_stack.empty()) - pop(); - close(_unclosed.size()); - nl_if_pending(); - } -}; - struct SmtSort { FunctionalIR::Sort sort; SmtSort(FunctionalIR::Sort sort) : sort(sort) {} diff --git a/kernel/sexpr.cc b/kernel/sexpr.cc new file mode 100644 index 00000000000..0b977d2a89c --- /dev/null +++ b/kernel/sexpr.cc @@ -0,0 +1,163 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Emily Schmidt + * + * 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 "sexpr.h" + +YOSYS_NAMESPACE_BEGIN + +std::ostream &operator<<(std::ostream &os, SExpr const &sexpr) { + if(sexpr.is_atom()) + os << sexpr.atom(); + else if(sexpr.is_list()){ + os << "("; + auto l = sexpr.list(); + for(size_t i = 0; i < l.size(); i++) { + if(i > 0) os << " "; + os << l[i]; + } + os << ")"; + }else + os << ""; + return os; +} + + std::string SExpr::to_string() const { + std::stringstream ss; + ss << *this; + return ss.str(); +} + +void SExprWriter::nl_if_pending() { + if(_pending_nl) { + os << '\n'; + _pos = 0; + _pending_nl = false; + } +} + +void SExprWriter::puts(std::string_view s) { + if(s.empty()) return; + nl_if_pending(); + for(auto c : s) { + if(c == '\n') { + os << c; + _pos = 0; + } else { + if(_pos == 0) { + for(int i = 0; i < _indent; i++) + os << " "; + _pos = 2 * _indent; + } + os << c; + _pos++; + } + } +} + + +// Calculate how much space would be left if expression was written +// out in full horizontally. Returns any negative value if it doesn't fit. +// +// (Ideally we would avoid recalculating the widths of subexpression, +// but I can't figure out how to store the widths. As an alternative, +// we bail out of the calculation as soon as we can tell the expression +// doesn't fit in the available space.) +int SExprWriter::check_fit(SExpr const &sexpr, int space) { + if(sexpr.is_atom()) + return space - sexpr.atom().size(); + else if(sexpr.is_list()) { + space -= 2; + if(sexpr.list().size() > 1) + space -= sexpr.list().size() - 1; + for(auto arg : sexpr.list()) { + if(space < 0) break; + space = check_fit(arg, space); + } + return space; + } else + return -1; +} + +void SExprWriter::print(SExpr const &sexpr, bool close, bool indent_rest) { + if(sexpr.is_atom()) + puts(sexpr.atom()); + else if(sexpr.is_list()) { + auto args = sexpr.list(); + puts("("); + // Expressions are printed horizontally if they fit on the line. + // We do the check *after* puts("(") to make sure that _pos is accurate. + // (Otherwise there could be a pending newline + indentation) + bool vertical = args.size() > 1 && check_fit(sexpr, _max_line_width - _pos + 1) < 0; + if(vertical) _indent++; + for(size_t i = 0; i < args.size(); i++) { + if(i > 0) puts(vertical ? "\n" : " "); + print(args[i]); + } + // Any remaining arguments are currently always printed vertically, + // but are not indented if indent_rest = false. + _indent += (!close && indent_rest) - vertical; + if(close) + puts(")"); + else { + _unclosed.push_back(indent_rest); + _pending_nl = true; + } + }else + log_error("shouldn't happen: SExpr '%s' is neither an atom nor a list", sexpr.to_string().c_str()); +} + +void SExprWriter::close(size_t n) { + log_assert(_unclosed.size() - (_unclosed_stack.empty() ? 0 : _unclosed_stack.back()) >= n); + while(n-- > 0) { + bool indented = _unclosed[_unclosed.size() - 1]; + _unclosed.pop_back(); + // Only print ) on the same line if it fits. + _pending_nl = _pos >= _max_line_width; + if(indented) + _indent--; + puts(")"); + _pending_nl = true; + } +} + +void SExprWriter::comment(std::string const &str, bool hanging) { + if(hanging) { + if(_pending_nl) { + _pending_nl = false; + puts(" "); + } + } + size_t i = 0, e; + do{ + e = str.find('\n', i); + puts("; "); + puts(std::string_view(str).substr(i, e - i)); + puts("\n"); + i = e + 1; + }while(e != std::string::npos); +} + +SExprWriter::~SExprWriter() { + while(!_unclosed_stack.empty()) + pop(); + close(_unclosed.size()); + nl_if_pending(); +} + +YOSYS_NAMESPACE_END diff --git a/kernel/sexpr.h b/kernel/sexpr.h new file mode 100644 index 00000000000..41073ea2327 --- /dev/null +++ b/kernel/sexpr.h @@ -0,0 +1,122 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Emily Schmidt + * + * 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 SEXPR_H +#define SEXPR_H + +#include "kernel/yosys.h" + +YOSYS_NAMESPACE_BEGIN + +class SExpr { +public: + std::variant, std::string> _v; +public: + SExpr(std::string a) : _v(std::move(a)) {} + SExpr(const char *a) : _v(a) {} + // FIXME: should maybe be defined for all integral types + SExpr(int n) : _v(std::to_string(n)) {} + SExpr(std::vector const &l) : _v(l) {} + SExpr(std::vector &&l) : _v(std::move(l)) {} + // It would be nicer to have an std::initializer_list constructor, + // but that causes confusing issues with overload resolution sometimes. + template static SExpr list(Args&&... args) { + return SExpr(std::vector{std::forward(args)...}); + } + bool is_atom() const { return std::holds_alternative(_v); } + std::string const &atom() const { return std::get(_v); } + bool is_list() const { return std::holds_alternative>(_v); } + std::vector const &list() const { return std::get>(_v); } + std::string to_string() const; +}; + +std::ostream &operator<<(std::ostream &os, SExpr const &sexpr); + +namespace SExprUtil { + // A little hack so that `using SExprUtil::list` lets you import a shortcut to `SExpr::list` + template SExpr list(Args&&... args) { + return SExpr(std::vector{std::forward(args)...}); + } +} + +// SExprWriter is a pretty printer for s-expr. It does not try very hard to get a good layout. +class SExprWriter { + std::ostream &os; + int _max_line_width; + int _indent = 0; + int _pos = 0; + // If _pending_nl is set, print a newline before the next character. + // This lets us "undo" the last newline so we can put + // closing parentheses or a hanging comment on the same line. + bool _pending_nl = false; + // Unclosed parentheses (boolean stored is indent_rest) + vector _unclosed; + // Used only for push() and pop() (stores _unclosed.size()) + vector _unclosed_stack; + void nl_if_pending(); + void puts(std::string_view s); + int check_fit(SExpr const &sexpr, int space); + void print(SExpr const &sexpr, bool close = true, bool indent_rest = true); +public: + SExprWriter(std::ostream &os, int max_line_width = 80) + : os(os) + , _max_line_width(max_line_width) + {} + // Print an s-expr. + SExprWriter &operator <<(SExpr const &sexpr) { + print(sexpr); + _pending_nl = true; + return *this; + } + // Print an s-expr (which must be a list), but leave room for extra elements + // which may be printed using either << or further calls to open. + // If indent_rest = false, the remaining elements are not intended + // (for avoiding unreasonable indentation on deeply nested structures). + void open(SExpr const &sexpr, bool indent_rest = true) { + log_assert(sexpr.is_list()); + print(sexpr, false, indent_rest); + } + // Close the s-expr opened with the last call to open + // (if an argument is given, close that many s-exprs). + void close(size_t n = 1); + // push() remembers how many s-exprs are currently open + void push() { + _unclosed_stack.push_back(_unclosed.size()); + } + // pop() closes all s-expr opened since the corresponding call to push() + void pop() { + auto t = _unclosed_stack.back(); + log_assert(_unclosed.size() >= t); + close(_unclosed.size() - t); + _unclosed_stack.pop_back(); + } + // Print a comment. + // If hanging = true, append it to the end of the last printed s-expr. + void comment(std::string const &str, bool hanging = false); + // Flush any unprinted characters to the std::ostream, but does not close unclosed parentheses. + void flush() { + nl_if_pending(); + } + // Destructor closes any unclosed parentheses and flushes. + ~SExprWriter(); +}; + +YOSYS_NAMESPACE_END + +#endif From 6e7ae88c6a22980bdcc290851b5a4533b211a84c Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Fri, 12 Jul 2024 10:45:31 +0100 Subject: [PATCH 50/92] fix bugs in smtlib backend --- backends/functional/smtlib.cc | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index 43e6f5bf489..113fb710080 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -28,6 +28,7 @@ PRIVATE_NAMESPACE_BEGIN using SExprUtil::list; const char *reserved_keywords[] = { + // reserved keywords from the smtlib spec "BINARY", "DECIMAL", "HEXADECIMAL", "NUMERAL", "STRING", "_", "!", "as", "let", "exists", "forall", "match", "par", "assert", "check-sat", "check-sat-assuming", "declare-const", "declare-datatype", "declare-datatypes", "declare-fun", "declare-sort", "define-fun", "define-fun-rec", "define-funs-rec", "define-sort", @@ -35,6 +36,7 @@ const char *reserved_keywords[] = { "get-option", "get-proof", "get-unsat-assumptions", "get-unsat-core", "get-value", "pop", "push", "reset", "reset-assertions", "set-info", "set-logic", "set-option", + // reserved for our own purposes "pair", "Pair", "first", "second", "inputs", "state", nullptr @@ -89,12 +91,17 @@ class SmtStruct { w.close(3); } template void write_value(SExprWriter &w, Fn fn) { - w.open(list(name)); - for(auto field_name : field_names) { - w << fn(field_name); - w.comment(RTLIL::unescape_id(field_name), true); + if(field_names.empty()) { + // Zero-argument constructors in SMTLIB must not be called as functions. + w << name; + } else { + w.open(list(name)); + for(auto field_name : field_names) { + w << fn(field_name); + w.comment(RTLIL::unescape_id(field_name), true); + } + w.close(); } - w.close(); } SExpr access(SExpr record, IdString name) { size_t i = field_names.at(name); @@ -131,7 +138,7 @@ struct SmtPrintVisitor { SExpr slice(Node, Node a, int, int offset, int out_width) { return extract(n(a), offset, out_width); } SExpr zero_extend(Node, Node a, int, int out_width) { return list(list("_", "zero_extend", out_width - a.width()), n(a)); } SExpr sign_extend(Node, Node a, int, int out_width) { return list(list("_", "sign_extend", out_width - a.width()), n(a)); } - SExpr concat(Node, Node a, int, Node b, int) { return list("concat", n(a), n(b)); } + SExpr concat(Node, Node a, int, Node b, int) { return list("concat", n(b), n(a)); } SExpr add(Node, Node a, Node b, int) { return list("bvadd", n(a), n(b)); } SExpr sub(Node, Node a, Node b, int) { return list("bvsub", n(a), n(b)); } SExpr mul(Node, Node a, Node b, int) { return list("bvmul", n(a), n(b)); } @@ -143,7 +150,7 @@ struct SmtPrintVisitor { SExpr bitwise_not(Node, Node a, int) { return list("bvnot", n(a)); } SExpr unary_minus(Node, Node a, int) { return list("bvneg", n(a)); } SExpr reduce_and(Node, Node a, int) { return from_bool(list("=", n(a), literal(RTLIL::Const(State::S1, a.width())))); } - SExpr reduce_or(Node, Node a, int) { return from_bool(list("=", n(a), literal(RTLIL::Const(State::S0, a.width())))); } + SExpr reduce_or(Node, Node a, int) { return from_bool(list("distinct", n(a), literal(RTLIL::Const(State::S0, a.width())))); } SExpr reduce_xor(Node, Node a, int) { vector s { "bvxor" }; for(int i = 0; i < a.width(); i++) @@ -164,9 +171,9 @@ struct SmtPrintVisitor { return std::move(a); } SExpr logical_shift_left(Node, Node a, Node b, int, int) { return list("bvshl", n(a), extend(n(b), b.width(), a.width())); } - SExpr logical_shift_right(Node, Node a, Node b, int, int) { return list("bvshr", n(a), extend(n(b), b.width(), a.width())); } - SExpr arithmetic_shift_right(Node, Node a, Node b, int, int) { return list("bvasr", n(a), extend(n(b), b.width(), a.width())); } - SExpr mux(Node, Node a, Node b, Node s, int) { return list("ite", to_bool(n(s)), n(a), n(b)); } + SExpr logical_shift_right(Node, Node a, Node b, int, int) { return list("bvlshr", n(a), extend(n(b), b.width(), a.width())); } + SExpr arithmetic_shift_right(Node, Node a, Node b, int, int) { return list("bvashr", n(a), extend(n(b), b.width(), a.width())); } + SExpr mux(Node, Node a, Node b, Node s, int) { return list("ite", to_bool(n(s)), n(b), n(a)); } SExpr pmux(Node, Node a, Node b, Node s, int, int) { SExpr rv = n(a); for(int i = 0; i < s.width(); i++) From 9ad859fc0a9828ba339ec1fc40eecb842a42acac Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Fri, 12 Jul 2024 10:45:58 +0100 Subject: [PATCH 51/92] add bwmux, bweqx, bmux, demux cells --- kernel/functionalir.cc | 80 +++++++++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 24 deletions(-) diff --git a/kernel/functionalir.cc b/kernel/functionalir.cc index c5406f41283..6983facfb72 100644 --- a/kernel/functionalir.cc +++ b/kernel/functionalir.cc @@ -96,6 +96,15 @@ class CellSimplifier { } return r; } + T handle_bmux(T a, T s, int a_width, int a_offset, int width, int s_width, int sn) { + if(sn < 1) + return factory.slice(a, a_width, a_offset, width); + else { + T y0 = handle_bmux(a, s, a_width, a_offset, width, s_width, sn - 1); + T y1 = handle_bmux(a, s, a_width, a_offset + (width << (sn - 1)), width, s_width, sn - 1); + return factory.mux(y0, y1, factory.slice(s, s_width, sn - 1, 1), width); + } + } public: T handle(IdString cellType, dict parameters, dict inputs) { @@ -248,31 +257,54 @@ class CellSimplifier { return extend(factory.unsigned_div(a, b, width), width, y_width, false); } } else if(cellType == ID($pow)) { - return handle_pow(inputs.at(ID(A)), a_width, inputs.at(ID(B)), b_width, y_width, a_signed && b_signed); + return handle_pow(inputs.at(ID(A)), a_width, inputs.at(ID(B)), b_width, y_width, a_signed && b_signed); } else if (cellType == ID($lut)) { - int width = parameters.at(ID(WIDTH)).as_int(); - Const lut_table = parameters.at(ID(LUT)); - T a = inputs.at(ID(A)); - - // Output initialization - T y = factory.constant(Const(0, 1)); - - // Iterate over each possible input combination - for (int i = 0; i < (1 << width); ++i) { - // Create a constant representing the value of i - T i_val = factory.constant(Const(i, width)); - // Check if the input matches this value - T match = factory.equal(a, i_val, width); - // Get the corresponding LUT value - bool lut_val = lut_table.bits[i] == State::S1; - T lut_output = factory.constant(Const(lut_val, 1)); - // Use a multiplexer to select the correct output based on the match - y = factory.mux(y, lut_output, match, 1); - } - - return y; - } else{ - log_error("unhandled cell in CellSimplifier %s\n", cellType.c_str()); + int width = parameters.at(ID(WIDTH)).as_int(); + Const lut_table = parameters.at(ID(LUT)); + T a = inputs.at(ID(A)); + // Output initialization + T y = factory.constant(Const(0, 1)); + // Iterate over each possible input combination + for (int i = 0; i < (1 << width); ++i) { + // Create a constant representing the value of i + T i_val = factory.constant(Const(i, width)); + // Check if the input matches this value + T match = factory.equal(a, i_val, width); + // Get the corresponding LUT value + bool lut_val = lut_table.bits[i] == State::S1; + T lut_output = factory.constant(Const(lut_val, 1)); + // Use a multiplexer to select the correct output based on the match + y = factory.mux(y, lut_output, match, 1); + } + return y; + } else if (cellType == ID($bwmux)) { + int width = parameters.at(ID(WIDTH)).as_int(); + T a = inputs.at(ID(A)); + T b = inputs.at(ID(B)); + T s = inputs.at(ID(S)); + return factory.bitwise_or( + factory.bitwise_and(a, factory.bitwise_not(s, width), width), + factory.bitwise_and(b, s, width), width); + } else if (cellType == ID($bweqx)) { + int width = parameters.at(ID(WIDTH)).as_int(); + T a = inputs.at(ID(A)); + T b = inputs.at(ID(B)); + return factory.bitwise_not(factory.bitwise_xor(a, b, width), width); + } else if(cellType == ID($bmux)) { + int width = parameters.at(ID(WIDTH)).as_int(); + int s_width = parameters.at(ID(S_WIDTH)).as_int(); + return handle_bmux(inputs.at(ID(A)), inputs.at(ID(S)), width << s_width, 0, width, s_width, s_width); + } else if(cellType == ID($demux)) { + int width = parameters.at(ID(WIDTH)).as_int(); + int s_width = parameters.at(ID(S_WIDTH)).as_int(); + int y_width = width << s_width; + int b_width = ceil_log2(y_width + 1); + T a = extend(inputs.at(ID(A)), width, y_width, false); + T s = factory.extend(inputs.at(ID(S)), s_width, b_width, false); + T b = factory.mul(s, factory.constant(Const(width, b_width)), b_width); + return factory.logical_shift_left(a, b, y_width, b_width); + } else { + log_error("unhandled cell in CellSimplifier %s\n", cellType.c_str()); } } }; From 674e6d201de267806f435077007d41441dedae8d Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Fri, 12 Jul 2024 11:07:33 +0100 Subject: [PATCH 52/92] rewrite functional backend test code in python --- tests/functional/README.md | 19 ++ tests/functional/conftest.py | 14 + tests/functional/rtlil_cells.py | 275 ++++++++++++++++++ tests/functional/run-test.sh | 2 + tests/functional/single_cells/rtlil/README.md | 2 - tests/functional/single_cells/rtlil/ff.il | 83 ------ .../single_cells/rtlil/multiple_outputs.il | 69 ----- .../single_cells/rtlil/test_cell_add_00000.il | 17 -- .../single_cells/rtlil/test_cell_alu_00000.il | 25 -- .../single_cells/rtlil/test_cell_and_00000.il | 17 -- .../rtlil/test_cell_bmux_00000.il | 14 - .../rtlil/test_cell_demux_00000.il | 14 - .../single_cells/rtlil/test_cell_div_00000.il | 17 -- .../rtlil/test_cell_divfloor_00000.il | 17 -- .../single_cells/rtlil/test_cell_eq_00000.il | 17 -- .../single_cells/rtlil/test_cell_fa_00000.il | 17 -- .../single_cells/rtlil/test_cell_ge_00000.il | 17 -- .../single_cells/rtlil/test_cell_gt_00000.il | 17 -- .../single_cells/rtlil/test_cell_lcu_00000.il | 15 - .../single_cells/rtlil/test_cell_le_00000.il | 17 -- .../rtlil/test_cell_logic_and_00000.il | 17 -- .../rtlil/test_cell_logic_not_00000.il | 13 - .../rtlil/test_cell_logic_or_00000.il | 17 -- .../single_cells/rtlil/test_cell_lt_00000.il | 17 -- .../single_cells/rtlil/test_cell_lut_00000.il | 12 - .../rtlil/test_cell_macc_00000.il | 17 -- .../single_cells/rtlil/test_cell_mod_00000.il | 17 -- .../rtlil/test_cell_modfloor_00000.il | 17 -- .../single_cells/rtlil/test_cell_mul_00000.il | 17 -- .../single_cells/rtlil/test_cell_mux_00000.il | 15 - .../single_cells/rtlil/test_cell_ne_00000.il | 17 -- .../single_cells/rtlil/test_cell_neg_00000.il | 13 - .../single_cells/rtlil/test_cell_not_00000.il | 13 - .../single_cells/rtlil/test_cell_or_00000.il | 17 -- .../single_cells/rtlil/test_cell_pos_00000.il | 13 - .../single_cells/rtlil/test_cell_pos_00001.il | 13 - .../rtlil/test_cell_reduce_and_00000.il | 13 - .../rtlil/test_cell_reduce_bool_00000.il | 13 - .../rtlil/test_cell_reduce_or_00000.il | 13 - .../rtlil/test_cell_reduce_xnor_00000.il | 13 - .../rtlil/test_cell_reduce_xor_00000.il | 13 - .../rtlil/test_cell_shift_00000.il | 17 -- .../rtlil/test_cell_shiftx_00000.il | 17 -- .../single_cells/rtlil/test_cell_shl_00000.il | 17 -- .../single_cells/rtlil/test_cell_shr_00000.il | 17 -- .../single_cells/rtlil/test_cell_sop_00000.il | 13 - .../rtlil/test_cell_sshl_00000.il | 17 -- .../rtlil/test_cell_sshr_00000.il | 17 -- .../single_cells/rtlil/test_cell_sub_00000.il | 17 -- .../rtlil/test_cell_xnor_00000.il | 17 -- .../single_cells/rtlil/test_cell_xor_00000.il | 17 -- tests/functional/single_cells/run-test.sh | 172 ----------- .../single_cells/vcd_harness_smt.py | 214 -------------- tests/functional/smt_vcd.py | 180 ++++++++++++ {backends => tests}/functional/smtio.py | 0 tests/functional/test_functional.py | 63 ++++ .../{single_cells => }/vcd_harness.cc | 2 +- 57 files changed, 554 insertions(+), 1238 deletions(-) create mode 100644 tests/functional/README.md create mode 100644 tests/functional/conftest.py create mode 100644 tests/functional/rtlil_cells.py create mode 100755 tests/functional/run-test.sh delete mode 100644 tests/functional/single_cells/rtlil/README.md delete mode 100644 tests/functional/single_cells/rtlil/ff.il delete mode 100644 tests/functional/single_cells/rtlil/multiple_outputs.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_add_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_alu_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_and_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_bmux_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_demux_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_div_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_divfloor_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_eq_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_fa_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_ge_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_gt_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_lcu_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_le_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_logic_and_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_logic_not_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_logic_or_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_lt_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_lut_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_macc_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_mod_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_modfloor_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_mul_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_mux_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_ne_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_neg_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_not_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_or_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_pos_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_pos_00001.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_reduce_and_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_reduce_bool_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_reduce_or_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_reduce_xnor_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_reduce_xor_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_shift_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_shiftx_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_shl_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_shr_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_sop_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_sshl_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_sshr_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_sub_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_xnor_00000.il delete mode 100644 tests/functional/single_cells/rtlil/test_cell_xor_00000.il delete mode 100755 tests/functional/single_cells/run-test.sh delete mode 100644 tests/functional/single_cells/vcd_harness_smt.py create mode 100644 tests/functional/smt_vcd.py rename {backends => tests}/functional/smtio.py (100%) create mode 100644 tests/functional/test_functional.py rename tests/functional/{single_cells => }/vcd_harness.cc (98%) diff --git a/tests/functional/README.md b/tests/functional/README.md new file mode 100644 index 00000000000..1459c3198b8 --- /dev/null +++ b/tests/functional/README.md @@ -0,0 +1,19 @@ +Tests for the functional backend use pytest as a testrunner. + +Run with `pytest -v` + +Pytest options you might want: + +- `-v`: More progress indication. + +- `--basetemp tmp`: Store test files (including vcd results) in tmp. + CAREFUL: contents of tmp will be deleted + +- `-k `: Run only tests that contain the pattern, e.g. + `-k cxx` or `-k smt` or `-k demux` or `-k 'cxx[demux` + +- `-s`: Don't hide stdout/stderr from the test code. + +Custom options for functional backend tests: + +- `--per-cell N`: Run only N tests for each cell. diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py new file mode 100644 index 00000000000..3a66abaab21 --- /dev/null +++ b/tests/functional/conftest.py @@ -0,0 +1,14 @@ +import pytest +from rtlil_cells import generate_test_cases + +def pytest_addoption(parser): + parser.addoption( + "--per-cell", type=int, default=None, help="run only N tests per cell" + ) + +def pytest_generate_tests(metafunc): + if "cell" in metafunc.fixturenames: + print(dir(metafunc.config)) + per_cell = metafunc.config.getoption("per_cell", default=None) + names, cases = generate_test_cases(per_cell) + metafunc.parametrize("cell,parameters", cases, ids=names) \ No newline at end of file diff --git a/tests/functional/rtlil_cells.py b/tests/functional/rtlil_cells.py new file mode 100644 index 00000000000..e96baa18ee8 --- /dev/null +++ b/tests/functional/rtlil_cells.py @@ -0,0 +1,275 @@ +from itertools import chain +import random + +widths = [ + (16, 32, 48, True), + (16, 32, 48, False), + (32, 16, 48, True), + (32, 16, 48, False), + (32, 32, 16, True), + (32, 32, 16, False) +] + +shift_widths = [ + (32, 6, 32, True, False), + (32, 6, 32, False, False), + (32, 6, 64, True, False), + (32, 6, 64, False, False), + (32, 32, 16, True, False), + (32, 32, 16, False, False), + (32, 6, 32, True, True), + (32, 6, 32, False, True), + (32, 6, 64, True, True), + (32, 6, 64, False, True), + (32, 32, 16, True, True), + (32, 32, 16, False, True), +] + +def write_rtlil_cell(f, cell_type, inputs, outputs, parameters): + f.write('autoidx 1\n') + f.write('module \\gold\n') + idx = 1 + for name, width in inputs.items(): + f.write(f'\twire width {width} input {idx} \\{name}\n') + idx += 1 + for name, width in outputs.items(): + f.write(f'\twire width {width} output {idx} \\{name}\n') + idx += 1 + f.write(f'\tcell ${cell_type} \\UUT\n') + for (name, value) in parameters.items(): + f.write(f'\t\tparameter \\{name} {value}\n') + for name in chain(inputs.keys(), outputs.keys()): + f.write(f'\t\tconnect \\{name} \\{name}\n') + f.write(f'\tend\nend\n') + +class BaseCell: + def __init__(self, name): + self.name = name + +class UnaryCell(BaseCell): + def __init__(self, name): + super().__init__(name) + def generate_tests(self): + for (a_width, _, y_width, signed) in widths: + yield (f'{a_width}-{y_width}-{'S' if signed else 'U'}', + {'A_WIDTH' : a_width, + 'A_SIGNED' : int(signed), + 'Y_WIDTH' : y_width}) + def write_rtlil_file(self, f, parameters): + write_rtlil_cell(f, self.name, {'A': parameters['A_WIDTH']}, {'Y': parameters['Y_WIDTH']}, parameters) + +class BinaryCell(BaseCell): + def __init__(self, name): + super().__init__(name) + def generate_tests(self): + for (a_width, b_width, y_width, signed) in widths: + yield (f'{a_width}-{b_width}-{y_width}-{'S' if signed else 'U'}', + {'A_WIDTH' : a_width, + 'A_SIGNED' : int(signed), + 'B_WIDTH' : b_width, + 'B_SIGNED' : int(signed), + 'Y_WIDTH' : y_width}) + def write_rtlil_file(self, f, parameters): + write_rtlil_cell(f, self.name, {'A': parameters['A_WIDTH'], 'B': parameters['B_WIDTH']}, {'Y': parameters['Y_WIDTH']}, parameters) + +class ShiftCell(BaseCell): + def __init__(self, name): + super().__init__(name) + def generate_tests(self): + for (a_width, b_width, y_width, a_signed, b_signed) in shift_widths: + if not self.name in ('shift', 'shiftx') and b_signed: continue + if self.name == 'shiftx' and a_signed: continue + yield (f'{a_width}-{b_width}-{y_width}-{'S' if a_signed else 'U'}{'S' if b_signed else 'U'}', + {'A_WIDTH' : a_width, + 'A_SIGNED' : int(a_signed), + 'B_WIDTH' : b_width, + 'B_SIGNED' : int(b_signed), + 'Y_WIDTH' : y_width}) + def write_rtlil_file(self, f, parameters): + write_rtlil_cell(f, self.name, {'A': parameters['A_WIDTH'], 'B': parameters['B_WIDTH']}, {'Y': parameters['Y_WIDTH']}, parameters) + +class MuxCell(BaseCell): + def __init__(self, name): + super().__init__(name) + def generate_tests(self): + for width in [10, 20, 40]: + yield (f'{width}', {'WIDTH' : width}) + def write_rtlil_file(self, f, parameters): + write_rtlil_cell(f, self.name, {'A': parameters['WIDTH'], 'B': parameters['WIDTH'], 'S': 1}, {'Y': parameters['WIDTH']}, parameters) + +class BWCell(BaseCell): + def __init__(self, name): + super().__init__(name) + def generate_tests(self): + for width in [10, 20, 40]: + yield (f'{width}', {'WIDTH' : width}) + def write_rtlil_file(self, f, parameters): + inputs = {'A': parameters['WIDTH'], 'B': parameters['WIDTH']} + if self.name == "bwmux": inputs['S'] = parameters['WIDTH'] + write_rtlil_cell(f, self.name, inputs, {'Y': parameters['WIDTH']}, parameters) + +class PMuxCell(BaseCell): + def __init__(self, name): + super().__init__(name) + def generate_tests(self): + for (width, s_width) in [(10, 1), (10, 4), (20, 4)]: + yield (f'{width}-{s_width}', + {'WIDTH' : width, + 'S_WIDTH' : s_width}) + def write_rtlil_file(self, f, parameters): + s_width = parameters['S_WIDTH'] + b_width = parameters['WIDTH'] * s_width + write_rtlil_cell(f, self.name, {'A': parameters['WIDTH'], 'B': b_width, 'S': s_width}, {'Y': parameters['WIDTH']}, parameters) + +class BMuxCell(BaseCell): + def __init__(self, name): + super().__init__(name) + def generate_tests(self): + for (width, s_width) in [(10, 1), (10, 2), (10, 4)]: + yield (f'{width}-{s_width}', {'WIDTH' : width, 'S_WIDTH' : s_width}) + def write_rtlil_file(self, f, parameters): + write_rtlil_cell(f, self.name, {'A': parameters['WIDTH'] << parameters['S_WIDTH'], 'S': parameters['S_WIDTH']}, {'Y': parameters['WIDTH']}, parameters) + +class DemuxCell(BaseCell): + def __init__(self, name): + super().__init__(name) + def generate_tests(self): + for (width, s_width) in [(10, 1), (32, 2), (16, 4)]: + yield (f'{width}-{s_width}', {'WIDTH' : width, 'S_WIDTH' : s_width}) + def write_rtlil_file(self, f, parameters): + write_rtlil_cell(f, self.name, {'A': parameters['WIDTH'], 'S': parameters['S_WIDTH']}, {'Y': parameters['WIDTH'] << parameters['S_WIDTH']}, parameters) + +def seeded_randint(seed, a, b): + r = random.getstate() + random.seed(seed) + n = random.randint(a, b) + random.setstate(r) + return n + +class LUTCell(BaseCell): + def __init__(self, name): + super().__init__(name) + def generate_tests(self): + for width in [4, 6, 8]: + lut = seeded_randint(width, 0, 2**width - 1) + yield (f'{width}', {'WIDTH' : width, 'LUT' : lut}) + def write_rtlil_file(self, f, parameters): + write_rtlil_cell(f, self.name, {'A': parameters['WIDTH']}, {'Y': 1}, parameters) + +class ConcatCell(BaseCell): + def __init__(self, name): + super().__init__(name) + def generate_tests(self): + for (a_width, b_width) in [(16, 16), (8, 14), (20, 10)]: + yield (f'{a_width}-{b_width}', {'A_WIDTH' : a_width, 'B_WIDTH' : b_width}) + def write_rtlil_file(self, f, parameters): + write_rtlil_cell(f, self.name, {'A': parameters['A_WIDTH'], 'B' : parameters['B_WIDTH']}, {'Y': parameters['A_WIDTH'] + parameters['B_WIDTH']}, parameters) + +class SliceCell(BaseCell): + def __init__(self, name): + super().__init__(name) + def generate_tests(self): + for (a_width, offset, y_width) in [(32, 10, 15), (8, 0, 4), (10, 0, 10)]: + yield (f'{a_width}-{offset}-{y_width}', {'A_WIDTH' : a_width, 'OFFSET' : offset, 'Y_WIDTH': y_width}) + def write_rtlil_file(self, f, parameters): + write_rtlil_cell(f, self.name, {'A': parameters['A_WIDTH']}, {'Y': parameters['Y_WIDTH']}, parameters) + +class FailCell(BaseCell): + def __init__(self, name): + super().__init__(name) + def generate_tests(self): + yield ('', {}) + def write_rtlil_file(self, f, parameters): + raise Exception(f'\'{self.name}\' cell unimplemented in test generator') + +rtlil_cells = [ + UnaryCell("not"), + UnaryCell("pos"), + UnaryCell("neg"), + BinaryCell("and"), + BinaryCell("or"), + BinaryCell("xor"), + BinaryCell("xnor"), + UnaryCell("reduce_and"), + UnaryCell("reduce_or"), + UnaryCell("reduce_xor"), + UnaryCell("reduce_xnor"), + UnaryCell("reduce_bool"), + ShiftCell("shl"), + ShiftCell("shr"), + ShiftCell("sshl"), + ShiftCell("sshr"), + ShiftCell("shift"), + ShiftCell("shiftx"), +# ("fa", ["A", "B", "C", "X", "Y"]), +# ("lcu", ["P", "G", "CI", "CO"]), +# ("alu", ["A", "B", "CI", "BI", "X", "Y", "CO"]), + BinaryCell("lt"), + BinaryCell("le"), + BinaryCell("eq"), + BinaryCell("ne"), + BinaryCell("eqx"), + BinaryCell("nex"), + BinaryCell("ge"), + BinaryCell("gt"), + BinaryCell("add"), + BinaryCell("sub"), + BinaryCell("mul"), +# BinaryCell("macc"), + BinaryCell("div"), + BinaryCell("mod"), + BinaryCell("divfloor"), + BinaryCell("modfloor"), + BinaryCell("pow"), + UnaryCell("logic_not"), + BinaryCell("logic_and"), + BinaryCell("logic_or"), + SliceCell("slice"), + ConcatCell("concat"), + MuxCell("mux"), + BMuxCell("bmux"), + PMuxCell("pmux"), + DemuxCell("demux"), + LUTCell("lut"), +# ("sop", ["A", "Y"]), +# ("tribuf", ["A", "EN", "Y"]), +# ("specify2", ["EN", "SRC", "DST"]), +# ("specify3", ["EN", "SRC", "DST", "DAT"]), +# ("specrule", ["EN_SRC", "EN_DST", "SRC", "DST"]), + BWCell("bweqx"), + BWCell("bwmux"), +# ("assert", ["A", "EN"]), +# ("assume", ["A", "EN"]), +# ("live", ["A", "EN"]), +# ("fair", ["A", "EN"]), +# ("cover", ["A", "EN"]), +# ("initstate", ["Y"]), +# ("anyconst", ["Y"]), +# ("anyseq", ["Y"]), +# ("anyinit", ["D", "Q"]), +# ("allconst", ["Y"]), +# ("allseq", ["Y"]), +# ("equiv", ["A", "B", "Y"]), +# ("print", ["EN", "TRG", "ARGS"]), +# ("check", ["A", "EN", "TRG", "ARGS"]), +# ("set_tag", ["A", "SET", "CLR", "Y"]), +# ("get_tag", ["A", "Y"]), +# ("overwrite_tag", ["A", "SET", "CLR"]), +# ("original_tag", ["A", "Y"]), +# ("future_ff", ["A", "Y"]), +# ("scopeinfo", []), +] + +def generate_test_cases(per_cell): + tests = [] + names = [] + for cell in rtlil_cells: + seen_names = set() + for (name, parameters) in cell.generate_tests(): + if not name in seen_names: + seen_names.add(name) + tests.append((cell, parameters)) + names.append(f'{cell.name}-{name}' if name != '' else cell.name) + if per_cell is not None and len(seen_names) >= per_cell: + break + return (names, tests) \ No newline at end of file diff --git a/tests/functional/run-test.sh b/tests/functional/run-test.sh new file mode 100755 index 00000000000..b9a0595f663 --- /dev/null +++ b/tests/functional/run-test.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +pytest -v "$@" diff --git a/tests/functional/single_cells/rtlil/README.md b/tests/functional/single_cells/rtlil/README.md deleted file mode 100644 index 4bb84565b7e..00000000000 --- a/tests/functional/single_cells/rtlil/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# Creation of test files -yosys -p "test_cell -n 1 -w test_cell all" \ No newline at end of file diff --git a/tests/functional/single_cells/rtlil/ff.il b/tests/functional/single_cells/rtlil/ff.il deleted file mode 100644 index 6e5a1a339d0..00000000000 --- a/tests/functional/single_cells/rtlil/ff.il +++ /dev/null @@ -1,83 +0,0 @@ -# Generated by Yosys 0.40+7 (git sha1 4fd5b29f9, g++ 13.2.0 -Og -fPIC) -autoidx 19 -attribute \cells_not_processed 1 -attribute \src "dff.v:1.1-16.10" -module \gold - attribute \src "dff.v:8.5-14.8" - wire $0\q[0:0] - attribute \init 1'0 - wire $auto$clk2fflogic.cc:65:sample_control$\reset#sampled$13 - attribute \init 1'1 - wire $auto$clk2fflogic.cc:77:sample_control_edge$\clk#sampled$7 - attribute \init 1'0 - wire $auto$clk2fflogic.cc:91:sample_data$\d#sampled$5 - attribute \init 1'x - wire $auto$clk2fflogic.cc:91:sample_data$\q#sampled$3 - wire $auto$rtlil.cc:2525:Eqx$10 - wire $auto$rtlil.cc:2582:Mux$12 - wire $auto$rtlil.cc:2582:Mux$16 - wire $auto$rtlil.cc:2582:Mux$18 - attribute \src "dff.v:2.16-2.19" - wire input 1 \clk - attribute \src "dff.v:4.16-4.17" - wire input 3 \d - attribute \keep 1 - attribute \src "dff.v:5.16-5.17" - wire output 4 \q - attribute \src "dff.v:3.16-3.21" - wire input 2 \reset - cell $mux $auto$clk2fflogic.cc:113:mux$11 - parameter \WIDTH 1 - connect \A $auto$clk2fflogic.cc:91:sample_data$\q#sampled$3 - connect \B $auto$clk2fflogic.cc:91:sample_data$\d#sampled$5 - connect \S $auto$rtlil.cc:2525:Eqx$10 - connect \Y $auto$rtlil.cc:2582:Mux$12 - end - cell $mux $auto$clk2fflogic.cc:113:mux$15 - parameter \WIDTH 1 - connect \A $auto$rtlil.cc:2582:Mux$12 - connect \B 1'0 - connect \S $auto$clk2fflogic.cc:65:sample_control$\reset#sampled$13 - connect \Y $auto$rtlil.cc:2582:Mux$16 - end - cell $mux $auto$clk2fflogic.cc:113:mux$17 - parameter \WIDTH 1 - connect \A $auto$rtlil.cc:2582:Mux$16 - connect \B 1'0 - connect \S \reset - connect \Y $auto$rtlil.cc:2582:Mux$18 - end - cell $ff $auto$clk2fflogic.cc:70:sample_control$14 - parameter \WIDTH 1 - connect \D \reset - connect \Q $auto$clk2fflogic.cc:65:sample_control$\reset#sampled$13 - end - cell $ff $auto$clk2fflogic.cc:82:sample_control_edge$8 - parameter \WIDTH 1 - connect \D \clk - connect \Q $auto$clk2fflogic.cc:77:sample_control_edge$\clk#sampled$7 - end - cell $eqx $auto$clk2fflogic.cc:83:sample_control_edge$9 - parameter \A_SIGNED 0 - parameter \A_WIDTH 2 - parameter \B_SIGNED 0 - parameter \B_WIDTH 2 - parameter \Y_WIDTH 1 - connect \A { $auto$clk2fflogic.cc:77:sample_control_edge$\clk#sampled$7 \clk } - connect \B 2'01 - connect \Y $auto$rtlil.cc:2525:Eqx$10 - end - attribute \clk2fflogic 1 - cell $ff $auto$clk2fflogic.cc:98:sample_data$4 - parameter \WIDTH 1 - connect \D \q - connect \Q $auto$clk2fflogic.cc:91:sample_data$\q#sampled$3 - end - cell $ff $auto$clk2fflogic.cc:98:sample_data$6 - parameter \WIDTH 1 - connect \D \d - connect \Q $auto$clk2fflogic.cc:91:sample_data$\d#sampled$5 - end - connect $0\q[0:0] \d - connect \q $auto$rtlil.cc:2582:Mux$18 -end diff --git a/tests/functional/single_cells/rtlil/multiple_outputs.il b/tests/functional/single_cells/rtlil/multiple_outputs.il deleted file mode 100644 index 5e9141a20ad..00000000000 --- a/tests/functional/single_cells/rtlil/multiple_outputs.il +++ /dev/null @@ -1,69 +0,0 @@ -# Generated by Yosys 0.40+7 (git sha1 cc795a3f5, g++ 13.2.0 -Og -fPIC) -autoidx 5 -attribute \cells_not_processed 1 -attribute \src "multiple_outputs.v:1.1-15.10" -module \gold - attribute \src "multiple_outputs.v:11.12-11.20" - wire $and$multiple_outputs.v:11$2_Y - attribute \src "multiple_outputs.v:10.12-10.14" - wire $not$multiple_outputs.v:10$1_Y - attribute \src "multiple_outputs.v:12.12-12.20" - wire $or$multiple_outputs.v:12$3_Y - attribute \src "multiple_outputs.v:13.12-13.20" - wire $xor$multiple_outputs.v:13$4_Y - attribute \src "multiple_outputs.v:2.16-2.17" - wire input 1 \a - attribute \src "multiple_outputs.v:3.17-3.18" - wire output 2 \b - attribute \src "multiple_outputs.v:4.17-4.18" - wire output 3 \c - attribute \src "multiple_outputs.v:5.17-5.18" - wire output 4 \d - attribute \src "multiple_outputs.v:6.17-6.18" - wire output 5 \e - attribute \src "multiple_outputs.v:11.12-11.20" - cell $and $and$multiple_outputs.v:11$2 - parameter \A_SIGNED 0 - parameter \A_WIDTH 1 - parameter \B_SIGNED 0 - parameter \B_WIDTH 1 - parameter \Y_WIDTH 1 - connect \A \a - connect \B 1'1 - connect \Y $and$multiple_outputs.v:11$2_Y - end - attribute \src "multiple_outputs.v:10.12-10.14" - cell $not $not$multiple_outputs.v:10$1 - parameter \A_SIGNED 0 - parameter \A_WIDTH 1 - parameter \Y_WIDTH 1 - connect \A \a - connect \Y $not$multiple_outputs.v:10$1_Y - end - attribute \src "multiple_outputs.v:12.12-12.20" - cell $or $or$multiple_outputs.v:12$3 - parameter \A_SIGNED 0 - parameter \A_WIDTH 1 - parameter \B_SIGNED 0 - parameter \B_WIDTH 1 - parameter \Y_WIDTH 1 - connect \A \a - connect \B 1'0 - connect \Y $or$multiple_outputs.v:12$3_Y - end - attribute \src "multiple_outputs.v:13.12-13.20" - cell $xor $xor$multiple_outputs.v:13$4 - parameter \A_SIGNED 0 - parameter \A_WIDTH 1 - parameter \B_SIGNED 0 - parameter \B_WIDTH 1 - parameter \Y_WIDTH 1 - connect \A \a - connect \B 1'1 - connect \Y $xor$multiple_outputs.v:13$4_Y - end - connect \b $not$multiple_outputs.v:10$1_Y - connect \c $and$multiple_outputs.v:11$2_Y - connect \d $or$multiple_outputs.v:12$3_Y - connect \e $xor$multiple_outputs.v:13$4_Y -end diff --git a/tests/functional/single_cells/rtlil/test_cell_add_00000.il b/tests/functional/single_cells/rtlil/test_cell_add_00000.il deleted file mode 100644 index 531dabe41fa..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_add_00000.il +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 5 input 1 \A - wire width 4 input 2 \B - wire width 6 output 3 \Y - cell $add \UUT - parameter \A_SIGNED 1 - parameter \A_WIDTH 5 - parameter \B_SIGNED 1 - parameter \B_WIDTH 4 - parameter \Y_WIDTH 6 - connect \A \A - connect \B \B - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_alu_00000.il b/tests/functional/single_cells/rtlil/test_cell_alu_00000.il deleted file mode 100644 index ebdd3b7343b..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_alu_00000.il +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 8 input 1 \A - wire width 7 input 2 \B - wire input 3 \BI - wire input 4 \CI - wire width 6 output 5 \CO - wire width 6 output 6 \X - wire width 6 output 7 \Y - cell $alu \UUT - parameter \A_SIGNED 0 - parameter \A_WIDTH 8 - parameter \B_SIGNED 0 - parameter \B_WIDTH 7 - parameter \Y_WIDTH 6 - connect \A \A - connect \B \B - connect \BI \BI - connect \CI \CI - connect \CO \CO - connect \X \X - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_and_00000.il b/tests/functional/single_cells/rtlil/test_cell_and_00000.il deleted file mode 100644 index 2578f60b774..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_and_00000.il +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 2 input 1 \A - wire width 3 input 2 \B - wire width 2 output 3 \Y - cell $and \UUT - parameter \A_SIGNED 1 - parameter \A_WIDTH 2 - parameter \B_SIGNED 1 - parameter \B_WIDTH 3 - parameter \Y_WIDTH 2 - connect \A \A - connect \B \B - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_bmux_00000.il b/tests/functional/single_cells/rtlil/test_cell_bmux_00000.il deleted file mode 100644 index 1b613bd698c..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_bmux_00000.il +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 8 input 1 \A - wire input 2 \S - wire width 4 output 3 \Y - cell $bmux \UUT - parameter \S_WIDTH 1 - parameter \WIDTH 4 - connect \A \A - connect \S \S - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_demux_00000.il b/tests/functional/single_cells/rtlil/test_cell_demux_00000.il deleted file mode 100644 index dbe2dee62ac..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_demux_00000.il +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 6 input 1 \A - wire width 5 input 2 \S - wire width 192 output 3 \Y - cell $demux \UUT - parameter \S_WIDTH 5 - parameter \WIDTH 6 - connect \A \A - connect \S \S - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_div_00000.il b/tests/functional/single_cells/rtlil/test_cell_div_00000.il deleted file mode 100644 index 6f278a7a079..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_div_00000.il +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 4 input 1 \A - wire width 6 input 2 \B - wire output 3 \Y - cell $div \UUT - parameter \A_SIGNED 0 - parameter \A_WIDTH 4 - parameter \B_SIGNED 0 - parameter \B_WIDTH 6 - parameter \Y_WIDTH 1 - connect \A \A - connect \B \B - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_divfloor_00000.il b/tests/functional/single_cells/rtlil/test_cell_divfloor_00000.il deleted file mode 100644 index 67fce36b331..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_divfloor_00000.il +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 4 input 1 \A - wire width 4 input 2 \B - wire width 6 output 3 \Y - cell $divfloor \UUT - parameter \A_SIGNED 0 - parameter \A_WIDTH 4 - parameter \B_SIGNED 0 - parameter \B_WIDTH 4 - parameter \Y_WIDTH 6 - connect \A \A - connect \B \B - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_eq_00000.il b/tests/functional/single_cells/rtlil/test_cell_eq_00000.il deleted file mode 100644 index 06fef02b5b2..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_eq_00000.il +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 5 input 1 \A - wire input 2 \B - wire width 4 output 3 \Y - cell $eq \UUT - parameter \A_SIGNED 0 - parameter \A_WIDTH 5 - parameter \B_SIGNED 0 - parameter \B_WIDTH 1 - parameter \Y_WIDTH 4 - connect \A \A - connect \B \B - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_fa_00000.il b/tests/functional/single_cells/rtlil/test_cell_fa_00000.il deleted file mode 100644 index 97f0eb0297d..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_fa_00000.il +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire input 1 \A - wire input 2 \B - wire input 3 \C - wire output 4 \X - wire output 5 \Y - cell $fa \UUT - parameter \WIDTH 1 - connect \A \A - connect \B \B - connect \C \C - connect \X \X - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_ge_00000.il b/tests/functional/single_cells/rtlil/test_cell_ge_00000.il deleted file mode 100644 index 0ac72e9433d..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_ge_00000.il +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 3 input 1 \A - wire width 7 input 2 \B - wire width 6 output 3 \Y - cell $ge \UUT - parameter \A_SIGNED 1 - parameter \A_WIDTH 3 - parameter \B_SIGNED 1 - parameter \B_WIDTH 7 - parameter \Y_WIDTH 6 - connect \A \A - connect \B \B - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_gt_00000.il b/tests/functional/single_cells/rtlil/test_cell_gt_00000.il deleted file mode 100644 index d4498d36f9e..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_gt_00000.il +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 7 input 1 \A - wire width 3 input 2 \B - wire width 4 output 3 \Y - cell $gt \UUT - parameter \A_SIGNED 1 - parameter \A_WIDTH 7 - parameter \B_SIGNED 1 - parameter \B_WIDTH 3 - parameter \Y_WIDTH 4 - connect \A \A - connect \B \B - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_lcu_00000.il b/tests/functional/single_cells/rtlil/test_cell_lcu_00000.il deleted file mode 100644 index e1bda0b433c..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_lcu_00000.il +++ /dev/null @@ -1,15 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire input 1 \CI - wire width 2 output 2 \CO - wire width 2 input 3 \G - wire width 2 input 4 \P - cell $lcu \UUT - parameter \WIDTH 2 - connect \CI \CI - connect \CO \CO - connect \G \G - connect \P \P - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_le_00000.il b/tests/functional/single_cells/rtlil/test_cell_le_00000.il deleted file mode 100644 index 609d99ce8a4..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_le_00000.il +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 5 input 1 \A - wire width 4 input 2 \B - wire width 6 output 3 \Y - cell $le \UUT - parameter \A_SIGNED 1 - parameter \A_WIDTH 5 - parameter \B_SIGNED 1 - parameter \B_WIDTH 4 - parameter \Y_WIDTH 6 - connect \A \A - connect \B \B - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_logic_and_00000.il b/tests/functional/single_cells/rtlil/test_cell_logic_and_00000.il deleted file mode 100644 index 2eae6e3fcb1..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_logic_and_00000.il +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 2 input 1 \A - wire width 7 input 2 \B - wire output 3 \Y - cell $logic_and \UUT - parameter \A_SIGNED 0 - parameter \A_WIDTH 2 - parameter \B_SIGNED 0 - parameter \B_WIDTH 7 - parameter \Y_WIDTH 1 - connect \A \A - connect \B \B - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_logic_not_00000.il b/tests/functional/single_cells/rtlil/test_cell_logic_not_00000.il deleted file mode 100644 index c7898491ca5..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_logic_not_00000.il +++ /dev/null @@ -1,13 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 3 input 1 \A - wire width 8 output 2 \Y - cell $logic_not \UUT - parameter \A_SIGNED 1 - parameter \A_WIDTH 3 - parameter \Y_WIDTH 8 - connect \A \A - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_logic_or_00000.il b/tests/functional/single_cells/rtlil/test_cell_logic_or_00000.il deleted file mode 100644 index 7a1ef6cbda0..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_logic_or_00000.il +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 8 input 1 \A - wire width 7 input 2 \B - wire width 2 output 3 \Y - cell $logic_or \UUT - parameter \A_SIGNED 0 - parameter \A_WIDTH 8 - parameter \B_SIGNED 0 - parameter \B_WIDTH 7 - parameter \Y_WIDTH 2 - connect \A \A - connect \B \B - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_lt_00000.il b/tests/functional/single_cells/rtlil/test_cell_lt_00000.il deleted file mode 100644 index fd3ee1f8bad..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_lt_00000.il +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 8 input 1 \A - wire width 5 input 2 \B - wire width 6 output 3 \Y - cell $lt \UUT - parameter \A_SIGNED 0 - parameter \A_WIDTH 8 - parameter \B_SIGNED 0 - parameter \B_WIDTH 5 - parameter \Y_WIDTH 6 - connect \A \A - connect \B \B - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_lut_00000.il b/tests/functional/single_cells/rtlil/test_cell_lut_00000.il deleted file mode 100644 index 7c4882bb61a..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_lut_00000.il +++ /dev/null @@ -1,12 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 2 input 1 \A - wire output 2 \Y - cell $lut \UUT - parameter \LUT 4'1111 - parameter \WIDTH 2 - connect \A \A - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_macc_00000.il b/tests/functional/single_cells/rtlil/test_cell_macc_00000.il deleted file mode 100644 index 66b51634e97..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_macc_00000.il +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 3 input 1 \A - wire width 0 input 2 \B - wire width 2 output 3 \Y - cell $macc \UUT - parameter \A_WIDTH 3 - parameter \B_WIDTH 0 - parameter \CONFIG 10'0110000010 - parameter \CONFIG_WIDTH 10 - parameter \Y_WIDTH 2 - connect \A \A - connect \B { } - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_mod_00000.il b/tests/functional/single_cells/rtlil/test_cell_mod_00000.il deleted file mode 100644 index a484918f05c..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_mod_00000.il +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 6 input 1 \A - wire width 8 input 2 \B - wire width 2 output 3 \Y - cell $mod \UUT - parameter \A_SIGNED 0 - parameter \A_WIDTH 6 - parameter \B_SIGNED 0 - parameter \B_WIDTH 8 - parameter \Y_WIDTH 2 - connect \A \A - connect \B \B - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_modfloor_00000.il b/tests/functional/single_cells/rtlil/test_cell_modfloor_00000.il deleted file mode 100644 index 6cd00aeea5d..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_modfloor_00000.il +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 5 input 1 \A - wire width 7 input 2 \B - wire width 4 output 3 \Y - cell $modfloor \UUT - parameter \A_SIGNED 0 - parameter \A_WIDTH 5 - parameter \B_SIGNED 0 - parameter \B_WIDTH 7 - parameter \Y_WIDTH 4 - connect \A \A - connect \B \B - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_mul_00000.il b/tests/functional/single_cells/rtlil/test_cell_mul_00000.il deleted file mode 100644 index c77aaffb87e..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_mul_00000.il +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 6 input 1 \A - wire width 2 input 2 \B - wire width 5 output 3 \Y - cell $mul \UUT - parameter \A_SIGNED 0 - parameter \A_WIDTH 6 - parameter \B_SIGNED 0 - parameter \B_WIDTH 2 - parameter \Y_WIDTH 5 - connect \A \A - connect \B \B - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_mux_00000.il b/tests/functional/single_cells/rtlil/test_cell_mux_00000.il deleted file mode 100644 index 3fc52ac7716..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_mux_00000.il +++ /dev/null @@ -1,15 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 4 input 1 \A - wire width 4 input 2 \B - wire input 3 \S - wire width 4 output 4 \Y - cell $mux \UUT - parameter \WIDTH 4 - connect \A \A - connect \B \B - connect \S \S - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_ne_00000.il b/tests/functional/single_cells/rtlil/test_cell_ne_00000.il deleted file mode 100644 index 47ba31397ed..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_ne_00000.il +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 7 input 1 \A - wire width 5 input 2 \B - wire width 4 output 3 \Y - cell $ne \UUT - parameter \A_SIGNED 0 - parameter \A_WIDTH 7 - parameter \B_SIGNED 0 - parameter \B_WIDTH 5 - parameter \Y_WIDTH 4 - connect \A \A - connect \B \B - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_neg_00000.il b/tests/functional/single_cells/rtlil/test_cell_neg_00000.il deleted file mode 100644 index ee4273508d1..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_neg_00000.il +++ /dev/null @@ -1,13 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 2 input 1 \A - wire width 5 output 2 \Y - cell $neg \UUT - parameter \A_SIGNED 0 - parameter \A_WIDTH 2 - parameter \Y_WIDTH 5 - connect \A \A - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_not_00000.il b/tests/functional/single_cells/rtlil/test_cell_not_00000.il deleted file mode 100644 index 9281ded0dfa..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_not_00000.il +++ /dev/null @@ -1,13 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 7 input 1 \A - wire width 7 output 2 \Y - cell $not \UUT - parameter \A_SIGNED 1 - parameter \A_WIDTH 7 - parameter \Y_WIDTH 7 - connect \A \A - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_or_00000.il b/tests/functional/single_cells/rtlil/test_cell_or_00000.il deleted file mode 100644 index 100ea42cd38..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_or_00000.il +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 7 input 1 \A - wire input 2 \B - wire width 2 output 3 \Y - cell $or \UUT - parameter \A_SIGNED 1 - parameter \A_WIDTH 7 - parameter \B_SIGNED 1 - parameter \B_WIDTH 1 - parameter \Y_WIDTH 2 - connect \A \A - connect \B \B - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_pos_00000.il b/tests/functional/single_cells/rtlil/test_cell_pos_00000.il deleted file mode 100644 index 182f0acd38f..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_pos_00000.il +++ /dev/null @@ -1,13 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire input 1 \A - wire width 3 output 2 \Y - cell $pos \UUT - parameter \A_SIGNED 1 - parameter \A_WIDTH 1 - parameter \Y_WIDTH 3 - connect \A \A - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_pos_00001.il b/tests/functional/single_cells/rtlil/test_cell_pos_00001.il deleted file mode 100644 index 96e9c895a18..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_pos_00001.il +++ /dev/null @@ -1,13 +0,0 @@ -# Generated by Yosys 0.40+7 (git sha1 d8b5b90e7, g++ 13.2.0 -Og -fPIC) -autoidx 1 -module \gold - wire width 6 input 1 \A - wire width 6 output 2 \Y - cell $pos \UUT - parameter \A_SIGNED 0 - parameter \A_WIDTH 6 - parameter \Y_WIDTH 6 - connect \A \A - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_reduce_and_00000.il b/tests/functional/single_cells/rtlil/test_cell_reduce_and_00000.il deleted file mode 100644 index 079ed33ade5..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_reduce_and_00000.il +++ /dev/null @@ -1,13 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 4 input 1 \A - wire width 5 output 2 \Y - cell $reduce_and \UUT - parameter \A_SIGNED 0 - parameter \A_WIDTH 4 - parameter \Y_WIDTH 5 - connect \A \A - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_reduce_bool_00000.il b/tests/functional/single_cells/rtlil/test_cell_reduce_bool_00000.il deleted file mode 100644 index a417179c818..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_reduce_bool_00000.il +++ /dev/null @@ -1,13 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire input 1 \A - wire width 2 output 2 \Y - cell $reduce_bool \UUT - parameter \A_SIGNED 1 - parameter \A_WIDTH 1 - parameter \Y_WIDTH 2 - connect \A \A - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_reduce_or_00000.il b/tests/functional/single_cells/rtlil/test_cell_reduce_or_00000.il deleted file mode 100644 index 881c6dfe3a1..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_reduce_or_00000.il +++ /dev/null @@ -1,13 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 2 input 1 \A - wire width 7 output 2 \Y - cell $reduce_or \UUT - parameter \A_SIGNED 0 - parameter \A_WIDTH 2 - parameter \Y_WIDTH 7 - connect \A \A - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_reduce_xnor_00000.il b/tests/functional/single_cells/rtlil/test_cell_reduce_xnor_00000.il deleted file mode 100644 index c06562729ea..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_reduce_xnor_00000.il +++ /dev/null @@ -1,13 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 4 input 1 \A - wire width 4 output 2 \Y - cell $reduce_xnor \UUT - parameter \A_SIGNED 0 - parameter \A_WIDTH 4 - parameter \Y_WIDTH 4 - connect \A \A - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_reduce_xor_00000.il b/tests/functional/single_cells/rtlil/test_cell_reduce_xor_00000.il deleted file mode 100644 index 428d2ea87a2..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_reduce_xor_00000.il +++ /dev/null @@ -1,13 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 4 input 1 \A - wire output 2 \Y - cell $reduce_xor \UUT - parameter \A_SIGNED 0 - parameter \A_WIDTH 4 - parameter \Y_WIDTH 1 - connect \A \A - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_shift_00000.il b/tests/functional/single_cells/rtlil/test_cell_shift_00000.il deleted file mode 100644 index f82a2a79df7..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_shift_00000.il +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire input 1 \A - wire width 6 input 2 \B - wire width 4 output 3 \Y - cell $shift \UUT - parameter \A_SIGNED 1 - parameter \A_WIDTH 1 - parameter \B_SIGNED 1 - parameter \B_WIDTH 6 - parameter \Y_WIDTH 4 - connect \A \A - connect \B \B - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_shiftx_00000.il b/tests/functional/single_cells/rtlil/test_cell_shiftx_00000.il deleted file mode 100644 index b89ac5bef91..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_shiftx_00000.il +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire input 1 \A - wire width 5 input 2 \B - wire width 3 output 3 \Y - cell $shiftx \UUT - parameter \A_SIGNED 0 - parameter \A_WIDTH 1 - parameter \B_SIGNED 0 - parameter \B_WIDTH 5 - parameter \Y_WIDTH 3 - connect \A \A - connect \B \B - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_shl_00000.il b/tests/functional/single_cells/rtlil/test_cell_shl_00000.il deleted file mode 100644 index 1cfee2a52b5..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_shl_00000.il +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 8 input 1 \A - wire width 2 input 2 \B - wire width 3 output 3 \Y - cell $shl \UUT - parameter \A_SIGNED 0 - parameter \A_WIDTH 8 - parameter \B_SIGNED 0 - parameter \B_WIDTH 2 - parameter \Y_WIDTH 3 - connect \A \A - connect \B \B - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_shr_00000.il b/tests/functional/single_cells/rtlil/test_cell_shr_00000.il deleted file mode 100644 index 35dd379099c..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_shr_00000.il +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 7 input 1 \A - wire width 6 input 2 \B - wire width 4 output 3 \Y - cell $shr \UUT - parameter \A_SIGNED 1 - parameter \A_WIDTH 7 - parameter \B_SIGNED 0 - parameter \B_WIDTH 6 - parameter \Y_WIDTH 4 - connect \A \A - connect \B \B - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_sop_00000.il b/tests/functional/single_cells/rtlil/test_cell_sop_00000.il deleted file mode 100644 index df0f8f20abe..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_sop_00000.il +++ /dev/null @@ -1,13 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 8 input 1 \A - wire output 2 \Y - cell $sop \UUT - parameter \DEPTH 8 - parameter \TABLE 128'10010000100100000101101010001001101000101010010100010000010100000101010100000001001010010110101010101010101000100100011001000110 - parameter \WIDTH 8 - connect \A \A - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_sshl_00000.il b/tests/functional/single_cells/rtlil/test_cell_sshl_00000.il deleted file mode 100644 index 798288754ce..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_sshl_00000.il +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 5 input 1 \A - wire width 3 input 2 \B - wire width 6 output 3 \Y - cell $sshl \UUT - parameter \A_SIGNED 0 - parameter \A_WIDTH 5 - parameter \B_SIGNED 0 - parameter \B_WIDTH 3 - parameter \Y_WIDTH 6 - connect \A \A - connect \B \B - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_sshr_00000.il b/tests/functional/single_cells/rtlil/test_cell_sshr_00000.il deleted file mode 100644 index 3f59a693f24..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_sshr_00000.il +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 3 input 1 \A - wire width 2 input 2 \B - wire width 2 output 3 \Y - cell $sshr \UUT - parameter \A_SIGNED 1 - parameter \A_WIDTH 3 - parameter \B_SIGNED 0 - parameter \B_WIDTH 2 - parameter \Y_WIDTH 2 - connect \A \A - connect \B \B - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_sub_00000.il b/tests/functional/single_cells/rtlil/test_cell_sub_00000.il deleted file mode 100644 index cb9ec2f7904..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_sub_00000.il +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 6 input 1 \A - wire width 6 input 2 \B - wire width 6 output 3 \Y - cell $sub \UUT - parameter \A_SIGNED 0 - parameter \A_WIDTH 6 - parameter \B_SIGNED 0 - parameter \B_WIDTH 6 - parameter \Y_WIDTH 6 - connect \A \A - connect \B \B - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_xnor_00000.il b/tests/functional/single_cells/rtlil/test_cell_xnor_00000.il deleted file mode 100644 index 85bd7d239df..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_xnor_00000.il +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 7 input 1 \A - wire width 8 input 2 \B - wire width 7 output 3 \Y - cell $xnor \UUT - parameter \A_SIGNED 1 - parameter \A_WIDTH 7 - parameter \B_SIGNED 1 - parameter \B_WIDTH 8 - parameter \Y_WIDTH 7 - connect \A \A - connect \B \B - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/rtlil/test_cell_xor_00000.il b/tests/functional/single_cells/rtlil/test_cell_xor_00000.il deleted file mode 100644 index 2359698b55b..00000000000 --- a/tests/functional/single_cells/rtlil/test_cell_xor_00000.il +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Yosys 0.41+101 (git sha1 83a8e5de4, g++ 13.2.0 -fPIC -Os) -autoidx 1 -module \gold - wire width 6 input 1 \A - wire width 2 input 2 \B - wire width 8 output 3 \Y - cell $xor \UUT - parameter \A_SIGNED 0 - parameter \A_WIDTH 6 - parameter \B_SIGNED 0 - parameter \B_WIDTH 2 - parameter \Y_WIDTH 8 - connect \A \A - connect \B \B - connect \Y \Y - end -end diff --git a/tests/functional/single_cells/run-test.sh b/tests/functional/single_cells/run-test.sh deleted file mode 100755 index cc6bcf16086..00000000000 --- a/tests/functional/single_cells/run-test.sh +++ /dev/null @@ -1,172 +0,0 @@ -#!/bin/bash - -declare -A cxx_failing_files -declare -A smt_failing_files -declare -A cxx_successful_files -declare -A smt_successful_files - -run_cxx_test() { - BASE_PATH="../../../" - - local rtlil_file=$1 - - local base_name=$(basename "$rtlil_file" .v) - - if ${BASE_PATH}yosys -p "read_rtlil $rtlil_file; write_functional_cxx my_module_functional_cxx.cc"; then - echo "Yosys processed $rtlil_file successfully." - - if ${CXX:-g++} -g -fprofile-arcs -ftest-coverage vcd_harness.cc -I ${BASE_PATH}backends/functional/cxx_runtime/ -std=c++17 -o vcd_harness; then - echo "Compilation successful." - if ./vcd_harness ${base_name}_functional_cxx.vcd; then - - if ${BASE_PATH}yosys -p "read_rtlil $rtlil_file; sim -r ${base_name}_functional_cxx.vcd -scope gold -vcd ${base_name}_yosys_sim.vcd -timescale 1us -sim-gold"; then - echo "Yosys sim $rtlil_file successfully." - cxx_successful_files["$rtlil_file"]="Success" - else - ${BASE_PATH}yosys -p "read_rtlil $rtlil_file; sim -vcd ${base_name}_yosys_sim.vcd -r ${base_name}_functional_cxx.vcd -scope gold -timescale 1us" - echo "Yosys simulation of $rtlil_file failed. There is a discrepancy with functional cxx" - cxx_failing_files["$rtlil_file"]="Yosys sim failure" - fi - - else - echo "Failed to generate VCD files for $rtlil_file." - cxx_failing_files["$rtlil_file"]="VCD generation failure" - fi - else - echo "Failed to compile harness for $rtlil_file." - cxx_failing_files["$rtlil_file"]="Compilation failure" - fi - else - echo "Yosys failed to process $rtlil_file." - cxx_failing_files["$rtlil_file"]="Yosys failure" - fi -} - -run_smt_test() { - BASE_PATH="../../../" - - local rtlil_file=$1 - - local base_name=$(basename "$rtlil_file" .il) - - if ${BASE_PATH}yosys -p "read_rtlil $rtlil_file; write_functional_smt2 ${base_name}.smt2"; then - echo "Yosys processed $rtlil_file successfully." - # TODO: which SMT solver should be run? - if z3 "${base_name}.smt2"; then - echo "SMT file ${base_name}.smt2 is valid ." - if python3 vcd_harness_smt.py "${base_name}.smt2"; then - echo "Python script generated VCD file for $rtlil_file successfully." - - if [ -f "${base_name}.smt2.vcd" ]; then - echo "VCD file ${base_name}.vcd generated successfully by Python." - - if ${BASE_PATH}yosys -p "read_rtlil $rtlil_file; sim -vcd ${base_name}_yosys.vcd -r ${base_name}.smt2.vcd -scope gold -timescale 1us"; then - echo "Yosys simulation for $rtlil_file completed successfully." - smt_successful_files["$rtlil_file"]="Success" - else - echo "Yosys simulation failed for $rtlil_file." - smt_failing_files["$rtlil_file"]="Yosys simulation failure" - fi - else - echo "Failed to generate VCD file (${base_name}.vcd) for $rtlil_file. " - smt_failing_files["$rtlil_file"]="VCD generation failure" - fi - else - echo "Failed to run Python script for $rtlil_file." - smt_failing_files["$rtlil_file"]="Python script failure" - fi - else - echo "SMT file for $rtlil_file is invalid" - smt_failing_files["$rtlil_file"]="Invalid SMT" - fi - else - echo "Yosys failed to process $rtlil_file." - smt_failing_files["$rtlil_file"]="Yosys failure" - fi -} - - -run_all_tests() { - declare -A cxx_failing_files - declare -A smt_failing_files - declare -A cxx_successful_files - declare -A smt_successful_files - return_code=0 - for rtlil_file in rtlil/*.il; do - run_cxx_test "$rtlil_file" - run_smt_test "$rtlil_file" - done - - echo "C++ tests results:" - if [ ${#cxx_failing_files[@]} -eq 0 ]; then - echo "All files passed." - echo "The following files passed:" - for file in "${!cxx_successful_files[@]}"; do - echo "$file" - done - else - echo "The following files failed:" - for file in "${!cxx_failing_files[@]}"; do - echo "$file: ${cxx_failing_files[$file]}" - done - echo "The following files passed:" - for file in "${!cxx_successful_files[@]}"; do - echo "$file" - done - return_code=1 - fi - - echo "SMT tests results:" - if [ ${#smt_failing_files[@]} -eq 0 ]; then - echo "All files passed." - echo "The following files passed:" - for file in "${!smt_successful_files[@]}"; do - echo "$file" - done - else - echo "The following files failed:" - for file in "${!smt_failing_files[@]}"; do - echo "$file: ${smt_failing_files[$file]}" - done - echo "The following files passed:" - for file in "${!smt_successful_files[@]}"; do - echo "$file" - done - return_code=1 - fi - return $return_code -} - -run_smt_tests() { - declare -A smt_failing_files - declare -A smt_successful_files - return_code=0 - for rtlil_file in rtlil/*.il; do - run_smt_test "$rtlil_file" - done - - echo "SMT tests results:" - if [ ${#smt_failing_files[@]} -eq 0 ]; then - echo "All files passed." - echo "The following files passed:" - for file in "${!smt_successful_files[@]}"; do - echo "$file" - done - else - echo "The following files failed:" - for file in "${!smt_failing_files[@]}"; do - echo "$file: ${smt_failing_files[$file]}" - done - echo "The following files passed:" - for file in "${!smt_successful_files[@]}"; do - echo "$file" - done - return_code=1 - fi - return $return_code -} - -# If the script is being sourced, do not execute the tests -if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then - run_all_tests -fi diff --git a/tests/functional/single_cells/vcd_harness_smt.py b/tests/functional/single_cells/vcd_harness_smt.py deleted file mode 100644 index 95947748ffe..00000000000 --- a/tests/functional/single_cells/vcd_harness_smt.py +++ /dev/null @@ -1,214 +0,0 @@ -import sys -import argparse -import random -import os - -# Parse command line arguments -parser = argparse.ArgumentParser(description='Process SMT file for simulation.') -parser.add_argument('smt_file', help='Path to the SMT file to simulate.') -args = parser.parse_args() - -# Get the SMT file path from the command line argument -smt_file_path = args.smt_file -current_file_path = os.path.abspath(__file__) -# Navigate to the directory you need relative to the current script -# Assuming 'backends/smt2' is located three directories up from the current script -desired_path = os.path.join(os.path.dirname(current_file_path), '..', '..', '..', 'backends', 'functional') - -# Normalize the path to resolve any '..' -normalized_path = os.path.normpath(desired_path) - -# Append the directory to the Python path -sys.path.append(normalized_path) - -# Import the module -import smtio - -# Assuming the SmtIo class and other dependencies are available in your environment -# You may need to include the entire script provided above in your script or have it available as a module - -# Step 1: Initialize an SmtIo object - -so = smtio.SmtOpts() -so.solver = "z3" -so.logic = "BV" -so.debug_print = True -# so.debug_file = "my_debug" -smt_io = smtio.SmtIo(opts=so) -# List to store the parsed results -parsed_results = [] - -# Load and execute the SMT file -with open(smt_file_path, 'r') as smt_file: - for line in smt_file: - smt_io.write(line.strip()) -smt_io.check_sat() -# Read and parse the SMT file line by line -with open(smt_file_path, 'r') as smt_file: - for line in smt_file: - # Strip leading/trailing whitespace - stripped_line = line.strip() - - # Ignore empty lines - if not stripped_line: - continue - # comments - if stripped_line.startswith(';'): - smt_io.info(stripped_line) - - # Parse the line using SmtIo's parse method - parsed_line = smt_io.parse(stripped_line) - - # Append the parsed result to the list - parsed_results.append(parsed_line) - -# Display the parsed results -for result in parsed_results: - print(result) - -inputs = {} -outputs = {} - -for lst in parsed_results: - if lst[0] == 'declare-datatypes': - for datatype_group in lst[2]: # Navigate into the nested lists - datatype_name = datatype_group[0] - declarations = datatype_group[1][1:] # Skip the first item (e.g., 'mk_inputs') - if datatype_name == 'Inputs': - for declaration in declarations: - print("My declaration") - print(declaration) - input_name = declaration[0] - bitvec_size = declaration[1][2] - inputs[input_name] = int(bitvec_size) - elif datatype_name == 'Outputs': - for declaration in declarations: - output_name = declaration[0] - bitvec_size = declaration[1][2] - outputs[output_name] = int(bitvec_size) - -print(inputs) -print(outputs) -def set_step(inputs, step): - # This function assumes 'inputs' is a dictionary like {"A": 5, "B": 4} - # and 'input_values' is a dictionary like {"A": 5, "B": 13} specifying the concrete values for each input. - - # Construct input definitions - mk_inputs_parts = [] - for input_name, width in inputs.items(): - value = random.getrandbits(width) # Generate a random number up to the maximum value for the bit size - binary_string = format(value, '0{}b'.format(width)) # Convert value to binary with leading zeros - mk_inputs_parts.append(f"#b{binary_string}") - - # Join all parts for the mk_inputs constructor - mk_inputs_call = "mk_inputs " + " ".join(mk_inputs_parts) - define_inputs = f"(define-const test_inputs_step_n{step} Inputs ({mk_inputs_call}))\n" - - # Create the output definition by calling the gold_step function - define_output = f"(define-const test_outputs_step_n{step} Outputs (gold_step test_inputs_step_n{step}))\n" - smt_commands = [] - smt_commands.append(define_inputs) - smt_commands.append(define_output) - return smt_commands - -num_steps = 10 -smt_commands = [] -# Loop to generate SMT commands for each step -for step in range(num_steps): - for step_command in set_step(inputs, step): - smt_commands.append(step_command) - -# Print or write the SMT commands to a file -smt_file_content = ''.join(smt_commands) -print(smt_file_content) # Print to console - -# Optionally, write to a file -with open('dynamic_commands.smt2', 'w') as smt_file: - smt_file.write(smt_file_content) - -# Execute the SMT commands -for command in smt_commands: - print(command) - smt_io.write(command.strip()) - -# Check for satisfiability -# smt_io.setup() -result = smt_io.check_sat() -print(f'SAT result: {result}') -if result != 'sat': - smt_io.p_close() - sys.exit(1) - -# Store signal values -signals = {name: [] for name in list(inputs.keys()) + list(outputs.keys())} -# Retrieve and print values for each state -def hex_to_bin(value): - if value.startswith('x'): - hex_value = value[1:] # Remove the 'x' prefix - bin_value = bin(int(hex_value, 16))[2:] # Convert to binary and remove the '0b' prefix - return f'b{bin_value.zfill(len(hex_value) * 4)}' # Add 'b' prefix and pad with zeros - return value - -combined_assertions = [] -for step in range(num_steps): - print(f"Values for step {step + 1}:") - for input_name, width in inputs.items(): - value = smt_io.get(f'({input_name} test_inputs_step_n{step})') - value = hex_to_bin(value[1:]) - print(f" {input_name}: {value}") - signals[input_name].append((step, value)) - for output_name, width in outputs.items(): - value = smt_io.get(f'({output_name} test_outputs_step_n{step})') - value = hex_to_bin(value[1:]) - print(f" {output_name}: {value}") - signals[output_name].append((step, value)) - combined_assertions.append(f'(= ({output_name} test_outputs_step_n{step}) #{value})') -# Create a single assertion covering all timesteps -combined_condition = " ".join(combined_assertions) -smt_io.write(f'(assert (not (and {combined_condition})))') - -# Check the combined assertion -result = smt_io.check_sat(["unsat"]) -if result != 'unsat': - smt_io.p_close() - sys.exit(1) - -smt_io.p_close() - -def write_vcd(filename, signals, timescale='1 ns', date='today'): - with open(filename, 'w') as f: - # Write the header - f.write(f"$date\n {date}\n$end\n") - f.write(f"$timescale {timescale} $end\n") - - # Declare signals - f.write("$scope module gold $end\n") - for signal_name, changes in signals.items(): - signal_size = len(changes[0][1]) - f.write(f"$var wire {signal_size - 1} {signal_name} {signal_name} $end\n") - f.write("$upscope $end\n") - f.write("$enddefinitions $end\n") - - # Collect all unique timestamps - timestamps = sorted(set(time for changes in signals.values() for time, _ in changes)) - - # Write initial values - f.write("#0\n") - for signal_name, changes in signals.items(): - for time, value in changes: - if time == 0: - f.write(f"{value} {signal_name}\n") - - # Write value changes - for time in timestamps: - if time != 0: - f.write(f"#{time}\n") - for signal_name, changes in signals.items(): - for change_time, value in changes: - if change_time == time: - f.write(f"b{value} {signal_name}\n") - - -# Write the VCD file -write_vcd(smt_file_path + '.vcd', signals) -sys.exit(0) diff --git a/tests/functional/smt_vcd.py b/tests/functional/smt_vcd.py new file mode 100644 index 00000000000..c38fe1bde7e --- /dev/null +++ b/tests/functional/smt_vcd.py @@ -0,0 +1,180 @@ +import sys +import argparse +import random +import os +import smtio +import re + +class SExprParserError(Exception): + pass + +class SExprParser: + def __init__(self): + self.peekbuf = None + self.stack = [[]] + self.atom_pattern = re.compile(r'[a-zA-Z0-9~!@$%^&*_\-+=<>.?/#]+') + def parse_line(self, line): + ptr = 0 + while ptr < len(line): + if line[ptr].isspace(): + ptr += 1 + elif line[ptr] == ';': + break + elif line[ptr] == '(': + ptr += 1 + self.stack.append([]) + elif line[ptr] == ')': + ptr += 1 + assert len(self.stack) > 1, "too many closed parentheses" + v = self.stack.pop() + self.stack[-1].append(v) + else: + match = self.atom_pattern.match(line, ptr) + if match is None: + raise SExprParserError(f"invalid character '{line[ptr]}' in line '{line}'") + start, ptr = match.span() + self.stack[-1].append(line[start:ptr]) + def finish(self): + assert len(self.stack) == 1, "too many open parentheses" + def retrieve(self): + rv, self.stack[0] = self.stack[0], [] + return rv + +def simulate_smt_with_smtio(smt_file_path, vcd_path, smt_io): + inputs = {} + outputs = {} + + def handle_datatype(lst): + print(lst) + datatype_name = lst[1] + declarations = lst[2][0][1:] # Skip the first item (e.g., 'mk_inputs') + if datatype_name.endswith("_Inputs"): + for declaration in declarations: + input_name = declaration[0] + bitvec_size = declaration[1][2] + assert input_name.startswith("gold_Inputs_") + inputs[input_name[len("gold_Inputs_"):]] = int(bitvec_size) + elif datatype_name.endswith("_Outputs"): + for declaration in declarations: + output_name = declaration[0] + bitvec_size = declaration[1][2] + assert output_name.startswith("gold_Outputs_") + outputs[output_name[len("gold_Outputs_"):]] = int(bitvec_size) + + parser = SExprParser() + with open(smt_file_path, 'r') as smt_file: + for line in smt_file: + parser.parse_line(line) + for expr in parser.retrieve(): + smt_io.write(smt_io.unparse(expr)) + if expr[0] == "declare-datatype": + handle_datatype(expr) + + parser.finish() + assert smt_io.check_sat() == 'sat' + + def set_step(inputs, step): + # This function assumes 'inputs' is a dictionary like {"A": 5, "B": 4} + # and 'input_values' is a dictionary like {"A": 5, "B": 13} specifying the concrete values for each input. + + mk_inputs_parts = [] + for input_name, width in inputs.items(): + value = random.getrandbits(width) # Generate a random number up to the maximum value for the bit size + binary_string = format(value, '0{}b'.format(width)) # Convert value to binary with leading zeros + mk_inputs_parts.append(f"#b{binary_string}") + + mk_inputs_call = "gold_Inputs " + " ".join(mk_inputs_parts) + define_inputs = f"(define-const test_inputs_step_n{step} gold_Inputs ({mk_inputs_call}))\n" + + define_outputs = f"(define-const test_outputs_step_n{step} gold_Outputs (first (gold test_inputs_step_n{step} gold_State)))\n" + smt_commands = [define_inputs, define_outputs] + return smt_commands + + num_steps = 1000 + smt_commands = [] + for step in range(num_steps): + for step_command in set_step(inputs, step): + smt_commands.append(step_command) + + for command in smt_commands: + smt_io.write(command) + + assert smt_io.check_sat() == 'sat' + + # Store signal values + signals = {name: [] for name in list(inputs.keys()) + list(outputs.keys())} + # Retrieve and print values for each state + def hex_to_bin(value): + if value.startswith('x'): + hex_value = value[1:] # Remove the 'x' prefix + bin_value = bin(int(hex_value, 16))[2:] # Convert to binary and remove the '0b' prefix + return f'b{bin_value.zfill(len(hex_value) * 4)}' # Add 'b' prefix and pad with zeros + return value + + combined_assertions = [] + for step in range(num_steps): + print(f"Values for step {step + 1}:") + for input_name, width in inputs.items(): + value = smt_io.get(f'(gold_Inputs_{input_name} test_inputs_step_n{step})') + value = hex_to_bin(value[1:]) + print(f" {input_name}: {value}") + signals[input_name].append((step, value)) + for output_name, width in outputs.items(): + value = smt_io.get(f'(gold_Outputs_{output_name} test_outputs_step_n{step})') + value = hex_to_bin(value[1:]) + print(f" {output_name}: {value}") + signals[output_name].append((step, value)) + combined_assertions.append(f'(= (gold_Outputs_{output_name} test_outputs_step_n{step}) #{value})') + # Create a single assertion covering all timesteps + combined_condition = " ".join(combined_assertions) + smt_io.write(f'(assert (not (and {combined_condition})))') + + # Check the combined assertion + assert smt_io.check_sat(["unsat"]) == "unsat" + + def write_vcd(filename, signals, timescale='1 ns', date='today'): + with open(filename, 'w') as f: + # Write the header + f.write(f"$date\n {date}\n$end\n") + f.write(f"$timescale {timescale} $end\n") + + # Declare signals + f.write("$scope module gold $end\n") + for signal_name, changes in signals.items(): + signal_size = len(changes[0][1]) + f.write(f"$var wire {signal_size - 1} {signal_name} {signal_name} $end\n") + f.write("$upscope $end\n") + f.write("$enddefinitions $end\n") + + # Collect all unique timestamps + timestamps = sorted(set(time for changes in signals.values() for time, _ in changes)) + + # Write initial values + f.write("#0\n") + for signal_name, changes in signals.items(): + for time, value in changes: + if time == 0: + f.write(f"{value} {signal_name}\n") + + # Write value changes + for time in timestamps: + if time != 0: + f.write(f"#{time}\n") + for signal_name, changes in signals.items(): + for change_time, value in changes: + if change_time == time: + f.write(f"{value} {signal_name}\n") + + + write_vcd(vcd_path, signals) + +def simulate_smt(smt_file_path, vcd_path): + so = smtio.SmtOpts() + so.solver = "z3" + so.logic = "BV" + so.debug_print = True + smt_io = smtio.SmtIo(opts=so) + try: + simulate_smt_with_smtio(smt_file_path, vcd_path, smt_io) + finally: + smt_io.p_close() \ No newline at end of file diff --git a/backends/functional/smtio.py b/tests/functional/smtio.py similarity index 100% rename from backends/functional/smtio.py rename to tests/functional/smtio.py diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py new file mode 100644 index 00000000000..9b4fab970f1 --- /dev/null +++ b/tests/functional/test_functional.py @@ -0,0 +1,63 @@ +import subprocess +import pytest +import sys +import shlex +from pathlib import Path + +base_path = Path(__file__).resolve().parent.parent.parent + +def quote(path): + return shlex.quote(str(path)) + +def run(cmd, **kwargs): + print(' '.join([shlex.quote(str(x)) for x in cmd])) + status = subprocess.run(cmd, **kwargs) + assert status.returncode == 0, f"{cmd[0]} failed" + +def yosys(script): + run([base_path / 'yosys', '-Q', '-p', script]) + +def compile_cpp(in_path, out_path, args): + run(['g++', '-g', '-std=c++17'] + args + [str(in_path), '-o', str(out_path)]) + +def test_cxx(cell, parameters, tmp_path): + rtlil_file = tmp_path / 'rtlil.il' + vcdharness_cc_file = base_path / 'tests/functional/vcd_harness.cc' + cc_file = tmp_path / 'my_module_functional_cxx.cc' + vcdharness_exe_file = tmp_path / 'a.out' + vcd_functional_file = tmp_path / 'functional.vcd' + vcd_yosys_sim_file = tmp_path / 'yosys.vcd' + + with open(rtlil_file, 'w') as f: + cell.write_rtlil_file(f, parameters) + yosys(f"read_rtlil {quote(rtlil_file)} ; write_functional_cxx {quote(cc_file)}") + compile_cpp(vcdharness_cc_file, vcdharness_exe_file, ['-I', tmp_path, '-I', str(base_path / 'backends/functional/cxx_runtime')]) + run([str(vcdharness_exe_file.resolve()), str(vcd_functional_file)]) + try: + yosys(f"read_rtlil {quote(rtlil_file)}; sim -r {quote(vcd_functional_file)} -scope gold -vcd {quote(vcd_yosys_sim_file)} -timescale 1us -sim-gold") + except: + subprocess.run([base_path / 'yosys', '-Q', '-p', + f'read_rtlil {quote(rtlil_file)}; sim -vcd {quote(vcd_yosys_sim_file)} -r {quote(vcd_functional_file)} -scope gold -timescale 1us'], + capture_output=True, check=False) + raise + +def test_smt(cell, parameters, tmp_path): + import smt_vcd + + rtlil_file = tmp_path / 'rtlil.il' + smt_file = tmp_path / 'smtlib.smt' + vcd_functional_file = tmp_path / 'functional.vcd' + vcd_yosys_sim_file = tmp_path / 'yosys.vcd' + + with open(rtlil_file, 'w') as f: + cell.write_rtlil_file(f, parameters) + yosys(f"read_rtlil {quote(rtlil_file)} ; write_functional_smt2 {quote(smt_file)}") + run(['z3', smt_file]) + smt_vcd.simulate_smt(smt_file, vcd_functional_file) + try: + yosys(f"read_rtlil {quote(rtlil_file)}; sim -r {quote(vcd_functional_file)} -scope gold -vcd {quote(vcd_yosys_sim_file)} -timescale 1us -sim-gold") + except: + subprocess.run([base_path / 'yosys', '-Q', '-p', + f'read_rtlil {quote(rtlil_file)}; sim -vcd {quote(vcd_yosys_sim_file)} -r {quote(vcd_functional_file)} -scope gold -timescale 1us'], + capture_output=True, check=False) + raise \ No newline at end of file diff --git a/tests/functional/single_cells/vcd_harness.cc b/tests/functional/vcd_harness.cc similarity index 98% rename from tests/functional/single_cells/vcd_harness.cc rename to tests/functional/vcd_harness.cc index 9b3777c37fd..f99d2909ba0 100644 --- a/tests/functional/single_cells/vcd_harness.cc +++ b/tests/functional/vcd_harness.cc @@ -68,7 +68,7 @@ int main(int argc, char **argv) const std::string functional_vcd_filename = argv[1]; - constexpr int steps = 10; + constexpr int steps = 1000; constexpr int number_timescale = 1; const std::string units_timescale = "us"; gold::Inputs inputs; From 6922633b0b1255975ceda7f3dd734946d85f08ea Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Tue, 16 Jul 2024 17:55:26 +0100 Subject: [PATCH 53/92] fix a few bugs in the functional backend and refactor the testing --- kernel/functionalir.cc | 40 ++- tests/functional/conftest.py | 26 +- tests/functional/rtlil_cells.py | 393 +++++++++++++++------------- tests/functional/smt_vcd.py | 55 +++- tests/functional/test_functional.py | 55 ++-- tests/functional/vcd_harness.cc | 69 +++-- 6 files changed, 367 insertions(+), 271 deletions(-) diff --git a/kernel/functionalir.cc b/kernel/functionalir.cc index 6983facfb72..b8fa6b88e5f 100644 --- a/kernel/functionalir.cc +++ b/kernel/functionalir.cc @@ -226,26 +226,36 @@ class CellSimplifier { T b = extend(inputs.at(ID(B)), b_width, width, is_signed); if(is_signed) { if(cellType == ID($div)) { + // divide absolute values, then flip the sign if input signs differ + // but extend the width first, to handle the case (most negative value) / (-1) T abs_y = factory.unsigned_div(abs(a, width), abs(b, width), width); T out_sign = factory.not_equal(sign(a, width), sign(b, width), 1); - return neg_if(extend(abs_y, width, y_width, true), y_width, out_sign); + return neg_if(extend(abs_y, width, y_width, false), y_width, out_sign); } else if(cellType == ID($mod)) { + // similar to division but output sign == divisor sign T abs_y = factory.unsigned_mod(abs(a, width), abs(b, width), width); - return neg_if(extend(abs_y, width, y_width, true), y_width, sign(a, width)); + return neg_if(extend(abs_y, width, y_width, false), y_width, sign(a, width)); } else if(cellType == ID($divfloor)) { + // if b is negative, flip both signs so that b is positive T b_sign = sign(b, width); T a1 = neg_if(a, width, b_sign); T b1 = neg_if(b, width, b_sign); - T a1_sign = sign(a1, width); + // if a is now negative, calculate ~((~a) / b) = -((-a - 1) / b + 1) + // which equals the negative of (-a) / b with rounding up rather than down + // note that to handle the case where a = most negative value properly, + // we have to calculate a1_sign from the original values rather than using sign(a1, width) + T a1_sign = factory.bitwise_and(factory.not_equal(sign(a, width), sign(b, width), 1), reduce_or(a, width), 1); T a2 = factory.mux(a1, factory.bitwise_not(a1, width), a1_sign, width); T y1 = factory.unsigned_div(a2, b1, width); - T y2 = factory.mux(y1, factory.bitwise_not(y1, width), a1_sign, width); - return extend(y2, width, y_width, true); + T y2 = extend(y1, width, y_width, false); + return factory.mux(y2, factory.bitwise_not(y2, y_width), a1_sign, y_width); } else if(cellType == ID($modfloor)) { + // calculate |a| % |b| and then subtract from |b| if input signs differ and the remainder is non-zero T abs_b = abs(b, width); T abs_y = factory.unsigned_mod(abs(a, width), abs_b, width); T flip_y = factory.bitwise_and(factory.bitwise_xor(sign(a, width), sign(b, width), 1), factory.reduce_or(abs_y, width), 1); T y_flipped = factory.mux(abs_y, factory.sub(abs_b, abs_y, width), flip_y, width); + // since y_flipped is strictly less than |b|, the top bit is always 0 and we can just sign extend the flipped result T y = neg_if(y_flipped, width, sign(b, b_width)); return extend(y, width, y_width, true); } else @@ -261,22 +271,8 @@ class CellSimplifier { } else if (cellType == ID($lut)) { int width = parameters.at(ID(WIDTH)).as_int(); Const lut_table = parameters.at(ID(LUT)); - T a = inputs.at(ID(A)); - // Output initialization - T y = factory.constant(Const(0, 1)); - // Iterate over each possible input combination - for (int i = 0; i < (1 << width); ++i) { - // Create a constant representing the value of i - T i_val = factory.constant(Const(i, width)); - // Check if the input matches this value - T match = factory.equal(a, i_val, width); - // Get the corresponding LUT value - bool lut_val = lut_table.bits[i] == State::S1; - T lut_output = factory.constant(Const(lut_val, 1)); - // Use a multiplexer to select the correct output based on the match - y = factory.mux(y, lut_output, match, 1); - } - return y; + lut_table.extu(1 << width); + return handle_bmux(factory.constant(lut_table), inputs.at(ID(A)), 1 << width, 0, 1, width, width); } else if (cellType == ID($bwmux)) { int width = parameters.at(ID(WIDTH)).as_int(); T a = inputs.at(ID(A)); @@ -526,7 +522,7 @@ void FunctionalIR::topological_sort() { if(scc) log_error("combinational loops, aborting\n"); } -IdString merge_name(IdString a, IdString b) { +static IdString merge_name(IdString a, IdString b) { if(a[0] == '$' && b[0] == '\\') return b; else diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py index 3a66abaab21..12db7f1d09b 100644 --- a/tests/functional/conftest.py +++ b/tests/functional/conftest.py @@ -1,14 +1,30 @@ import pytest from rtlil_cells import generate_test_cases +import random + +random_seed = random.getrandbits(32) def pytest_addoption(parser): - parser.addoption( - "--per-cell", type=int, default=None, help="run only N tests per cell" - ) + parser.addoption("--per-cell", type=int, default=None, help="run only N tests per cell") + parser.addoption("--steps", type=int, default=1000, help="run each test for N steps") + parser.addoption("--seed", type=int, default=random_seed, help="seed for random number generation, use random seed if unspecified") + +def pytest_collection_finish(session): + print('random seed: {}'.format(session.config.getoption("seed"))) + +@pytest.fixture +def num_steps(request): + return request.config.getoption("steps") + +@pytest.fixture +def rnd(request): + seed1 = request.config.getoption("seed") + return lambda seed2: random.Random('{}-{}'.format(seed1, seed2)) def pytest_generate_tests(metafunc): if "cell" in metafunc.fixturenames: - print(dir(metafunc.config)) per_cell = metafunc.config.getoption("per_cell", default=None) - names, cases = generate_test_cases(per_cell) + seed1 = metafunc.config.getoption("seed") + rnd = lambda seed2: random.Random('{}-{}'.format(seed1, seed2)) + names, cases = generate_test_cases(per_cell, rnd) metafunc.parametrize("cell,parameters", cases, ids=names) \ No newline at end of file diff --git a/tests/functional/rtlil_cells.py b/tests/functional/rtlil_cells.py index e96baa18ee8..b4c0ad1c1ef 100644 --- a/tests/functional/rtlil_cells.py +++ b/tests/functional/rtlil_cells.py @@ -1,30 +1,6 @@ from itertools import chain import random -widths = [ - (16, 32, 48, True), - (16, 32, 48, False), - (32, 16, 48, True), - (32, 16, 48, False), - (32, 32, 16, True), - (32, 32, 16, False) -] - -shift_widths = [ - (32, 6, 32, True, False), - (32, 6, 32, False, False), - (32, 6, 64, True, False), - (32, 6, 64, False, False), - (32, 32, 16, True, False), - (32, 32, 16, False, False), - (32, 6, 32, True, True), - (32, 6, 32, False, True), - (32, 6, 64, True, True), - (32, 6, 64, False, True), - (32, 32, 16, True, True), - (32, 32, 16, False, True), -] - def write_rtlil_cell(f, cell_type, inputs, outputs, parameters): f.write('autoidx 1\n') f.write('module \\gold\n') @@ -37,207 +13,260 @@ def write_rtlil_cell(f, cell_type, inputs, outputs, parameters): idx += 1 f.write(f'\tcell ${cell_type} \\UUT\n') for (name, value) in parameters.items(): - f.write(f'\t\tparameter \\{name} {value}\n') + if value >= 2**32: + f.write(f'\t\tparameter \\{name} {value.bit_length()}\'{value:b}\n') + else: + f.write(f'\t\tparameter \\{name} {value}\n') for name in chain(inputs.keys(), outputs.keys()): f.write(f'\t\tconnect \\{name} \\{name}\n') f.write(f'\tend\nend\n') class BaseCell: - def __init__(self, name): + def __init__(self, name, parameters, inputs, outputs, test_values): self.name = name + self.parameters = parameters + self.inputs = inputs + self.outputs = outputs + self.test_values = test_values + def get_port_width(self, port, parameters): + def parse_specifier(spec): + if isinstance(spec, int): + return spec + if isinstance(spec, str): + return parameters[spec] + if callable(spec): + return spec(parameters) + assert False, "expected int, str or lambda" + if port in self.inputs: + return parse_specifier(self.inputs[port]) + elif port in self.outputs: + return parse_specifier(self.outputs[port]) + else: + assert False, "expected input or output" + def generate_tests(self, rnd): + def print_parameter(v): + if isinstance(v, bool): + return "S" if v else "U" + else: + return str(v) + for values in self.test_values: + if isinstance(values, int): + values = [values] + name = '-'.join([print_parameter(v) for v in values]) + parameters = {parameter: int(values[i]) for i, parameter in enumerate(self.parameters)} + if self.is_test_valid(values): + yield (name, parameters) + def write_rtlil_file(self, path, parameters): + inputs = {port: self.get_port_width(port, parameters) for port in self.inputs} + outputs = {port: self.get_port_width(port, parameters) for port in self.outputs} + with open(path, 'w') as f: + write_rtlil_cell(f, self.name, inputs, outputs, parameters) + def is_test_valid(self, values): + return True class UnaryCell(BaseCell): - def __init__(self, name): - super().__init__(name) - def generate_tests(self): - for (a_width, _, y_width, signed) in widths: - yield (f'{a_width}-{y_width}-{'S' if signed else 'U'}', - {'A_WIDTH' : a_width, - 'A_SIGNED' : int(signed), - 'Y_WIDTH' : y_width}) - def write_rtlil_file(self, f, parameters): - write_rtlil_cell(f, self.name, {'A': parameters['A_WIDTH']}, {'Y': parameters['Y_WIDTH']}, parameters) + def __init__(self, name, values): + super().__init__(name, ['A_WIDTH', 'Y_WIDTH', 'A_SIGNED'], {'A': 'A_WIDTH'}, {'Y': 'Y_WIDTH'}, values) class BinaryCell(BaseCell): - def __init__(self, name): - super().__init__(name) - def generate_tests(self): - for (a_width, b_width, y_width, signed) in widths: - yield (f'{a_width}-{b_width}-{y_width}-{'S' if signed else 'U'}', - {'A_WIDTH' : a_width, - 'A_SIGNED' : int(signed), - 'B_WIDTH' : b_width, - 'B_SIGNED' : int(signed), - 'Y_WIDTH' : y_width}) - def write_rtlil_file(self, f, parameters): - write_rtlil_cell(f, self.name, {'A': parameters['A_WIDTH'], 'B': parameters['B_WIDTH']}, {'Y': parameters['Y_WIDTH']}, parameters) + def __init__(self, name, values): + super().__init__(name, ['A_WIDTH', 'B_WIDTH', 'Y_WIDTH', 'A_SIGNED', 'B_SIGNED'], {'A': 'A_WIDTH', 'B': 'B_WIDTH'}, {'Y': 'Y_WIDTH'}, values) class ShiftCell(BaseCell): - def __init__(self, name): - super().__init__(name) - def generate_tests(self): - for (a_width, b_width, y_width, a_signed, b_signed) in shift_widths: - if not self.name in ('shift', 'shiftx') and b_signed: continue - if self.name == 'shiftx' and a_signed: continue - yield (f'{a_width}-{b_width}-{y_width}-{'S' if a_signed else 'U'}{'S' if b_signed else 'U'}', - {'A_WIDTH' : a_width, - 'A_SIGNED' : int(a_signed), - 'B_WIDTH' : b_width, - 'B_SIGNED' : int(b_signed), - 'Y_WIDTH' : y_width}) - def write_rtlil_file(self, f, parameters): - write_rtlil_cell(f, self.name, {'A': parameters['A_WIDTH'], 'B': parameters['B_WIDTH']}, {'Y': parameters['Y_WIDTH']}, parameters) + def __init__(self, name, values): + super().__init__(name, ['A_WIDTH', 'B_WIDTH', 'Y_WIDTH', 'A_SIGNED', 'B_SIGNED'], {'A': 'A_WIDTH', 'B': 'B_WIDTH'}, {'Y': 'Y_WIDTH'}, values) + def is_test_valid(self, values): + (a_width, b_width, y_width, a_signed, b_signed) = values + if not self.name in ('shift', 'shiftx') and b_signed: return False + if self.name == 'shiftx' and a_signed: return False + return True class MuxCell(BaseCell): - def __init__(self, name): - super().__init__(name) - def generate_tests(self): - for width in [10, 20, 40]: - yield (f'{width}', {'WIDTH' : width}) - def write_rtlil_file(self, f, parameters): - write_rtlil_cell(f, self.name, {'A': parameters['WIDTH'], 'B': parameters['WIDTH'], 'S': 1}, {'Y': parameters['WIDTH']}, parameters) + def __init__(self, name, values): + super().__init__(name, ['WIDTH'], {'A': 'WIDTH', 'B': 'WIDTH', 'S': 1}, {'Y': 'WIDTH'}, values) class BWCell(BaseCell): - def __init__(self, name): - super().__init__(name) - def generate_tests(self): - for width in [10, 20, 40]: - yield (f'{width}', {'WIDTH' : width}) - def write_rtlil_file(self, f, parameters): - inputs = {'A': parameters['WIDTH'], 'B': parameters['WIDTH']} - if self.name == "bwmux": inputs['S'] = parameters['WIDTH'] - write_rtlil_cell(f, self.name, inputs, {'Y': parameters['WIDTH']}, parameters) + def __init__(self, name, values): + inputs = {'A': 'WIDTH', 'B': 'WIDTH'} + if name == "bwmux": inputs['S'] = 'WIDTH' + super().__init__(name, ['WIDTH'], inputs, {'Y': 'WIDTH'}, values) class PMuxCell(BaseCell): - def __init__(self, name): - super().__init__(name) - def generate_tests(self): - for (width, s_width) in [(10, 1), (10, 4), (20, 4)]: - yield (f'{width}-{s_width}', - {'WIDTH' : width, - 'S_WIDTH' : s_width}) - def write_rtlil_file(self, f, parameters): - s_width = parameters['S_WIDTH'] - b_width = parameters['WIDTH'] * s_width - write_rtlil_cell(f, self.name, {'A': parameters['WIDTH'], 'B': b_width, 'S': s_width}, {'Y': parameters['WIDTH']}, parameters) + def __init__(self, name, values): + b_width = lambda par: par['WIDTH'] * par['S_WIDTH'] + super().__init__(name, ['WIDTH', 'S_WIDTH'], {'A': 'WIDTH', 'B': b_width, 'S': 'S_WIDTH'}, {'Y': 'WIDTH'}, values) class BMuxCell(BaseCell): - def __init__(self, name): - super().__init__(name) - def generate_tests(self): - for (width, s_width) in [(10, 1), (10, 2), (10, 4)]: - yield (f'{width}-{s_width}', {'WIDTH' : width, 'S_WIDTH' : s_width}) - def write_rtlil_file(self, f, parameters): - write_rtlil_cell(f, self.name, {'A': parameters['WIDTH'] << parameters['S_WIDTH'], 'S': parameters['S_WIDTH']}, {'Y': parameters['WIDTH']}, parameters) + def __init__(self, name, values): + a_width = lambda par: par['WIDTH'] << par['S_WIDTH'] + super().__init__(name, ['WIDTH', 'S_WIDTH'], {'A': a_width, 'S': 'S_WIDTH'}, {'Y': 'WIDTH'}, values) class DemuxCell(BaseCell): - def __init__(self, name): - super().__init__(name) - def generate_tests(self): - for (width, s_width) in [(10, 1), (32, 2), (16, 4)]: - yield (f'{width}-{s_width}', {'WIDTH' : width, 'S_WIDTH' : s_width}) - def write_rtlil_file(self, f, parameters): - write_rtlil_cell(f, self.name, {'A': parameters['WIDTH'], 'S': parameters['S_WIDTH']}, {'Y': parameters['WIDTH'] << parameters['S_WIDTH']}, parameters) - -def seeded_randint(seed, a, b): - r = random.getstate() - random.seed(seed) - n = random.randint(a, b) - random.setstate(r) - return n + def __init__(self, name, values): + y_width = lambda par: par['WIDTH'] << par['S_WIDTH'] + super().__init__(name, ['WIDTH', 'S_WIDTH'], {'A': 'WIDTH', 'S': 'S_WIDTH'}, {'Y': y_width}, values) class LUTCell(BaseCell): - def __init__(self, name): - super().__init__(name) - def generate_tests(self): - for width in [4, 6, 8]: - lut = seeded_randint(width, 0, 2**width - 1) + def __init__(self, name, values): + super().__init__(name, ['WIDTH', 'LUT'], {'A': 'WIDTH'}, {'Y': 1}, values) + def generate_tests(self, rnd): + for width in self.test_values: + lut = rnd(f'lut-{width}').getrandbits(2**width) yield (f'{width}', {'WIDTH' : width, 'LUT' : lut}) - def write_rtlil_file(self, f, parameters): - write_rtlil_cell(f, self.name, {'A': parameters['WIDTH']}, {'Y': 1}, parameters) class ConcatCell(BaseCell): - def __init__(self, name): - super().__init__(name) - def generate_tests(self): - for (a_width, b_width) in [(16, 16), (8, 14), (20, 10)]: - yield (f'{a_width}-{b_width}', {'A_WIDTH' : a_width, 'B_WIDTH' : b_width}) - def write_rtlil_file(self, f, parameters): - write_rtlil_cell(f, self.name, {'A': parameters['A_WIDTH'], 'B' : parameters['B_WIDTH']}, {'Y': parameters['A_WIDTH'] + parameters['B_WIDTH']}, parameters) + def __init__(self, name, values): + y_width = lambda par: par['A_WIDTH'] + par['B_WIDTH'] + super().__init__(name, ['A_WIDTH', 'B_WIDTH'], {'A': 'A_WIDTH', 'B': 'B_WIDTH'}, {'Y': y_width}, values) class SliceCell(BaseCell): - def __init__(self, name): - super().__init__(name) - def generate_tests(self): - for (a_width, offset, y_width) in [(32, 10, 15), (8, 0, 4), (10, 0, 10)]: - yield (f'{a_width}-{offset}-{y_width}', {'A_WIDTH' : a_width, 'OFFSET' : offset, 'Y_WIDTH': y_width}) - def write_rtlil_file(self, f, parameters): - write_rtlil_cell(f, self.name, {'A': parameters['A_WIDTH']}, {'Y': parameters['Y_WIDTH']}, parameters) + def __init__(self, name, values): + super().__init__(name, ['A_WIDTH', 'OFFSET', 'Y_WIDTH'], {'A': 'A_WIDTH'}, {'Y': 'Y_WIDTH'}, values) class FailCell(BaseCell): def __init__(self, name): - super().__init__(name) - def generate_tests(self): + super().__init__(name, [], {}, {}) + def generate_tests(self, rnd): yield ('', {}) - def write_rtlil_file(self, f, parameters): + def write_rtlil_file(self, path, parameters): raise Exception(f'\'{self.name}\' cell unimplemented in test generator') +class FFCell(BaseCell): + def __init__(self, name, values): + super().__init__(name, ['WIDTH'], ['D'], ['Q'], values) + def write_rtlil_file(self, path, parameters): + from test_functional import yosys_synth + verilog_file = path.parent / 'verilog.v' + with open(verilog_file, 'w') as f: + f.write(""" +module gold( + input wire clk, + input wire [{0}:0] D, + output reg [{0}:0] Q +); + always @(posedge clk) + Q <= D; +endmodule""".format(parameters['WIDTH'] - 1)) + yosys_synth(verilog_file, path) + +class MemCell(BaseCell): + def __init__(self, name, values): + super().__init__(name, ['DATA_WIDTH', 'ADDR_WIDTH'], {'WA': 'ADDR_WIDTH', 'RA': 'ADDR_WIDTH', 'WD': 'DATA_WIDTH'}, {'RD': 'DATA_WIDTH'}, values) + def write_rtlil_file(self, path, parameters): + from test_functional import yosys_synth + verilog_file = path.parent / 'verilog.v' + with open(verilog_file, 'w') as f: + f.write(""" +module gold( + input wire clk, + input wire [{1}:0] WA, + input wire [{0}:0] WD, + output reg [{0}:0] RD +); + reg [{0}:0] mem[0:{1}]; + always @(*) + RD = mem[RA]; + always @(posedge clk) + mem[WA] <= WD; +endmodule""".format(parameters['DATA_WIDTH'] - 1, parameters['ADDR_WIDTH'] - 1)) + yosys_synth(verilog_file, path) + +binary_widths = [ + # try to cover extending A operand, extending B operand, extending/truncating result + (16, 32, 48, True, True), + (16, 32, 48, False, False), + (32, 16, 48, True, True), + (32, 16, 48, False, False), + (32, 32, 16, True, True), + (32, 32, 16, False, False), + # have at least one test that checks small inputs, which will exercise the cornercases more + (4, 4, 8, True, True), + (4, 4, 8, False, False) +] + +unary_widths = [ + (6, 12, True), + (6, 12, False), + (32, 16, True), + (32, 16, False) +] + +# note that meaningless combinations of signednesses are eliminated, +# like e.g. most shift operations don't take signed shift amounts +shift_widths = [ + # one set of tests that definitely checks all possible shift amounts + # with a bigger result width to make sure it's not truncated + (32, 6, 64, True, False), + (32, 6, 64, False, False), + (32, 6, 64, True, True), + (32, 6, 64, False, True), + # one set that checks very oversized shifts + (32, 32, 64, True, False), + (32, 32, 64, False, False), + (32, 32, 64, True, True), + (32, 32, 64, False, True), + # at least one test where the result is going to be truncated + (32, 6, 16, False, False) +] + rtlil_cells = [ - UnaryCell("not"), - UnaryCell("pos"), - UnaryCell("neg"), - BinaryCell("and"), - BinaryCell("or"), - BinaryCell("xor"), - BinaryCell("xnor"), - UnaryCell("reduce_and"), - UnaryCell("reduce_or"), - UnaryCell("reduce_xor"), - UnaryCell("reduce_xnor"), - UnaryCell("reduce_bool"), - ShiftCell("shl"), - ShiftCell("shr"), - ShiftCell("sshl"), - ShiftCell("sshr"), - ShiftCell("shift"), - ShiftCell("shiftx"), + UnaryCell("not", unary_widths), + UnaryCell("pos", unary_widths), + UnaryCell("neg", unary_widths), + BinaryCell("and", binary_widths), + BinaryCell("or", binary_widths), + BinaryCell("xor", binary_widths), + BinaryCell("xnor", binary_widths), + UnaryCell("reduce_and", unary_widths), + UnaryCell("reduce_or", unary_widths), + UnaryCell("reduce_xor", unary_widths), + UnaryCell("reduce_xnor", unary_widths), + UnaryCell("reduce_bool", unary_widths), + ShiftCell("shl", shift_widths), + ShiftCell("shr", shift_widths), + ShiftCell("sshl", shift_widths), + ShiftCell("sshr", shift_widths), + ShiftCell("shift", shift_widths), + ShiftCell("shiftx", shift_widths), # ("fa", ["A", "B", "C", "X", "Y"]), # ("lcu", ["P", "G", "CI", "CO"]), # ("alu", ["A", "B", "CI", "BI", "X", "Y", "CO"]), - BinaryCell("lt"), - BinaryCell("le"), - BinaryCell("eq"), - BinaryCell("ne"), - BinaryCell("eqx"), - BinaryCell("nex"), - BinaryCell("ge"), - BinaryCell("gt"), - BinaryCell("add"), - BinaryCell("sub"), - BinaryCell("mul"), + BinaryCell("lt", binary_widths), + BinaryCell("le", binary_widths), + BinaryCell("eq", binary_widths), + BinaryCell("ne", binary_widths), + BinaryCell("eqx", binary_widths), + BinaryCell("nex", binary_widths), + BinaryCell("ge", binary_widths), + BinaryCell("gt", binary_widths), + BinaryCell("add", binary_widths), + BinaryCell("sub", binary_widths), + BinaryCell("mul", binary_widths), # BinaryCell("macc"), - BinaryCell("div"), - BinaryCell("mod"), - BinaryCell("divfloor"), - BinaryCell("modfloor"), - BinaryCell("pow"), - UnaryCell("logic_not"), - BinaryCell("logic_and"), - BinaryCell("logic_or"), - SliceCell("slice"), - ConcatCell("concat"), - MuxCell("mux"), - BMuxCell("bmux"), - PMuxCell("pmux"), - DemuxCell("demux"), - LUTCell("lut"), + BinaryCell("div", binary_widths), + BinaryCell("mod", binary_widths), + BinaryCell("divfloor", binary_widths), + BinaryCell("modfloor", binary_widths), + BinaryCell("pow", binary_widths), + UnaryCell("logic_not", unary_widths), + BinaryCell("logic_and", binary_widths), + BinaryCell("logic_or", binary_widths), + SliceCell("slice", [(32, 10, 15), (8, 0, 4), (10, 0, 10)]), + ConcatCell("concat", [(16, 16), (8, 14), (20, 10)]), + MuxCell("mux", [10, 16, 40]), + BMuxCell("bmux", [(10, 1), (10, 2), (10, 4)]), + PMuxCell("pmux", [(10, 1), (10, 4), (20, 4)]), + DemuxCell("demux", [(10, 1), (32, 2), (16, 4)]), + LUTCell("lut", [4, 6, 8]), # ("sop", ["A", "Y"]), # ("tribuf", ["A", "EN", "Y"]), # ("specify2", ["EN", "SRC", "DST"]), # ("specify3", ["EN", "SRC", "DST", "DAT"]), # ("specrule", ["EN_SRC", "EN_DST", "SRC", "DST"]), - BWCell("bweqx"), - BWCell("bwmux"), + BWCell("bweqx", [10, 16, 40]), + BWCell("bwmux", [10, 16, 40]), + FFCell("ff", [10, 20, 40]), + MemCell("mem", [(32, 4)]) # ("assert", ["A", "EN"]), # ("assume", ["A", "EN"]), # ("live", ["A", "EN"]), @@ -260,12 +289,12 @@ def write_rtlil_file(self, f, parameters): # ("scopeinfo", []), ] -def generate_test_cases(per_cell): +def generate_test_cases(per_cell, rnd): tests = [] names = [] for cell in rtlil_cells: seen_names = set() - for (name, parameters) in cell.generate_tests(): + for (name, parameters) in cell.generate_tests(rnd): if not name in seen_names: seen_names.add(name) tests.append((cell, parameters)) diff --git a/tests/functional/smt_vcd.py b/tests/functional/smt_vcd.py index c38fe1bde7e..37d2a209f71 100644 --- a/tests/functional/smt_vcd.py +++ b/tests/functional/smt_vcd.py @@ -1,6 +1,5 @@ import sys import argparse -import random import os import smtio import re @@ -40,9 +39,10 @@ def retrieve(self): rv, self.stack[0] = self.stack[0], [] return rv -def simulate_smt_with_smtio(smt_file_path, vcd_path, smt_io): +def simulate_smt_with_smtio(smt_file_path, vcd_path, smt_io, num_steps, rnd): inputs = {} outputs = {} + states = {} def handle_datatype(lst): print(lst) @@ -60,6 +60,14 @@ def handle_datatype(lst): bitvec_size = declaration[1][2] assert output_name.startswith("gold_Outputs_") outputs[output_name[len("gold_Outputs_"):]] = int(bitvec_size) + elif datatype_name.endswith("_State"): + for declaration in declarations: + state_name = declaration[0] + assert state_name.startswith("gold_State_") + if declaration[1][0] == "_": + states[state_name[len("gold_State_"):]] = int(declaration[1][2]) + else: + states[state_name[len("gold_State_"):]] = (declaration[1][1][2], declaration[1][2][2]) parser = SExprParser() with open(smt_file_path, 'r') as smt_file: @@ -73,25 +81,44 @@ def handle_datatype(lst): parser.finish() assert smt_io.check_sat() == 'sat' + def initial_state(states): + mk_state_parts = [] + rv = [] + for name, width in states.items(): + if isinstance(width, int): + binary_string = format(0, '0{}b'.format(width)) + mk_state_parts.append(f"#b{binary_string}") + else: + binary_string = format(0, '0{}b'.format(width[1])) + rv.append(f"(declare-const test_state_initial_mem_{name} (Array (_ BitVec {width[0]}) (_ BitVec {width[1]})))") + rv.append(f"(assert (forall ((i (_ BitVec {width[0]}))) (= (select test_state_initial_mem_{name} i) #b{binary_string})))") + mk_state_parts.append(f"test_state_initial_mem_{name}") + if len(states) == 0: + mk_state_call = "gold_State" + else: + mk_state_call = "(gold_State {})".format(" ".join(mk_state_parts)) + rv.append(f"(define-const test_state_step_n0 gold_State {mk_state_call})\n") + return rv + def set_step(inputs, step): # This function assumes 'inputs' is a dictionary like {"A": 5, "B": 4} # and 'input_values' is a dictionary like {"A": 5, "B": 13} specifying the concrete values for each input. mk_inputs_parts = [] for input_name, width in inputs.items(): - value = random.getrandbits(width) # Generate a random number up to the maximum value for the bit size + value = rnd.getrandbits(width) # Generate a random number up to the maximum value for the bit size binary_string = format(value, '0{}b'.format(width)) # Convert value to binary with leading zeros mk_inputs_parts.append(f"#b{binary_string}") mk_inputs_call = "gold_Inputs " + " ".join(mk_inputs_parts) - define_inputs = f"(define-const test_inputs_step_n{step} gold_Inputs ({mk_inputs_call}))\n" - - define_outputs = f"(define-const test_outputs_step_n{step} gold_Outputs (first (gold test_inputs_step_n{step} gold_State)))\n" - smt_commands = [define_inputs, define_outputs] - return smt_commands - - num_steps = 1000 - smt_commands = [] + return [ + f"(define-const test_inputs_step_n{step} gold_Inputs ({mk_inputs_call}))\n", + f"(define-const test_results_step_n{step} (Pair gold_Outputs gold_State) (gold test_inputs_step_n{step} test_state_step_n{step}))\n", + f"(define-const test_outputs_step_n{step} gold_Outputs (first test_results_step_n{step}))\n", + f"(define-const test_state_step_n{step+1} gold_State (second test_results_step_n{step}))\n", + ] + + smt_commands = initial_state(states) for step in range(num_steps): for step_command in set_step(inputs, step): smt_commands.append(step_command) @@ -168,13 +195,13 @@ def write_vcd(filename, signals, timescale='1 ns', date='today'): write_vcd(vcd_path, signals) -def simulate_smt(smt_file_path, vcd_path): +def simulate_smt(smt_file_path, vcd_path, num_steps, rnd): so = smtio.SmtOpts() so.solver = "z3" - so.logic = "BV" + so.logic = "ABV" so.debug_print = True smt_io = smtio.SmtIo(opts=so) try: - simulate_smt_with_smtio(smt_file_path, vcd_path, smt_io) + simulate_smt_with_smtio(smt_file_path, vcd_path, smt_io, num_steps, rnd) finally: smt_io.p_close() \ No newline at end of file diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index 9b4fab970f1..86e515d90a9 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -6,11 +6,14 @@ base_path = Path(__file__).resolve().parent.parent.parent +# quote a string or pathlib path so that it can be used by bash or yosys +# TODO: is this really appropriate for yosys? def quote(path): return shlex.quote(str(path)) +# run a shell command and require the return code to be 0 def run(cmd, **kwargs): - print(' '.join([shlex.quote(str(x)) for x in cmd])) + print(' '.join([quote(x) for x in cmd])) status = subprocess.run(cmd, **kwargs) assert status.returncode == 0, f"{cmd[0]} failed" @@ -20,7 +23,24 @@ def yosys(script): def compile_cpp(in_path, out_path, args): run(['g++', '-g', '-std=c++17'] + args + [str(in_path), '-o', str(out_path)]) -def test_cxx(cell, parameters, tmp_path): +def yosys_synth(verilog_file, rtlil_file): + yosys(f"read_verilog {quote(verilog_file)} ; prep ; clk2fflogic ; write_rtlil {quote(rtlil_file)}") + +# simulate an rtlil file with yosys, comparing with a given vcd file, and writing out the yosys simulation results into a second vcd file +def yosys_sim(rtlil_file, vcd_reference_file, vcd_out_file): + try: + yosys(f"read_rtlil {quote(rtlil_file)}; sim -r {quote(vcd_reference_file)} -scope gold -vcd {quote(vcd_out_file)} -timescale 1us -sim-gold") + except: + # if yosys sim fails it's probably because of a simulation mismatch + # since yosys sim aborts on simulation mismatch to generate vcd output + # we have to re-run with a different set of flags + # on this run we ignore output and return code, we just want a best-effort attempt to get a vcd + subprocess.run([base_path / 'yosys', '-Q', '-p', + f'read_rtlil {quote(rtlil_file)}; sim -vcd {quote(vcd_out_file)} -a -r {quote(vcd_reference_file)} -scope gold -timescale 1us'], + capture_output=True, check=False) + raise + +def test_cxx(cell, parameters, tmp_path, num_steps, rnd): rtlil_file = tmp_path / 'rtlil.il' vcdharness_cc_file = base_path / 'tests/functional/vcd_harness.cc' cc_file = tmp_path / 'my_module_functional_cxx.cc' @@ -28,20 +48,14 @@ def test_cxx(cell, parameters, tmp_path): vcd_functional_file = tmp_path / 'functional.vcd' vcd_yosys_sim_file = tmp_path / 'yosys.vcd' - with open(rtlil_file, 'w') as f: - cell.write_rtlil_file(f, parameters) + cell.write_rtlil_file(rtlil_file, parameters) yosys(f"read_rtlil {quote(rtlil_file)} ; write_functional_cxx {quote(cc_file)}") compile_cpp(vcdharness_cc_file, vcdharness_exe_file, ['-I', tmp_path, '-I', str(base_path / 'backends/functional/cxx_runtime')]) - run([str(vcdharness_exe_file.resolve()), str(vcd_functional_file)]) - try: - yosys(f"read_rtlil {quote(rtlil_file)}; sim -r {quote(vcd_functional_file)} -scope gold -vcd {quote(vcd_yosys_sim_file)} -timescale 1us -sim-gold") - except: - subprocess.run([base_path / 'yosys', '-Q', '-p', - f'read_rtlil {quote(rtlil_file)}; sim -vcd {quote(vcd_yosys_sim_file)} -r {quote(vcd_functional_file)} -scope gold -timescale 1us'], - capture_output=True, check=False) - raise + seed = str(rnd(cell.name + "-cxx").getrandbits(32)) + run([str(vcdharness_exe_file.resolve()), str(vcd_functional_file), str(num_steps), str(seed)]) + yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file) -def test_smt(cell, parameters, tmp_path): +def test_smt(cell, parameters, tmp_path, num_steps, rnd): import smt_vcd rtlil_file = tmp_path / 'rtlil.il' @@ -49,15 +63,8 @@ def test_smt(cell, parameters, tmp_path): vcd_functional_file = tmp_path / 'functional.vcd' vcd_yosys_sim_file = tmp_path / 'yosys.vcd' - with open(rtlil_file, 'w') as f: - cell.write_rtlil_file(f, parameters) + cell.write_rtlil_file(rtlil_file, parameters) yosys(f"read_rtlil {quote(rtlil_file)} ; write_functional_smt2 {quote(smt_file)}") - run(['z3', smt_file]) - smt_vcd.simulate_smt(smt_file, vcd_functional_file) - try: - yosys(f"read_rtlil {quote(rtlil_file)}; sim -r {quote(vcd_functional_file)} -scope gold -vcd {quote(vcd_yosys_sim_file)} -timescale 1us -sim-gold") - except: - subprocess.run([base_path / 'yosys', '-Q', '-p', - f'read_rtlil {quote(rtlil_file)}; sim -vcd {quote(vcd_yosys_sim_file)} -r {quote(vcd_functional_file)} -scope gold -timescale 1us'], - capture_output=True, check=False) - raise \ No newline at end of file + run(['z3', smt_file]) # check if output is valid smtlib before continuing + smt_vcd.simulate_smt(smt_file, vcd_functional_file, num_steps, rnd(cell.name + "-smt")) + yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file) \ No newline at end of file diff --git a/tests/functional/vcd_harness.cc b/tests/functional/vcd_harness.cc index f99d2909ba0..f01adf21832 100644 --- a/tests/functional/vcd_harness.cc +++ b/tests/functional/vcd_harness.cc @@ -2,15 +2,46 @@ #include #include #include +#include +#include #include "my_module_functional_cxx.cc" +std::string vcd_name_mangle(std::string name) { + std::string ret = name; + bool escape = ret.empty() || !isalpha(ret[0]) && ret[0] != '_'; + for(size_t i = 0; i < ret.size(); i++) { + if(isspace(ret[i])) ret[i] = '_'; + if(!isalnum(ret[i]) && ret[i] != '_' && ret[i] != '$') + escape = true; + } + if(escape) + return "\\" + ret; + else + return ret; +} +std::unordered_map codes; + struct DumpHeader { std::ofstream &ofs; + std::string code = "!"; DumpHeader(std::ofstream &ofs) : ofs(ofs) {} + void increment_code() { + for(size_t i = 0; i < code.size(); i++) + if(code[i]++ == '~') + code[i] = '!'; + else + return; + code.push_back('!'); + } template void operator()(const char *name, Signal value) { - ofs << "$var wire " << n << " " << name[0] << " " << name << " $end\n"; + ofs << "$var wire " << n << " " << code << " " << vcd_name_mangle(name) << " $end\n"; + codes[name] = code; + increment_code(); + } + template + void operator()(const char *name, Memory value) { } }; @@ -22,14 +53,17 @@ struct Dump { // Bit if (n == 1) { ofs << (value[0] ? '1' : '0'); - ofs << name[0] << "\n"; + ofs << codes[name] << "\n"; return; } // vector (multi-bit) signals ofs << "b"; for (size_t i = n; i-- > 0;) ofs << (value[i] ? '1' : '0'); - ofs << " " << name[0] << "\n"; + ofs << " " << codes[name] << "\n"; + } + template + void operator()(const char *name, Memory value) { } }; @@ -61,14 +95,15 @@ struct Randomize { int main(int argc, char **argv) { - if (argc != 2) { - std::cerr << "Usage: " << argv[0] << " \n"; + if (argc != 4) { + std::cerr << "Usage: " << argv[0] << " \n"; return 1; } const std::string functional_vcd_filename = argv[1]; + const int steps = atoi(argv[2]); + const uint32_t seed = atoi(argv[3]); - constexpr int steps = 1000; constexpr int number_timescale = 1; const std::string units_timescale = "us"; gold::Inputs inputs; @@ -87,27 +122,12 @@ int main(int argc, char **argv) state.visit(d); } vcd_file << "$enddefinitions $end\n$dumpvars\n"; - vcd_file << "#0\n"; - // Set all signals to false - inputs.visit(Reset()); + std::mt19937 gen(seed); - gold::eval(inputs, outputs, state, next_state); - { - Dump d(vcd_file); - inputs.visit(d); - outputs.visit(d); - state.visit(d); - } - - // Initialize random number generator once - std::random_device rd; - std::mt19937 gen(rd()); + inputs.visit(Reset()); for (int step = 0; step < steps; ++step) { - // Functional backend cxx - vcd_file << "#" << (step + 1) << "\n"; - inputs.visit(Randomize(gen)); - + vcd_file << "#" << step << "\n"; gold::eval(inputs, outputs, state, next_state); { Dump d(vcd_file); @@ -117,6 +137,7 @@ int main(int argc, char **argv) } state = next_state; + inputs.visit(Randomize(gen)); } vcd_file.close(); From 55c2c1785332ce862528ae06c60e551f7e5316a5 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Wed, 17 Jul 2024 11:53:42 +0100 Subject: [PATCH 54/92] document functionalir.h and change visitors to derive from AbstractVisitor. remove extraneous widths arguments from visitors. --- backends/functional/cxx.cc | 72 ++++---- backends/functional/smtlib.cc | 72 ++++---- kernel/functional.h | 2 +- kernel/functionalir.cc | 93 +++++++++- kernel/functionalir.h | 331 +++++++++++++++++++++++++--------- 5 files changed, 400 insertions(+), 170 deletions(-) diff --git a/backends/functional/cxx.cc b/backends/functional/cxx.cc index 4ab9d53a1d4..920ccf363bc 100644 --- a/backends/functional/cxx.cc +++ b/backends/functional/cxx.cc @@ -98,7 +98,7 @@ struct CxxStruct { } }; -template struct CxxPrintVisitor { +template struct CxxPrintVisitor : public FunctionalIR::AbstractVisitor { using Node = FunctionalIR::Node; CxxWriter &f; NodePrinter np; @@ -108,36 +108,36 @@ template struct CxxPrintVisitor { template void print(const char *fmt, Args&&... args) { f.print_with(np, fmt, std::forward(args)...); } - void buf(Node, Node n) { print("{}", n); } - void slice(Node, Node a, int, int offset, int out_width) { print("{0}.slice<{2}>({1})", a, offset, out_width); } - void zero_extend(Node, Node a, int, int out_width) { print("{}.zero_extend<{}>()", a, out_width); } - void sign_extend(Node, Node a, int, int out_width) { print("{}.sign_extend<{}>()", a, out_width); } - void concat(Node, Node a, int, Node b, int) { print("{}.concat({})", a, b); } - void add(Node, Node a, Node b, int) { print("{} + {}", a, b); } - void sub(Node, Node a, Node b, int) { print("{} - {}", a, b); } - void mul(Node, Node a, Node b, int) { print("{} * {}", a, b); } - void unsigned_div(Node, Node a, Node b, int) { print("{} / {}", a, b); } - void unsigned_mod(Node, Node a, Node b, int) { print("{} % {}", a, b); } - void bitwise_and(Node, Node a, Node b, int) { print("{} & {}", a, b); } - void bitwise_or(Node, Node a, Node b, int) { print("{} | {}", a, b); } - void bitwise_xor(Node, Node a, Node b, int) { print("{} ^ {}", a, b); } - void bitwise_not(Node, Node a, int) { print("~{}", a); } - void unary_minus(Node, Node a, int) { print("-{}", a); } - void reduce_and(Node, Node a, int) { print("{}.all()", a); } - void reduce_or(Node, Node a, int) { print("{}.any()", a); } - void reduce_xor(Node, Node a, int) { print("{}.parity()", a); } - void equal(Node, Node a, Node b, int) { print("{} == {}", a, b); } - void not_equal(Node, Node a, Node b, int) { print("{} != {}", a, b); } - void signed_greater_than(Node, Node a, Node b, int) { print("{}.signed_greater_than({})", a, b); } - void signed_greater_equal(Node, Node a, Node b, int) { print("{}.signed_greater_equal({})", a, b); } - void unsigned_greater_than(Node, Node a, Node b, int) { print("{} > {}", a, b); } - void unsigned_greater_equal(Node, Node a, Node b, int) { print("{} >= {}", a, b); } - void logical_shift_left(Node, Node a, Node b, int, int) { print("{} << {}", a, b); } - void logical_shift_right(Node, Node a, Node b, int, int) { print("{} >> {}", a, b); } - void arithmetic_shift_right(Node, Node a, Node b, int, int) { print("{}.arithmetic_shift_right({})", a, b); } - void mux(Node, Node a, Node b, Node s, int) { print("{2}.any() ? {1} : {0}", a, b, s); } - void pmux(Node, Node a, Node b, Node s, int, int) { print("{0}.pmux({1}, {2})", a, b, s); } - void constant(Node, RTLIL::Const value) { + void buf(Node, Node n) override { print("{}", n); } + void slice(Node, Node a, int offset, int out_width) override { print("{0}.slice<{2}>({1})", a, offset, out_width); } + void zero_extend(Node, Node a, int out_width) override { print("{}.zero_extend<{}>()", a, out_width); } + void sign_extend(Node, Node a, int out_width) override { print("{}.sign_extend<{}>()", a, out_width); } + void concat(Node, Node a, Node b) override { print("{}.concat({})", a, b); } + void add(Node, Node a, Node b) override { print("{} + {}", a, b); } + void sub(Node, Node a, Node b) override { print("{} - {}", a, b); } + void mul(Node, Node a, Node b) override { print("{} * {}", a, b); } + void unsigned_div(Node, Node a, Node b) override { print("{} / {}", a, b); } + void unsigned_mod(Node, Node a, Node b) override { print("{} % {}", a, b); } + void bitwise_and(Node, Node a, Node b) override { print("{} & {}", a, b); } + void bitwise_or(Node, Node a, Node b) override { print("{} | {}", a, b); } + void bitwise_xor(Node, Node a, Node b) override { print("{} ^ {}", a, b); } + void bitwise_not(Node, Node a) override { print("~{}", a); } + void unary_minus(Node, Node a) override { print("-{}", a); } + void reduce_and(Node, Node a) override { print("{}.all()", a); } + void reduce_or(Node, Node a) override { print("{}.any()", a); } + void reduce_xor(Node, Node a) override { print("{}.parity()", a); } + void equal(Node, Node a, Node b) override { print("{} == {}", a, b); } + void not_equal(Node, Node a, Node b) override { print("{} != {}", a, b); } + void signed_greater_than(Node, Node a, Node b) override { print("{}.signed_greater_than({})", a, b); } + void signed_greater_equal(Node, Node a, Node b) override { print("{}.signed_greater_equal({})", a, b); } + void unsigned_greater_than(Node, Node a, Node b) override { print("{} > {}", a, b); } + void unsigned_greater_equal(Node, Node a, Node b) override { print("{} >= {}", a, b); } + void logical_shift_left(Node, Node a, Node b) override { print("{} << {}", a, b); } + void logical_shift_right(Node, Node a, Node b) override { print("{} >> {}", a, b); } + void arithmetic_shift_right(Node, Node a, Node b) override { print("{}.arithmetic_shift_right({})", a, b); } + void mux(Node, Node a, Node b, Node s) override { print("{2}.any() ? {1} : {0}", a, b, s); } + void pmux(Node, Node a, Node b, Node s) override { print("{0}.pmux({1}, {2})", a, b, s); } + void constant(Node, RTLIL::Const value) override { std::stringstream ss; bool multiple = value.size() > 32; ss << "Signal<" << value.size() << ">(" << std::hex << std::showbase; @@ -151,11 +151,11 @@ template struct CxxPrintVisitor { ss << ")"; print("{}", ss.str()); } - void input(Node, IdString name) { print("input.{}", input_struct[name]); } - void state(Node, IdString name) { print("current_state.{}", state_struct[name]); } - void memory_read(Node, Node mem, Node addr, int, int) { print("{}.read({})", mem, addr); } - void memory_write(Node, Node mem, Node addr, Node data, int, int) { print("{}.write({}, {})", mem, addr, data); } - void undriven(Node, int width) { print("Signal<{}>(0)", width); } + void input(Node, IdString name) override { print("input.{}", input_struct[name]); } + void state(Node, IdString name) override { print("current_state.{}", state_struct[name]); } + void memory_read(Node, Node mem, Node addr) override { print("{}.read({})", mem, addr); } + void memory_write(Node, Node mem, Node addr, Node data) override { print("{}.write({}, {})", mem, addr, data); } + void undriven(Node, int width) override { print("Signal<{}>(0)", width); } }; struct CxxModule { diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index 113fb710080..c35a48dd809 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -109,7 +109,7 @@ class SmtStruct { } }; -struct SmtPrintVisitor { +struct SmtPrintVisitor : public FunctionalIR::AbstractVisitor { using Node = FunctionalIR::Node; std::function n; SmtStruct &input_struct; @@ -134,35 +134,35 @@ struct SmtPrintVisitor { return list(list("_", "extract", offset + out_width - 1, offset), std::move(arg)); } - SExpr buf(Node, Node a) { return n(a); } - SExpr slice(Node, Node a, int, int offset, int out_width) { return extract(n(a), offset, out_width); } - SExpr zero_extend(Node, Node a, int, int out_width) { return list(list("_", "zero_extend", out_width - a.width()), n(a)); } - SExpr sign_extend(Node, Node a, int, int out_width) { return list(list("_", "sign_extend", out_width - a.width()), n(a)); } - SExpr concat(Node, Node a, int, Node b, int) { return list("concat", n(b), n(a)); } - SExpr add(Node, Node a, Node b, int) { return list("bvadd", n(a), n(b)); } - SExpr sub(Node, Node a, Node b, int) { return list("bvsub", n(a), n(b)); } - SExpr mul(Node, Node a, Node b, int) { return list("bvmul", n(a), n(b)); } - SExpr unsigned_div(Node, Node a, Node b, int) { return list("bvudiv", n(a), n(b)); } - SExpr unsigned_mod(Node, Node a, Node b, int) { return list("bvurem", n(a), n(b)); } - SExpr bitwise_and(Node, Node a, Node b, int) { return list("bvand", n(a), n(b)); } - SExpr bitwise_or(Node, Node a, Node b, int) { return list("bvor", n(a), n(b)); } - SExpr bitwise_xor(Node, Node a, Node b, int) { return list("bvxor", n(a), n(b)); } - SExpr bitwise_not(Node, Node a, int) { return list("bvnot", n(a)); } - SExpr unary_minus(Node, Node a, int) { return list("bvneg", n(a)); } - SExpr reduce_and(Node, Node a, int) { return from_bool(list("=", n(a), literal(RTLIL::Const(State::S1, a.width())))); } - SExpr reduce_or(Node, Node a, int) { return from_bool(list("distinct", n(a), literal(RTLIL::Const(State::S0, a.width())))); } - SExpr reduce_xor(Node, Node a, int) { + SExpr buf(Node, Node a) override { return n(a); } + SExpr slice(Node, Node a, int offset, int out_width) override { return extract(n(a), offset, out_width); } + SExpr zero_extend(Node, Node a, int out_width) override { return list(list("_", "zero_extend", out_width - a.width()), n(a)); } + SExpr sign_extend(Node, Node a, int out_width) override { return list(list("_", "sign_extend", out_width - a.width()), n(a)); } + SExpr concat(Node, Node a, Node b) override { return list("concat", n(b), n(a)); } + SExpr add(Node, Node a, Node b) override { return list("bvadd", n(a), n(b)); } + SExpr sub(Node, Node a, Node b) override { return list("bvsub", n(a), n(b)); } + SExpr mul(Node, Node a, Node b) override { return list("bvmul", n(a), n(b)); } + SExpr unsigned_div(Node, Node a, Node b) override { return list("bvudiv", n(a), n(b)); } + SExpr unsigned_mod(Node, Node a, Node b) override { return list("bvurem", n(a), n(b)); } + SExpr bitwise_and(Node, Node a, Node b) override { return list("bvand", n(a), n(b)); } + SExpr bitwise_or(Node, Node a, Node b) override { return list("bvor", n(a), n(b)); } + SExpr bitwise_xor(Node, Node a, Node b) override { return list("bvxor", n(a), n(b)); } + SExpr bitwise_not(Node, Node a) override { return list("bvnot", n(a)); } + SExpr unary_minus(Node, Node a) override { return list("bvneg", n(a)); } + SExpr reduce_and(Node, Node a) override { return from_bool(list("=", n(a), literal(RTLIL::Const(State::S1, a.width())))); } + SExpr reduce_or(Node, Node a) override { return from_bool(list("distinct", n(a), literal(RTLIL::Const(State::S0, a.width())))); } + SExpr reduce_xor(Node, Node a) override { vector s { "bvxor" }; for(int i = 0; i < a.width(); i++) s.push_back(extract(n(a), i)); return s; } - SExpr equal(Node, Node a, Node b, int) { return from_bool(list("=", n(a), n(b))); } - SExpr not_equal(Node, Node a, Node b, int) { return from_bool(list("distinct", n(a), n(b))); } - SExpr signed_greater_than(Node, Node a, Node b, int) { return from_bool(list("bvsgt", n(a), n(b))); } - SExpr signed_greater_equal(Node, Node a, Node b, int) { return from_bool(list("bvsge", n(a), n(b))); } - SExpr unsigned_greater_than(Node, Node a, Node b, int) { return from_bool(list("bvugt", n(a), n(b))); } - SExpr unsigned_greater_equal(Node, Node a, Node b, int) { return from_bool(list("bvuge", n(a), n(b))); } + SExpr equal(Node, Node a, Node b) override { return from_bool(list("=", n(a), n(b))); } + SExpr not_equal(Node, Node a, Node b) override { return from_bool(list("distinct", n(a), n(b))); } + SExpr signed_greater_than(Node, Node a, Node b) override { return from_bool(list("bvsgt", n(a), n(b))); } + SExpr signed_greater_equal(Node, Node a, Node b) override { return from_bool(list("bvsge", n(a), n(b))); } + SExpr unsigned_greater_than(Node, Node a, Node b) override { return from_bool(list("bvugt", n(a), n(b))); } + SExpr unsigned_greater_equal(Node, Node a, Node b) override { return from_bool(list("bvuge", n(a), n(b))); } SExpr extend(SExpr &&a, int in_width, int out_width) { if(in_width < out_width) @@ -170,24 +170,24 @@ struct SmtPrintVisitor { else return std::move(a); } - SExpr logical_shift_left(Node, Node a, Node b, int, int) { return list("bvshl", n(a), extend(n(b), b.width(), a.width())); } - SExpr logical_shift_right(Node, Node a, Node b, int, int) { return list("bvlshr", n(a), extend(n(b), b.width(), a.width())); } - SExpr arithmetic_shift_right(Node, Node a, Node b, int, int) { return list("bvashr", n(a), extend(n(b), b.width(), a.width())); } - SExpr mux(Node, Node a, Node b, Node s, int) { return list("ite", to_bool(n(s)), n(b), n(a)); } - SExpr pmux(Node, Node a, Node b, Node s, int, int) { + SExpr logical_shift_left(Node, Node a, Node b) override { return list("bvshl", n(a), extend(n(b), b.width(), a.width())); } + SExpr logical_shift_right(Node, Node a, Node b) override { return list("bvlshr", n(a), extend(n(b), b.width(), a.width())); } + SExpr arithmetic_shift_right(Node, Node a, Node b) override { return list("bvashr", n(a), extend(n(b), b.width(), a.width())); } + SExpr mux(Node, Node a, Node b, Node s) override { return list("ite", to_bool(n(s)), n(b), n(a)); } + SExpr pmux(Node, Node a, Node b, Node s) override { SExpr rv = n(a); for(int i = 0; i < s.width(); i++) rv = list("ite", to_bool(extract(n(s), i)), extract(n(b), a.width() * i, a.width()), rv); return rv; } - SExpr constant(Node, RTLIL::Const value) { return literal(value); } - SExpr memory_read(Node, Node mem, Node addr, int, int) { return list("select", n(mem), n(addr)); } - SExpr memory_write(Node, Node mem, Node addr, Node data, int, int) { return list("store", n(mem), n(addr), n(data)); } + SExpr constant(Node, RTLIL::Const value) override { return literal(value); } + SExpr memory_read(Node, Node mem, Node addr) override { return list("select", n(mem), n(addr)); } + SExpr memory_write(Node, Node mem, Node addr, Node data) override { return list("store", n(mem), n(addr), n(data)); } - SExpr input(Node, IdString name) { return input_struct.access("inputs", name); } - SExpr state(Node, IdString name) { return state_struct.access("state", name); } + SExpr input(Node, IdString name) override { return input_struct.access("inputs", name); } + SExpr state(Node, IdString name) override { return state_struct.access("state", name); } - SExpr undriven(Node, int width) { return literal(RTLIL::Const(State::S0, width)); } + SExpr undriven(Node, int width) override { return literal(RTLIL::Const(State::S0, width)); } }; struct SmtModule { diff --git a/kernel/functional.h b/kernel/functional.h index 09a8826433c..09f66c9813b 100644 --- a/kernel/functional.h +++ b/kernel/functional.h @@ -109,7 +109,7 @@ struct ComputeGraph { auto found = graph_->sparse_attrs.find(index_); log_assert(found != graph_->sparse_attrs.end()); - return *found; + return found->second; } }; diff --git a/kernel/functionalir.cc b/kernel/functionalir.cc index b8fa6b88e5f..9b8076a0ef7 100644 --- a/kernel/functionalir.cc +++ b/kernel/functionalir.cc @@ -21,6 +21,83 @@ YOSYS_NAMESPACE_BEGIN +const char *FunctionalIR::fn_to_string(FunctionalIR::Fn fn) { + switch(fn) { + case FunctionalIR::Fn::invalid: return "invalid"; + case FunctionalIR::Fn::buf: return "buf"; + case FunctionalIR::Fn::slice: return "slice"; + case FunctionalIR::Fn::zero_extend: return "zero_extend"; + case FunctionalIR::Fn::sign_extend: return "sign_extend"; + case FunctionalIR::Fn::concat: return "concat"; + case FunctionalIR::Fn::add: return "add"; + case FunctionalIR::Fn::sub: return "sub"; + case FunctionalIR::Fn::mul: return "mul"; + case FunctionalIR::Fn::unsigned_div: return "unsigned_div"; + case FunctionalIR::Fn::unsigned_mod: return "unsigned_mod"; + case FunctionalIR::Fn::bitwise_and: return "bitwise_and"; + case FunctionalIR::Fn::bitwise_or: return "bitwise_or"; + case FunctionalIR::Fn::bitwise_xor: return "bitwise_xor"; + case FunctionalIR::Fn::bitwise_not: return "bitwise_not"; + case FunctionalIR::Fn::reduce_and: return "reduce_and"; + case FunctionalIR::Fn::reduce_or: return "reduce_or"; + case FunctionalIR::Fn::reduce_xor: return "reduce_xor"; + case FunctionalIR::Fn::unary_minus: return "unary_minus"; + case FunctionalIR::Fn::equal: return "equal"; + case FunctionalIR::Fn::not_equal: return "not_equal"; + case FunctionalIR::Fn::signed_greater_than: return "signed_greater_than"; + case FunctionalIR::Fn::signed_greater_equal: return "signed_greater_equal"; + case FunctionalIR::Fn::unsigned_greater_than: return "unsigned_greater_than"; + case FunctionalIR::Fn::unsigned_greater_equal: return "unsigned_greater_equal"; + case FunctionalIR::Fn::logical_shift_left: return "logical_shift_left"; + case FunctionalIR::Fn::logical_shift_right: return "logical_shift_right"; + case FunctionalIR::Fn::arithmetic_shift_right: return "arithmetic_shift_right"; + case FunctionalIR::Fn::mux: return "mux"; + case FunctionalIR::Fn::pmux: return "pmux"; + case FunctionalIR::Fn::constant: return "constant"; + case FunctionalIR::Fn::input: return "input"; + case FunctionalIR::Fn::state: return "state"; + case FunctionalIR::Fn::multiple: return "multiple"; + case FunctionalIR::Fn::undriven: return "undriven"; + case FunctionalIR::Fn::memory_read: return "memory_read"; + case FunctionalIR::Fn::memory_write: return "memory_write"; + } + log_error("fn_to_string: unknown FunctionalIR::Fn value %d", (int)fn); +} + +struct PrintVisitor : FunctionalIR::DefaultVisitor { + using Node = FunctionalIR::Node; + std::function np; + PrintVisitor(std::function np) : np(np) { } + // as a general rule the default handler is good enough iff the only arguments are of type Node + std::string slice(Node, Node a, int offset, int out_width) override { return "slice(" + np(a) + ", " + std::to_string(offset) + ", " + std::to_string(out_width) + ")"; } + std::string zero_extend(Node, Node a, int out_width) override { return "zero_extend(" + np(a) + ", " + std::to_string(out_width) + ")"; } + std::string sign_extend(Node, Node a, int out_width) override { return "sign_extend(" + np(a) + ", " + std::to_string(out_width) + ")"; } + std::string constant(Node, RTLIL::Const value) override { return "constant(" + value.as_string() + ")"; } + std::string input(Node, IdString name) override { return "input(" + name.str() + ")"; } + std::string state(Node, IdString name) override { return "state(" + name.str() + ")"; } + std::string undriven(Node, int width) override { return "undriven(" + std::to_string(width) + ")"; } + std::string default_handler(Node self) override { + std::string ret = FunctionalIR::fn_to_string(self.fn()); + ret += "("; + for(size_t i = 0; i < self.arg_count(); i++) { + if(i > 0) ret += ", "; + ret += np(self.arg(i)); + } + ret += ")"; + return ret; + } +}; + +std::string FunctionalIR::Node::to_string() +{ + return to_string([](Node n) { return RTLIL::unescape_id(n.name()); }); +} + +std::string FunctionalIR::Node::to_string(std::function np) +{ + return visit(PrintVisitor(np)); +} + template class CellSimplifier { Factory &factory; @@ -47,11 +124,6 @@ class CellSimplifier { return neg_if(a, a_width, sign(a, a_width)); } public: - T reduce_or(T a, int width) { - if (width == 1) - return a; - return factory.reduce_or(a, width); - } T extend(T a, int in_width, int out_width, bool is_signed) { if(in_width == out_width) return a; @@ -153,8 +225,8 @@ class CellSimplifier { else log_abort(); }else if(cellType.in({ID($logic_or), ID($logic_and)})){ - T a = reduce_or(inputs.at(ID(A)), a_width); - T b = reduce_or(inputs.at(ID(B)), b_width); + T a = factory.reduce_or(inputs.at(ID(A)), a_width); + T b = factory.reduce_or(inputs.at(ID(B)), b_width); T y = cellType == ID($logic_and) ? factory.bitwise_and(a, b, 1) : factory.bitwise_or(a, b, 1); return extend(y, 1, y_width, false); }else if(cellType == ID($not)){ @@ -166,11 +238,11 @@ class CellSimplifier { T a = extend(inputs.at(ID(A)), a_width, y_width, a_signed); return factory.unary_minus(a, y_width); }else if(cellType == ID($logic_not)){ - T a = reduce_or(inputs.at(ID(A)), a_width); + T a = factory.reduce_or(inputs.at(ID(A)), a_width); T y = factory.bitwise_not(a, 1); return extend(y, 1, y_width, false); }else if(cellType.in({ID($reduce_or), ID($reduce_bool)})){ - T a = reduce_or(inputs.at(ID(A)), a_width); + T a = factory.reduce_or(inputs.at(ID(A)), a_width); return extend(a, 1, y_width, false); }else if(cellType == ID($reduce_and)){ T a = factory.reduce_and(inputs.at(ID(A)), a_width); @@ -244,7 +316,7 @@ class CellSimplifier { // which equals the negative of (-a) / b with rounding up rather than down // note that to handle the case where a = most negative value properly, // we have to calculate a1_sign from the original values rather than using sign(a1, width) - T a1_sign = factory.bitwise_and(factory.not_equal(sign(a, width), sign(b, width), 1), reduce_or(a, width), 1); + T a1_sign = factory.bitwise_and(factory.not_equal(sign(a, width), sign(b, width), 1), factory.reduce_or(a, width), 1); T a2 = factory.mux(a1, factory.bitwise_not(a1, width), a1_sign, width); T y1 = factory.unsigned_div(a2, b1, width); T y2 = extend(y1, width, y_width, false); @@ -560,6 +632,7 @@ void FunctionalIR::forward_buf() { _graph.permute(perm, alias); } +// Quoting routine to make error messages nicer static std::string quote_fmt(const char *fmt) { std::string r; diff --git a/kernel/functionalir.h b/kernel/functionalir.h index 99fc872a81a..e586695695a 100644 --- a/kernel/functionalir.h +++ b/kernel/functionalir.h @@ -31,45 +31,123 @@ YOSYS_NAMESPACE_BEGIN class FunctionalIR { public: + // each function is documented with a short pseudocode declaration or definition + // standard C/Verilog operators are used to describe the result + // + // the types used in this are: + // - bit[N]: a bitvector of N bits + // bit[N] can be indicated as signed or unsigned. this is not tracked by the functional backend + // but is meant to indicate how the value is interpreted + // if a bit[N] is marked as neither signed nor unsigned, this means the result should be valid with *either* interpretation + // - memory[N, M]: a memory with N address and M data bits + // - int: C++ int + // - Const[N]: yosys RTLIL::Const (with size() == N) + // - IdString: yosys IdString + // - any: used in documentation to indicate that the type is unconstrained + // + // nodes in the functional backend are either of type bit[N] or memory[N,M] (for some N, M: int) + // additionally, they can carry a constant of type int, Const[N] or IdString + // each node has a 'sort' field that stores the type of the node + // slice, zero_extend, sign_extend use the type field to store out_width enum class Fn { + // invalid() = known-invalid/shouldn't happen value + // TODO: maybe remove this and use e.g. std::optional instead? invalid, + // buf(a: any): any = a + // no-op operation + // when constructing the compute graph we generate invalid buf() nodes as a placeholder + // and later insert the argument buf, + // slice(a: bit[in_width], offset: int, out_width: int): bit[out_width] = a[offset +: out_width] + // required: offset + out_width <= in_width slice, + // zero_extend(a: unsigned bit[in_width], out_width: int): unsigned bit[out_width] = a (zero extended) + // required: out_width > in_width zero_extend, + // sign_extend(a: signed bit[in_width], out_width: int): signed bit[out_width] = a (sign extended) + // required: out_width > in_width sign_extend, + // concat(a: bit[N], b: bit[M]): bit[N+M] = {b, a} (verilog syntax) + // concatenates two bitvectors, with a in the least significant position and b in the more significant position concat, + // add(a: bit[N], b: bit[N]): bit[N] = a + b add, + // sub(a: bit[N], b: bit[N]): bit[N] = a - b sub, + // mul(a: bit[N], b: bit[N]): bit[N] = a * b mul, + // unsigned_div(a: unsigned bit[N], b: unsigned bit[N]): bit[N] = a / b unsigned_div, + // unsigned_mod(a: signed bit[N], b: signed bit[N]): bit[N] = a % b unsigned_mod, + // bitwise_and(a: bit[N], b: bit[N]): bit[N] = a & b bitwise_and, + // bitwise_or(a: bit[N], b: bit[N]): bit[N] = a | b bitwise_or, + // bitwise_xor(a: bit[N], b: bit[N]): bit[N] = a ^ b bitwise_xor, + // bitwise_not(a: bit[N]): bit[N] = ~a bitwise_not, + // reduce_and(a: bit[N]): bit[1] = &a reduce_and, + // reduce_or(a: bit[N]): bit[1] = |a reduce_or, + // reduce_xor(a: bit[N]): bit[1] = ^a reduce_xor, + // unary_minus(a: bit[N]): bit[N] = -a unary_minus, + // equal(a: bit[N], b: bit[N]): bit[1] = (a == b) equal, + // not_equal(a: bit[N], b: bit[N]): bit[1] = (a != b) not_equal, + // signed_greater_than(a: signed bit[N], b: signed bit[N]): bit[1] = (a > b) signed_greater_than, + // signed_greater_equal(a: signed bit[N], b: signed bit[N]): bit[1] = (a >= b) signed_greater_equal, + // unsigned_greater_than(a: unsigned bit[N], b: unsigned bit[N]): bit[1] = (a > b) unsigned_greater_than, + // unsigned_greater_equal(a: unsigned bit[N], b: unsigned bit[N]): bit[1] = (a >= b) unsigned_greater_equal, + // logical_shift_left(a: bit[N], b: unsigned bit[M]): bit[N] = a << b + // required: M <= clog2(N + 1) logical_shift_left, + // logical_shift_right(a: unsigned bit[N], b: unsigned bit[M]): unsigned bit[N] = a >> b + // required: M <= clog2(N + 1) logical_shift_right, + // arithmetic_shift_right(a: signed bit[N], b: unsigned bit[M]): signed bit[N] = a >> b + // required: M <= clog2(N + 1) arithmetic_shift_right, + // mux(a: bit[N], b: bit[N], s: bit[1]): bit[N] = s ? b : a mux, + // pmux(a: bit[N], b: bit[N*M], s: bit[M]): bit[N] + // required: no more than one bit in b is set + // if s[i] = 1 for any i, then returns b[i * N +: N] + // returns a if s == 0 pmux, + // constant(a: Const[N]): bit[N] = a constant, + // input(a: IdString): any + // returns the current value of the input with the specified name input, + // state(a: IdString): any + // returns the current value of the state variable with the specified name state, + // multiple(a: any, b: any, c: any, ...): any + // indicates a value driven by multiple inputs multiple, + // undriven(width: int): bit[width] + // indicates an undriven value undriven, + // memory_read(memory: memory[addr_width, data_width], addr: bit[addr_width]): bit[data_width] = memory[addr] memory_read, + // memory_write(memory: memory[addr_width, data_width], addr: bit[addr_width], data: bit[data_width]): memory[addr_width, data_width] + // returns a copy of `memory` but with the value at `addr` changed to `data` memory_write }; + // returns the name of a FunctionalIR::Fn value, as a string literal + static const char *fn_to_string(Fn); + // FunctionalIR::Sort represents the sort or type of a node + // currently the only two types are signal/bit and memory class Sort { std::variant> _v; public: @@ -77,13 +155,18 @@ class FunctionalIR { Sort(int addr_width, int data_width) : _v(std::make_pair(addr_width, data_width)) { } bool is_signal() const { return _v.index() == 0; } bool is_memory() const { return _v.index() == 1; } + // returns the width of a bitvector type, errors out for other types int width() const { return std::get<0>(_v); } + // returns the address width of a bitvector type, errors out for other types int addr_width() const { return std::get<1>(_v).first; } + // returns the data width of a bitvector type, errors out for other types int data_width() const { return std::get<1>(_v).second; } bool operator==(Sort const& other) const { return _v == other._v; } unsigned int hash() const { return mkhash(_v); } }; private: + // one NodeData is stored per Node, containing the function and non-node arguments + // note that NodeData is deduplicated by ComputeGraph class NodeData { Fn _fn; std::variant< @@ -107,9 +190,14 @@ class FunctionalIR { return _fn == other._fn && _extra == other._extra; } }; + // Attr contains all the information about a note that should not be deduplicated struct Attr { Sort sort; }; + // our specialised version of ComputeGraph + // the sparse_attr IdString stores a naming suggestion, retrieved with name() + // the key is currently used to identify the nodes that represent output and next state values + // the bool is true for next state values using Graph = ComputeGraph>; Graph _graph; dict _inputs; @@ -132,53 +220,17 @@ class FunctionalIR { } public: class Factory; + // Node is an immutable reference to a FunctionalIR node class Node { friend class Factory; friend class FunctionalIR; - Graph::Ref _ref; - explicit Node(Graph::Ref ref) : _ref(ref) { } - operator Graph::Ref() { return _ref; } - template struct PrintVisitor { - NodePrinter np; - PrintVisitor(NodePrinter np) : np(np) { } - std::string buf(Node, Node n) { return "buf(" + np(n) + ")"; } - std::string slice(Node, Node a, int, int offset, int out_width) { return "slice(" + np(a) + ", " + std::to_string(offset) + ", " + std::to_string(out_width) + ")"; } - std::string zero_extend(Node, Node a, int, int out_width) { return "zero_extend(" + np(a) + ", " + std::to_string(out_width) + ")"; } - std::string sign_extend(Node, Node a, int, int out_width) { return "sign_extend(" + np(a) + ", " + std::to_string(out_width) + ")"; } - std::string concat(Node, Node a, int, Node b, int) { return "concat(" + np(a) + ", " + np(b) + ")"; } - std::string add(Node, Node a, Node b, int) { return "add(" + np(a) + ", " + np(b) + ")"; } - std::string sub(Node, Node a, Node b, int) { return "sub(" + np(a) + ", " + np(b) + ")"; } - std::string mul(Node, Node a, Node b, int) { return "mul(" + np(a) + ", " + np(b) + ")"; } - std::string unsigned_div(Node, Node a, Node b, int) { return "unsigned_div(" + np(a) + ", " + np(b) + ")"; } - std::string unsigned_mod(Node, Node a, Node b, int) { return "unsigned_mod(" + np(a) + ", " + np(b) + ")"; } - std::string bitwise_and(Node, Node a, Node b, int) { return "bitwise_and(" + np(a) + ", " + np(b) + ")"; } - std::string bitwise_or(Node, Node a, Node b, int) { return "bitwise_or(" + np(a) + ", " + np(b) + ")"; } - std::string bitwise_xor(Node, Node a, Node b, int) { return "bitwise_xor(" + np(a) + ", " + np(b) + ")"; } - std::string bitwise_not(Node, Node a, int) { return "bitwise_not(" + np(a) + ")"; } - std::string unary_minus(Node, Node a, int) { return "unary_minus(" + np(a) + ")"; } - std::string reduce_and(Node, Node a, int) { return "reduce_and(" + np(a) + ")"; } - std::string reduce_or(Node, Node a, int) { return "reduce_or(" + np(a) + ")"; } - std::string reduce_xor(Node, Node a, int) { return "reduce_xor(" + np(a) + ")"; } - std::string equal(Node, Node a, Node b, int) { return "equal(" + np(a) + ", " + np(b) + ")"; } - std::string not_equal(Node, Node a, Node b, int) { return "not_equal(" + np(a) + ", " + np(b) + ")"; } - std::string signed_greater_than(Node, Node a, Node b, int) { return "signed_greater_than(" + np(a) + ", " + np(b) + ")"; } - std::string signed_greater_equal(Node, Node a, Node b, int) { return "signed_greater_equal(" + np(a) + ", " + np(b) + ")"; } - std::string unsigned_greater_than(Node, Node a, Node b, int) { return "unsigned_greater_than(" + np(a) + ", " + np(b) + ")"; } - std::string unsigned_greater_equal(Node, Node a, Node b, int) { return "unsigned_greater_equal(" + np(a) + ", " + np(b) + ")"; } - std::string logical_shift_left(Node, Node a, Node b, int, int) { return "logical_shift_left(" + np(a) + ", " + np(b) + ")"; } - std::string logical_shift_right(Node, Node a, Node b, int, int) { return "logical_shift_right(" + np(a) + ", " + np(b) + ")"; } - std::string arithmetic_shift_right(Node, Node a, Node b, int, int) { return "arithmetic_shift_right(" + np(a) + ", " + np(b) + ")"; } - std::string mux(Node, Node a, Node b, Node s, int) { return "mux(" + np(a) + ", " + np(b) + ", " + np(s) + ")"; } - std::string pmux(Node, Node a, Node b, Node s, int, int) { return "pmux(" + np(a) + ", " + np(b) + ", " + np(s) + ")"; } - std::string constant(Node, RTLIL::Const value) { return "constant(" + value.as_string() + ")"; } - std::string input(Node, IdString name) { return "input(" + name.str() + ")"; } - std::string state(Node, IdString name) { return "state(" + name.str() + ")"; } - std::string memory_read(Node, Node mem, Node addr, int, int) { return "memory_read(" + np(mem) + ", " + np(addr) + ")"; } - std::string memory_write(Node, Node mem, Node addr, Node data, int, int) { return "memory_write(" + np(mem) + ", " + np(addr) + ", " + np(data) + ")"; } - std::string undriven(Node, int width) { return "undriven(" + std::to_string(width) + ")"; } - }; + Graph::ConstRef _ref; + explicit Node(Graph::ConstRef ref) : _ref(ref) { } + explicit operator Graph::ConstRef() { return _ref; } public: + // the node's index. may change if nodes are added or removed int id() const { return _ref.index(); } + // a name suggestion for the node, which need not be unique IdString name() const { if(_ref.has_sparse_attr()) return _ref.sparse_attr(); @@ -187,55 +239,135 @@ class FunctionalIR { } Fn fn() const { return _ref.function().fn(); } Sort sort() const { return _ref.attr().sort; } + // returns the width of a bitvector node, errors out for other nodes int width() const { return sort().width(); } + size_t arg_count() const { return _ref.size(); } Node arg(int n) const { return Node(_ref.arg(n)); } + // visit calls the appropriate visitor method depending on the type of the node template auto visit(Visitor v) const { + // currently templated but could be switched to AbstractVisitor & switch(_ref.function().fn()) { case Fn::invalid: log_error("invalid node in visit"); break; case Fn::buf: return v.buf(*this, arg(0)); break; - case Fn::slice: return v.slice(*this, arg(0), arg(0).width(), _ref.function().as_int(), sort().width()); break; - case Fn::zero_extend: return v.zero_extend(*this, arg(0), arg(0).width(), width()); break; - case Fn::sign_extend: return v.sign_extend(*this, arg(0), arg(0).width(), width()); break; - case Fn::concat: return v.concat(*this, arg(0), arg(0).width(), arg(1), arg(1).width()); break; - case Fn::add: return v.add(*this, arg(0), arg(1), sort().width()); break; - case Fn::sub: return v.sub(*this, arg(0), arg(1), sort().width()); break; - case Fn::mul: return v.mul(*this, arg(0), arg(1), sort().width()); break; - case Fn::unsigned_div: return v.unsigned_div(*this, arg(0), arg(1), sort().width()); break; - case Fn::unsigned_mod: return v.unsigned_mod(*this, arg(0), arg(1), sort().width()); break; - case Fn::bitwise_and: return v.bitwise_and(*this, arg(0), arg(1), sort().width()); break; - case Fn::bitwise_or: return v.bitwise_or(*this, arg(0), arg(1), sort().width()); break; - case Fn::bitwise_xor: return v.bitwise_xor(*this, arg(0), arg(1), sort().width()); break; - case Fn::bitwise_not: return v.bitwise_not(*this, arg(0), sort().width()); break; - case Fn::unary_minus: return v.unary_minus(*this, arg(0), sort().width()); break; - case Fn::reduce_and: return v.reduce_and(*this, arg(0), arg(0).width()); break; - case Fn::reduce_or: return v.reduce_or(*this, arg(0), arg(0).width()); break; - case Fn::reduce_xor: return v.reduce_xor(*this, arg(0), arg(0).width()); break; - case Fn::equal: return v.equal(*this, arg(0), arg(1), arg(0).width()); break; - case Fn::not_equal: return v.not_equal(*this, arg(0), arg(1), arg(0).width()); break; - case Fn::signed_greater_than: return v.signed_greater_than(*this, arg(0), arg(1), arg(0).width()); break; - case Fn::signed_greater_equal: return v.signed_greater_equal(*this, arg(0), arg(1), arg(0).width()); break; - case Fn::unsigned_greater_than: return v.unsigned_greater_than(*this, arg(0), arg(1), arg(0).width()); break; - case Fn::unsigned_greater_equal: return v.unsigned_greater_equal(*this, arg(0), arg(1), arg(0).width()); break; - case Fn::logical_shift_left: return v.logical_shift_left(*this, arg(0), arg(1), arg(0).width(), arg(1).width()); break; - case Fn::logical_shift_right: return v.logical_shift_right(*this, arg(0), arg(1), arg(0).width(), arg(1).width()); break; - case Fn::arithmetic_shift_right: return v.arithmetic_shift_right(*this, arg(0), arg(1), arg(0).width(), arg(1).width()); break; - case Fn::mux: return v.mux(*this, arg(0), arg(1), arg(2), arg(0).width()); break; - case Fn::pmux: return v.pmux(*this, arg(0), arg(1), arg(2), arg(0).width(), arg(2).width()); break; + case Fn::slice: return v.slice(*this, arg(0), _ref.function().as_int(), sort().width()); break; + case Fn::zero_extend: return v.zero_extend(*this, arg(0), width()); break; + case Fn::sign_extend: return v.sign_extend(*this, arg(0), width()); break; + case Fn::concat: return v.concat(*this, arg(0), arg(1)); break; + case Fn::add: return v.add(*this, arg(0), arg(1)); break; + case Fn::sub: return v.sub(*this, arg(0), arg(1)); break; + case Fn::mul: return v.mul(*this, arg(0), arg(1)); break; + case Fn::unsigned_div: return v.unsigned_div(*this, arg(0), arg(1)); break; + case Fn::unsigned_mod: return v.unsigned_mod(*this, arg(0), arg(1)); break; + case Fn::bitwise_and: return v.bitwise_and(*this, arg(0), arg(1)); break; + case Fn::bitwise_or: return v.bitwise_or(*this, arg(0), arg(1)); break; + case Fn::bitwise_xor: return v.bitwise_xor(*this, arg(0), arg(1)); break; + case Fn::bitwise_not: return v.bitwise_not(*this, arg(0)); break; + case Fn::unary_minus: return v.unary_minus(*this, arg(0)); break; + case Fn::reduce_and: return v.reduce_and(*this, arg(0)); break; + case Fn::reduce_or: return v.reduce_or(*this, arg(0)); break; + case Fn::reduce_xor: return v.reduce_xor(*this, arg(0)); break; + case Fn::equal: return v.equal(*this, arg(0), arg(1)); break; + case Fn::not_equal: return v.not_equal(*this, arg(0), arg(1)); break; + case Fn::signed_greater_than: return v.signed_greater_than(*this, arg(0), arg(1)); break; + case Fn::signed_greater_equal: return v.signed_greater_equal(*this, arg(0), arg(1)); break; + case Fn::unsigned_greater_than: return v.unsigned_greater_than(*this, arg(0), arg(1)); break; + case Fn::unsigned_greater_equal: return v.unsigned_greater_equal(*this, arg(0), arg(1)); break; + case Fn::logical_shift_left: return v.logical_shift_left(*this, arg(0), arg(1)); break; + case Fn::logical_shift_right: return v.logical_shift_right(*this, arg(0), arg(1)); break; + case Fn::arithmetic_shift_right: return v.arithmetic_shift_right(*this, arg(0), arg(1)); break; + case Fn::mux: return v.mux(*this, arg(0), arg(1), arg(2)); break; + case Fn::pmux: return v.pmux(*this, arg(0), arg(1), arg(2)); break; case Fn::constant: return v.constant(*this, _ref.function().as_const()); break; case Fn::input: return v.input(*this, _ref.function().as_idstring()); break; case Fn::state: return v.state(*this, _ref.function().as_idstring()); break; - case Fn::memory_read: return v.memory_read(*this, arg(0), arg(1), arg(1).width(), width()); break; - case Fn::memory_write: return v.memory_write(*this, arg(0), arg(1), arg(2), arg(1).width(), arg(2).width()); break; + case Fn::memory_read: return v.memory_read(*this, arg(0), arg(1)); break; + case Fn::memory_write: return v.memory_write(*this, arg(0), arg(1), arg(2)); break; case Fn::multiple: log_error("multiple in visit"); break; case Fn::undriven: return v.undriven(*this, width()); break; } } - template std::string to_string(NodePrinter np) - { - return visit(PrintVisitor(np)); - } + std::string to_string(); + std::string to_string(std::function); + }; + // AbstractVisitor provides an abstract base class for visitors + template struct AbstractVisitor { + virtual T buf(Node self, Node n) = 0; + virtual T slice(Node self, Node a, int offset, int out_width) = 0; + virtual T zero_extend(Node self, Node a, int out_width) = 0; + virtual T sign_extend(Node self, Node a, int out_width) = 0; + virtual T concat(Node self, Node a, Node b) = 0; + virtual T add(Node self, Node a, Node b) = 0; + virtual T sub(Node self, Node a, Node b) = 0; + virtual T mul(Node self, Node a, Node b) = 0; + virtual T unsigned_div(Node self, Node a, Node b) = 0; + virtual T unsigned_mod(Node self, Node a, Node b) = 0; + virtual T bitwise_and(Node self, Node a, Node b) = 0; + virtual T bitwise_or(Node self, Node a, Node b) = 0; + virtual T bitwise_xor(Node self, Node a, Node b) = 0; + virtual T bitwise_not(Node self, Node a) = 0; + virtual T unary_minus(Node self, Node a) = 0; + virtual T reduce_and(Node self, Node a) = 0; + virtual T reduce_or(Node self, Node a) = 0; + virtual T reduce_xor(Node self, Node a) = 0; + virtual T equal(Node self, Node a, Node b) = 0; + virtual T not_equal(Node self, Node a, Node b) = 0; + virtual T signed_greater_than(Node self, Node a, Node b) = 0; + virtual T signed_greater_equal(Node self, Node a, Node b) = 0; + virtual T unsigned_greater_than(Node self, Node a, Node b) = 0; + virtual T unsigned_greater_equal(Node self, Node a, Node b) = 0; + virtual T logical_shift_left(Node self, Node a, Node b) = 0; + virtual T logical_shift_right(Node self, Node a, Node b) = 0; + virtual T arithmetic_shift_right(Node self, Node a, Node b) = 0; + virtual T mux(Node self, Node a, Node b, Node s) = 0; + virtual T pmux(Node self, Node a, Node b, Node s) = 0; + virtual T constant(Node self, RTLIL::Const value) = 0; + virtual T input(Node self, IdString name) = 0; + virtual T state(Node self, IdString name) = 0; + virtual T memory_read(Node self, Node mem, Node addr) = 0; + virtual T memory_write(Node self, Node mem, Node addr, Node data) = 0; + virtual T undriven(Node self, int width) = 0; + }; + // DefaultVisitor provides defaults for all visitor methods which just calls default_handler + template struct DefaultVisitor : public AbstractVisitor { + virtual T default_handler(Node self) = 0; + T buf(Node self, Node) override { return default_handler(self); } + T slice(Node self, Node, int, int) override { return default_handler(self); } + T zero_extend(Node self, Node, int) override { return default_handler(self); } + T sign_extend(Node self, Node, int) override { return default_handler(self); } + T concat(Node self, Node, Node) override { return default_handler(self); } + T add(Node self, Node, Node) override { return default_handler(self); } + T sub(Node self, Node, Node) override { return default_handler(self); } + T mul(Node self, Node, Node) override { return default_handler(self); } + T unsigned_div(Node self, Node, Node) override { return default_handler(self); } + T unsigned_mod(Node self, Node, Node) override { return default_handler(self); } + T bitwise_and(Node self, Node, Node) override { return default_handler(self); } + T bitwise_or(Node self, Node, Node) override { return default_handler(self); } + T bitwise_xor(Node self, Node, Node) override { return default_handler(self); } + T bitwise_not(Node self, Node) override { return default_handler(self); } + T unary_minus(Node self, Node) override { return default_handler(self); } + T reduce_and(Node self, Node) override { return default_handler(self); } + T reduce_or(Node self, Node) override { return default_handler(self); } + T reduce_xor(Node self, Node) override { return default_handler(self); } + T equal(Node self, Node, Node) override { return default_handler(self); } + T not_equal(Node self, Node, Node) override { return default_handler(self); } + T signed_greater_than(Node self, Node, Node) override { return default_handler(self); } + T signed_greater_equal(Node self, Node, Node) override { return default_handler(self); } + T unsigned_greater_than(Node self, Node, Node) override { return default_handler(self); } + T unsigned_greater_equal(Node self, Node, Node) override { return default_handler(self); } + T logical_shift_left(Node self, Node, Node) override { return default_handler(self); } + T logical_shift_right(Node self, Node, Node) override { return default_handler(self); } + T arithmetic_shift_right(Node self, Node, Node) override { return default_handler(self); } + T mux(Node self, Node, Node, Node) override { return default_handler(self); } + T pmux(Node self, Node, Node, Node) override { return default_handler(self); } + T constant(Node self, RTLIL::Const) override { return default_handler(self); } + T input(Node self, IdString) override { return default_handler(self); } + T state(Node self, IdString) override { return default_handler(self); } + T memory_read(Node self, Node, Node) override { return default_handler(self); } + T memory_write(Node self, Node, Node, Node) override { return default_handler(self); } + T undriven(Node self, int) override { return default_handler(self); } }; + // a factory is used to modify a FunctionalIR. it creates new nodes and allows for some modification of existing nodes. class Factory { FunctionalIR &_ir; friend class FunctionalIR; @@ -243,19 +375,29 @@ class FunctionalIR { Node add(NodeData &&fn, Sort &&sort, std::initializer_list args) { Graph::Ref ref = _ir._graph.add(std::move(fn), {std::move(sort)}); for (auto arg : args) - ref.append_arg(Graph::Ref(arg)); + ref.append_arg(Graph::ConstRef(arg)); return Node(ref); } + Graph::Ref mutate(Node n) { + return _ir._graph[n._ref.index()]; + } void check_basic_binary(Node const &a, Node const &b) { log_assert(a.sort().is_signal() && a.sort() == b.sort()); } void check_shift(Node const &a, Node const &b) { log_assert(a.sort().is_signal() && b.sort().is_signal()); } void check_unary(Node const &a) { log_assert(a.sort().is_signal()); } public: Node slice(Node a, int, int offset, int out_width) { log_assert(a.sort().is_signal() && offset + out_width <= a.sort().width()); + if(offset == 0 && out_width == a.width()) + return a; return add(NodeData(Fn::slice, offset), Sort(out_width), {a}); } Node extend(Node a, int, int out_width, bool is_signed) { - log_assert(a.sort().is_signal() && a.sort().width() < out_width); + int in_width = a.sort().width(); + log_assert(a.sort().is_signal()); + if(in_width == out_width) + return a; + if(in_width < out_width) + return slice(a, in_width, 0, out_width); if(is_signed) return add(Fn::sign_extend, Sort(out_width), {a}); else @@ -275,9 +417,24 @@ class FunctionalIR { Node bitwise_xor(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::bitwise_xor, a.sort(), {a, b}); } Node bitwise_not(Node a, int) { check_unary(a); return add(Fn::bitwise_not, a.sort(), {a}); } Node unary_minus(Node a, int) { check_unary(a); return add(Fn::unary_minus, a.sort(), {a}); } - Node reduce_and(Node a, int) { check_unary(a); return add(Fn::reduce_and, Sort(1), {a}); } - Node reduce_or(Node a, int) { check_unary(a); return add(Fn::reduce_or, Sort(1), {a}); } - Node reduce_xor(Node a, int) { check_unary(a); return add(Fn::reduce_xor, Sort(1), {a}); } + Node reduce_and(Node a, int) { + check_unary(a); + if(a.width() == 1) + return a; + return add(Fn::reduce_and, Sort(1), {a}); + } + Node reduce_or(Node a, int) { + check_unary(a); + if(a.width() == 1) + return a; + return add(Fn::reduce_or, Sort(1), {a}); + } + Node reduce_xor(Node a, int) { + check_unary(a); + if(a.width() == 1) + return a; + return add(Fn::reduce_xor, Sort(1), {a}); + } Node equal(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::equal, Sort(1), {a, b}); } Node not_equal(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::not_equal, Sort(1), {a, b}); } Node signed_greater_than(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::signed_greater_than, Sort(1), {a, b}); } @@ -313,7 +470,7 @@ class FunctionalIR { void update_pending(Node node, Node value) { log_assert(node._ref.function() == Fn::buf && node._ref.size() == 0); log_assert(node.sort() == value.sort()); - node._ref.append_arg(value._ref); + mutate(node).append_arg(value._ref); } Node input(IdString name, int width) { _ir.add_input(name, Sort(width)); @@ -333,7 +490,7 @@ class FunctionalIR { Node multiple(vector args, int width) { auto node = add(Fn::multiple, Sort(width), {}); for(const auto &arg : args) - node._ref.append_arg(arg._ref); + mutate(node).append_arg(arg._ref); return node; } Node undriven(int width) { @@ -341,18 +498,18 @@ class FunctionalIR { } void declare_output(Node node, IdString name, int width) { _ir.add_output(name, Sort(width)); - node._ref.assign_key({name, false}); + mutate(node).assign_key({name, false}); } void declare_state(Node node, IdString name, int width) { _ir.add_state(name, Sort(width)); - node._ref.assign_key({name, true}); + mutate(node).assign_key({name, true}); } void declare_state_memory(Node node, IdString name, int addr_width, int data_width) { _ir.add_state(name, Sort(addr_width, data_width)); - node._ref.assign_key({name, true}); + mutate(node).assign_key({name, true}); } void suggest_name(Node node, IdString name) { - node._ref.sparse_attr() = name; + mutate(node).sparse_attr() = name; } }; static FunctionalIR from_module(Module *module); From 7f8f21b980770f2771aae61f830a5784fc16fbe6 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Wed, 17 Jul 2024 12:42:24 +0100 Subject: [PATCH 55/92] remove widths parameters from FunctionalIR factory methods and from functionalir.cc --- kernel/functionalir.cc | 391 +++++++++++++++++++---------------------- kernel/functionalir.h | 66 ++++--- 2 files changed, 216 insertions(+), 241 deletions(-) diff --git a/kernel/functionalir.cc b/kernel/functionalir.cc index 9b8076a0ef7..48baad7168b 100644 --- a/kernel/functionalir.cc +++ b/kernel/functionalir.cc @@ -98,87 +98,75 @@ std::string FunctionalIR::Node::to_string(std::function np) return visit(PrintVisitor(np)); } -template class CellSimplifier { - Factory &factory; - T reduce_shift_width(T b, int b_width, int y_width, int &reduced_b_width) { + using Node = FunctionalIR::Node; + FunctionalIR::Factory &factory; + Node reduce_shift_width(Node b, int y_width) { log_assert(y_width > 0); int new_width = ceil_log2(y_width + 1); - if (b_width <= new_width) { - reduced_b_width = b_width; + if (b.width() <= new_width) { return b; } else { - reduced_b_width = new_width; - T lower_b = factory.slice(b, b_width, 0, new_width); - T overflow = factory.unsigned_greater_than(b, factory.constant(RTLIL::Const(y_width, b_width)), b_width); - return factory.mux(lower_b, factory.constant(RTLIL::Const(y_width, new_width)), overflow, new_width); + Node lower_b = factory.slice(b, 0, new_width); + Node overflow = factory.unsigned_greater_than(b, factory.constant(RTLIL::Const(y_width, b.width()))); + return factory.mux(lower_b, factory.constant(RTLIL::Const(y_width, new_width)), overflow); } } - T sign(T a, int a_width) { - return factory.slice(a, a_width, a_width - 1, 1); + Node sign(Node a) { + return factory.slice(a, a.width() - 1, 1); } - T neg_if(T a, int a_width, T s) { - return factory.mux(a, factory.unary_minus(a, a_width), s, a_width); + Node neg_if(Node a, Node s) { + return factory.mux(a, factory.unary_minus(a), s); } - T abs(T a, int a_width) { - return neg_if(a, a_width, sign(a, a_width)); + Node abs(Node a) { + return neg_if(a, sign(a)); } public: - T extend(T a, int in_width, int out_width, bool is_signed) { - if(in_width == out_width) - return a; - if(in_width > out_width) - return factory.slice(a, in_width, 0, out_width); - return factory.extend(a, in_width, out_width, is_signed); - } - T logical_shift_left(T a, T b, int y_width, int b_width) { - int reduced_b_width; - T reduced_b = reduce_shift_width(b, b_width, y_width, reduced_b_width); - return factory.logical_shift_left(a, reduced_b, y_width, reduced_b_width); + Node logical_shift_left(Node a, Node b) { + Node reduced_b = reduce_shift_width(b, a.width()); + return factory.logical_shift_left(a, reduced_b); } - T logical_shift_right(T a, T b, int y_width, int b_width) { - int reduced_b_width; - T reduced_b = reduce_shift_width(b, b_width, y_width, reduced_b_width); - return factory.logical_shift_right(a, reduced_b, y_width, reduced_b_width); + Node logical_shift_right(Node a, Node b) { + Node reduced_b = reduce_shift_width(b, a.width()); + return factory.logical_shift_right(a, reduced_b); } - T arithmetic_shift_right(T a, T b, int y_width, int b_width) { - int reduced_b_width; - T reduced_b = reduce_shift_width(b, b_width, y_width, reduced_b_width); - return factory.arithmetic_shift_right(a, reduced_b, y_width, reduced_b_width); + Node arithmetic_shift_right(Node a, Node b) { + Node reduced_b = reduce_shift_width(b, a.width()); + return factory.arithmetic_shift_right(a, reduced_b); } - T bitwise_mux(T a, T b, T s, int width) { - T aa = factory.bitwise_and(a, factory.bitwise_not(s, width), width); - T bb = factory.bitwise_and(b, s, width); - return factory.bitwise_or(aa, bb, width); + Node bitwise_mux(Node a, Node b, Node s) { + Node aa = factory.bitwise_and(a, factory.bitwise_not(s)); + Node bb = factory.bitwise_and(b, s); + return factory.bitwise_or(aa, bb); } - CellSimplifier(Factory &f) : factory(f) {} + CellSimplifier(FunctionalIR::Factory &f) : factory(f) {} private: - T handle_pow(T a0, int a_width, T b, int b_width, int y_width, bool is_signed) { - T a = extend(a0, a_width, y_width, is_signed); - T r = factory.constant(Const(1, y_width)); - for(int i = 0; i < b_width; i++) { - T b_bit = factory.slice(b, b_width, i, 1); - r = factory.mux(r, factory.mul(r, a, y_width), b_bit, y_width); - a = factory.mul(a, a, y_width); + Node handle_pow(Node a0, Node b, int y_width, bool is_signed) { + Node a = factory.extend(a0, y_width, is_signed); + Node r = factory.constant(Const(1, y_width)); + for(int i = 0; i < b.width(); i++) { + Node b_bit = factory.slice(b, i, 1); + r = factory.mux(r, factory.mul(r, a), b_bit); + a = factory.mul(a, a); } if (is_signed) { - T a_ge_1 = factory.unsigned_greater_than(abs(a0, a_width), factory.constant(Const(1, a_width)), a_width); - T zero_result = factory.bitwise_and(a_ge_1, sign(b, b_width), 1); - r = factory.mux(r, factory.constant(Const(0, y_width)), zero_result, y_width); + Node a_ge_1 = factory.unsigned_greater_than(abs(a0), factory.constant(Const(1, a0.width()))); + Node zero_result = factory.bitwise_and(a_ge_1, sign(b)); + r = factory.mux(r, factory.constant(Const(0, y_width)), zero_result); } return r; } - T handle_bmux(T a, T s, int a_width, int a_offset, int width, int s_width, int sn) { + Node handle_bmux(Node a, Node s, int a_offset, int width, int sn) { if(sn < 1) - return factory.slice(a, a_width, a_offset, width); + return factory.slice(a, a_offset, width); else { - T y0 = handle_bmux(a, s, a_width, a_offset, width, s_width, sn - 1); - T y1 = handle_bmux(a, s, a_width, a_offset + (width << (sn - 1)), width, s_width, sn - 1); - return factory.mux(y0, y1, factory.slice(s, s_width, sn - 1, 1), width); + Node y0 = handle_bmux(a, s, a_offset, width, sn - 1); + Node y1 = handle_bmux(a, s, a_offset + (width << (sn - 1)), width, sn - 1); + return factory.mux(y0, y1, factory.slice(s, sn - 1, 1)); } } public: - T handle(IdString cellType, dict parameters, dict inputs) + Node handle(IdString cellType, dict parameters, dict inputs) { int a_width = parameters.at(ID(A_WIDTH), Const(-1)).as_int(); int b_width = parameters.at(ID(B_WIDTH), Const(-1)).as_int(); @@ -187,208 +175,202 @@ class CellSimplifier { bool b_signed = parameters.at(ID(B_SIGNED), Const(0)).as_bool(); if(cellType.in({ID($add), ID($sub), ID($and), ID($or), ID($xor), ID($xnor), ID($mul)})){ bool is_signed = a_signed && b_signed; - T a = extend(inputs.at(ID(A)), a_width, y_width, is_signed); - T b = extend(inputs.at(ID(B)), b_width, y_width, is_signed); + Node a = factory.extend(inputs.at(ID(A)), y_width, is_signed); + Node b = factory.extend(inputs.at(ID(B)), y_width, is_signed); if(cellType == ID($add)) - return factory.add(a, b, y_width); + return factory.add(a, b); else if(cellType == ID($sub)) - return factory.sub(a, b, y_width); + return factory.sub(a, b); else if(cellType == ID($mul)) - return factory.mul(a, b, y_width); + return factory.mul(a, b); else if(cellType == ID($and)) - return factory.bitwise_and(a, b, y_width); + return factory.bitwise_and(a, b); else if(cellType == ID($or)) - return factory.bitwise_or(a, b, y_width); + return factory.bitwise_or(a, b); else if(cellType == ID($xor)) - return factory.bitwise_xor(a, b, y_width); + return factory.bitwise_xor(a, b); else if(cellType == ID($xnor)) - return factory.bitwise_not(factory.bitwise_xor(a, b, y_width), y_width); + return factory.bitwise_not(factory.bitwise_xor(a, b)); else log_abort(); }else if(cellType.in({ID($eq), ID($ne), ID($eqx), ID($nex), ID($le), ID($lt), ID($ge), ID($gt)})){ bool is_signed = a_signed && b_signed; int width = max(a_width, b_width); - T a = extend(inputs.at(ID(A)), a_width, width, is_signed); - T b = extend(inputs.at(ID(B)), b_width, width, is_signed); + Node a = factory.extend(inputs.at(ID(A)), width, is_signed); + Node b = factory.extend(inputs.at(ID(B)), width, is_signed); if(cellType.in({ID($eq), ID($eqx)})) - return extend(factory.equal(a, b, width), 1, y_width, false); + return factory.extend(factory.equal(a, b), y_width, false); else if(cellType.in({ID($ne), ID($nex)})) - return extend(factory.not_equal(a, b, width), 1, y_width, false); + return factory.extend(factory.not_equal(a, b), y_width, false); else if(cellType == ID($lt)) - return extend(is_signed ? factory.signed_greater_than(b, a, width) : factory.unsigned_greater_than(b, a, width), 1, y_width, false); + return factory.extend(is_signed ? factory.signed_greater_than(b, a) : factory.unsigned_greater_than(b, a), y_width, false); else if(cellType == ID($le)) - return extend(is_signed ? factory.signed_greater_equal(b, a, width) : factory.unsigned_greater_equal(b, a, width), 1, y_width, false); + return factory.extend(is_signed ? factory.signed_greater_equal(b, a) : factory.unsigned_greater_equal(b, a), y_width, false); else if(cellType == ID($gt)) - return extend(is_signed ? factory.signed_greater_than(a, b, width) : factory.unsigned_greater_than(a, b, width), 1, y_width, false); + return factory.extend(is_signed ? factory.signed_greater_than(a, b) : factory.unsigned_greater_than(a, b), y_width, false); else if(cellType == ID($ge)) - return extend(is_signed ? factory.signed_greater_equal(a, b, width) : factory.unsigned_greater_equal(a, b, width), 1, y_width, false); + return factory.extend(is_signed ? factory.signed_greater_equal(a, b) : factory.unsigned_greater_equal(a, b), y_width, false); else log_abort(); }else if(cellType.in({ID($logic_or), ID($logic_and)})){ - T a = factory.reduce_or(inputs.at(ID(A)), a_width); - T b = factory.reduce_or(inputs.at(ID(B)), b_width); - T y = cellType == ID($logic_and) ? factory.bitwise_and(a, b, 1) : factory.bitwise_or(a, b, 1); - return extend(y, 1, y_width, false); + Node a = factory.reduce_or(inputs.at(ID(A))); + Node b = factory.reduce_or(inputs.at(ID(B))); + Node y = cellType == ID($logic_and) ? factory.bitwise_and(a, b) : factory.bitwise_or(a, b); + return factory.extend(y, y_width, false); }else if(cellType == ID($not)){ - T a = extend(inputs.at(ID(A)), a_width, y_width, a_signed); - return factory.bitwise_not(a, y_width); + Node a = factory.extend(inputs.at(ID(A)), y_width, a_signed); + return factory.bitwise_not(a); }else if(cellType == ID($pos)){ - return extend(inputs.at(ID(A)), a_width, y_width, a_signed); + return factory.extend(inputs.at(ID(A)), y_width, a_signed); }else if(cellType == ID($neg)){ - T a = extend(inputs.at(ID(A)), a_width, y_width, a_signed); - return factory.unary_minus(a, y_width); + Node a = factory.extend(inputs.at(ID(A)), y_width, a_signed); + return factory.unary_minus(a); }else if(cellType == ID($logic_not)){ - T a = factory.reduce_or(inputs.at(ID(A)), a_width); - T y = factory.bitwise_not(a, 1); - return extend(y, 1, y_width, false); + Node a = factory.reduce_or(inputs.at(ID(A))); + Node y = factory.bitwise_not(a); + return factory.extend(y, y_width, false); }else if(cellType.in({ID($reduce_or), ID($reduce_bool)})){ - T a = factory.reduce_or(inputs.at(ID(A)), a_width); - return extend(a, 1, y_width, false); + Node a = factory.reduce_or(inputs.at(ID(A))); + return factory.extend(a, y_width, false); }else if(cellType == ID($reduce_and)){ - T a = factory.reduce_and(inputs.at(ID(A)), a_width); - return extend(a, 1, y_width, false); + Node a = factory.reduce_and(inputs.at(ID(A))); + return factory.extend(a, y_width, false); }else if(cellType.in({ID($reduce_xor), ID($reduce_xnor)})){ - T a = factory.reduce_xor(inputs.at(ID(A)), a_width); - T y = cellType == ID($reduce_xnor) ? factory.bitwise_not(a, 1) : a; - return extend(y, 1, y_width, false); + Node a = factory.reduce_xor(inputs.at(ID(A))); + Node y = cellType == ID($reduce_xnor) ? factory.bitwise_not(a) : a; + return factory.extend(y, y_width, false); }else if(cellType == ID($shl) || cellType == ID($sshl)){ - T a = extend(inputs.at(ID(A)), a_width, y_width, a_signed); - T b = inputs.at(ID(B)); - return logical_shift_left(a, b, y_width, b_width); + Node a = factory.extend(inputs.at(ID(A)), y_width, a_signed); + Node b = inputs.at(ID(B)); + return logical_shift_left(a, b); }else if(cellType == ID($shr) || cellType == ID($sshr)){ int width = max(a_width, y_width); - T a = extend(inputs.at(ID(A)), a_width, width, a_signed); - T b = inputs.at(ID(B)); - T y = a_signed && cellType == ID($sshr) ? - arithmetic_shift_right(a, b, width, b_width) : - logical_shift_right(a, b, width, b_width); - return extend(y, width, y_width, a_signed); + Node a = factory.extend(inputs.at(ID(A)), width, a_signed); + Node b = inputs.at(ID(B)); + Node y = a_signed && cellType == ID($sshr) ? + arithmetic_shift_right(a, b) : + logical_shift_right(a, b); + return factory.extend(y, y_width, a_signed); }else if(cellType == ID($shiftx) || cellType == ID($shift)){ int width = max(a_width, y_width); - T a = extend(inputs.at(ID(A)), a_width, width, cellType == ID($shift) && a_signed); - T b = inputs.at(ID(B)); - T shr = logical_shift_right(a, b, width, b_width); + Node a = factory.extend(inputs.at(ID(A)), width, cellType == ID($shift) && a_signed); + Node b = inputs.at(ID(B)); + Node shr = logical_shift_right(a, b); if(b_signed) { - T sign_b = sign(b, b_width); - T shl = logical_shift_left(a, factory.unary_minus(b, b_width), width, b_width); - T y = factory.mux(shr, shl, sign_b, width); - return extend(y, width, y_width, false); + Node shl = logical_shift_left(a, factory.unary_minus(b)); + Node y = factory.mux(shr, shl, sign(b)); + return factory.extend(y, y_width, false); } else { - return extend(shr, width, y_width, false); + return factory.extend(shr, y_width, false); } }else if(cellType == ID($mux)){ - int width = parameters.at(ID(WIDTH)).as_int(); - return factory.mux(inputs.at(ID(A)), inputs.at(ID(B)), inputs.at(ID(S)), width); + return factory.mux(inputs.at(ID(A)), inputs.at(ID(B)), inputs.at(ID(S))); }else if(cellType == ID($pmux)){ - int width = parameters.at(ID(WIDTH)).as_int(); - int s_width = parameters.at(ID(S_WIDTH)).as_int(); - return factory.pmux(inputs.at(ID(A)), inputs.at(ID(B)), inputs.at(ID(S)), width, s_width); + return factory.pmux(inputs.at(ID(A)), inputs.at(ID(B)), inputs.at(ID(S))); }else if(cellType == ID($concat)){ - T a = inputs.at(ID(A)); - T b = inputs.at(ID(B)); - return factory.concat(a, a_width, b, b_width); + Node a = inputs.at(ID(A)); + Node b = inputs.at(ID(B)); + return factory.concat(a, b); }else if(cellType == ID($slice)){ int offset = parameters.at(ID(OFFSET)).as_int(); - T a = inputs.at(ID(A)); - return factory.slice(a, a_width, offset, y_width); + Node a = inputs.at(ID(A)); + return factory.slice(a, offset, y_width); }else if(cellType.in({ID($div), ID($mod), ID($divfloor), ID($modfloor)})) { int width = max(a_width, b_width); bool is_signed = a_signed && b_signed; - T a = extend(inputs.at(ID(A)), a_width, width, is_signed); - T b = extend(inputs.at(ID(B)), b_width, width, is_signed); + Node a = factory.extend(inputs.at(ID(A)), width, is_signed); + Node b = factory.extend(inputs.at(ID(B)), width, is_signed); if(is_signed) { if(cellType == ID($div)) { // divide absolute values, then flip the sign if input signs differ // but extend the width first, to handle the case (most negative value) / (-1) - T abs_y = factory.unsigned_div(abs(a, width), abs(b, width), width); - T out_sign = factory.not_equal(sign(a, width), sign(b, width), 1); - return neg_if(extend(abs_y, width, y_width, false), y_width, out_sign); + Node abs_y = factory.unsigned_div(abs(a), abs(b)); + Node out_sign = factory.not_equal(sign(a), sign(b)); + return neg_if(factory.extend(abs_y, y_width, false), out_sign); } else if(cellType == ID($mod)) { // similar to division but output sign == divisor sign - T abs_y = factory.unsigned_mod(abs(a, width), abs(b, width), width); - return neg_if(extend(abs_y, width, y_width, false), y_width, sign(a, width)); + Node abs_y = factory.unsigned_mod(abs(a), abs(b)); + return neg_if(factory.extend(abs_y, y_width, false), sign(a)); } else if(cellType == ID($divfloor)) { // if b is negative, flip both signs so that b is positive - T b_sign = sign(b, width); - T a1 = neg_if(a, width, b_sign); - T b1 = neg_if(b, width, b_sign); + Node b_sign = sign(b); + Node a1 = neg_if(a, b_sign); + Node b1 = neg_if(b, b_sign); // if a is now negative, calculate ~((~a) / b) = -((-a - 1) / b + 1) // which equals the negative of (-a) / b with rounding up rather than down // note that to handle the case where a = most negative value properly, - // we have to calculate a1_sign from the original values rather than using sign(a1, width) - T a1_sign = factory.bitwise_and(factory.not_equal(sign(a, width), sign(b, width), 1), factory.reduce_or(a, width), 1); - T a2 = factory.mux(a1, factory.bitwise_not(a1, width), a1_sign, width); - T y1 = factory.unsigned_div(a2, b1, width); - T y2 = extend(y1, width, y_width, false); - return factory.mux(y2, factory.bitwise_not(y2, y_width), a1_sign, y_width); + // we have to calculate a1_sign from the original values rather than using sign(a1) + Node a1_sign = factory.bitwise_and(factory.not_equal(sign(a), sign(b)), factory.reduce_or(a)); + Node a2 = factory.mux(a1, factory.bitwise_not(a1), a1_sign); + Node y1 = factory.unsigned_div(a2, b1); + Node y2 = factory.extend(y1, y_width, false); + return factory.mux(y2, factory.bitwise_not(y2), a1_sign); } else if(cellType == ID($modfloor)) { // calculate |a| % |b| and then subtract from |b| if input signs differ and the remainder is non-zero - T abs_b = abs(b, width); - T abs_y = factory.unsigned_mod(abs(a, width), abs_b, width); - T flip_y = factory.bitwise_and(factory.bitwise_xor(sign(a, width), sign(b, width), 1), factory.reduce_or(abs_y, width), 1); - T y_flipped = factory.mux(abs_y, factory.sub(abs_b, abs_y, width), flip_y, width); + Node abs_b = abs(b); + Node abs_y = factory.unsigned_mod(abs(a), abs_b); + Node flip_y = factory.bitwise_and(factory.bitwise_xor(sign(a), sign(b)), factory.reduce_or(abs_y)); + Node y_flipped = factory.mux(abs_y, factory.sub(abs_b, abs_y), flip_y); // since y_flipped is strictly less than |b|, the top bit is always 0 and we can just sign extend the flipped result - T y = neg_if(y_flipped, width, sign(b, b_width)); - return extend(y, width, y_width, true); + Node y = neg_if(y_flipped, sign(b)); + return factory.extend(y, y_width, true); } else log_error("unhandled cell in CellSimplifier %s\n", cellType.c_str()); } else { if(cellType.in({ID($mod), ID($modfloor)})) - return extend(factory.unsigned_mod(a, b, width), width, y_width, false); + return factory.extend(factory.unsigned_mod(a, b), y_width, false); else - return extend(factory.unsigned_div(a, b, width), width, y_width, false); + return factory.extend(factory.unsigned_div(a, b), y_width, false); } } else if(cellType == ID($pow)) { - return handle_pow(inputs.at(ID(A)), a_width, inputs.at(ID(B)), b_width, y_width, a_signed && b_signed); + return handle_pow(inputs.at(ID(A)), inputs.at(ID(B)), y_width, a_signed && b_signed); } else if (cellType == ID($lut)) { int width = parameters.at(ID(WIDTH)).as_int(); Const lut_table = parameters.at(ID(LUT)); lut_table.extu(1 << width); - return handle_bmux(factory.constant(lut_table), inputs.at(ID(A)), 1 << width, 0, 1, width, width); + return handle_bmux(factory.constant(lut_table), inputs.at(ID(A)), 0, 1, width); } else if (cellType == ID($bwmux)) { - int width = parameters.at(ID(WIDTH)).as_int(); - T a = inputs.at(ID(A)); - T b = inputs.at(ID(B)); - T s = inputs.at(ID(S)); + Node a = inputs.at(ID(A)); + Node b = inputs.at(ID(B)); + Node s = inputs.at(ID(S)); return factory.bitwise_or( - factory.bitwise_and(a, factory.bitwise_not(s, width), width), - factory.bitwise_and(b, s, width), width); + factory.bitwise_and(a, factory.bitwise_not(s)), + factory.bitwise_and(b, s)); } else if (cellType == ID($bweqx)) { - int width = parameters.at(ID(WIDTH)).as_int(); - T a = inputs.at(ID(A)); - T b = inputs.at(ID(B)); - return factory.bitwise_not(factory.bitwise_xor(a, b, width), width); + Node a = inputs.at(ID(A)); + Node b = inputs.at(ID(B)); + return factory.bitwise_not(factory.bitwise_xor(a, b)); } else if(cellType == ID($bmux)) { int width = parameters.at(ID(WIDTH)).as_int(); int s_width = parameters.at(ID(S_WIDTH)).as_int(); - return handle_bmux(inputs.at(ID(A)), inputs.at(ID(S)), width << s_width, 0, width, s_width, s_width); + return handle_bmux(inputs.at(ID(A)), inputs.at(ID(S)), 0, width, s_width); } else if(cellType == ID($demux)) { int width = parameters.at(ID(WIDTH)).as_int(); int s_width = parameters.at(ID(S_WIDTH)).as_int(); int y_width = width << s_width; int b_width = ceil_log2(y_width + 1); - T a = extend(inputs.at(ID(A)), width, y_width, false); - T s = factory.extend(inputs.at(ID(S)), s_width, b_width, false); - T b = factory.mul(s, factory.constant(Const(width, b_width)), b_width); - return factory.logical_shift_left(a, b, y_width, b_width); + Node a = factory.extend(inputs.at(ID(A)), y_width, false); + Node s = factory.extend(inputs.at(ID(S)), b_width, false); + Node b = factory.mul(s, factory.constant(Const(width, b_width))); + return factory.logical_shift_left(a, b); } else { log_error("unhandled cell in CellSimplifier %s\n", cellType.c_str()); } } }; -template class FunctionalIRConstruction { + using Node = FunctionalIR::Node; std::deque queue; - dict graph_nodes; + dict graph_nodes; idict cells; DriverMap driver_map; - Factory& factory; - CellSimplifier simplifier; + FunctionalIR::Factory& factory; + CellSimplifier simplifier; vector memories_vector; dict memories; - T enqueue(DriveSpec const &spec) + Node enqueue(DriveSpec const &spec) { auto it = graph_nodes.find(spec); if(it == graph_nodes.end()){ @@ -400,7 +382,7 @@ class FunctionalIRConstruction { return it->second; } public: - FunctionalIRConstruction(Factory &f) : factory(f), simplifier(f) {} + FunctionalIRConstruction(FunctionalIR::Factory &f) : factory(f), simplifier(f) {} void add_module(Module *module) { driver_map.add(module); @@ -410,7 +392,7 @@ class FunctionalIRConstruction { } for (auto wire : module->wires()) { if (wire->port_output) { - T node = enqueue(DriveChunk(DriveChunkWire(wire, 0, wire->width))); + Node node = enqueue(DriveChunk(DriveChunkWire(wire, 0, wire->width))); factory.declare_output(node, wire->name, wire->width); } } @@ -420,37 +402,34 @@ class FunctionalIRConstruction { memories[mem.cell] = &mem; } } - T concatenate_read_results(Mem *, vector results) + Node concatenate_read_results(Mem *, vector results) { /* TODO: write code to check that this is ok to do */ if(results.size() == 0) return factory.undriven(0); - T node = results[0]; - int size = results[0].width(); - for(size_t i = 1; i < results.size(); i++) { - node = factory.concat(node, size, results[i], results[i].width()); - size += results[i].width(); - } + Node node = results[0]; + for(size_t i = 1; i < results.size(); i++) + node = factory.concat(node, results[i]); return node; } - T handle_memory(Mem *mem) + Node handle_memory(Mem *mem) { - vector read_results; + vector read_results; int addr_width = ceil_log2(mem->size); int data_width = mem->width; - T node = factory.state_memory(mem->cell->name, addr_width, data_width); + Node node = factory.state_memory(mem->cell->name, addr_width, data_width); for (auto &rd : mem->rd_ports) { log_assert(!rd.clk_enable); - T addr = enqueue(driver_map(DriveSpec(rd.addr))); - read_results.push_back(factory.memory_read(node, addr, addr_width, data_width)); + Node addr = enqueue(driver_map(DriveSpec(rd.addr))); + read_results.push_back(factory.memory_read(node, addr)); } for (auto &wr : mem->wr_ports) { - T en = enqueue(driver_map(DriveSpec(wr.en))); - T addr = enqueue(driver_map(DriveSpec(wr.addr))); - T new_data = enqueue(driver_map(DriveSpec(wr.data))); - T old_data = factory.memory_read(node, addr, addr_width, data_width); - T wr_data = simplifier.bitwise_mux(old_data, new_data, en, data_width); - node = factory.memory_write(node, addr, wr_data, addr_width, data_width); + Node en = enqueue(driver_map(DriveSpec(wr.en))); + Node addr = enqueue(driver_map(DriveSpec(wr.addr))); + Node new_data = enqueue(driver_map(DriveSpec(wr.data))); + Node old_data = factory.memory_read(node, addr); + Node wr_data = simplifier.bitwise_mux(old_data, new_data, en); + node = factory.memory_write(node, addr, wr_data); } factory.declare_state_memory(node, mem->cell->name, addr_width, data_width); return concatenate_read_results(mem, read_results); @@ -459,16 +438,13 @@ class FunctionalIRConstruction { { for (; !queue.empty(); queue.pop_front()) { DriveSpec spec = queue.front(); - T pending = graph_nodes.at(spec); + Node pending = graph_nodes.at(spec); if (spec.chunks().size() > 1) { auto chunks = spec.chunks(); - T node = enqueue(chunks[0]); - int width = chunks[0].size(); - for(size_t i = 1; i < chunks.size(); i++) { - node = factory.concat(node, width, enqueue(chunks[i]), chunks[i].size()); - width += chunks[i].size(); - } + Node node = enqueue(chunks[0]); + for(size_t i = 1; i < chunks.size(); i++) + node = factory.concat(node, enqueue(chunks[i])); factory.update_pending(pending, node); } else if (spec.chunks().size() == 1) { DriveChunk chunk = spec.chunks()[0]; @@ -476,18 +452,18 @@ class FunctionalIRConstruction { DriveChunkWire wire_chunk = chunk.wire(); if (wire_chunk.is_whole()) { if (wire_chunk.wire->port_input) { - T node = factory.input(wire_chunk.wire->name, wire_chunk.width); + Node node = factory.input(wire_chunk.wire->name, wire_chunk.width); factory.suggest_name(node, wire_chunk.wire->name); factory.update_pending(pending, node); } else { DriveSpec driver = driver_map(DriveSpec(wire_chunk)); - T node = enqueue(driver); + Node node = enqueue(driver); factory.suggest_name(node, wire_chunk.wire->name); factory.update_pending(pending, node); } } else { DriveChunkWire whole_wire(wire_chunk.wire, 0, wire_chunk.wire->width); - T node = factory.slice(enqueue(whole_wire), wire_chunk.wire->width, wire_chunk.offset, wire_chunk.width); + Node node = factory.slice(enqueue(whole_wire), wire_chunk.offset, wire_chunk.width); factory.update_pending(pending, node); } } else if (chunk.is_port()) { @@ -497,21 +473,22 @@ class FunctionalIRConstruction { if (port_chunk.cell->type.in(ID($dff), ID($ff))) { Cell *cell = port_chunk.cell; - T node = factory.state(cell->name, port_chunk.width); + Node node = factory.state(cell->name, port_chunk.width); factory.suggest_name(node, port_chunk.cell->name); factory.update_pending(pending, node); for (auto const &conn : cell->connections()) { if (driver_map.celltypes.cell_input(cell->type, conn.first)) { - T node = enqueue(DriveChunkPort(cell, conn)); + Node node = enqueue(DriveChunkPort(cell, conn)); factory.declare_state(node, cell->name, port_chunk.width); } } } else { - T cell = enqueue(DriveChunkMarker(cells(port_chunk.cell), 0, port_chunk.width)); + Node cell = enqueue(DriveChunkMarker(cells(port_chunk.cell), 0, port_chunk.width)); factory.suggest_name(cell, port_chunk.cell->name); - T node = factory.cell_output(cell, port_chunk.cell->type, port_chunk.port, port_chunk.width); + //Node node = factory.cell_output(cell, port_chunk.cell->type, port_chunk.port, port_chunk.width); + Node node = cell; factory.suggest_name(node, port_chunk.cell->name.str() + "$" + port_chunk.port.str()); factory.update_pending(pending, node); } @@ -521,37 +498,37 @@ class FunctionalIRConstruction { } } else { DriveChunkPort whole_port(port_chunk.cell, port_chunk.port, 0, GetSize(port_chunk.cell->connections().at(port_chunk.port))); - T node = factory.slice(enqueue(whole_port), whole_port.width, port_chunk.offset, port_chunk.width); + Node node = factory.slice(enqueue(whole_port), port_chunk.offset, port_chunk.width); factory.update_pending(pending, node); } } else if (chunk.is_constant()) { - T node = factory.constant(chunk.constant()); + Node node = factory.constant(chunk.constant()); factory.suggest_name(node, "$const" + std::to_string(chunk.size()) + "b" + chunk.constant().as_string()); factory.update_pending(pending, node); } else if (chunk.is_multiple()) { - vector args; + vector args; for (auto const &driver : chunk.multiple().multiple()) args.push_back(enqueue(driver)); - T node = factory.multiple(args, chunk.size()); + Node node = factory.multiple(args, chunk.size()); factory.update_pending(pending, node); } else if (chunk.is_marker()) { Cell *cell = cells[chunk.marker().marker]; if (cell->is_mem_cell()) { Mem *mem = memories.at(cell, nullptr); log_assert(mem != nullptr); - T node = handle_memory(mem); + Node node = handle_memory(mem); factory.update_pending(pending, node); } else { - dict connections; + dict connections; for(auto const &conn : cell->connections()) { if(driver_map.celltypes.cell_input(cell->type, conn.first)) connections.insert({ conn.first, enqueue(DriveChunkPort(cell, conn)) }); } - T node = simplifier.handle(cell->type, cell->parameters, connections); + Node node = simplifier.handle(cell->type, cell->parameters, connections); factory.update_pending(pending, node); } } else if (chunk.is_none()) { - T node = factory.undriven(chunk.size()); + Node node = factory.undriven(chunk.size()); factory.update_pending(pending, node); } else { log_error("unhandled drivespec: %s\n", log_signal(chunk)); @@ -567,7 +544,7 @@ class FunctionalIRConstruction { FunctionalIR FunctionalIR::from_module(Module *module) { FunctionalIR ir; auto factory = ir.factory(); - FunctionalIRConstruction ctor(factory); + FunctionalIRConstruction ctor(factory); ctor.add_module(module); ctor.process_queue(); ir.topological_sort(); diff --git a/kernel/functionalir.h b/kernel/functionalir.h index e586695695a..44e3589db2d 100644 --- a/kernel/functionalir.h +++ b/kernel/functionalir.h @@ -385,78 +385,79 @@ class FunctionalIR { void check_shift(Node const &a, Node const &b) { log_assert(a.sort().is_signal() && b.sort().is_signal()); } void check_unary(Node const &a) { log_assert(a.sort().is_signal()); } public: - Node slice(Node a, int, int offset, int out_width) { + Node slice(Node a, int offset, int out_width) { log_assert(a.sort().is_signal() && offset + out_width <= a.sort().width()); if(offset == 0 && out_width == a.width()) return a; return add(NodeData(Fn::slice, offset), Sort(out_width), {a}); } - Node extend(Node a, int, int out_width, bool is_signed) { + // extend will either extend or truncate the provided value to reach the desired width + Node extend(Node a, int out_width, bool is_signed) { int in_width = a.sort().width(); log_assert(a.sort().is_signal()); if(in_width == out_width) return a; - if(in_width < out_width) - return slice(a, in_width, 0, out_width); + if(in_width > out_width) + return slice(a, 0, out_width); if(is_signed) return add(Fn::sign_extend, Sort(out_width), {a}); else return add(Fn::zero_extend, Sort(out_width), {a}); } - Node concat(Node a, int, Node b, int) { + Node concat(Node a, Node b) { log_assert(a.sort().is_signal() && b.sort().is_signal()); return add(Fn::concat, Sort(a.sort().width() + b.sort().width()), {a, b}); } - Node add(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::add, a.sort(), {a, b}); } - Node sub(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::sub, a.sort(), {a, b}); } - Node mul(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::mul, a.sort(), {a, b}); } - Node unsigned_div(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::unsigned_div, a.sort(), {a, b}); } - Node unsigned_mod(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::unsigned_mod, a.sort(), {a, b}); } - Node bitwise_and(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::bitwise_and, a.sort(), {a, b}); } - Node bitwise_or(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::bitwise_or, a.sort(), {a, b}); } - Node bitwise_xor(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::bitwise_xor, a.sort(), {a, b}); } - Node bitwise_not(Node a, int) { check_unary(a); return add(Fn::bitwise_not, a.sort(), {a}); } - Node unary_minus(Node a, int) { check_unary(a); return add(Fn::unary_minus, a.sort(), {a}); } - Node reduce_and(Node a, int) { + Node add(Node a, Node b) { check_basic_binary(a, b); return add(Fn::add, a.sort(), {a, b}); } + Node sub(Node a, Node b) { check_basic_binary(a, b); return add(Fn::sub, a.sort(), {a, b}); } + Node mul(Node a, Node b) { check_basic_binary(a, b); return add(Fn::mul, a.sort(), {a, b}); } + Node unsigned_div(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_div, a.sort(), {a, b}); } + Node unsigned_mod(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_mod, a.sort(), {a, b}); } + Node bitwise_and(Node a, Node b) { check_basic_binary(a, b); return add(Fn::bitwise_and, a.sort(), {a, b}); } + Node bitwise_or(Node a, Node b) { check_basic_binary(a, b); return add(Fn::bitwise_or, a.sort(), {a, b}); } + Node bitwise_xor(Node a, Node b) { check_basic_binary(a, b); return add(Fn::bitwise_xor, a.sort(), {a, b}); } + Node bitwise_not(Node a) { check_unary(a); return add(Fn::bitwise_not, a.sort(), {a}); } + Node unary_minus(Node a) { check_unary(a); return add(Fn::unary_minus, a.sort(), {a}); } + Node reduce_and(Node a) { check_unary(a); if(a.width() == 1) return a; return add(Fn::reduce_and, Sort(1), {a}); } - Node reduce_or(Node a, int) { + Node reduce_or(Node a) { check_unary(a); if(a.width() == 1) return a; return add(Fn::reduce_or, Sort(1), {a}); } - Node reduce_xor(Node a, int) { + Node reduce_xor(Node a) { check_unary(a); if(a.width() == 1) return a; return add(Fn::reduce_xor, Sort(1), {a}); } - Node equal(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::equal, Sort(1), {a, b}); } - Node not_equal(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::not_equal, Sort(1), {a, b}); } - Node signed_greater_than(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::signed_greater_than, Sort(1), {a, b}); } - Node signed_greater_equal(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::signed_greater_equal, Sort(1), {a, b}); } - Node unsigned_greater_than(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::unsigned_greater_than, Sort(1), {a, b}); } - Node unsigned_greater_equal(Node a, Node b, int) { check_basic_binary(a, b); return add(Fn::unsigned_greater_equal, Sort(1), {a, b}); } - Node logical_shift_left(Node a, Node b, int, int) { check_shift(a, b); return add(Fn::logical_shift_left, a.sort(), {a, b}); } - Node logical_shift_right(Node a, Node b, int, int) { check_shift(a, b); return add(Fn::logical_shift_right, a.sort(), {a, b}); } - Node arithmetic_shift_right(Node a, Node b, int, int) { check_shift(a, b); return add(Fn::arithmetic_shift_right, a.sort(), {a, b}); } - Node mux(Node a, Node b, Node s, int) { + Node equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::equal, Sort(1), {a, b}); } + Node not_equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::not_equal, Sort(1), {a, b}); } + Node signed_greater_than(Node a, Node b) { check_basic_binary(a, b); return add(Fn::signed_greater_than, Sort(1), {a, b}); } + Node signed_greater_equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::signed_greater_equal, Sort(1), {a, b}); } + Node unsigned_greater_than(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_greater_than, Sort(1), {a, b}); } + Node unsigned_greater_equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_greater_equal, Sort(1), {a, b}); } + Node logical_shift_left(Node a, Node b) { check_shift(a, b); return add(Fn::logical_shift_left, a.sort(), {a, b}); } + Node logical_shift_right(Node a, Node b) { check_shift(a, b); return add(Fn::logical_shift_right, a.sort(), {a, b}); } + Node arithmetic_shift_right(Node a, Node b) { check_shift(a, b); return add(Fn::arithmetic_shift_right, a.sort(), {a, b}); } + Node mux(Node a, Node b, Node s) { log_assert(a.sort().is_signal() && a.sort() == b.sort() && s.sort() == Sort(1)); return add(Fn::mux, a.sort(), {a, b, s}); } - Node pmux(Node a, Node b, Node s, int, int) { + Node pmux(Node a, Node b, Node s) { log_assert(a.sort().is_signal() && b.sort().is_signal() && s.sort().is_signal() && a.sort().width() * s.sort().width() == b.sort().width()); return add(Fn::pmux, a.sort(), {a, b, s}); } - Node memory_read(Node mem, Node addr, int, int) { + Node memory_read(Node mem, Node addr) { log_assert(mem.sort().is_memory() && addr.sort().is_signal() && mem.sort().addr_width() == addr.sort().width()); return add(Fn::memory_read, Sort(mem.sort().data_width()), {mem, addr}); } - Node memory_write(Node mem, Node addr, Node data, int, int) { + Node memory_write(Node mem, Node addr, Node data) { log_assert(mem.sort().is_memory() && addr.sort().is_signal() && data.sort().is_signal() && mem.sort().addr_width() == addr.sort().width() && mem.sort().data_width() == data.sort().width()); return add(Fn::memory_write, mem.sort(), {mem, addr, data}); @@ -484,9 +485,6 @@ class FunctionalIR { _ir.add_state(name, Sort(addr_width, data_width)); return add(NodeData(Fn::state, name), Sort(addr_width, data_width), {}); } - Node cell_output(Node node, IdString, IdString, int) { - return node; - } Node multiple(vector args, int width) { auto node = add(Fn::multiple, Sort(width), {}); for(const auto &arg : args) From c0c90c2c312e8f1675613904a67d5e016a62bb03 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Wed, 17 Jul 2024 13:26:53 +0100 Subject: [PATCH 56/92] functional backend: require shift width == clog2(operand width) --- kernel/functionalir.cc | 48 ++++++++++++++++----------------- kernel/functionalir.h | 10 ++++--- tests/functional/rtlil_cells.py | 5 +++- 3 files changed, 34 insertions(+), 29 deletions(-) diff --git a/kernel/functionalir.cc b/kernel/functionalir.cc index 48baad7168b..ddd821adafa 100644 --- a/kernel/functionalir.cc +++ b/kernel/functionalir.cc @@ -101,17 +101,6 @@ std::string FunctionalIR::Node::to_string(std::function np) class CellSimplifier { using Node = FunctionalIR::Node; FunctionalIR::Factory &factory; - Node reduce_shift_width(Node b, int y_width) { - log_assert(y_width > 0); - int new_width = ceil_log2(y_width + 1); - if (b.width() <= new_width) { - return b; - } else { - Node lower_b = factory.slice(b, 0, new_width); - Node overflow = factory.unsigned_greater_than(b, factory.constant(RTLIL::Const(y_width, b.width()))); - return factory.mux(lower_b, factory.constant(RTLIL::Const(y_width, new_width)), overflow); - } - } Node sign(Node a) { return factory.slice(a, a.width() - 1, 1); } @@ -121,19 +110,30 @@ class CellSimplifier { Node abs(Node a) { return neg_if(a, sign(a)); } -public: - Node logical_shift_left(Node a, Node b) { - Node reduced_b = reduce_shift_width(b, a.width()); - return factory.logical_shift_left(a, reduced_b); - } - Node logical_shift_right(Node a, Node b) { - Node reduced_b = reduce_shift_width(b, a.width()); - return factory.logical_shift_right(a, reduced_b); - } - Node arithmetic_shift_right(Node a, Node b) { - Node reduced_b = reduce_shift_width(b, a.width()); - return factory.arithmetic_shift_right(a, reduced_b); + Node handle_shift(Node a, Node b, bool is_right, bool is_signed) { + // to prevent new_width == 0, we handle this case separately + if(a.width() == 1) { + if(!is_signed) + return factory.bitwise_and(a, factory.bitwise_not(factory.reduce_or(b))); + else + return a; + } + int new_width = ceil_log2(a.width()); + Node b_truncated = factory.extend(b, new_width, false); + Node y = + !is_right ? factory.logical_shift_left(a, b_truncated) : + !is_signed ? factory.logical_shift_right(a, b_truncated) : + factory.arithmetic_shift_right(a, b_truncated); + if(b.width() <= new_width) + return y; + Node overflow = factory.unsigned_greater_equal(b, factory.constant(RTLIL::Const(a.width(), b.width()))); + Node y_if_overflow = is_signed ? factory.extend(sign(a), a.width(), true) : factory.constant(RTLIL::Const(State::S0, a.width())); + return factory.mux(y, y_if_overflow, overflow); } +public: + Node logical_shift_left(Node a, Node b) { return handle_shift(a, b, false, false); } + Node logical_shift_right(Node a, Node b) { return handle_shift(a, b, true, false); } + Node arithmetic_shift_right(Node a, Node b) { return handle_shift(a, b, true, true); } Node bitwise_mux(Node a, Node b, Node s) { Node aa = factory.bitwise_and(a, factory.bitwise_not(s)); Node bb = factory.bitwise_and(b, s); @@ -348,7 +348,7 @@ class CellSimplifier { int width = parameters.at(ID(WIDTH)).as_int(); int s_width = parameters.at(ID(S_WIDTH)).as_int(); int y_width = width << s_width; - int b_width = ceil_log2(y_width + 1); + int b_width = ceil_log2(y_width); Node a = factory.extend(inputs.at(ID(A)), y_width, false); Node s = factory.extend(inputs.at(ID(S)), b_width, false); Node b = factory.mul(s, factory.constant(Const(width, b_width))); diff --git a/kernel/functionalir.h b/kernel/functionalir.h index 44e3589db2d..0559eae8921 100644 --- a/kernel/functionalir.h +++ b/kernel/functionalir.h @@ -109,13 +109,13 @@ class FunctionalIR { // unsigned_greater_equal(a: unsigned bit[N], b: unsigned bit[N]): bit[1] = (a >= b) unsigned_greater_equal, // logical_shift_left(a: bit[N], b: unsigned bit[M]): bit[N] = a << b - // required: M <= clog2(N + 1) + // required: M == clog2(N) logical_shift_left, // logical_shift_right(a: unsigned bit[N], b: unsigned bit[M]): unsigned bit[N] = a >> b - // required: M <= clog2(N + 1) + // required: M == clog2(N) logical_shift_right, // arithmetic_shift_right(a: signed bit[N], b: unsigned bit[M]): signed bit[N] = a >> b - // required: M <= clog2(N + 1) + // required: M == clog2(N) arithmetic_shift_right, // mux(a: bit[N], b: bit[N], s: bit[1]): bit[N] = s ? b : a mux, @@ -373,6 +373,8 @@ class FunctionalIR { friend class FunctionalIR; explicit Factory(FunctionalIR &ir) : _ir(ir) {} Node add(NodeData &&fn, Sort &&sort, std::initializer_list args) { + log_assert(!sort.is_signal() || sort.width() > 0); + log_assert(!sort.is_memory() || sort.addr_width() > 0 && sort.data_width() > 0); Graph::Ref ref = _ir._graph.add(std::move(fn), {std::move(sort)}); for (auto arg : args) ref.append_arg(Graph::ConstRef(arg)); @@ -382,7 +384,7 @@ class FunctionalIR { return _ir._graph[n._ref.index()]; } void check_basic_binary(Node const &a, Node const &b) { log_assert(a.sort().is_signal() && a.sort() == b.sort()); } - void check_shift(Node const &a, Node const &b) { log_assert(a.sort().is_signal() && b.sort().is_signal()); } + void check_shift(Node const &a, Node const &b) { log_assert(a.sort().is_signal() && b.sort().is_signal() && b.width() == ceil_log2(a.width())); } void check_unary(Node const &a) { log_assert(a.sort().is_signal()); } public: Node slice(Node a, int offset, int out_width) { diff --git a/tests/functional/rtlil_cells.py b/tests/functional/rtlil_cells.py index b4c0ad1c1ef..7858e3781d5 100644 --- a/tests/functional/rtlil_cells.py +++ b/tests/functional/rtlil_cells.py @@ -206,7 +206,10 @@ def write_rtlil_file(self, path, parameters): (32, 32, 64, True, True), (32, 32, 64, False, True), # at least one test where the result is going to be truncated - (32, 6, 16, False, False) + (32, 6, 16, False, False), + # since 1-bit shifts are special cased + (1, 4, 1, False, False), + (1, 4, 1, True, False), ] rtlil_cells = [ From 13bacc5c8f77fc98cf20d3d2c67d1f5dee1c84a6 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Wed, 17 Jul 2024 13:35:58 +0100 Subject: [PATCH 57/92] eliminate pmux in functional backend --- backends/functional/cxx.cc | 1 - backends/functional/cxx_runtime/sim.h | 19 ------------------- backends/functional/smtlib.cc | 6 ------ kernel/functionalir.cc | 11 +++++++++-- kernel/functionalir.h | 12 ------------ 5 files changed, 9 insertions(+), 40 deletions(-) diff --git a/backends/functional/cxx.cc b/backends/functional/cxx.cc index 920ccf363bc..0fa310164a6 100644 --- a/backends/functional/cxx.cc +++ b/backends/functional/cxx.cc @@ -136,7 +136,6 @@ template struct CxxPrintVisitor : public FunctionalIR::Abstra void logical_shift_right(Node, Node a, Node b) override { print("{} >> {}", a, b); } void arithmetic_shift_right(Node, Node a, Node b) override { print("{}.arithmetic_shift_right({})", a, b); } void mux(Node, Node a, Node b, Node s) override { print("{2}.any() ? {1} : {0}", a, b, s); } - void pmux(Node, Node a, Node b, Node s) override { print("{0}.pmux({1}, {2})", a, b, s); } void constant(Node, RTLIL::Const value) override { std::stringstream ss; bool multiple = value.size() > 32; diff --git a/backends/functional/cxx_runtime/sim.h b/backends/functional/cxx_runtime/sim.h index 950d4c5e10b..ed1a25ed40f 100644 --- a/backends/functional/cxx_runtime/sim.h +++ b/backends/functional/cxx_runtime/sim.h @@ -368,25 +368,6 @@ class Signal { return ret; } - template - Signal pmux(Signal const &b, Signal const &s) const - { - bool found; - Signal ret; - - found = false; - ret = *this; - for(size_t i = 0; i < ns; i++){ - if(s._bits[i]){ - if(found) - return 0; - found = true; - ret = b.template slice(n * i); - } - } - return ret; - } - template Signal concat(Signal const& b) const { diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index c35a48dd809..a2bb6666c3a 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -174,12 +174,6 @@ struct SmtPrintVisitor : public FunctionalIR::AbstractVisitor { SExpr logical_shift_right(Node, Node a, Node b) override { return list("bvlshr", n(a), extend(n(b), b.width(), a.width())); } SExpr arithmetic_shift_right(Node, Node a, Node b) override { return list("bvashr", n(a), extend(n(b), b.width(), a.width())); } SExpr mux(Node, Node a, Node b, Node s) override { return list("ite", to_bool(n(s)), n(b), n(a)); } - SExpr pmux(Node, Node a, Node b, Node s) override { - SExpr rv = n(a); - for(int i = 0; i < s.width(); i++) - rv = list("ite", to_bool(extract(n(s), i)), extract(n(b), a.width() * i, a.width()), rv); - return rv; - } SExpr constant(Node, RTLIL::Const value) override { return literal(value); } SExpr memory_read(Node, Node mem, Node addr) override { return list("select", n(mem), n(addr)); } SExpr memory_write(Node, Node mem, Node addr, Node data) override { return list("store", n(mem), n(addr), n(data)); } diff --git a/kernel/functionalir.cc b/kernel/functionalir.cc index ddd821adafa..4b2ad8c0bf9 100644 --- a/kernel/functionalir.cc +++ b/kernel/functionalir.cc @@ -52,7 +52,6 @@ const char *FunctionalIR::fn_to_string(FunctionalIR::Fn fn) { case FunctionalIR::Fn::logical_shift_right: return "logical_shift_right"; case FunctionalIR::Fn::arithmetic_shift_right: return "arithmetic_shift_right"; case FunctionalIR::Fn::mux: return "mux"; - case FunctionalIR::Fn::pmux: return "pmux"; case FunctionalIR::Fn::constant: return "constant"; case FunctionalIR::Fn::input: return "input"; case FunctionalIR::Fn::state: return "state"; @@ -165,6 +164,14 @@ class CellSimplifier { return factory.mux(y0, y1, factory.slice(s, sn - 1, 1)); } } + Node handle_pmux(Node a, Node b, Node s) { + // TODO : what to do about multiple b bits set ? + log_assert(b.width() == a.width() * s.width()); + Node y = a; + for(int i = 0; i < s.width(); i++) + y = factory.mux(y, factory.slice(b, a.width() * i, a.width()), factory.slice(s, i, 1)); + return y; + } public: Node handle(IdString cellType, dict parameters, dict inputs) { @@ -266,7 +273,7 @@ class CellSimplifier { }else if(cellType == ID($mux)){ return factory.mux(inputs.at(ID(A)), inputs.at(ID(B)), inputs.at(ID(S))); }else if(cellType == ID($pmux)){ - return factory.pmux(inputs.at(ID(A)), inputs.at(ID(B)), inputs.at(ID(S))); + return handle_pmux(inputs.at(ID(A)), inputs.at(ID(B)), inputs.at(ID(S))); }else if(cellType == ID($concat)){ Node a = inputs.at(ID(A)); Node b = inputs.at(ID(B)); diff --git a/kernel/functionalir.h b/kernel/functionalir.h index 0559eae8921..d0ec1bce6ba 100644 --- a/kernel/functionalir.h +++ b/kernel/functionalir.h @@ -119,11 +119,6 @@ class FunctionalIR { arithmetic_shift_right, // mux(a: bit[N], b: bit[N], s: bit[1]): bit[N] = s ? b : a mux, - // pmux(a: bit[N], b: bit[N*M], s: bit[M]): bit[N] - // required: no more than one bit in b is set - // if s[i] = 1 for any i, then returns b[i * N +: N] - // returns a if s == 0 - pmux, // constant(a: Const[N]): bit[N] = a constant, // input(a: IdString): any @@ -277,7 +272,6 @@ class FunctionalIR { case Fn::logical_shift_right: return v.logical_shift_right(*this, arg(0), arg(1)); break; case Fn::arithmetic_shift_right: return v.arithmetic_shift_right(*this, arg(0), arg(1)); break; case Fn::mux: return v.mux(*this, arg(0), arg(1), arg(2)); break; - case Fn::pmux: return v.pmux(*this, arg(0), arg(1), arg(2)); break; case Fn::constant: return v.constant(*this, _ref.function().as_const()); break; case Fn::input: return v.input(*this, _ref.function().as_idstring()); break; case Fn::state: return v.state(*this, _ref.function().as_idstring()); break; @@ -320,7 +314,6 @@ class FunctionalIR { virtual T logical_shift_right(Node self, Node a, Node b) = 0; virtual T arithmetic_shift_right(Node self, Node a, Node b) = 0; virtual T mux(Node self, Node a, Node b, Node s) = 0; - virtual T pmux(Node self, Node a, Node b, Node s) = 0; virtual T constant(Node self, RTLIL::Const value) = 0; virtual T input(Node self, IdString name) = 0; virtual T state(Node self, IdString name) = 0; @@ -359,7 +352,6 @@ class FunctionalIR { T logical_shift_right(Node self, Node, Node) override { return default_handler(self); } T arithmetic_shift_right(Node self, Node, Node) override { return default_handler(self); } T mux(Node self, Node, Node, Node) override { return default_handler(self); } - T pmux(Node self, Node, Node, Node) override { return default_handler(self); } T constant(Node self, RTLIL::Const) override { return default_handler(self); } T input(Node self, IdString) override { return default_handler(self); } T state(Node self, IdString) override { return default_handler(self); } @@ -451,10 +443,6 @@ class FunctionalIR { log_assert(a.sort().is_signal() && a.sort() == b.sort() && s.sort() == Sort(1)); return add(Fn::mux, a.sort(), {a, b, s}); } - Node pmux(Node a, Node b, Node s) { - log_assert(a.sort().is_signal() && b.sort().is_signal() && s.sort().is_signal() && a.sort().width() * s.sort().width() == b.sort().width()); - return add(Fn::pmux, a.sort(), {a, b, s}); - } Node memory_read(Node mem, Node addr) { log_assert(mem.sort().is_memory() && addr.sort().is_signal() && mem.sort().addr_width() == addr.sort().width()); return add(Fn::memory_read, Sort(mem.sort().data_width()), {mem, addr}); From 3cd5f4ed83413cf4df695217ff85e3190ae4a8c7 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Wed, 17 Jul 2024 16:33:34 +0100 Subject: [PATCH 58/92] add support for RTLIL cells with multiple outputs to the functional backend, implement $fa,$lcu,$alu --- kernel/functionalir.cc | 123 ++++++++++++++++++++++------ tests/functional/rtlil_cells.py | 21 ++++- tests/functional/test_functional.py | 8 +- 3 files changed, 118 insertions(+), 34 deletions(-) diff --git a/kernel/functionalir.cc b/kernel/functionalir.cc index 4b2ad8c0bf9..e768be4005d 100644 --- a/kernel/functionalir.cc +++ b/kernel/functionalir.cc @@ -18,6 +18,7 @@ */ #include "kernel/functionalir.h" +#include YOSYS_NAMESPACE_BEGIN @@ -172,8 +173,38 @@ class CellSimplifier { y = factory.mux(y, factory.slice(b, a.width() * i, a.width()), factory.slice(s, i, 1)); return y; } + dict handle_fa(Node a, Node b, Node c) { + Node t1 = factory.bitwise_xor(a, b); + Node t2 = factory.bitwise_and(a, b); + Node t3 = factory.bitwise_and(c, t1); + Node y = factory.bitwise_xor(c, t1); + Node x = factory.bitwise_or(t2, t3); + return {{ID(X), x}, {ID(Y), y}}; + } + Node handle_lcu(Node p, Node g, Node ci) { + Node rv = factory.bitwise_or(factory.slice(g, 0, 1), factory.bitwise_and(factory.slice(p, 0, 1), ci)); + Node c = rv; + for(int i = 1; i < p.width(); i++) { + c = factory.bitwise_or(factory.slice(g, i, 1), factory.bitwise_and(factory.slice(p, i, 1), c)); + rv = factory.concat(rv, c); + } + return rv; + } + dict handle_alu(Node a_in, Node b_in, int y_width, bool is_signed, Node ci, Node bi) { + Node a = factory.extend(a_in, y_width, is_signed); + Node b_uninverted = factory.extend(b_in, y_width, is_signed); + Node b = factory.mux(b_uninverted, factory.bitwise_not(b_uninverted), bi); + Node x = factory.bitwise_xor(a, b); + Node a_extra = factory.extend(a, y_width + 1, false); + Node b_extra = factory.extend(b, y_width + 1, false); + Node y_extra = factory.add(factory.add(a_extra, b_extra), factory.extend(ci, a.width() + 1, false)); + Node y = factory.slice(y_extra, 0, y_width); + Node carries = factory.bitwise_xor(y_extra, factory.bitwise_xor(a_extra, b_extra)); + Node co = factory.slice(carries, 1, y_width); + return {{ID(X), x}, {ID(Y), y}, {ID(CO), co}}; + } public: - Node handle(IdString cellType, dict parameters, dict inputs) + std::variant, Node> handle(IdString cellType, dict parameters, dict inputs) { int a_width = parameters.at(ID(A_WIDTH), Const(-1)).as_int(); int b_width = parameters.at(ID(B_WIDTH), Const(-1)).as_int(); @@ -360,17 +391,23 @@ class CellSimplifier { Node s = factory.extend(inputs.at(ID(S)), b_width, false); Node b = factory.mul(s, factory.constant(Const(width, b_width))); return factory.logical_shift_left(a, b); + } else if(cellType == ID($fa)) { + return handle_fa(inputs.at(ID(A)), inputs.at(ID(B)), inputs.at(ID(C))); + } else if(cellType == ID($lcu)) { + return handle_lcu(inputs.at(ID(P)), inputs.at(ID(G)), inputs.at(ID(CI))); + } else if(cellType == ID($alu)) { + return handle_alu(inputs.at(ID(A)), inputs.at(ID(B)), y_width, a_signed && b_signed, inputs.at(ID(CI)), inputs.at(ID(BI))); } else { - log_error("unhandled cell in CellSimplifier %s\n", cellType.c_str()); + log_error("`%s' cells are not supported by the functional backend\n", cellType.c_str()); } } }; class FunctionalIRConstruction { using Node = FunctionalIR::Node; - std::deque queue; + std::deque> queue; dict graph_nodes; - idict cells; + dict, Node> cell_outputs; DriverMap driver_map; FunctionalIR::Factory& factory; CellSimplifier simplifier; @@ -388,6 +425,24 @@ class FunctionalIRConstruction { }else return it->second; } + Node enqueue_cell(Cell *cell, IdString port_name) + { + auto it = cell_outputs.find({cell, port_name}); + if(it == cell_outputs.end()) { + queue.emplace_back(cell); + std::optional rv; + for(auto const &[name, sigspec] : cell->connections()) + if(driver_map.celltypes.cell_output(cell->type, name)) { + auto node = factory.create_pending(sigspec.size()); + factory.suggest_name(node, cell->name.str() + "$" + name.str()); + cell_outputs.emplace({cell, name}, node); + if(name == port_name) + rv = node; + } + return *rv; + } else + return it->second; + } public: FunctionalIRConstruction(FunctionalIR::Factory &f) : factory(f), simplifier(f) {} void add_module(Module *module) @@ -395,7 +450,7 @@ class FunctionalIRConstruction { driver_map.add(module); for (auto cell : module->cells()) { if (cell->type.in(ID($assert), ID($assume), ID($cover), ID($check))) - enqueue(DriveBitMarker(cells(cell), 0)); + queue.emplace_back(cell); } for (auto wire : module->wires()) { if (wire->port_output) { @@ -441,10 +496,44 @@ class FunctionalIRConstruction { factory.declare_state_memory(node, mem->cell->name, addr_width, data_width); return concatenate_read_results(mem, read_results); } + void process_cell(Cell *cell) + { + if (cell->is_mem_cell()) { + Mem *mem = memories.at(cell, nullptr); + log_assert(mem != nullptr); + Node node = handle_memory(mem); + factory.update_pending(cell_outputs.at({cell, ID(RD_DATA)}), node); + } else { + dict connections; + IdString output_name; // for the single output case + int n_outputs = 0; + for(auto const &[name, sigspec] : cell->connections()) { + if(driver_map.celltypes.cell_input(cell->type, name)) + connections.insert({ name, enqueue(DriveChunkPort(cell, {name, sigspec})) }); + if(driver_map.celltypes.cell_output(cell->type, name)) { + output_name = name; + n_outputs++; + } + } + std::variant, Node> outputs = simplifier.handle(cell->type, cell->parameters, connections); + if(auto *nodep = std::get_if(&outputs); nodep != nullptr) { + log_assert(n_outputs == 1); + factory.update_pending(cell_outputs.at({cell, output_name}), *nodep); + } else { + for(auto [name, node] : std::get>(outputs)) + factory.update_pending(cell_outputs.at({cell, name}), node); + } + } + } void process_queue() { for (; !queue.empty(); queue.pop_front()) { - DriveSpec spec = queue.front(); + if(auto p = std::get_if(&queue.front()); p != nullptr) { + process_cell(*p); + continue; + } + + DriveSpec spec = std::get(queue.front()); Node pending = graph_nodes.at(spec); if (spec.chunks().size() > 1) { @@ -492,11 +581,7 @@ class FunctionalIRConstruction { } else { - Node cell = enqueue(DriveChunkMarker(cells(port_chunk.cell), 0, port_chunk.width)); - factory.suggest_name(cell, port_chunk.cell->name); - //Node node = factory.cell_output(cell, port_chunk.cell->type, port_chunk.port, port_chunk.width); - Node node = cell; - factory.suggest_name(node, port_chunk.cell->name.str() + "$" + port_chunk.port.str()); + Node node = enqueue_cell(port_chunk.cell, port_chunk.port); factory.update_pending(pending, node); } } else { @@ -518,22 +603,6 @@ class FunctionalIRConstruction { args.push_back(enqueue(driver)); Node node = factory.multiple(args, chunk.size()); factory.update_pending(pending, node); - } else if (chunk.is_marker()) { - Cell *cell = cells[chunk.marker().marker]; - if (cell->is_mem_cell()) { - Mem *mem = memories.at(cell, nullptr); - log_assert(mem != nullptr); - Node node = handle_memory(mem); - factory.update_pending(pending, node); - } else { - dict connections; - for(auto const &conn : cell->connections()) { - if(driver_map.celltypes.cell_input(cell->type, conn.first)) - connections.insert({ conn.first, enqueue(DriveChunkPort(cell, conn)) }); - } - Node node = simplifier.handle(cell->type, cell->parameters, connections); - factory.update_pending(pending, node); - } } else if (chunk.is_none()) { Node node = factory.undriven(chunk.size()); factory.update_pending(pending, node); diff --git a/tests/functional/rtlil_cells.py b/tests/functional/rtlil_cells.py index 7858e3781d5..4dbea6015bd 100644 --- a/tests/functional/rtlil_cells.py +++ b/tests/functional/rtlil_cells.py @@ -123,6 +123,21 @@ class SliceCell(BaseCell): def __init__(self, name, values): super().__init__(name, ['A_WIDTH', 'OFFSET', 'Y_WIDTH'], {'A': 'A_WIDTH'}, {'Y': 'Y_WIDTH'}, values) +class FACell(BaseCell): + def __init__(self, name, values): + super().__init__(name, ['WIDTH'], {'A': 'WIDTH', 'B': 'WIDTH', 'C': 'WIDTH'}, {'X': 'WIDTH', 'Y': 'WIDTH'}, values) + self.sim_preprocessing = "techmap" # because FA is not implemented in yosys sim + +class LCUCell(BaseCell): + def __init__(self, name, values): + super().__init__(name, ['WIDTH'], {'P': 'WIDTH', 'G': 'WIDTH', 'CI': 1}, {'CO': 'WIDTH'}, values) + self.sim_preprocessing = "techmap" # because LCU is not implemented in yosys sim + +class ALUCell(BaseCell): + def __init__(self, name, values): + super().__init__(name, ['A_WIDTH', 'B_WIDTH', 'Y_WIDTH', 'A_SIGNED', 'B_SIGNED'], {'A': 'A_WIDTH', 'B': 'B_WIDTH', 'CI': 1, 'BI': 1}, {'X': 'Y_WIDTH', 'Y': 'Y_WIDTH', 'CO': 'Y_WIDTH'}, values) + self.sim_preprocessing = "techmap" # because ALU is not implemented in yosys sim + class FailCell(BaseCell): def __init__(self, name): super().__init__(name, [], {}, {}) @@ -231,9 +246,9 @@ def write_rtlil_file(self, path, parameters): ShiftCell("sshr", shift_widths), ShiftCell("shift", shift_widths), ShiftCell("shiftx", shift_widths), -# ("fa", ["A", "B", "C", "X", "Y"]), -# ("lcu", ["P", "G", "CI", "CO"]), -# ("alu", ["A", "B", "CI", "BI", "X", "Y", "CO"]), + FACell("fa", [8, 20]), + LCUCell("lcu", [1, 10]), + ALUCell("alu", binary_widths), BinaryCell("lt", binary_widths), BinaryCell("le", binary_widths), BinaryCell("eq", binary_widths), diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index 86e515d90a9..150642a00db 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -27,9 +27,9 @@ def yosys_synth(verilog_file, rtlil_file): yosys(f"read_verilog {quote(verilog_file)} ; prep ; clk2fflogic ; write_rtlil {quote(rtlil_file)}") # simulate an rtlil file with yosys, comparing with a given vcd file, and writing out the yosys simulation results into a second vcd file -def yosys_sim(rtlil_file, vcd_reference_file, vcd_out_file): +def yosys_sim(rtlil_file, vcd_reference_file, vcd_out_file, preprocessing = ""): try: - yosys(f"read_rtlil {quote(rtlil_file)}; sim -r {quote(vcd_reference_file)} -scope gold -vcd {quote(vcd_out_file)} -timescale 1us -sim-gold") + yosys(f"read_rtlil {quote(rtlil_file)}; {preprocessing}; sim -r {quote(vcd_reference_file)} -scope gold -vcd {quote(vcd_out_file)} -timescale 1us -sim-gold") except: # if yosys sim fails it's probably because of a simulation mismatch # since yosys sim aborts on simulation mismatch to generate vcd output @@ -53,7 +53,7 @@ def test_cxx(cell, parameters, tmp_path, num_steps, rnd): compile_cpp(vcdharness_cc_file, vcdharness_exe_file, ['-I', tmp_path, '-I', str(base_path / 'backends/functional/cxx_runtime')]) seed = str(rnd(cell.name + "-cxx").getrandbits(32)) run([str(vcdharness_exe_file.resolve()), str(vcd_functional_file), str(num_steps), str(seed)]) - yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file) + yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file, getattr(cell, 'sim_preprocessing', '')) def test_smt(cell, parameters, tmp_path, num_steps, rnd): import smt_vcd @@ -67,4 +67,4 @@ def test_smt(cell, parameters, tmp_path, num_steps, rnd): yosys(f"read_rtlil {quote(rtlil_file)} ; write_functional_smt2 {quote(smt_file)}") run(['z3', smt_file]) # check if output is valid smtlib before continuing smt_vcd.simulate_smt(smt_file, vcd_functional_file, num_steps, rnd(cell.name + "-smt")) - yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file) \ No newline at end of file + yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file, getattr(cell, 'sim_preprocessing', '')) \ No newline at end of file From 4722f13a5d917d2c464ef74de7facabd85fc63aa Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Thu, 18 Jul 2024 07:50:49 +0100 Subject: [PATCH 59/92] functional backend: reduce $lcu to $alu --- kernel/functionalir.cc | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/kernel/functionalir.cc b/kernel/functionalir.cc index e768be4005d..778b6a825d3 100644 --- a/kernel/functionalir.cc +++ b/kernel/functionalir.cc @@ -181,20 +181,14 @@ class CellSimplifier { Node x = factory.bitwise_or(t2, t3); return {{ID(X), x}, {ID(Y), y}}; } - Node handle_lcu(Node p, Node g, Node ci) { - Node rv = factory.bitwise_or(factory.slice(g, 0, 1), factory.bitwise_and(factory.slice(p, 0, 1), ci)); - Node c = rv; - for(int i = 1; i < p.width(); i++) { - c = factory.bitwise_or(factory.slice(g, i, 1), factory.bitwise_and(factory.slice(p, i, 1), c)); - rv = factory.concat(rv, c); - } - return rv; - } dict handle_alu(Node a_in, Node b_in, int y_width, bool is_signed, Node ci, Node bi) { Node a = factory.extend(a_in, y_width, is_signed); Node b_uninverted = factory.extend(b_in, y_width, is_signed); Node b = factory.mux(b_uninverted, factory.bitwise_not(b_uninverted), bi); Node x = factory.bitwise_xor(a, b); + // we can compute the carry into each bit using (a+b+c)^a^b. since we want the carry out, + // i.e. the carry into the next bit, we have to add an extra bit to a and b, and + // then slice off the bottom bit of the result. Node a_extra = factory.extend(a, y_width + 1, false); Node b_extra = factory.extend(b, y_width + 1, false); Node y_extra = factory.add(factory.add(a_extra, b_extra), factory.extend(ci, a.width() + 1, false)); @@ -203,6 +197,9 @@ class CellSimplifier { Node co = factory.slice(carries, 1, y_width); return {{ID(X), x}, {ID(Y), y}, {ID(CO), co}}; } + Node handle_lcu(Node p, Node g, Node ci) { + return handle_alu(g, factory.bitwise_or(p, g), g.width(), false, ci, factory.constant(Const(State::S0, 1))).at(ID(CO)); + } public: std::variant, Node> handle(IdString cellType, dict parameters, dict inputs) { From 145af6f10dd38441f0b549bfcd7547b7e749b8bb Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Thu, 18 Jul 2024 14:10:15 +0100 Subject: [PATCH 60/92] fix memory handling in functional backend, add more error messages and comments for memory edgecases --- kernel/functionalir.cc | 52 +++++++++++++++++++++++------ tests/functional/rtlil_cells.py | 45 +++++++++++++++++++++++-- tests/functional/test_functional.py | 6 ++-- 3 files changed, 86 insertions(+), 17 deletions(-) diff --git a/kernel/functionalir.cc b/kernel/functionalir.cc index 778b6a825d3..f6d2f556061 100644 --- a/kernel/functionalir.cc +++ b/kernel/functionalir.cc @@ -461,11 +461,19 @@ class FunctionalIRConstruction { memories[mem.cell] = &mem; } } - Node concatenate_read_results(Mem *, vector results) + Node concatenate_read_results(Mem *mem, vector results) { - /* TODO: write code to check that this is ok to do */ - if(results.size() == 0) - return factory.undriven(0); + // sanity check: all read ports concatenated should equal to the RD_DATA port + const SigSpec &rd_data = mem->cell->connections().at(ID(RD_DATA)); + int current = 0; + for(size_t i = 0; i < mem->rd_ports.size(); i++) { + int width = mem->width << mem->rd_ports[i].wide_log2; + log_assert (results[i].width() == width); + log_assert (mem->rd_ports[i].data == rd_data.extract(current, width)); + current += width; + } + log_assert (current == rd_data.size()); + log_assert (!results.empty()); Node node = results[0]; for(size_t i = 1; i < results.size(); i++) node = factory.concat(node, results[i]); @@ -473,16 +481,23 @@ class FunctionalIRConstruction { } Node handle_memory(Mem *mem) { + // To simplify memory handling, the functional backend makes the following assumptions: + // - Since async2sync or clk2fflogic must be run to use the functional backend, + // we can assume that all ports are asynchronous. + // - Async rd/wr are always transparent and so we must do reads after writes, + // but we can ignore transparency_mask. + // - We ignore collision_x_mask because x is a dont care value for us anyway. + // - Since wr port j can only have priority over wr port i if j > i, if we do writes in + // ascending index order the result will obey the priorty relation. vector read_results; int addr_width = ceil_log2(mem->size); int data_width = mem->width; Node node = factory.state_memory(mem->cell->name, addr_width, data_width); - for (auto &rd : mem->rd_ports) { - log_assert(!rd.clk_enable); - Node addr = enqueue(driver_map(DriveSpec(rd.addr))); - read_results.push_back(factory.memory_read(node, addr)); - } - for (auto &wr : mem->wr_ports) { + for (size_t i = 0; i < mem->wr_ports.size(); i++) { + const auto &wr = mem->wr_ports[i]; + if (wr.clk_enable) + log_error("Write port %zd of memory %s.%s is clocked. This is not supported by the functional backend. " + "Call async2sync or clk2fflogic to avoid this error.\n", i, log_id(mem->module), log_id(mem->memid)); Node en = enqueue(driver_map(DriveSpec(wr.en))); Node addr = enqueue(driver_map(DriveSpec(wr.addr))); Node new_data = enqueue(driver_map(DriveSpec(wr.data))); @@ -490,6 +505,17 @@ class FunctionalIRConstruction { Node wr_data = simplifier.bitwise_mux(old_data, new_data, en); node = factory.memory_write(node, addr, wr_data); } + if (mem->rd_ports.empty()) + log_error("Memory %s.%s has no read ports. This is not supported by the functional backend. " + "Call opt_clean to remove it.", log_id(mem->module), log_id(mem->memid)); + for (size_t i = 0; i < mem->rd_ports.size(); i++) { + const auto &rd = mem->rd_ports[i]; + if (rd.clk_enable) + log_error("Read port %zd of memory %s.%s is clocked. This is not supported by the functional backend. " + "Call memory_nordff to avoid this error.\n", i, log_id(mem->module), log_id(mem->memid)); + Node addr = enqueue(driver_map(DriveSpec(rd.addr))); + read_results.push_back(factory.memory_read(node, addr)); + } factory.declare_state_memory(node, mem->cell->name, addr_width, data_width); return concatenate_read_results(mem, read_results); } @@ -497,7 +523,11 @@ class FunctionalIRConstruction { { if (cell->is_mem_cell()) { Mem *mem = memories.at(cell, nullptr); - log_assert(mem != nullptr); + if (mem == nullptr) { + log_assert(cell->has_memid()); + log_error("The design contains an unpacked memory at %s. This is not supported by the functional backend. " + "Call memory_collect to avoid this error.\n", log_const(cell->parameters.at(ID(MEMID)))); + } Node node = handle_memory(mem); factory.update_pending(cell_outputs.at({cell, ID(RD_DATA)}), node); } else { diff --git a/tests/functional/rtlil_cells.py b/tests/functional/rtlil_cells.py index 4dbea6015bd..90417579e5d 100644 --- a/tests/functional/rtlil_cells.py +++ b/tests/functional/rtlil_cells.py @@ -176,14 +176,52 @@ def write_rtlil_file(self, path, parameters): input wire clk, input wire [{1}:0] WA, input wire [{0}:0] WD, + input wire [{1}:0] RA, output reg [{0}:0] RD ); - reg [{0}:0] mem[0:{1}]; + reg [{0}:0] mem[0:{2}]; always @(*) RD = mem[RA]; always @(posedge clk) mem[WA] <= WD; -endmodule""".format(parameters['DATA_WIDTH'] - 1, parameters['ADDR_WIDTH'] - 1)) +endmodule""".format(parameters['DATA_WIDTH'] - 1, parameters['ADDR_WIDTH'] - 1, 2**parameters['ADDR_WIDTH'] - 1)) + yosys_synth(verilog_file, path) + +class MemDualCell(BaseCell): + def __init__(self, name, values): + super().__init__(name, ['DATA_WIDTH', 'ADDR_WIDTH'], + {'WA1': 'ADDR_WIDTH', 'WA2': 'ADDR_WIDTH', + 'RA1': 'ADDR_WIDTH', 'RA2': 'ADDR_WIDTH', + 'WD1': 'DATA_WIDTH', 'WD2': 'DATA_WIDTH'}, + {'RD1': 'DATA_WIDTH', 'RD2': 'DATA_WIDTH'}, values) + self.sim_preprocessing = "memory_map" # issue #4496 in yosys -sim prevents this example from working without memory_map + def write_rtlil_file(self, path, parameters): + from test_functional import yosys_synth + verilog_file = path.parent / 'verilog.v' + with open(verilog_file, 'w') as f: + f.write(""" +module gold( + input wire clk, + input wire [{1}:0] WA1, + input wire [{0}:0] WD1, + input wire [{1}:0] WA2, + input wire [{0}:0] WD2, + input wire [{1}:0] RA1, + input wire [{1}:0] RA2, + output reg [{0}:0] RD1, + output reg [{0}:0] RD2 +); + (*keep*) + reg [{0}:0] mem[0:{2}]; + always @(*) + RD1 = mem[RA1]; + always @(*) + RD2 = mem[RA2]; + always @(posedge clk) begin + mem[WA1] <= WD1; + mem[WA2] <= WD2; + end +endmodule""".format(parameters['DATA_WIDTH'] - 1, parameters['ADDR_WIDTH'] - 1, 2**parameters['ADDR_WIDTH'] - 1)) yosys_synth(verilog_file, path) binary_widths = [ @@ -284,7 +322,8 @@ def write_rtlil_file(self, path, parameters): BWCell("bweqx", [10, 16, 40]), BWCell("bwmux", [10, 16, 40]), FFCell("ff", [10, 20, 40]), - MemCell("mem", [(32, 4)]) + MemCell("mem", [(16, 4)]), + MemDualCell("mem-dual", [(16, 4)]), # ("assert", ["A", "EN"]), # ("assume", ["A", "EN"]), # ("live", ["A", "EN"]), diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index 150642a00db..c31f4b86f5c 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -24,7 +24,7 @@ def compile_cpp(in_path, out_path, args): run(['g++', '-g', '-std=c++17'] + args + [str(in_path), '-o', str(out_path)]) def yosys_synth(verilog_file, rtlil_file): - yosys(f"read_verilog {quote(verilog_file)} ; prep ; clk2fflogic ; write_rtlil {quote(rtlil_file)}") + yosys(f"read_verilog {quote(verilog_file)} ; prep ; write_rtlil {quote(rtlil_file)}") # simulate an rtlil file with yosys, comparing with a given vcd file, and writing out the yosys simulation results into a second vcd file def yosys_sim(rtlil_file, vcd_reference_file, vcd_out_file, preprocessing = ""): @@ -49,7 +49,7 @@ def test_cxx(cell, parameters, tmp_path, num_steps, rnd): vcd_yosys_sim_file = tmp_path / 'yosys.vcd' cell.write_rtlil_file(rtlil_file, parameters) - yosys(f"read_rtlil {quote(rtlil_file)} ; write_functional_cxx {quote(cc_file)}") + yosys(f"read_rtlil {quote(rtlil_file)} ; clk2fflogic ; write_functional_cxx {quote(cc_file)}") compile_cpp(vcdharness_cc_file, vcdharness_exe_file, ['-I', tmp_path, '-I', str(base_path / 'backends/functional/cxx_runtime')]) seed = str(rnd(cell.name + "-cxx").getrandbits(32)) run([str(vcdharness_exe_file.resolve()), str(vcd_functional_file), str(num_steps), str(seed)]) @@ -64,7 +64,7 @@ def test_smt(cell, parameters, tmp_path, num_steps, rnd): vcd_yosys_sim_file = tmp_path / 'yosys.vcd' cell.write_rtlil_file(rtlil_file, parameters) - yosys(f"read_rtlil {quote(rtlil_file)} ; write_functional_smt2 {quote(smt_file)}") + yosys(f"read_rtlil {quote(rtlil_file)} ; clk2fflogic ; write_functional_smt2 {quote(smt_file)}") run(['z3', smt_file]) # check if output is valid smtlib before continuing smt_vcd.simulate_smt(smt_file, vcd_functional_file, num_steps, rnd(cell.name + "-smt")) yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file, getattr(cell, 'sim_preprocessing', '')) \ No newline at end of file From 6d329e142dbe16edfe772864a5c9debc72896e0f Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Thu, 18 Jul 2024 14:23:00 +0100 Subject: [PATCH 61/92] functional backend: error out if multiply driven or undriven signals are seen, dont bother putting them in functionalir --- backends/functional/cxx.cc | 1 - backends/functional/smtlib.cc | 5 +---- kernel/functionalir.cc | 14 ++++---------- kernel/functionalir.h | 19 ------------------- 4 files changed, 5 insertions(+), 34 deletions(-) diff --git a/backends/functional/cxx.cc b/backends/functional/cxx.cc index 0fa310164a6..69f1973f2cb 100644 --- a/backends/functional/cxx.cc +++ b/backends/functional/cxx.cc @@ -154,7 +154,6 @@ template struct CxxPrintVisitor : public FunctionalIR::Abstra void state(Node, IdString name) override { print("current_state.{}", state_struct[name]); } void memory_read(Node, Node mem, Node addr) override { print("{}.read({})", mem, addr); } void memory_write(Node, Node mem, Node addr, Node data) override { print("{}.write({}, {})", mem, addr, data); } - void undriven(Node, int width) override { print("Signal<{}>(0)", width); } }; struct CxxModule { diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index a2bb6666c3a..d40a4489f6a 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -180,8 +180,6 @@ struct SmtPrintVisitor : public FunctionalIR::AbstractVisitor { SExpr input(Node, IdString name) override { return input_struct.access("inputs", name); } SExpr state(Node, IdString name) override { return state_struct.access("state", name); } - - SExpr undriven(Node, int width) override { return literal(RTLIL::Const(State::S0, width)); } }; struct SmtModule { @@ -227,8 +225,7 @@ struct SmtModule { list("state", state_struct.name)), list("Pair", output_struct.name, state_struct.name))); auto inlined = [&](FunctionalIR::Node n) { - return n.fn() == FunctionalIR::Fn::constant || - n.fn() == FunctionalIR::Fn::undriven; + return n.fn() == FunctionalIR::Fn::constant; }; SmtPrintVisitor visitor(input_struct, state_struct); auto node_to_sexpr = [&](FunctionalIR::Node n) -> SExpr { diff --git a/kernel/functionalir.cc b/kernel/functionalir.cc index f6d2f556061..155cb3bbc53 100644 --- a/kernel/functionalir.cc +++ b/kernel/functionalir.cc @@ -56,8 +56,6 @@ const char *FunctionalIR::fn_to_string(FunctionalIR::Fn fn) { case FunctionalIR::Fn::constant: return "constant"; case FunctionalIR::Fn::input: return "input"; case FunctionalIR::Fn::state: return "state"; - case FunctionalIR::Fn::multiple: return "multiple"; - case FunctionalIR::Fn::undriven: return "undriven"; case FunctionalIR::Fn::memory_read: return "memory_read"; case FunctionalIR::Fn::memory_write: return "memory_write"; } @@ -75,7 +73,6 @@ struct PrintVisitor : FunctionalIR::DefaultVisitor { std::string constant(Node, RTLIL::Const value) override { return "constant(" + value.as_string() + ")"; } std::string input(Node, IdString name) override { return "input(" + name.str() + ")"; } std::string state(Node, IdString name) override { return "state(" + name.str() + ")"; } - std::string undriven(Node, int width) override { return "undriven(" + std::to_string(width) + ")"; } std::string default_handler(Node self) override { std::string ret = FunctionalIR::fn_to_string(self.fn()); ret += "("; @@ -625,14 +622,11 @@ class FunctionalIRConstruction { factory.suggest_name(node, "$const" + std::to_string(chunk.size()) + "b" + chunk.constant().as_string()); factory.update_pending(pending, node); } else if (chunk.is_multiple()) { - vector args; - for (auto const &driver : chunk.multiple().multiple()) - args.push_back(enqueue(driver)); - Node node = factory.multiple(args, chunk.size()); - factory.update_pending(pending, node); + log_error("Signal %s has multiple drivers. This is not supported by the functional backend. " + "If tristate drivers are used, call tristate -formal to avoid this error.\n", log_signal(chunk)); } else if (chunk.is_none()) { - Node node = factory.undriven(chunk.size()); - factory.update_pending(pending, node); + log_error("The design contains an undriven signal %s. This is not supported by the functional backend. " + "Call setundef with appropriate options to avoid this error.\n", log_signal(chunk)); } else { log_error("unhandled drivespec: %s\n", log_signal(chunk)); log_abort(); diff --git a/kernel/functionalir.h b/kernel/functionalir.h index d0ec1bce6ba..852937612a7 100644 --- a/kernel/functionalir.h +++ b/kernel/functionalir.h @@ -127,12 +127,6 @@ class FunctionalIR { // state(a: IdString): any // returns the current value of the state variable with the specified name state, - // multiple(a: any, b: any, c: any, ...): any - // indicates a value driven by multiple inputs - multiple, - // undriven(width: int): bit[width] - // indicates an undriven value - undriven, // memory_read(memory: memory[addr_width, data_width], addr: bit[addr_width]): bit[data_width] = memory[addr] memory_read, // memory_write(memory: memory[addr_width, data_width], addr: bit[addr_width], data: bit[data_width]): memory[addr_width, data_width] @@ -277,8 +271,6 @@ class FunctionalIR { case Fn::state: return v.state(*this, _ref.function().as_idstring()); break; case Fn::memory_read: return v.memory_read(*this, arg(0), arg(1)); break; case Fn::memory_write: return v.memory_write(*this, arg(0), arg(1), arg(2)); break; - case Fn::multiple: log_error("multiple in visit"); break; - case Fn::undriven: return v.undriven(*this, width()); break; } } std::string to_string(); @@ -319,7 +311,6 @@ class FunctionalIR { virtual T state(Node self, IdString name) = 0; virtual T memory_read(Node self, Node mem, Node addr) = 0; virtual T memory_write(Node self, Node mem, Node addr, Node data) = 0; - virtual T undriven(Node self, int width) = 0; }; // DefaultVisitor provides defaults for all visitor methods which just calls default_handler template struct DefaultVisitor : public AbstractVisitor { @@ -357,7 +348,6 @@ class FunctionalIR { T state(Node self, IdString) override { return default_handler(self); } T memory_read(Node self, Node, Node) override { return default_handler(self); } T memory_write(Node self, Node, Node, Node) override { return default_handler(self); } - T undriven(Node self, int) override { return default_handler(self); } }; // a factory is used to modify a FunctionalIR. it creates new nodes and allows for some modification of existing nodes. class Factory { @@ -475,15 +465,6 @@ class FunctionalIR { _ir.add_state(name, Sort(addr_width, data_width)); return add(NodeData(Fn::state, name), Sort(addr_width, data_width), {}); } - Node multiple(vector args, int width) { - auto node = add(Fn::multiple, Sort(width), {}); - for(const auto &arg : args) - mutate(node).append_arg(arg._ref); - return node; - } - Node undriven(int width) { - return add(Fn::undriven, Sort(width), {}); - } void declare_output(Node node, IdString name, int width) { _ir.add_output(name, Sort(width)); mutate(node).assign_key({name, false}); From 12a31a4418d5fcc33c93cef192fc3066ab999f86 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Wed, 24 Jul 2024 17:23:18 +0100 Subject: [PATCH 62/92] add MemContents class to mem.h --- kernel/mem.cc | 216 ++++++++++++++++++++++++++++++++++++++++++++++++++ kernel/mem.h | 116 +++++++++++++++++++++++++++ 2 files changed, 332 insertions(+) diff --git a/kernel/mem.cc b/kernel/mem.cc index 01c866770f5..46e4331c5a7 100644 --- a/kernel/mem.cc +++ b/kernel/mem.cc @@ -1679,3 +1679,219 @@ SigSpec MemWr::decompress_en(const std::vector &swizzle, SigSpec sig) { res.append(sig[i]); return res; } + +using addr_t = MemContents::addr_t; + +MemContents::MemContents(Mem *mem) : + MemContents(ceil_log2(mem->size), mem->width) +{ + for(const auto &init : mem->inits) { + if(init.en.is_fully_zero()) continue; + log_assert(init.en.size() == _data_width); + if(init.en.is_fully_ones()) + insert_concatenated(init.addr.as_int(), init.data); + else { + // TODO: this case could be handled more efficiently by adding + // a flag to reserve_range that tells it to preserve + // previous contents + addr_t addr = init.addr.as_int(); + addr_t words = init.data.size() / _data_width; + RTLIL::Const data = init.data; + log_assert(data.size() % _data_width == 0); + for(addr_t i = 0; i < words; i++) { + RTLIL::Const previous = (*this)[addr + i]; + for(int j = 0; j < _data_width; j++) + if(init.en[j] != State::S1) + data[_data_width * i + j] = previous[j]; + } + insert_concatenated(init.addr.as_int(), data); + } + } +} + +MemContents::iterator & MemContents::iterator::operator++() { + auto it = _memory->_values.upper_bound(_addr); + if(it == _memory->_values.end()) { + _memory = nullptr; + _addr = ~(addr_t) 0; + } else + _addr = it->first; + return *this; +} + +void MemContents::check() { + log_assert(_addr_width > 0 && _addr_width < (int)sizeof(addr_t) * 8); + log_assert(_data_width > 0); + log_assert(_default_value.size() == _data_width); + if(_values.empty()) return; + auto it = _values.begin(); + for(;;) { + log_assert(!it->second.empty()); + log_assert(it->second.size() % _data_width == 0); + auto end1 = _range_end(it); + log_assert(_range_begin(it) < (1<<_addr_width)); + log_assert(end1 <= (1<<_addr_width)); + if(++it == _values.end()) + break; + // check that ranges neither overlap nor touch + log_assert(_range_begin(it) > end1); + } +} + +bool MemContents::_range_contains(std::map::iterator it, addr_t addr) const { + // if addr < begin, the subtraction will overflow, and the comparison will always fail + // (since we have an invariant that begin + size <= 2^(addr_t bits)) + return it != _values.end() && addr - _range_begin(it) < _range_size(it); +} + + +bool MemContents::_range_contains(std::map::iterator it, addr_t begin_addr, addr_t end_addr) const { + // note that we assume begin_addr <= end_addr + return it != _values.end() && _range_begin(it) <= begin_addr && end_addr - _range_begin(it) <= _range_size(it); +} + +bool MemContents::_range_overlaps(std::map::iterator it, addr_t begin_addr, addr_t end_addr) const { + if(it == _values.end() || begin_addr >= end_addr) + return false; + auto top1 = _range_end(it) - 1; + auto top2 = end_addr - 1; + return !(top1 < begin_addr || top2 < _range_begin(it)); +} + +std::map::iterator MemContents::_range_at(addr_t addr) const { + // allow addr == 1<<_addr_width (which will just return end()) + log_assert(addr <= 1<<_addr_width); + // get the first range with base > addr + // (we use const_cast since map::iterators are only passed around internally and not exposed to the user + // and using map::iterator in both the const and non-const case simplifies the code a little, + // at the cost of having to be a little careful when implementing const methods) + auto it = const_cast &>(_values).upper_bound(addr); + // if we get the very first range, all ranges are past the addr, so return the first one + if(it == _values.begin()) + return it; + // otherwise, go back to the previous interval + // this must be the last interval with base <= addr + auto it_prev = std::next(it, -1); + if(_range_contains(it_prev, addr)) + return it_prev; + else + return it; +} + +RTLIL::Const MemContents::operator[](addr_t addr) const { + auto it = _range_at(addr); + if(_range_contains(it, addr)) + return it->second.extract(_range_offset(it, addr), _data_width); + else + return _default_value; +} + +addr_t MemContents::count_range(addr_t begin_addr, addr_t end_addr) const { + addr_t count = 0; + for(auto it = _range_at(begin_addr); _range_overlaps(it, begin_addr, end_addr); it++) { + auto first = std::max(_range_begin(it), begin_addr); + auto last = std::min(_range_end(it), end_addr); + count += last - first; + } + return count; +} + +void MemContents::clear_range(addr_t begin_addr, addr_t end_addr) { + if(begin_addr >= end_addr) return; + // identify which ranges are affected by this operation + // the first iterator affected is the first one containing any addr >= begin_addr + auto begin_it = _range_at(begin_addr); + // the first iterator *not* affected is the first one with base addr > end_addr - 1 + auto end_it = _values.upper_bound(end_addr - 1); + if(begin_it == end_it) + return; // nothing to do + // the last iterator affected is one before the first one not affected + auto last_it = std::next(end_it, -1); + // the first and last range may need to be truncated, the rest can just be deleted + // to handle the begin_it == last_it case correctly, do the end case first by inserting a new range past the end + if(_range_contains(last_it, end_addr - 1)) { + auto new_begin = end_addr; + auto end = _range_end(last_it); + // if there is data past the end address, preserve it by creating a new range + if(new_begin != end) + end_it = _values.emplace_hint(last_it, new_begin, last_it->second.extract(_range_offset(last_it, new_begin), (_range_end(last_it) - new_begin) * _data_width)); + // the original range will either be truncated in the next if() block or deleted in the erase, so we can leave it untruncated + } + if(_range_contains(begin_it, begin_addr)) { + auto new_end = begin_addr; + // if there is data before the start address, truncate but don't delete + if(new_end != begin_it->first) { + begin_it->second.extu(_range_offset(begin_it, new_end)); + ++begin_it; + } + // else: begin_it will be deleted + } + _values.erase(begin_it, end_it); +} + +std::map::iterator MemContents::_reserve_range(addr_t begin_addr, addr_t end_addr) { + if(begin_addr >= end_addr) + return _values.end(); // need a dummy value to return, end() is cheap + // find the first range containing any addr >= begin_addr - 1 + auto lower_it = begin_addr == 0 ? _values.begin() : _range_at(begin_addr - 1); + // check if our range is already covered by a single range + // note that since ranges are not allowed to touch, if any range contains begin_addr, lower_it equals that range + if (_range_contains(lower_it, begin_addr, end_addr)) + return lower_it; + // find the first range containing any addr >= end_addr + auto upper_it = _range_at(end_addr); + // check if either of the two ranges we just found touch our range + bool lower_touch = begin_addr > 0 && _range_contains(lower_it, begin_addr - 1); + bool upper_touch = _range_contains(upper_it, end_addr); + if (lower_touch && upper_touch) { + log_assert (lower_it != upper_it); // lower_it == upper_it should be excluded by the check above + // we have two different ranges touching at either end, we need to merge them + auto upper_end = _range_end(upper_it); + // make range bigger (maybe reserve here instead of resize?) + lower_it->second.bits.resize(_range_offset(lower_it, upper_end), State::Sx); + // copy only the data beyond our range + std::copy(_range_data(upper_it, end_addr), _range_data(upper_it, upper_end), _range_data(lower_it, end_addr)); + // keep lower_it, but delete upper_it + _values.erase(std::next(lower_it), std::next(upper_it)); + return lower_it; + } else if (lower_touch) { + // we have a range to the left, just make it bigger and delete any other that may exist. + lower_it->second.bits.resize(_range_offset(lower_it, end_addr), State::Sx); + // keep lower_it and upper_it + _values.erase(std::next(lower_it), upper_it); + return lower_it; + } else if (upper_touch) { + // we have a range to the right, we need to expand it + // since we need to erase and reinsert to a new address, steal the data + RTLIL::Const data = std::move(upper_it->second); + // note that begin_addr is not in upper_it, otherwise the whole range covered check would have tripped + data.bits.insert(data.bits.begin(), (_range_begin(upper_it) - begin_addr) * _data_width, State::Sx); + // delete lower_it and upper_it, then reinsert + _values.erase(lower_it, std::next(upper_it)); + return _values.emplace(begin_addr, std::move(data)).first; + } else { + // no ranges are touching, so just delete all ranges in our range and allocate a new one + // could try to resize an existing range but not sure if that actually helps + _values.erase(lower_it, upper_it); + return _values.emplace(begin_addr, RTLIL::Const(State::Sx, (end_addr - begin_addr) * _data_width)).first; + } +} + +void MemContents::insert_concatenated(addr_t addr, RTLIL::Const const &values) { + addr_t words = (values.size() + _data_width - 1) / _data_width; + log_assert(addr < 1<<_addr_width); + log_assert(words <= (1<<_addr_width) - addr); + auto it = _reserve_range(addr, addr + words); + auto to_begin = _range_data(it, addr); + std::copy(values.bits.begin(), values.bits.end(), to_begin); + // if values is not word-aligned, fill any missing bits with 0 + std::fill(to_begin + values.size(), to_begin + words * _data_width, State::S0); +} + +std::vector::iterator MemContents::_range_write(std::vector::iterator it, RTLIL::Const const &word) { + auto from_end = word.size() <= _data_width ? word.bits.end() : word.bits.begin() + _data_width; + auto to_end = std::copy(word.bits.begin(), from_end, it); + auto it_next = std::next(it, _data_width); + std::fill(to_end, it_next, State::S0); + return it_next; +} \ No newline at end of file diff --git a/kernel/mem.h b/kernel/mem.h index 8c484274ccf..4be4b68649e 100644 --- a/kernel/mem.h +++ b/kernel/mem.h @@ -224,6 +224,122 @@ struct Mem : RTLIL::AttrObject { Mem(Module *module, IdString memid, int width, int start_offset, int size) : module(module), memid(memid), packed(false), mem(nullptr), cell(nullptr), width(width), start_offset(start_offset), size(size) {} }; +// this class is used for implementing operator-> on iterators that return values rather than references +// it's necessary because in C++ operator-> is called recursively until a raw pointer is obtained +template +struct arrow_proxy { + T v; + explicit arrow_proxy(T const & v) : v(v) {} + T* operator->() { return &v; } +}; + +// MemContents efficiently represents the contents of a potentially sparse memory by storing only those segments that are actually defined +class MemContents { +public: + class range; class iterator; + using addr_t = uint32_t; +private: + // we ban _addr_width == sizeof(addr_t) * 8 because it adds too many cornercases + int _addr_width; + int _data_width; + RTLIL::Const _default_value; + // for each range, store the concatenation of the words at the start address + // invariants: + // - no overlapping or adjacent ranges + // - no empty ranges + // - all Consts are a multiple of the word size + std::map _values; + // returns an iterator to the range containing addr, if it exists, or the first range past addr + std::map::iterator _range_at(addr_t addr) const; + addr_t _range_size(std::map::iterator it) const { return it->second.size() / _data_width; } + addr_t _range_begin(std::map::iterator it) const { return it->first; } + addr_t _range_end(std::map::iterator it) const { return _range_begin(it) + _range_size(it); } + // check if the iterator points to a range containing addr + bool _range_contains(std::map::iterator it, addr_t addr) const; + // check if the iterator points to a range containing [begin_addr, end_addr). assumes end_addr >= begin_addr. + bool _range_contains(std::map::iterator it, addr_t begin_addr, addr_t end_addr) const; + // check if the iterator points to a range overlapping with [begin_addr, end_addr) + bool _range_overlaps(std::map::iterator it, addr_t begin_addr, addr_t end_addr) const; + // return the offset the addr would have in the range at `it` + size_t _range_offset(std::map::iterator it, addr_t addr) const { return (addr - it->first) * _data_width; } + // assuming _range_contains(it, addr), return an iterator pointing to the data at addr + std::vector::iterator _range_data(std::map::iterator it, addr_t addr) { return it->second.bits.begin() + _range_offset(it, addr); } + // internal version of reserve_range that returns an iterator to the range + std::map::iterator _reserve_range(addr_t begin_addr, addr_t end_addr); + // write a single word at addr, return iterator to next word + std::vector::iterator _range_write(std::vector::iterator it, RTLIL::Const const &data); +public: + class range { + int _data_width; + addr_t _base; + RTLIL::Const const &_values; + friend class iterator; + range(int data_width, addr_t base, RTLIL::Const const &values) + : _data_width(data_width), _base(base), _values(values) {} + public: + addr_t base() const { return _base; } + addr_t size() const { return ((addr_t) _values.size()) / _data_width; } + addr_t limit() const { return _base + size(); } + RTLIL::Const const &concatenated() const { return _values; } + RTLIL::Const operator[](addr_t addr) const { + log_assert(addr - _base < size()); + return _values.extract((addr - _base) * _data_width, _data_width); + } + RTLIL::Const at_offset(addr_t offset) const { return (*this)[_base + offset]; } + }; + class iterator { + MemContents const *_memory; + // storing addr instead of an iterator gives more well-defined behaviour under insertions/deletions + // use ~0 for end so that all end iterators compare the same + addr_t _addr; + friend class MemContents; + iterator(MemContents const *memory, addr_t addr) : _memory(memory), _addr(addr) {} + public: + using iterator_category = std::input_iterator_tag; + using value_type = range; + using pointer = arrow_proxy; + using reference = range; + using difference_type = addr_t; + reference operator *() const { return range(_memory->_data_width, _addr, _memory->_values.at(_addr)); } + pointer operator->() const { return arrow_proxy(**this); } + bool operator !=(iterator const &other) const { return _memory != other._memory || _addr != other._addr; } + iterator &operator++(); + }; + MemContents(int addr_width, int data_width, RTLIL::Const default_value) + : _addr_width(addr_width), _data_width(data_width) + , _default_value((default_value.extu(data_width), std::move(default_value))) + { log_assert(_addr_width > 0 && _addr_width < (int)sizeof(addr_t) * 8); log_assert(_data_width > 0); } + MemContents(int addr_width, int data_width) : MemContents(addr_width, data_width, RTLIL::Const(State::Sx, data_width)) {} + explicit MemContents(Mem *mem); + int addr_width() const { return _addr_width; } + int data_width() const { return _data_width; } + RTLIL::Const const &default_value() const { return _default_value; } + // return the value at the address if it exists, the default_value of the memory otherwise. address must not exceed 2**addr_width. + RTLIL::Const operator [](addr_t addr) const; + // return the number of defined words in the range [begin_addr, end_addr) + addr_t count_range(addr_t begin_addr, addr_t end_addr) const; + // allocate memory for the range [begin_addr, end_addr), but leave the contents undefined. + void reserve_range(addr_t begin_addr, addr_t end_addr) { _reserve_range(begin_addr, end_addr); } + // insert multiple words (provided as a single concatenated RTLIL::Const) at the given address, overriding any previous assignment. + void insert_concatenated(addr_t addr, RTLIL::Const const &values); + // insert multiple words at the given address, overriding any previous assignment. + template void insert_range(addr_t addr, Iterator begin, Iterator end) { + auto words = end - begin; + log_assert(addr < 1<<_addr_width); log_assert(words <= (1<<_addr_width) - addr); + auto range = _reserve_range(addr, addr + words); + auto it = _range_data(range, addr); + for(; begin != end; ++begin) + it = _range_write(it, *begin); + } + // undefine all words in the range [begin_addr, end_addr) + void clear_range(addr_t begin_addr, addr_t end_addr); + // check invariants, abort if invariants failed + void check(); + iterator end() const { return iterator(nullptr, ~(addr_t) 0); } + iterator begin() const { return _values.empty() ? end() : iterator(this, _values.begin()->first); } + bool empty() const { return _values.empty(); } +}; + YOSYS_NAMESPACE_END #endif From bdb59ffc8e82985b99dc9fed3311f177c0caa885 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Wed, 24 Jul 2024 17:33:32 +0100 Subject: [PATCH 63/92] add -fst-noinit flag to sim for not initializing the state from the fst file --- passes/sat/sim.cc | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc index dd17a2eba9d..c83dfe254aa 100644 --- a/passes/sat/sim.cc +++ b/passes/sat/sim.cc @@ -123,6 +123,7 @@ struct SimShared std::vector triggered_assertions; std::vector display_output; bool serious_asserts = false; + bool fst_noinit = false; bool initstate = true; }; @@ -1553,7 +1554,7 @@ struct SimWorker : SimShared bool did_something = top->setInputs(); if (initial) { - did_something |= top->setInitState(); + if (!fst_noinit) did_something |= top->setInitState(); initialize_stable_past(); initial = false; } @@ -2688,6 +2689,10 @@ struct SimPass : public Pass { log(" fail the simulation command if, in the course of simulating,\n"); log(" any of the asserts in the design fail\n"); log("\n"); + log(" -fst-noinit\n"); + log(" do not initialize latches and memories from an input FST or VCD file\n"); + log(" (use the initial defined by the design instead)\n"); + log("\n"); log(" -q\n"); log(" disable per-cycle/sample log message\n"); log("\n"); @@ -2850,6 +2855,10 @@ struct SimPass : public Pass { worker.serious_asserts = true; continue; } + if (args[argidx] == "-fst-noinit") { + worker.fst_noinit = true; + continue; + } if (args[argidx] == "-x") { worker.ignore_x = true; continue; From 99effb6789b1c3ff62879d5404102da91a2bf70d Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Wed, 24 Jul 2024 17:37:17 +0100 Subject: [PATCH 64/92] add support for initializing registers and memories to the functional backend --- backends/functional/cxx.cc | 101 ++++++----- backends/functional/cxx_runtime/sim.h | 3 + backends/functional/smtlib.cc | 68 +++++--- backends/functional/test_generic.cc | 99 +++++++++++ kernel/functionalir.cc | 64 +++---- kernel/functionalir.h | 84 +++++----- tests/functional/rtlil_cells.py | 19 ++- tests/functional/smt_vcd.py | 21 +-- tests/functional/test_functional.py | 4 +- tests/functional/vcd_harness.cc | 233 +++++++++++++------------- 10 files changed, 416 insertions(+), 280 deletions(-) diff --git a/backends/functional/cxx.cc b/backends/functional/cxx.cc index 69f1973f2cb..324a8831c86 100644 --- a/backends/functional/cxx.cc +++ b/backends/functional/cxx.cc @@ -69,35 +69,48 @@ struct CxxType { using CxxWriter = FunctionalTools::Writer; struct CxxStruct { - std::string name; - dict types; - CxxScope scope; - CxxStruct(std::string name) - : name(name) { - scope.reserve("fn"); - scope.reserve("visit"); - } - void insert(IdString name, CxxType type) { - scope(name, name); - types.insert({name, type}); - } - void print(CxxWriter &f) { - f.print("\tstruct {} {{\n", name); - for (auto p : types) { - f.print("\t\t{} {};\n", p.second.to_string(), scope(p.first, p.first)); - } - f.print("\n\t\ttemplate void visit(T &&fn) {{\n"); - for (auto p : types) { - f.print("\t\t\tfn(\"{}\", {});\n", RTLIL::unescape_id(p.first), scope(p.first, p.first)); - } - f.print("\t\t}}\n"); - f.print("\t}};\n\n"); - }; - std::string operator[](IdString field) { - return scope(field, field); - } + std::string name; + dict types; + CxxScope scope; + CxxStruct(std::string name) : name(name) + { + scope.reserve("fn"); + scope.reserve("visit"); + } + void insert(IdString name, CxxType type) { + scope(name, name); + types.insert({name, type}); + } + void print(CxxWriter &f) { + f.print("\tstruct {} {{\n", name); + for (auto p : types) { + f.print("\t\t{} {};\n", p.second.to_string(), scope(p.first, p.first)); + } + f.print("\n\t\ttemplate void visit(T &&fn) {{\n"); + for (auto p : types) { + f.print("\t\t\tfn(\"{}\", {});\n", RTLIL::unescape_id(p.first), scope(p.first, p.first)); + } + f.print("\t\t}}\n"); + f.print("\t}};\n\n"); + }; + std::string operator[](IdString field) { + return scope(field, field); + } }; +std::string cxx_const(RTLIL::Const const &value) { + std::stringstream ss; + ss << "Signal<" << value.size() << ">(" << std::hex << std::showbase; + if(value.size() > 32) ss << "{"; + for(int i = 0; i < value.size(); i += 32) { + if(i > 0) ss << ", "; + ss << value.extract(i, 32).as_int(); + } + if(value.size() > 32) ss << "}"; + ss << ")"; + return ss.str(); +} + template struct CxxPrintVisitor : public FunctionalIR::AbstractVisitor { using Node = FunctionalIR::Node; CxxWriter &f; @@ -136,20 +149,7 @@ template struct CxxPrintVisitor : public FunctionalIR::Abstra void logical_shift_right(Node, Node a, Node b) override { print("{} >> {}", a, b); } void arithmetic_shift_right(Node, Node a, Node b) override { print("{}.arithmetic_shift_right({})", a, b); } void mux(Node, Node a, Node b, Node s) override { print("{2}.any() ? {1} : {0}", a, b, s); } - void constant(Node, RTLIL::Const value) override { - std::stringstream ss; - bool multiple = value.size() > 32; - ss << "Signal<" << value.size() << ">(" << std::hex << std::showbase; - if(multiple) ss << "{"; - while(value.size() > 32) { - ss << value.as_int() << ", "; - value = value.extract(32, value.size() - 32); - } - ss << value.as_int(); - if(multiple) ss << "}"; - ss << ")"; - print("{}", ss.str()); - } + void constant(Node, RTLIL::Const const & value) override { print("{}", cxx_const(value)); } void input(Node, IdString name) override { print("input.{}", input_struct[name]); } void state(Node, IdString name) override { print("current_state.{}", state_struct[name]); } void memory_read(Node, Node mem, Node addr) override { print("{}.read({})", mem, addr); } @@ -184,8 +184,24 @@ struct CxxModule { output_struct.print(f); state_struct.print(f); f.print("\tstatic void eval(Inputs const &, Outputs &, State const &, State &);\n"); + f.print("\tstatic void initialize(State &);\n"); f.print("}};\n\n"); } + void write_initial_def(CxxWriter &f) { + f.print("void {0}::initialize({0}::State &state)\n{{\n", module_name); + for (auto [name, sort] : ir.state()) { + if (sort.is_signal()) + f.print("\tstate.{} = {};\n", state_struct[name], cxx_const(ir.get_initial_state_signal(name))); + else if (sort.is_memory()) { + const auto &contents = ir.get_initial_state_memory(name); + f.print("\tstate.{}.fill({});\n", state_struct[name], cxx_const(contents.default_value())); + for(auto range : contents) + for(auto addr = range.base(); addr < range.limit(); addr++) + f.print("\tstate.{}[{}] = {};\n", state_struct[name], addr, cxx_const(range[addr])); + } + } + f.print("}}\n\n"); + } void write_eval_def(CxxWriter &f) { f.print("void {0}::eval({0}::Inputs const &input, {0}::Outputs &output, {0}::State const ¤t_state, {0}::State &next_state)\n{{\n", module_name); CxxScope locals; @@ -204,7 +220,7 @@ struct CxxModule { f.print("\tnext_state.{} = {};\n", state_struct[name], node_name(ir.get_state_next_node(name))); for (auto [name, sort] : ir.outputs()) f.print("\toutput.{} = {};\n", output_struct[name], node_name(ir.get_output_node(name))); - f.print("}}\n"); + f.print("}}\n\n"); } }; @@ -225,6 +241,7 @@ struct FunctionalCxxBackend : public Backend mod.write_header(f); mod.write_struct_def(f); mod.write_eval_def(f); + mod.write_initial_def(f); } void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override diff --git a/backends/functional/cxx_runtime/sim.h b/backends/functional/cxx_runtime/sim.h index ed1a25ed40f..6cce4e8456c 100644 --- a/backends/functional/cxx_runtime/sim.h +++ b/backends/functional/cxx_runtime/sim.h @@ -410,6 +410,9 @@ class Memory { ret._contents[addr.template as_numeric()] = data; return ret; } + // mutating methods for initializing a state + void fill(Signal data) { _contents.fill(data); } + Signal &operator[](Signal addr) { return _contents[addr.template as_numeric()]; } }; #endif diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index d40a4489f6a..0d2763d3201 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -109,6 +109,13 @@ class SmtStruct { } }; +std::string smt_const(RTLIL::Const const &c) { + std::string s = "#b"; + for(int i = c.size(); i-- > 0; ) + s += c[i] == State::S1 ? '1' : '0'; + return s; +} + struct SmtPrintVisitor : public FunctionalIR::AbstractVisitor { using Node = FunctionalIR::Node; std::function n; @@ -117,13 +124,6 @@ struct SmtPrintVisitor : public FunctionalIR::AbstractVisitor { SmtPrintVisitor(SmtStruct &input_struct, SmtStruct &state_struct) : input_struct(input_struct), state_struct(state_struct) {} - std::string literal(RTLIL::Const c) { - std::string s = "#b"; - for(int i = c.size(); i-- > 0; ) - s += c[i] == State::S1 ? '1' : '0'; - return s; - } - SExpr from_bool(SExpr &&arg) { return list("ite", std::move(arg), "#b1", "#b0"); } @@ -149,8 +149,8 @@ struct SmtPrintVisitor : public FunctionalIR::AbstractVisitor { SExpr bitwise_xor(Node, Node a, Node b) override { return list("bvxor", n(a), n(b)); } SExpr bitwise_not(Node, Node a) override { return list("bvnot", n(a)); } SExpr unary_minus(Node, Node a) override { return list("bvneg", n(a)); } - SExpr reduce_and(Node, Node a) override { return from_bool(list("=", n(a), literal(RTLIL::Const(State::S1, a.width())))); } - SExpr reduce_or(Node, Node a) override { return from_bool(list("distinct", n(a), literal(RTLIL::Const(State::S0, a.width())))); } + SExpr reduce_and(Node, Node a) override { return from_bool(list("=", n(a), smt_const(RTLIL::Const(State::S1, a.width())))); } + SExpr reduce_or(Node, Node a) override { return from_bool(list("distinct", n(a), smt_const(RTLIL::Const(State::S0, a.width())))); } SExpr reduce_xor(Node, Node a) override { vector s { "bvxor" }; for(int i = 0; i < a.width(); i++) @@ -174,7 +174,7 @@ struct SmtPrintVisitor : public FunctionalIR::AbstractVisitor { SExpr logical_shift_right(Node, Node a, Node b) override { return list("bvlshr", n(a), extend(n(b), b.width(), a.width())); } SExpr arithmetic_shift_right(Node, Node a, Node b) override { return list("bvashr", n(a), extend(n(b), b.width(), a.width())); } SExpr mux(Node, Node a, Node b, Node s) override { return list("ite", to_bool(n(s)), n(b), n(a)); } - SExpr constant(Node, RTLIL::Const value) override { return literal(value); } + SExpr constant(Node, RTLIL::Const const &value) override { return smt_const(value); } SExpr memory_read(Node, Node mem, Node addr) override { return list("select", n(mem), n(addr)); } SExpr memory_write(Node, Node mem, Node addr, Node data) override { return list("store", n(mem), n(addr), n(data)); } @@ -199,6 +199,7 @@ struct SmtModule { , output_struct(scope.unique_name(module->name.str() + "_Outputs"), scope) , state_struct(scope.unique_name(module->name.str() + "_State"), scope) { + scope.reserve(name + "-initial"); for (const auto &input : ir.inputs()) input_struct.insert(input.first, input.second); for (const auto &output : ir.outputs()) @@ -207,18 +208,8 @@ struct SmtModule { state_struct.insert(state.first, state.second); } - void write(std::ostream &out) - { - SExprWriter w(out); - - input_struct.write_definition(w); - output_struct.write_definition(w); - state_struct.write_definition(w); - - w << list("declare-datatypes", - list(list("Pair", 2)), - list(list("par", list("X", "Y"), list(list("pair", list("first", "X"), list("second", "Y")))))); - + void write_eval(SExprWriter &w) + { w.push(); w.open(list("define-fun", name, list(list("inputs", input_struct.name), @@ -245,6 +236,39 @@ struct SmtModule { state_struct.write_value(w, [&](IdString name) { return node_to_sexpr(ir.get_state_next_node(name)); }); w.pop(); } + + void write_initial(SExprWriter &w) + { + std::string initial = name + "-initial"; + w << list("declare-const", initial, state_struct.name); + for (const auto &[name, sort] : ir.state()) { + if(sort.is_signal()) + w << list("assert", list("=", state_struct.access(initial, name), smt_const(ir.get_initial_state_signal(name)))); + else if(sort.is_memory()) { + auto contents = ir.get_initial_state_memory(name); + for(int i = 0; i < 1< USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN +struct MemContentsTest { + int addr_width, data_width; + MemContents state; + using addr_t = MemContents::addr_t; + std::map reference; + MemContentsTest(int addr_width, int data_width) : addr_width(addr_width), data_width(data_width), state(addr_width, data_width, RTLIL::Const(State::S0, data_width)) {} + void check() { + state.check(); + for(auto addr = 0; addr < (1<second != state[addr]) goto error; + } else { + if(state.count_range(addr, addr + 1) != 0) goto error; + } + } + return; + error: + printf("FAIL\n"); + int digits = (data_width + 3) / 4; + + for(auto addr = 0; addr < (1<second : state.default_value(); + std::string ref_string = stringf("%.*x", digits, ref_value.as_int()); + bool sta_def = state.count_range(addr, addr + 1) == 1; + RTLIL::Const sta_value = state[addr]; + std::string sta_string = stringf("%.*x", digits, sta_value.as_int()); + if(ref_def && sta_def) { + if(ref_value == sta_value) printf("%s%s", ref_string.c_str(), string(digits, ' ').c_str()); + else printf("%s%s", ref_string.c_str(), sta_string.c_str()); + } else if(ref_def) { + printf("%s%s", ref_string.c_str(), string(digits, 'M').c_str()); + } else if(sta_def) { + printf("%s%s", sta_string.c_str(), string(digits, 'X').c_str()); + } else { + printf("%s", string(2*digits, ' ').c_str()); + } + printf(" "); + if(addr % 8 == 7) printf("\n"); + } + printf("\n"); + //log_abort(); + } + void clear_range(addr_t begin_addr, addr_t end_addr) { + for(auto addr = begin_addr; addr != end_addr; addr++) + reference.erase(addr); + state.clear_range(begin_addr, end_addr); + check(); + } + void insert_concatenated(addr_t addr, RTLIL::Const const &values) { + addr_t words = ((addr_t) values.size() + data_width - 1) / data_width; + for(addr_t i = 0; i < words; i++) { + reference.erase(addr + i); + reference.emplace(addr + i, values.extract(i * data_width, data_width)); + } + state.insert_concatenated(addr, values); + check(); + } + template void run(Rnd &rnd, int n) { + std::uniform_int_distribution addr_dist(0, (1< length_dist(10); + std::uniform_int_distribution data_dist(0, ((uint64_t)1< 0) { + addr_t low = addr_dist(rnd); + //addr_t length = std::min((1< high) std::swap(low, high); + if((rnd() & 7) == 0) { + log_debug("clear %.2x to %.2x\n", (int)low, (int)high); + clear_range(low, high + 1); + } else { + log_debug("insert %.2x to %.2x\n", (int)low, (int)high); + RTLIL::Const values; + for(addr_t addr = low; addr <= high; addr++) { + RTLIL::Const word(data_dist(rnd), data_width); + values.bits.insert(values.bits.end(), word.bits.begin(), word.bits.end()); + } + insert_concatenated(low, values); + } + } + } + +}; + struct FunctionalTestGeneric : public Pass { FunctionalTestGeneric() : Pass("test_generic", "test the generic compute graph") {} @@ -40,6 +130,14 @@ struct FunctionalTestGeneric : public Pass size_t argidx = 1; extra_args(args, argidx, design); + MemContentsTest test(8, 16); + + std::random_device seed_dev; + std::mt19937 rnd(23); //seed_dev()); + test.run(rnd, 1000); + +/* + for (auto module : design->selected_modules()) { log("Dumping module `%s'.\n", module->name.c_str()); auto fir = FunctionalIR::from_module(module); @@ -50,6 +148,7 @@ struct FunctionalTestGeneric : public Pass for(auto [name, sort] : fir.state()) std::cout << RTLIL::unescape_id(name) << " = " << RTLIL::unescape_id(fir.get_state_next_node(name).name()) << "\n"; } +*/ } } FunctionalCxxBackend; diff --git a/kernel/functionalir.cc b/kernel/functionalir.cc index 155cb3bbc53..bc21b4d3648 100644 --- a/kernel/functionalir.cc +++ b/kernel/functionalir.cc @@ -19,6 +19,8 @@ #include "kernel/functionalir.h" #include +#include "ff.h" +#include "ffinit.h" YOSYS_NAMESPACE_BEGIN @@ -70,7 +72,7 @@ struct PrintVisitor : FunctionalIR::DefaultVisitor { std::string slice(Node, Node a, int offset, int out_width) override { return "slice(" + np(a) + ", " + std::to_string(offset) + ", " + std::to_string(out_width) + ")"; } std::string zero_extend(Node, Node a, int out_width) override { return "zero_extend(" + np(a) + ", " + std::to_string(out_width) + ")"; } std::string sign_extend(Node, Node a, int out_width) override { return "sign_extend(" + np(a) + ", " + std::to_string(out_width) + ")"; } - std::string constant(Node, RTLIL::Const value) override { return "constant(" + value.as_string() + ")"; } + std::string constant(Node, RTLIL::Const const& value) override { return "constant(" + value.as_string() + ")"; } std::string input(Node, IdString name) override { return "input(" + name.str() + ")"; } std::string state(Node, IdString name) override { return "state(" + name.str() + ")"; } std::string default_handler(Node self) override { @@ -407,6 +409,8 @@ class FunctionalIRConstruction { CellSimplifier simplifier; vector memories_vector; dict memories; + SigMap sig_map; // TODO: this is only for FfInitVals, remove this once FfInitVals supports DriverMap + FfInitVals ff_initvals; Node enqueue(DriveSpec const &spec) { @@ -438,8 +442,11 @@ class FunctionalIRConstruction { return it->second; } public: - FunctionalIRConstruction(FunctionalIR::Factory &f) : factory(f), simplifier(f) {} - void add_module(Module *module) + FunctionalIRConstruction(Module *module, FunctionalIR::Factory &f) + : factory(f) + , simplifier(f) + , sig_map(module) + , ff_initvals(&sig_map, module) { driver_map.add(module); for (auto cell : module->cells()) { @@ -447,9 +454,12 @@ class FunctionalIRConstruction { queue.emplace_back(cell); } for (auto wire : module->wires()) { + if (wire->port_input) + factory.add_input(wire->name, wire->width); if (wire->port_output) { - Node node = enqueue(DriveChunk(DriveChunkWire(wire, 0, wire->width))); - factory.declare_output(node, wire->name, wire->width); + factory.add_output(wire->name, wire->width); + Node value = enqueue(DriveChunk(DriveChunkWire(wire, 0, wire->width))); + factory.set_output(wire->name, value); } } memories_vector = Mem::get_all_memories(module); @@ -487,9 +497,9 @@ class FunctionalIRConstruction { // - Since wr port j can only have priority over wr port i if j > i, if we do writes in // ascending index order the result will obey the priorty relation. vector read_results; - int addr_width = ceil_log2(mem->size); - int data_width = mem->width; - Node node = factory.state_memory(mem->cell->name, addr_width, data_width); + factory.add_state(mem->cell->name, FunctionalIR::Sort(ceil_log2(mem->size), mem->width)); + factory.set_initial_state(mem->cell->name, MemContents(mem)); + Node node = factory.get_current_state(mem->cell->name); for (size_t i = 0; i < mem->wr_ports.size(); i++) { const auto &wr = mem->wr_ports[i]; if (wr.clk_enable) @@ -513,7 +523,7 @@ class FunctionalIRConstruction { Node addr = enqueue(driver_map(DriveSpec(rd.addr))); read_results.push_back(factory.memory_read(node, addr)); } - factory.declare_state_memory(node, mem->cell->name, addr_width, data_width); + factory.set_next_state(mem->cell->name, node); return concatenate_read_results(mem, read_results); } void process_cell(Cell *cell) @@ -527,6 +537,17 @@ class FunctionalIRConstruction { } Node node = handle_memory(mem); factory.update_pending(cell_outputs.at({cell, ID(RD_DATA)}), node); + } else if (RTLIL::builtin_ff_cell_types().count(cell->type)) { + FfData ff(&ff_initvals, cell); + if (!ff.has_gclk) + log_error("The design contains a %s flip-flop at %s. This is not supported by the functional backend. " + "Call async2sync or clk2fflogic to avoid this error.\n", log_id(cell->type), log_id(cell)); + factory.add_state(ff.name, FunctionalIR::Sort(ff.width)); + Node q_value = factory.get_current_state(ff.name); + factory.suggest_name(q_value, ff.name); + factory.update_pending(cell_outputs.at({cell, ID(Q)}), q_value); + factory.set_next_state(ff.name, enqueue(ff.sig_d)); + factory.set_initial_state(ff.name, ff.val_init); } else { dict connections; IdString output_name; // for the single output case @@ -572,7 +593,7 @@ class FunctionalIRConstruction { DriveChunkWire wire_chunk = chunk.wire(); if (wire_chunk.is_whole()) { if (wire_chunk.wire->port_input) { - Node node = factory.input(wire_chunk.wire->name, wire_chunk.width); + Node node = factory.get_input(wire_chunk.wire->name); factory.suggest_name(node, wire_chunk.wire->name); factory.update_pending(pending, node); } else { @@ -590,24 +611,8 @@ class FunctionalIRConstruction { DriveChunkPort port_chunk = chunk.port(); if (port_chunk.is_whole()) { if (driver_map.celltypes.cell_output(port_chunk.cell->type, port_chunk.port)) { - if (port_chunk.cell->type.in(ID($dff), ID($ff))) - { - Cell *cell = port_chunk.cell; - Node node = factory.state(cell->name, port_chunk.width); - factory.suggest_name(node, port_chunk.cell->name); - factory.update_pending(pending, node); - for (auto const &conn : cell->connections()) { - if (driver_map.celltypes.cell_input(cell->type, conn.first)) { - Node node = enqueue(DriveChunkPort(cell, conn)); - factory.declare_state(node, cell->name, port_chunk.width); - } - } - } - else - { - Node node = enqueue_cell(port_chunk.cell, port_chunk.port); - factory.update_pending(pending, node); - } + Node node = enqueue_cell(port_chunk.cell, port_chunk.port); + factory.update_pending(pending, node); } else { DriveSpec driver = driver_map(DriveSpec(port_chunk)); factory.update_pending(pending, enqueue(driver)); @@ -641,8 +646,7 @@ class FunctionalIRConstruction { FunctionalIR FunctionalIR::from_module(Module *module) { FunctionalIR ir; auto factory = ir.factory(); - FunctionalIRConstruction ctor(factory); - ctor.add_module(module); + FunctionalIRConstruction ctor(module, factory); ctor.process_queue(); ir.topological_sort(); ir.forward_buf(); diff --git a/kernel/functionalir.h b/kernel/functionalir.h index 852937612a7..e0af89d9f3a 100644 --- a/kernel/functionalir.h +++ b/kernel/functionalir.h @@ -189,24 +189,11 @@ class FunctionalIR { // the bool is true for next state values using Graph = ComputeGraph>; Graph _graph; - dict _inputs; - dict _outputs; - dict _state; - void add_input(IdString name, Sort sort) { - auto [it, found] = _inputs.emplace(name, std::move(sort)); - if(found) - log_assert(it->second == sort); - } - void add_state(IdString name, Sort sort) { - auto [it, found] = _state.emplace(name, std::move(sort)); - if(found) - log_assert(it->second == sort); - } - void add_output(IdString name, Sort sort) { - auto [it, found] = _outputs.emplace(name, std::move(sort)); - if(found) - log_assert(it->second == sort); - } + dict _input_sorts; + dict _output_sorts; + dict _state_sorts; + dict _initial_state_signal; + dict _initial_state_memory; public: class Factory; // Node is an immutable reference to a FunctionalIR node @@ -306,7 +293,7 @@ class FunctionalIR { virtual T logical_shift_right(Node self, Node a, Node b) = 0; virtual T arithmetic_shift_right(Node self, Node a, Node b) = 0; virtual T mux(Node self, Node a, Node b, Node s) = 0; - virtual T constant(Node self, RTLIL::Const value) = 0; + virtual T constant(Node self, RTLIL::Const const & value) = 0; virtual T input(Node self, IdString name) = 0; virtual T state(Node self, IdString name) = 0; virtual T memory_read(Node self, Node mem, Node addr) = 0; @@ -343,7 +330,7 @@ class FunctionalIR { T logical_shift_right(Node self, Node, Node) override { return default_handler(self); } T arithmetic_shift_right(Node self, Node, Node) override { return default_handler(self); } T mux(Node self, Node, Node, Node) override { return default_handler(self); } - T constant(Node self, RTLIL::Const) override { return default_handler(self); } + T constant(Node self, RTLIL::Const const &) override { return default_handler(self); } T input(Node self, IdString) override { return default_handler(self); } T state(Node self, IdString) override { return default_handler(self); } T memory_read(Node self, Node, Node) override { return default_handler(self); } @@ -452,30 +439,41 @@ class FunctionalIR { log_assert(node._ref.function() == Fn::buf && node._ref.size() == 0); log_assert(node.sort() == value.sort()); mutate(node).append_arg(value._ref); - } - Node input(IdString name, int width) { - _ir.add_input(name, Sort(width)); - return add(NodeData(Fn::input, name), Sort(width), {}); } - Node state(IdString name, int width) { - _ir.add_state(name, Sort(width)); - return add(NodeData(Fn::state, name), Sort(width), {}); + void add_input(IdString name, int width) { + auto [it, inserted] = _ir._input_sorts.emplace(name, Sort(width)); + if (!inserted) log_error("input `%s` was re-defined", name.c_str()); } - Node state_memory(IdString name, int addr_width, int data_width) { - _ir.add_state(name, Sort(addr_width, data_width)); - return add(NodeData(Fn::state, name), Sort(addr_width, data_width), {}); + void add_output(IdString name, int width) { + auto [it, inserted] = _ir._output_sorts.emplace(name, Sort(width)); + if (!inserted) log_error("output `%s` was re-defined", name.c_str()); } - void declare_output(Node node, IdString name, int width) { - _ir.add_output(name, Sort(width)); - mutate(node).assign_key({name, false}); + void add_state(IdString name, Sort sort) { + auto [it, inserted] = _ir._state_sorts.emplace(name, sort); + if (!inserted) log_error("state `%s` was re-defined", name.c_str()); } - void declare_state(Node node, IdString name, int width) { - _ir.add_state(name, Sort(width)); - mutate(node).assign_key({name, true}); + Node get_input(IdString name) { + return add(NodeData(Fn::input, name), Sort(_ir._input_sorts.at(name)), {}); } - void declare_state_memory(Node node, IdString name, int addr_width, int data_width) { - _ir.add_state(name, Sort(addr_width, data_width)); - mutate(node).assign_key({name, true}); + Node get_current_state(IdString name) { + return add(NodeData(Fn::state, name), Sort(_ir._state_sorts.at(name)), {}); + } + void set_output(IdString output, Node value) { + log_assert(_ir._output_sorts.at(output) == value.sort()); + mutate(value).assign_key({output, false}); + } + void set_initial_state(IdString state, RTLIL::Const value) { + Sort &sort = _ir._state_sorts.at(state); + value.extu(sort.width()); + _ir._initial_state_signal.emplace(state, std::move(value)); + } + void set_initial_state(IdString state, MemContents value) { + log_assert(Sort(value.addr_width(), value.data_width()) == _ir._state_sorts.at(state)); + _ir._initial_state_memory.emplace(state, std::move(value)); + } + void set_next_state(IdString state, Node value) { + log_assert(_ir._state_sorts.at(state) == value.sort()); + mutate(value).assign_key({state, true}); } void suggest_name(Node node, IdString name) { mutate(node).sparse_attr() = name; @@ -487,9 +485,11 @@ class FunctionalIR { Node operator[](int i) { return Node(_graph[i]); } void topological_sort(); void forward_buf(); - dict inputs() const { return _inputs; } - dict outputs() const { return _outputs; } - dict state() const { return _state; } + dict inputs() const { return _input_sorts; } + dict outputs() const { return _output_sorts; } + dict state() const { return _state_sorts; } + RTLIL::Const const &get_initial_state_signal(IdString name) { return _initial_state_signal.at(name); } + MemContents const &get_initial_state_memory(IdString name) { return _initial_state_memory.at(name); } Node get_output_node(IdString name) { return Node(_graph({name, false})); } Node get_state_next_node(IdString name) { return Node(_graph({name, true})); } class Iterator { diff --git a/tests/functional/rtlil_cells.py b/tests/functional/rtlil_cells.py index 90417579e5d..ed867b695b4 100644 --- a/tests/functional/rtlil_cells.py +++ b/tests/functional/rtlil_cells.py @@ -153,15 +153,17 @@ def write_rtlil_file(self, path, parameters): from test_functional import yosys_synth verilog_file = path.parent / 'verilog.v' with open(verilog_file, 'w') as f: - f.write(""" + width = parameters['WIDTH'] + f.write(f""" module gold( input wire clk, - input wire [{0}:0] D, - output reg [{0}:0] Q + input wire [{width-1}:0] D, + output reg [{width-1}:0] Q ); + initial Q = {width}'b{("101" * width)[:width]}; always @(posedge clk) Q <= D; -endmodule""".format(parameters['WIDTH'] - 1)) +endmodule""") yosys_synth(verilog_file, path) class MemCell(BaseCell): @@ -180,6 +182,10 @@ def write_rtlil_file(self, path, parameters): output reg [{0}:0] RD ); reg [{0}:0] mem[0:{2}]; + integer i; + initial + for(i = 0; i <= {2}; i = i + 1) + mem[i] = 9192 * (i + 1); always @(*) RD = mem[RA]; always @(posedge clk) @@ -211,8 +217,11 @@ def write_rtlil_file(self, path, parameters): output reg [{0}:0] RD1, output reg [{0}:0] RD2 ); - (*keep*) reg [{0}:0] mem[0:{2}]; + integer i; + initial + for(i = 0; i <= {2}; i = i + 1) + mem[i] = 9192 * (i + 1); always @(*) RD1 = mem[RA1]; always @(*) diff --git a/tests/functional/smt_vcd.py b/tests/functional/smt_vcd.py index 37d2a209f71..c23be440e3b 100644 --- a/tests/functional/smt_vcd.py +++ b/tests/functional/smt_vcd.py @@ -81,25 +81,6 @@ def handle_datatype(lst): parser.finish() assert smt_io.check_sat() == 'sat' - def initial_state(states): - mk_state_parts = [] - rv = [] - for name, width in states.items(): - if isinstance(width, int): - binary_string = format(0, '0{}b'.format(width)) - mk_state_parts.append(f"#b{binary_string}") - else: - binary_string = format(0, '0{}b'.format(width[1])) - rv.append(f"(declare-const test_state_initial_mem_{name} (Array (_ BitVec {width[0]}) (_ BitVec {width[1]})))") - rv.append(f"(assert (forall ((i (_ BitVec {width[0]}))) (= (select test_state_initial_mem_{name} i) #b{binary_string})))") - mk_state_parts.append(f"test_state_initial_mem_{name}") - if len(states) == 0: - mk_state_call = "gold_State" - else: - mk_state_call = "(gold_State {})".format(" ".join(mk_state_parts)) - rv.append(f"(define-const test_state_step_n0 gold_State {mk_state_call})\n") - return rv - def set_step(inputs, step): # This function assumes 'inputs' is a dictionary like {"A": 5, "B": 4} # and 'input_values' is a dictionary like {"A": 5, "B": 13} specifying the concrete values for each input. @@ -118,7 +99,7 @@ def set_step(inputs, step): f"(define-const test_state_step_n{step+1} gold_State (second test_results_step_n{step}))\n", ] - smt_commands = initial_state(states) + smt_commands = [f"(define-const test_state_step_n0 gold_State gold-initial)\n"] for step in range(num_steps): for step_command in set_step(inputs, step): smt_commands.append(step_command) diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index c31f4b86f5c..7bfe19fc494 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -29,14 +29,14 @@ def yosys_synth(verilog_file, rtlil_file): # simulate an rtlil file with yosys, comparing with a given vcd file, and writing out the yosys simulation results into a second vcd file def yosys_sim(rtlil_file, vcd_reference_file, vcd_out_file, preprocessing = ""): try: - yosys(f"read_rtlil {quote(rtlil_file)}; {preprocessing}; sim -r {quote(vcd_reference_file)} -scope gold -vcd {quote(vcd_out_file)} -timescale 1us -sim-gold") + yosys(f"read_rtlil {quote(rtlil_file)}; {preprocessing}; sim -r {quote(vcd_reference_file)} -scope gold -vcd {quote(vcd_out_file)} -timescale 1us -sim-gold -fst-noinit") except: # if yosys sim fails it's probably because of a simulation mismatch # since yosys sim aborts on simulation mismatch to generate vcd output # we have to re-run with a different set of flags # on this run we ignore output and return code, we just want a best-effort attempt to get a vcd subprocess.run([base_path / 'yosys', '-Q', '-p', - f'read_rtlil {quote(rtlil_file)}; sim -vcd {quote(vcd_out_file)} -a -r {quote(vcd_reference_file)} -scope gold -timescale 1us'], + f'read_rtlil {quote(rtlil_file)}; sim -vcd {quote(vcd_out_file)} -a -r {quote(vcd_reference_file)} -scope gold -timescale 1us -fst-noinit'], capture_output=True, check=False) raise diff --git a/tests/functional/vcd_harness.cc b/tests/functional/vcd_harness.cc index f01adf21832..7232172348a 100644 --- a/tests/functional/vcd_harness.cc +++ b/tests/functional/vcd_harness.cc @@ -7,140 +7,139 @@ #include "my_module_functional_cxx.cc" -std::string vcd_name_mangle(std::string name) { - std::string ret = name; - bool escape = ret.empty() || !isalpha(ret[0]) && ret[0] != '_'; - for(size_t i = 0; i < ret.size(); i++) { - if(isspace(ret[i])) ret[i] = '_'; - if(!isalnum(ret[i]) && ret[i] != '_' && ret[i] != '$') - escape = true; - } - if(escape) - return "\\" + ret; - else - return ret; -} -std::unordered_map codes; - -struct DumpHeader { - std::ofstream &ofs; - std::string code = "!"; - DumpHeader(std::ofstream &ofs) : ofs(ofs) {} - void increment_code() { - for(size_t i = 0; i < code.size(); i++) - if(code[i]++ == '~') - code[i] = '!'; - else - return; - code.push_back('!'); - } - template - void operator()(const char *name, Signal value) { - ofs << "$var wire " << n << " " << code << " " << vcd_name_mangle(name) << " $end\n"; - codes[name] = code; - increment_code(); - } - template - void operator()(const char *name, Memory value) { - } -}; - -struct Dump { - std::ofstream &ofs; - Dump(std::ofstream &ofs) : ofs(ofs) {} - template - void operator()(const char *name, Signal value) { - // Bit - if (n == 1) { - ofs << (value[0] ? '1' : '0'); - ofs << codes[name] << "\n"; - return; - } - // vector (multi-bit) signals - ofs << "b"; - for (size_t i = n; i-- > 0;) - ofs << (value[i] ? '1' : '0'); - ofs << " " << codes[name] << "\n"; - } - template - void operator()(const char *name, Memory value) { - } +class VcdFile { + std::ofstream &ofs; + std::string code_alloc = "!"; + std::unordered_map codes; + std::string name_mangle(std::string name) { + std::string ret = name; + bool escape = ret.empty() || !isalpha(ret[0]) && ret[0] != '_'; + for(size_t i = 0; i < ret.size(); i++) { + if(isspace(ret[i])) ret[i] = '_'; + if(!isalnum(ret[i]) && ret[i] != '_' && ret[i] != '$') + escape = true; + } + if(escape) + return "\\" + ret; + else + return ret; + } + std::string allocate_code() { + std::string ret = code_alloc; + for (size_t i = 0; i < code_alloc.size(); i++) + if (code_alloc[i]++ == '~') + code_alloc[i] = '!'; + else + return ret; + code_alloc.push_back('!'); + return ret; + } +public: + VcdFile(std::ofstream &ofs) : ofs(ofs) {} + struct DumpHeader { + VcdFile *file; + explicit DumpHeader(VcdFile *file) : file(file) {} + template void operator()(const char *name, Signal value) + { + auto it = file->codes.find(name); + if(it == file->codes.end()) + it = file->codes.emplace(name, file->allocate_code()).first; + file->ofs << "$var wire " << n << " " << it->second << " " << file->name_mangle(name) << " $end\n"; + } + template void operator()(const char *name, Memory value) {} + }; + struct Dump { + VcdFile *file; + explicit Dump(VcdFile *file) : file(file) {} + template void operator()(const char *name, Signal value) + { + if (n == 1) { + file->ofs << (value[0] ? '1' : '0'); + file->ofs << file->codes.at(name) << "\n"; + } else { + file->ofs << "b"; + for (size_t i = n; i-- > 0;) + file->ofs << (value[i] ? '1' : '0'); + file->ofs << " " << file->codes.at(name) << "\n"; + } + } + template void operator()(const char *name, Memory value) {} + }; + void begin_header() { + constexpr int number_timescale = 1; + const std::string units_timescale = "us"; + ofs << "$timescale " << number_timescale << " " << units_timescale << " $end\n"; + ofs << "$scope module gold $end\n"; + } + void end_header() { + ofs << "$enddefinitions $end\n$dumpvars\n"; + } + template void header(Args ...args) { + begin_header(); + DumpHeader d(this); + (args.visit(d), ...); + end_header(); + } + void begin_data(int step) { + ofs << "#" << step << "\n"; + } + template void data(int step, Args ...args) { + begin_data(step); + Dump d(this); + (args.visit(d), ...); + } + DumpHeader dump_header() { return DumpHeader(this); } + Dump dump() { return Dump(this); } }; -template -Signal random_signal(std::mt19937 &gen) { - std::uniform_int_distribution dist; - std::array words; - for(auto &w : words) - w = dist(gen); - return Signal::from_array(words); +template Signal random_signal(std::mt19937 &gen) +{ + std::uniform_int_distribution dist; + std::array words; + for (auto &w : words) + w = dist(gen); + return Signal::from_array(words); } -struct Reset { - template - void operator()(const char *, Signal &signal) { - signal = 0; - } -}; - struct Randomize { - std::mt19937 &gen; - Randomize(std::mt19937 &gen) : gen(gen) {} + std::mt19937 &gen; + Randomize(std::mt19937 &gen) : gen(gen) {} - template - void operator()(const char *, Signal &signal) { - signal = random_signal(gen); - } + template void operator()(const char *, Signal &signal) { signal = random_signal(gen); } }; int main(int argc, char **argv) { - if (argc != 4) { - std::cerr << "Usage: " << argv[0] << " \n"; - return 1; - } + if (argc != 4) { + std::cerr << "Usage: " << argv[0] << " \n"; + return 1; + } - const std::string functional_vcd_filename = argv[1]; - const int steps = atoi(argv[2]); - const uint32_t seed = atoi(argv[3]); + const std::string functional_vcd_filename = argv[1]; + const int steps = atoi(argv[2]); + const uint32_t seed = atoi(argv[3]); - constexpr int number_timescale = 1; - const std::string units_timescale = "us"; - gold::Inputs inputs; - gold::Outputs outputs; - gold::State state; - gold::State next_state; + gold::Inputs inputs; + gold::Outputs outputs; + gold::State state; + gold::State next_state; - std::ofstream vcd_file(functional_vcd_filename); + std::ofstream vcd_file(functional_vcd_filename); + VcdFile vcd(vcd_file); + vcd.header(inputs, outputs, state); - vcd_file << "$timescale " << number_timescale << " " << units_timescale << " $end\n"; - vcd_file << "$scope module gold $end\n"; - { - DumpHeader d(vcd_file); - inputs.visit(d); - outputs.visit(d); - state.visit(d); - } - vcd_file << "$enddefinitions $end\n$dumpvars\n"; - std::mt19937 gen(seed); + std::mt19937 gen(seed); - inputs.visit(Reset()); + gold::initialize(state); - for (int step = 0; step < steps; ++step) { - vcd_file << "#" << step << "\n"; - gold::eval(inputs, outputs, state, next_state); - { - Dump d(vcd_file); - inputs.visit(d); - outputs.visit(d); - state.visit(d); - } + for (int step = 0; step < steps; ++step) { + inputs.visit(Randomize(gen)); - state = next_state; - inputs.visit(Randomize(gen)); - } + gold::eval(inputs, outputs, state, next_state); + vcd.data(step, inputs, outputs, state); - vcd_file.close(); + state = next_state; + } - return 0; + return 0; } From 95d28c22a2d6aecc7bdb400f85896b271561c95a Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Wed, 24 Jul 2024 18:50:03 +0100 Subject: [PATCH 65/92] functional backend: make Memory in the C++ simulation library read-only again --- backends/functional/cxx.cc | 17 +++++++++++++++-- backends/functional/cxx_runtime/sim.h | 5 ++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/backends/functional/cxx.cc b/backends/functional/cxx.cc index 324a8831c86..8d53c9e0391 100644 --- a/backends/functional/cxx.cc +++ b/backends/functional/cxx.cc @@ -156,6 +156,14 @@ template struct CxxPrintVisitor : public FunctionalIR::Abstra void memory_write(Node, Node mem, Node addr, Node data) override { print("{}.write({}, {})", mem, addr, data); } }; +bool equal_def(RTLIL::Const const &a, RTLIL::Const const &b) { + if(a.size() != b.size()) return false; + for(int i = 0; i < a.size(); i++) + if((a[i] == State::S1) != (b[i] == State::S1)) + return false; + return true; +} + struct CxxModule { FunctionalIR ir; CxxStruct input_struct, output_struct, state_struct; @@ -193,11 +201,16 @@ struct CxxModule { if (sort.is_signal()) f.print("\tstate.{} = {};\n", state_struct[name], cxx_const(ir.get_initial_state_signal(name))); else if (sort.is_memory()) { + f.print("\t{{\n"); + f.print("\t\tstd::array, {}> mem;\n", sort.data_width(), 1< class Memory { std::array, 1< _contents; public: + Memory() {} + Memory(std::array, 1< const &contents) : _contents(contents) {} Signal read(Signal addr) const { return _contents[addr.template as_numeric()]; @@ -410,9 +412,6 @@ class Memory { ret._contents[addr.template as_numeric()] = data; return ret; } - // mutating methods for initializing a state - void fill(Signal data) { _contents.fill(data); } - Signal &operator[](Signal addr) { return _contents[addr.template as_numeric()]; } }; #endif From 7ac0e92d35e49704a9eb37d6ed5b451fa0db60fd Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Thu, 25 Jul 2024 07:59:00 +0100 Subject: [PATCH 66/92] functional backend: rename get_input and get_current_state to input and current_state (more consistent with other methods) --- kernel/functionalir.cc | 6 +++--- kernel/functionalir.h | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/kernel/functionalir.cc b/kernel/functionalir.cc index bc21b4d3648..195e47b12ac 100644 --- a/kernel/functionalir.cc +++ b/kernel/functionalir.cc @@ -499,7 +499,7 @@ class FunctionalIRConstruction { vector read_results; factory.add_state(mem->cell->name, FunctionalIR::Sort(ceil_log2(mem->size), mem->width)); factory.set_initial_state(mem->cell->name, MemContents(mem)); - Node node = factory.get_current_state(mem->cell->name); + Node node = factory.current_state(mem->cell->name); for (size_t i = 0; i < mem->wr_ports.size(); i++) { const auto &wr = mem->wr_ports[i]; if (wr.clk_enable) @@ -543,7 +543,7 @@ class FunctionalIRConstruction { log_error("The design contains a %s flip-flop at %s. This is not supported by the functional backend. " "Call async2sync or clk2fflogic to avoid this error.\n", log_id(cell->type), log_id(cell)); factory.add_state(ff.name, FunctionalIR::Sort(ff.width)); - Node q_value = factory.get_current_state(ff.name); + Node q_value = factory.current_state(ff.name); factory.suggest_name(q_value, ff.name); factory.update_pending(cell_outputs.at({cell, ID(Q)}), q_value); factory.set_next_state(ff.name, enqueue(ff.sig_d)); @@ -593,7 +593,7 @@ class FunctionalIRConstruction { DriveChunkWire wire_chunk = chunk.wire(); if (wire_chunk.is_whole()) { if (wire_chunk.wire->port_input) { - Node node = factory.get_input(wire_chunk.wire->name); + Node node = factory.input(wire_chunk.wire->name); factory.suggest_name(node, wire_chunk.wire->name); factory.update_pending(pending, node); } else { diff --git a/kernel/functionalir.h b/kernel/functionalir.h index e0af89d9f3a..fdbdcbde306 100644 --- a/kernel/functionalir.h +++ b/kernel/functionalir.h @@ -452,10 +452,10 @@ class FunctionalIR { auto [it, inserted] = _ir._state_sorts.emplace(name, sort); if (!inserted) log_error("state `%s` was re-defined", name.c_str()); } - Node get_input(IdString name) { + Node input(IdString name) { return add(NodeData(Fn::input, name), Sort(_ir._input_sorts.at(name)), {}); } - Node get_current_state(IdString name) { + Node current_state(IdString name) { return add(NodeData(Fn::state, name), Sort(_ir._state_sorts.at(name)), {}); } void set_output(IdString output, Node value) { From 8c0f625c3aaeb378a4b86cb2e75fc801cf4bef2c Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Thu, 25 Jul 2024 10:35:32 +0100 Subject: [PATCH 67/92] functional backend: topological sort starts with the output and next states nodes, other nodes get deleted --- backends/functional/test_generic.cc | 10 +++----- kernel/functional.h | 18 +++++++++++++- kernel/functionalir.cc | 20 ++++++++++----- kernel/topo_scc.h | 38 +++++++++++++++++++---------- passes/cmds/example_dt.cc | 4 +-- 5 files changed, 62 insertions(+), 28 deletions(-) diff --git a/backends/functional/test_generic.cc b/backends/functional/test_generic.cc index ba713b36c32..5d93492767f 100644 --- a/backends/functional/test_generic.cc +++ b/backends/functional/test_generic.cc @@ -129,14 +129,13 @@ struct FunctionalTestGeneric : public Pass size_t argidx = 1; extra_args(args, argidx, design); - +/* MemContentsTest test(8, 16); std::random_device seed_dev; std::mt19937 rnd(23); //seed_dev()); test.run(rnd, 1000); - -/* +*/ for (auto module : design->selected_modules()) { log("Dumping module `%s'.\n", module->name.c_str()); @@ -144,11 +143,10 @@ struct FunctionalTestGeneric : public Pass for(auto node : fir) std::cout << RTLIL::unescape_id(node.name()) << " = " << node.to_string([](auto n) { return RTLIL::unescape_id(n.name()); }) << "\n"; for(auto [name, sort] : fir.outputs()) - std::cout << RTLIL::unescape_id(name) << " = " << RTLIL::unescape_id(fir.get_output_node(name).name()) << "\n"; + std::cout << "output " << RTLIL::unescape_id(name) << " = " << RTLIL::unescape_id(fir.get_output_node(name).name()) << "\n"; for(auto [name, sort] : fir.state()) - std::cout << RTLIL::unescape_id(name) << " = " << RTLIL::unescape_id(fir.get_state_next_node(name).name()) << "\n"; + std::cout << "state " << RTLIL::unescape_id(name) << " = " << RTLIL::unescape_id(fir.get_state_next_node(name).name()) << "\n"; } -*/ } } FunctionalCxxBackend; diff --git a/kernel/functional.h b/kernel/functional.h index 09f66c9813b..77e965f6474 100644 --- a/kernel/functional.h +++ b/kernel/functional.h @@ -268,6 +268,19 @@ struct ComputeGraph return added; } + void compact_args() + { + std::vector new_args; + for (auto &node : nodes) + { + int new_offset = GetSize(new_args); + for (int i = 0; i < node.arg_count; i++) + new_args.push_back(args[node.arg_offset + i]); + node.arg_offset = new_offset; + } + std::swap(args, new_args); + } + void permute(std::vector const &perm) { log_assert(perm.size() <= nodes.size()); @@ -276,7 +289,7 @@ struct ComputeGraph for (int i = 0; i < GetSize(perm); ++i) { int j = perm[i]; - log_assert(j >= 0 && j < GetSize(perm)); + log_assert(j >= 0 && j < GetSize(nodes)); log_assert(inv_perm[j] == -1); inv_perm[j] = i; } @@ -301,15 +314,18 @@ struct ComputeGraph std::swap(nodes, new_nodes); std::swap(sparse_attrs, new_sparse_attrs); + compact_args(); for (int &arg : args) { log_assert(arg < GetSize(inv_perm)); + log_assert(inv_perm[arg] >= 0); arg = inv_perm[arg]; } for (auto &key : keys_) { log_assert(key.second < GetSize(inv_perm)); + log_assert(inv_perm[key.second] >= 0); key.second = inv_perm[key.second]; } } diff --git a/kernel/functionalir.cc b/kernel/functionalir.cc index 195e47b12ac..223fdaa913e 100644 --- a/kernel/functionalir.cc +++ b/kernel/functionalir.cc @@ -657,19 +657,27 @@ void FunctionalIR::topological_sort() { Graph::SccAdaptor compute_graph_scc(_graph); bool scc = false; std::vector perm; - topo_sorted_sccs(compute_graph_scc, [&](int *begin, int *end) { + TopoSortedSccs toposort(compute_graph_scc, [&](int *begin, int *end) { perm.insert(perm.end(), begin, end); if (end > begin + 1) { - log_warning("SCC:"); - for (int *i = begin; i != end; ++i) - log(" %d", *i); + log_warning("Combinational loop:\n"); + for (int *i = begin; i != end; ++i) { + Node node(_graph[*i]); + log("- %s = %s\n", RTLIL::unescape_id(node.name()).c_str(), node.to_string().c_str()); + } log("\n"); scc = true; } - }, /* sources_first */ true); + }); + for(const auto &[name, sort]: _state_sorts) + toposort.process(get_state_next_node(name).id()); + for(const auto &[name, sort]: _output_sorts) + toposort.process(get_output_node(name).id()); + // any nodes untouched by this point are dead code and will be removed by permute _graph.permute(perm); - if(scc) log_error("combinational loops, aborting\n"); + if(scc) log_error("The design contains combinational loops. This is not supported by the functional backend. " + "Try `scc -select; simplemap; select -clear` to avoid this error.\n"); } static IdString merge_name(IdString a, IdString b) { diff --git a/kernel/topo_scc.h b/kernel/topo_scc.h index 89df8928aa2..7e730bb27dc 100644 --- a/kernel/topo_scc.h +++ b/kernel/topo_scc.h @@ -194,7 +194,7 @@ class IntGraph { }; template -void topo_sorted_sccs(G &graph, ComponentCallback component, bool sources_first = false) +class TopoSortedSccs { typedef typename G::node_enumerator node_enumerator; typedef typename G::successor_enumerator successor_enumerator; @@ -210,14 +210,20 @@ void topo_sorted_sccs(G &graph, ComponentCallback component, bool sources_first {} }; + G &graph; + ComponentCallback component; + std::vector dfs_stack; std::vector component_stack; int next_index = 0; +public: + TopoSortedSccs(G &graph, ComponentCallback component) + : graph(graph), component(component) {} - node_enumerator nodes = graph.enumerate_nodes(); - - if (sources_first) { + // process all sources (nodes without a successor) + TopoSortedSccs &process_sources() { + node_enumerator nodes = graph.enumerate_nodes(); while (!nodes.finished()) { node_type node = nodes.next(); successor_enumerator successors = graph.enumerate_successors(node); @@ -231,16 +237,23 @@ void topo_sorted_sccs(G &graph, ComponentCallback component, bool sources_first graph.dfs_index(node) = INT_MAX; } } - nodes = graph.enumerate_nodes(); + return *this; } - // iterate over all nodes to ensure we process the whole graph - while (!nodes.finished()) { - node_type node = nodes.next(); + // process all remaining nodes in the graph + TopoSortedSccs &process_all() { + node_enumerator nodes = graph.enumerate_nodes(); + // iterate over all nodes to ensure we process the whole graph + while (!nodes.finished()) + process(nodes.next()); + return *this; + } + + // process all nodes that are reachable from a given start node + TopoSortedSccs &process(node_type node) { // only start a new search if the node wasn't visited yet if (graph.dfs_index(node) >= 0) - continue; - + return *this; while (true) { // at this point we're visiting the node for the first time during // the DFS search @@ -299,7 +312,7 @@ void topo_sorted_sccs(G &graph, ComponentCallback component, bool sources_first // continues the search at the parent node or returns to // the outer loop if we already are at the root node. if (dfs_stack.empty()) - goto next_search; + return *this; auto &dfs_top = dfs_stack.back(); node = dfs_top.node; @@ -336,9 +349,8 @@ void topo_sorted_sccs(G &graph, ComponentCallback component, bool sources_first } } } - next_search:; } -} +}; YOSYS_NAMESPACE_END diff --git a/passes/cmds/example_dt.cc b/passes/cmds/example_dt.cc index 859b0b7ba4b..4b836d75b88 100644 --- a/passes/cmds/example_dt.cc +++ b/passes/cmds/example_dt.cc @@ -183,7 +183,7 @@ struct ExampleDtPass : public Pass std::vector perm; - topo_sorted_sccs(compute_graph_scc, [&](int *begin, int *end) { + TopoSortedSccs(compute_graph_scc, [&](int *begin, int *end) { perm.insert(perm.end(), begin, end); if (end > begin + 1) { @@ -192,7 +192,7 @@ struct ExampleDtPass : public Pass log(" %d", *i); log("\n"); } - }, /* sources_first */ true); + }).process_sources().process_all(); compute_graph.permute(perm); From 850b3a6c29b8b1ffd76ed24493d85ffa721f8109 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Thu, 25 Jul 2024 12:10:59 +0100 Subject: [PATCH 68/92] convert class FunctionalIR to a namespace Functional, rename functionalir.h to functional.h, rename functional.h to compute_graph.h --- Makefile | 2 +- backends/functional/cxx.cc | 20 +- backends/functional/smtlib.cc | 22 +- backends/functional/test_generic.cc | 4 +- kernel/compute_graph.h | 403 ++++++++++ kernel/{functionalir.cc => functional.cc} | 111 ++- kernel/functional.h | 935 +++++++++++++--------- kernel/functionalir.h | 575 ------------- kernel/mem.h | 11 +- kernel/utils.h | 9 + passes/cmds/example_dt.cc | 2 +- 11 files changed, 1055 insertions(+), 1039 deletions(-) create mode 100644 kernel/compute_graph.h rename kernel/{functionalir.cc => functional.cc} (90%) delete mode 100644 kernel/functionalir.h diff --git a/Makefile b/Makefile index 68e6fda4a0c..e61948cb4f1 100644 --- a/Makefile +++ b/Makefile @@ -640,7 +640,7 @@ $(eval $(call add_include_file,backends/rtlil/rtlil_backend.h)) OBJS += kernel/driver.o kernel/register.o kernel/rtlil.o kernel/log.o kernel/calc.o kernel/yosys.o OBJS += kernel/binding.o OBJS += kernel/cellaigs.o kernel/celledges.o kernel/cost.o kernel/satgen.o kernel/scopeinfo.o kernel/qcsat.o kernel/mem.o kernel/ffmerge.o kernel/ff.o kernel/yw.o kernel/json.o kernel/fmt.o kernel/sexpr.o -OBJS += kernel/drivertools.o kernel/functionalir.o +OBJS += kernel/drivertools.o kernel/functional.o ifeq ($(ENABLE_ZLIB),1) OBJS += kernel/fstdata.o endif diff --git a/backends/functional/cxx.cc b/backends/functional/cxx.cc index 8d53c9e0391..a4755e144c3 100644 --- a/backends/functional/cxx.cc +++ b/backends/functional/cxx.cc @@ -18,7 +18,7 @@ */ #include "kernel/yosys.h" -#include "kernel/functionalir.h" +#include "kernel/functional.h" #include USING_YOSYS_NAMESPACE @@ -42,7 +42,7 @@ const char *reserved_keywords[] = { nullptr }; -template struct CxxScope : public FunctionalTools::Scope { +template struct CxxScope : public Functional::Scope { CxxScope() { for(const char **p = reserved_keywords; *p != nullptr; p++) this->reserve(*p); @@ -53,8 +53,8 @@ template struct CxxScope : public FunctionalTools::Scope { }; struct CxxType { - FunctionalIR::Sort sort; - CxxType(FunctionalIR::Sort sort) : sort(sort) {} + Functional::Sort sort; + CxxType(Functional::Sort sort) : sort(sort) {} std::string to_string() const { if(sort.is_memory()) { return stringf("Memory<%d, %d>", sort.addr_width(), sort.data_width()); @@ -66,7 +66,7 @@ struct CxxType { } }; -using CxxWriter = FunctionalTools::Writer; +using CxxWriter = Functional::Writer; struct CxxStruct { std::string name; @@ -111,8 +111,8 @@ std::string cxx_const(RTLIL::Const const &value) { return ss.str(); } -template struct CxxPrintVisitor : public FunctionalIR::AbstractVisitor { - using Node = FunctionalIR::Node; +template struct CxxPrintVisitor : public Functional::AbstractVisitor { + using Node = Functional::Node; CxxWriter &f; NodePrinter np; CxxStruct &input_struct; @@ -165,12 +165,12 @@ bool equal_def(RTLIL::Const const &a, RTLIL::Const const &b) { } struct CxxModule { - FunctionalIR ir; + Functional::IR ir; CxxStruct input_struct, output_struct, state_struct; std::string module_name; explicit CxxModule(Module *module) : - ir(FunctionalIR::from_module(module)), + ir(Functional::IR::from_module(module)), input_struct("Inputs"), output_struct("Outputs"), state_struct("State") @@ -222,7 +222,7 @@ struct CxxModule { locals.reserve("output"); locals.reserve("current_state"); locals.reserve("next_state"); - auto node_name = [&](FunctionalIR::Node n) { return locals(n.id(), n.name()); }; + auto node_name = [&](Functional::Node n) { return locals(n.id(), n.name()); }; CxxPrintVisitor printVisitor(f, node_name, input_struct, state_struct); for (auto node : ir) { f.print("\t{} {} = ", CxxType(node.sort()).to_string(), node_name(node)); diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index 0d2763d3201..7fd6fe564c2 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -17,7 +17,7 @@ * */ -#include "kernel/functionalir.h" +#include "kernel/functional.h" #include "kernel/yosys.h" #include "kernel/sexpr.h" #include @@ -42,7 +42,7 @@ const char *reserved_keywords[] = { nullptr }; -struct SmtScope : public FunctionalTools::Scope { +struct SmtScope : public Functional::Scope { SmtScope() { for(const char **p = reserved_keywords; *p != nullptr; p++) reserve(*p); @@ -53,8 +53,8 @@ struct SmtScope : public FunctionalTools::Scope { }; struct SmtSort { - FunctionalIR::Sort sort; - SmtSort(FunctionalIR::Sort sort) : sort(sort) {} + Functional::Sort sort; + SmtSort(Functional::Sort sort) : sort(sort) {} SExpr to_sexpr() const { if(sort.is_memory()) { return list("Array", list("_", "BitVec", sort.addr_width()), list("_", "BitVec", sort.data_width())); @@ -116,8 +116,8 @@ std::string smt_const(RTLIL::Const const &c) { return s; } -struct SmtPrintVisitor : public FunctionalIR::AbstractVisitor { - using Node = FunctionalIR::Node; +struct SmtPrintVisitor : public Functional::AbstractVisitor { + using Node = Functional::Node; std::function n; SmtStruct &input_struct; SmtStruct &state_struct; @@ -183,7 +183,7 @@ struct SmtPrintVisitor : public FunctionalIR::AbstractVisitor { }; struct SmtModule { - FunctionalIR ir; + Functional::IR ir; SmtScope scope; std::string name; @@ -192,7 +192,7 @@ struct SmtModule { SmtStruct state_struct; SmtModule(Module *module) - : ir(FunctionalIR::from_module(module)) + : ir(Functional::IR::from_module(module)) , scope() , name(scope.unique_name(module->name)) , input_struct(scope.unique_name(module->name.str() + "_Inputs"), scope) @@ -215,11 +215,11 @@ struct SmtModule { list(list("inputs", input_struct.name), list("state", state_struct.name)), list("Pair", output_struct.name, state_struct.name))); - auto inlined = [&](FunctionalIR::Node n) { - return n.fn() == FunctionalIR::Fn::constant; + auto inlined = [&](Functional::Node n) { + return n.fn() == Functional::Fn::constant; }; SmtPrintVisitor visitor(input_struct, state_struct); - auto node_to_sexpr = [&](FunctionalIR::Node n) -> SExpr { + auto node_to_sexpr = [&](Functional::Node n) -> SExpr { if(inlined(n)) return n.visit(visitor); else diff --git a/backends/functional/test_generic.cc b/backends/functional/test_generic.cc index 5d93492767f..83ea09d8ddb 100644 --- a/backends/functional/test_generic.cc +++ b/backends/functional/test_generic.cc @@ -18,7 +18,7 @@ */ #include "kernel/yosys.h" -#include "kernel/functionalir.h" +#include "kernel/functional.h" #include USING_YOSYS_NAMESPACE @@ -139,7 +139,7 @@ struct FunctionalTestGeneric : public Pass for (auto module : design->selected_modules()) { log("Dumping module `%s'.\n", module->name.c_str()); - auto fir = FunctionalIR::from_module(module); + auto fir = Functional::IR::from_module(module); for(auto node : fir) std::cout << RTLIL::unescape_id(node.name()) << " = " << node.to_string([](auto n) { return RTLIL::unescape_id(n.name()); }) << "\n"; for(auto [name, sort] : fir.outputs()) diff --git a/kernel/compute_graph.h b/kernel/compute_graph.h new file mode 100644 index 00000000000..aeba17f8c1f --- /dev/null +++ b/kernel/compute_graph.h @@ -0,0 +1,403 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Jannis Harder + * + * 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 COMPUTE_GRAPH_H +#define COMPUTE_GRAPH_H + +#include +#include "kernel/yosys.h" + +YOSYS_NAMESPACE_BEGIN + +template< + typename Fn, // Function type (deduplicated across whole graph) + typename Attr = std::tuple<>, // Call attributes (present in every node) + typename SparseAttr = std::tuple<>, // Sparse call attributes (optional per node) + typename Key = std::tuple<> // Stable keys to refer to nodes +> +struct ComputeGraph +{ + struct Ref; +private: + + // Functions are deduplicated by assigning unique ids + idict functions; + + struct Node { + int fn_index; + int arg_offset; + int arg_count; + Attr attr; + + Node(int fn_index, Attr &&attr, int arg_offset, int arg_count = 0) + : fn_index(fn_index), arg_offset(arg_offset), arg_count(arg_count), attr(std::move(attr)) {} + + Node(int fn_index, Attr const &attr, int arg_offset, int arg_count = 0) + : fn_index(fn_index), arg_offset(arg_offset), arg_count(arg_count), attr(attr) {} + }; + + + std::vector nodes; + std::vector args; + dict keys_; + dict sparse_attrs; + +public: + template + struct BaseRef + { + protected: + friend struct ComputeGraph; + Graph *graph_; + int index_; + BaseRef(Graph *graph, int index) : graph_(graph), index_(index) { + log_assert(index_ >= 0); + check(); + } + + void check() const { log_assert(index_ < graph_->size()); } + + Node const &deref() const { check(); return graph_->nodes[index_]; } + + public: + ComputeGraph const &graph() const { return graph_; } + int index() const { return index_; } + + int size() const { return deref().arg_count; } + + BaseRef arg(int n) const + { + Node const &node = deref(); + log_assert(n >= 0 && n < node.arg_count); + return BaseRef(graph_, graph_->args[node.arg_offset + n]); + } + + std::vector::const_iterator arg_indices_cbegin() const + { + Node const &node = deref(); + return graph_->args.cbegin() + node.arg_offset; + } + + std::vector::const_iterator arg_indices_cend() const + { + Node const &node = deref(); + return graph_->args.cbegin() + node.arg_offset + node.arg_count; + } + + Fn const &function() const { return graph_->functions[deref().fn_index]; } + Attr const &attr() const { return deref().attr; } + + bool has_sparse_attr() const { return graph_->sparse_attrs.count(index_); } + + SparseAttr const &sparse_attr() const + { + auto found = graph_->sparse_attrs.find(index_); + log_assert(found != graph_->sparse_attrs.end()); + return found->second; + } + }; + + using ConstRef = BaseRef; + + struct Ref : public BaseRef + { + private: + friend struct ComputeGraph; + Ref(ComputeGraph *graph, int index) : BaseRef(graph, index) {} + Node &deref() const { this->check(); return this->graph_->nodes[this->index_]; } + + public: + Ref(BaseRef ref) : Ref(ref.graph_, ref.index_) {} + + void set_function(Fn const &function) const + { + deref().fn_index = this->graph_->functions(function); + } + + Attr &attr() const { return deref().attr; } + + void append_arg(ConstRef arg) const + { + log_assert(arg.graph_ == this->graph_); + append_arg(arg.index()); + } + + void append_arg(int arg) const + { + log_assert(arg >= 0 && arg < this->graph_->size()); + Node &node = deref(); + if (node.arg_offset + node.arg_count != GetSize(this->graph_->args)) + move_args(node); + this->graph_->args.push_back(arg); + node.arg_count++; + } + + operator ConstRef() const + { + return ConstRef(this->graph_, this->index_); + } + + SparseAttr &sparse_attr() const + { + return this->graph_->sparse_attrs[this->index_]; + } + + void clear_sparse_attr() const + { + this->graph_->sparse_attrs.erase(this->index_); + } + + void assign_key(Key const &key) const + { + this->graph_->keys_.emplace(key, this->index_); + } + + private: + void move_args(Node &node) const + { + auto &args = this->graph_->args; + int old_offset = node.arg_offset; + node.arg_offset = GetSize(args); + for (int i = 0; i != node.arg_count; ++i) + args.push_back(args[old_offset + i]); + } + + }; + + bool has_key(Key const &key) const + { + return keys_.count(key); + } + + dict const &keys() const + { + return keys_; + } + + ConstRef operator()(Key const &key) const + { + auto it = keys_.find(key); + log_assert(it != keys_.end()); + return (*this)[it->second]; + } + + Ref operator()(Key const &key) + { + auto it = keys_.find(key); + log_assert(it != keys_.end()); + return (*this)[it->second]; + } + + int size() const { return GetSize(nodes); } + + ConstRef operator[](int index) const { return ConstRef(this, index); } + Ref operator[](int index) { return Ref(this, index); } + + Ref add(Fn const &function, Attr &&attr) + { + int index = GetSize(nodes); + int fn_index = functions(function); + nodes.emplace_back(fn_index, std::move(attr), GetSize(args)); + return Ref(this, index); + } + + Ref add(Fn const &function, Attr const &attr) + { + int index = GetSize(nodes); + int fn_index = functions(function); + nodes.emplace_back(fn_index, attr, GetSize(args)); + return Ref(this, index); + } + + template + Ref add(Fn const &function, Attr const &attr, T &&args) + { + Ref added = add(function, attr); + for (auto arg : args) + added.append_arg(arg); + return added; + } + + template + Ref add(Fn const &function, Attr &&attr, T &&args) + { + Ref added = add(function, std::move(attr)); + for (auto arg : args) + added.append_arg(arg); + return added; + } + + Ref add(Fn const &function, Attr const &attr, std::initializer_list args) + { + Ref added = add(function, attr); + for (auto arg : args) + added.append_arg(arg); + return added; + } + + Ref add(Fn const &function, Attr &&attr, std::initializer_list args) + { + Ref added = add(function, std::move(attr)); + for (auto arg : args) + added.append_arg(arg); + return added; + } + + template + Ref add(Fn const &function, Attr const &attr, T begin, T end) + { + Ref added = add(function, attr); + for (; begin != end; ++begin) + added.append_arg(*begin); + return added; + } + + void compact_args() + { + std::vector new_args; + for (auto &node : nodes) + { + int new_offset = GetSize(new_args); + for (int i = 0; i < node.arg_count; i++) + new_args.push_back(args[node.arg_offset + i]); + node.arg_offset = new_offset; + } + std::swap(args, new_args); + } + + void permute(std::vector const &perm) + { + log_assert(perm.size() <= nodes.size()); + std::vector inv_perm; + inv_perm.resize(nodes.size(), -1); + for (int i = 0; i < GetSize(perm); ++i) + { + int j = perm[i]; + log_assert(j >= 0 && j < GetSize(nodes)); + log_assert(inv_perm[j] == -1); + inv_perm[j] = i; + } + permute(perm, inv_perm); + } + + void permute(std::vector const &perm, std::vector const &inv_perm) + { + log_assert(inv_perm.size() == nodes.size()); + std::vector new_nodes; + new_nodes.reserve(perm.size()); + dict new_sparse_attrs; + for (int i : perm) + { + int j = GetSize(new_nodes); + new_nodes.emplace_back(std::move(nodes[i])); + auto found = sparse_attrs.find(i); + if (found != sparse_attrs.end()) + new_sparse_attrs.emplace(j, std::move(found->second)); + } + + std::swap(nodes, new_nodes); + std::swap(sparse_attrs, new_sparse_attrs); + + compact_args(); + for (int &arg : args) + { + log_assert(arg < GetSize(inv_perm)); + log_assert(inv_perm[arg] >= 0); + arg = inv_perm[arg]; + } + + for (auto &key : keys_) + { + log_assert(key.second < GetSize(inv_perm)); + log_assert(inv_perm[key.second] >= 0); + key.second = inv_perm[key.second]; + } + } + + struct SccAdaptor + { + private: + ComputeGraph const &graph_; + std::vector indices_; + public: + SccAdaptor(ComputeGraph const &graph) : graph_(graph) + { + indices_.resize(graph.size(), -1); + } + + + typedef int node_type; + + struct node_enumerator { + private: + friend struct SccAdaptor; + int current, end; + node_enumerator(int current, int end) : current(current), end(end) {} + + public: + + bool finished() const { return current == end; } + node_type next() { + log_assert(!finished()); + node_type result = current; + ++current; + return result; + } + }; + + node_enumerator enumerate_nodes() { + return node_enumerator(0, GetSize(indices_)); + } + + + struct successor_enumerator { + private: + friend struct SccAdaptor; + std::vector::const_iterator current, end; + successor_enumerator(std::vector::const_iterator current, std::vector::const_iterator end) : + current(current), end(end) {} + + public: + bool finished() const { return current == end; } + node_type next() { + log_assert(!finished()); + node_type result = *current; + ++current; + return result; + } + }; + + successor_enumerator enumerate_successors(int index) const { + auto const &ref = graph_[index]; + return successor_enumerator(ref.arg_indices_cbegin(), ref.arg_indices_cend()); + } + + int &dfs_index(node_type const &node) { return indices_[node]; } + + std::vector const &dfs_indices() { return indices_; } + }; + +}; + + + +YOSYS_NAMESPACE_END + + +#endif diff --git a/kernel/functionalir.cc b/kernel/functional.cc similarity index 90% rename from kernel/functionalir.cc rename to kernel/functional.cc index 223fdaa913e..ad507187db2 100644 --- a/kernel/functionalir.cc +++ b/kernel/functional.cc @@ -17,55 +17,55 @@ * */ -#include "kernel/functionalir.h" -#include +#include "kernel/functional.h" +#include "kernel/topo_scc.h" #include "ff.h" #include "ffinit.h" YOSYS_NAMESPACE_BEGIN +namespace Functional { -const char *FunctionalIR::fn_to_string(FunctionalIR::Fn fn) { +const char *fn_to_string(Fn fn) { switch(fn) { - case FunctionalIR::Fn::invalid: return "invalid"; - case FunctionalIR::Fn::buf: return "buf"; - case FunctionalIR::Fn::slice: return "slice"; - case FunctionalIR::Fn::zero_extend: return "zero_extend"; - case FunctionalIR::Fn::sign_extend: return "sign_extend"; - case FunctionalIR::Fn::concat: return "concat"; - case FunctionalIR::Fn::add: return "add"; - case FunctionalIR::Fn::sub: return "sub"; - case FunctionalIR::Fn::mul: return "mul"; - case FunctionalIR::Fn::unsigned_div: return "unsigned_div"; - case FunctionalIR::Fn::unsigned_mod: return "unsigned_mod"; - case FunctionalIR::Fn::bitwise_and: return "bitwise_and"; - case FunctionalIR::Fn::bitwise_or: return "bitwise_or"; - case FunctionalIR::Fn::bitwise_xor: return "bitwise_xor"; - case FunctionalIR::Fn::bitwise_not: return "bitwise_not"; - case FunctionalIR::Fn::reduce_and: return "reduce_and"; - case FunctionalIR::Fn::reduce_or: return "reduce_or"; - case FunctionalIR::Fn::reduce_xor: return "reduce_xor"; - case FunctionalIR::Fn::unary_minus: return "unary_minus"; - case FunctionalIR::Fn::equal: return "equal"; - case FunctionalIR::Fn::not_equal: return "not_equal"; - case FunctionalIR::Fn::signed_greater_than: return "signed_greater_than"; - case FunctionalIR::Fn::signed_greater_equal: return "signed_greater_equal"; - case FunctionalIR::Fn::unsigned_greater_than: return "unsigned_greater_than"; - case FunctionalIR::Fn::unsigned_greater_equal: return "unsigned_greater_equal"; - case FunctionalIR::Fn::logical_shift_left: return "logical_shift_left"; - case FunctionalIR::Fn::logical_shift_right: return "logical_shift_right"; - case FunctionalIR::Fn::arithmetic_shift_right: return "arithmetic_shift_right"; - case FunctionalIR::Fn::mux: return "mux"; - case FunctionalIR::Fn::constant: return "constant"; - case FunctionalIR::Fn::input: return "input"; - case FunctionalIR::Fn::state: return "state"; - case FunctionalIR::Fn::memory_read: return "memory_read"; - case FunctionalIR::Fn::memory_write: return "memory_write"; + case Fn::invalid: return "invalid"; + case Fn::buf: return "buf"; + case Fn::slice: return "slice"; + case Fn::zero_extend: return "zero_extend"; + case Fn::sign_extend: return "sign_extend"; + case Fn::concat: return "concat"; + case Fn::add: return "add"; + case Fn::sub: return "sub"; + case Fn::mul: return "mul"; + case Fn::unsigned_div: return "unsigned_div"; + case Fn::unsigned_mod: return "unsigned_mod"; + case Fn::bitwise_and: return "bitwise_and"; + case Fn::bitwise_or: return "bitwise_or"; + case Fn::bitwise_xor: return "bitwise_xor"; + case Fn::bitwise_not: return "bitwise_not"; + case Fn::reduce_and: return "reduce_and"; + case Fn::reduce_or: return "reduce_or"; + case Fn::reduce_xor: return "reduce_xor"; + case Fn::unary_minus: return "unary_minus"; + case Fn::equal: return "equal"; + case Fn::not_equal: return "not_equal"; + case Fn::signed_greater_than: return "signed_greater_than"; + case Fn::signed_greater_equal: return "signed_greater_equal"; + case Fn::unsigned_greater_than: return "unsigned_greater_than"; + case Fn::unsigned_greater_equal: return "unsigned_greater_equal"; + case Fn::logical_shift_left: return "logical_shift_left"; + case Fn::logical_shift_right: return "logical_shift_right"; + case Fn::arithmetic_shift_right: return "arithmetic_shift_right"; + case Fn::mux: return "mux"; + case Fn::constant: return "constant"; + case Fn::input: return "input"; + case Fn::state: return "state"; + case Fn::memory_read: return "memory_read"; + case Fn::memory_write: return "memory_write"; } - log_error("fn_to_string: unknown FunctionalIR::Fn value %d", (int)fn); + log_error("fn_to_string: unknown Functional::Fn value %d", (int)fn); } -struct PrintVisitor : FunctionalIR::DefaultVisitor { - using Node = FunctionalIR::Node; +struct PrintVisitor : DefaultVisitor { std::function np; PrintVisitor(std::function np) : np(np) { } // as a general rule the default handler is good enough iff the only arguments are of type Node @@ -76,7 +76,7 @@ struct PrintVisitor : FunctionalIR::DefaultVisitor { std::string input(Node, IdString name) override { return "input(" + name.str() + ")"; } std::string state(Node, IdString name) override { return "state(" + name.str() + ")"; } std::string default_handler(Node self) override { - std::string ret = FunctionalIR::fn_to_string(self.fn()); + std::string ret = fn_to_string(self.fn()); ret += "("; for(size_t i = 0; i < self.arg_count(); i++) { if(i > 0) ret += ", "; @@ -87,19 +87,18 @@ struct PrintVisitor : FunctionalIR::DefaultVisitor { } }; -std::string FunctionalIR::Node::to_string() +std::string Node::to_string() { return to_string([](Node n) { return RTLIL::unescape_id(n.name()); }); } -std::string FunctionalIR::Node::to_string(std::function np) +std::string Node::to_string(std::function np) { return visit(PrintVisitor(np)); } class CellSimplifier { - using Node = FunctionalIR::Node; - FunctionalIR::Factory &factory; + Factory &factory; Node sign(Node a) { return factory.slice(a, a.width() - 1, 1); } @@ -138,7 +137,7 @@ class CellSimplifier { Node bb = factory.bitwise_and(b, s); return factory.bitwise_or(aa, bb); } - CellSimplifier(FunctionalIR::Factory &f) : factory(f) {} + CellSimplifier(Factory &f) : factory(f) {} private: Node handle_pow(Node a0, Node b, int y_width, bool is_signed) { Node a = factory.extend(a0, y_width, is_signed); @@ -400,12 +399,11 @@ class CellSimplifier { }; class FunctionalIRConstruction { - using Node = FunctionalIR::Node; std::deque> queue; dict graph_nodes; dict, Node> cell_outputs; DriverMap driver_map; - FunctionalIR::Factory& factory; + Factory& factory; CellSimplifier simplifier; vector memories_vector; dict memories; @@ -442,7 +440,7 @@ class FunctionalIRConstruction { return it->second; } public: - FunctionalIRConstruction(Module *module, FunctionalIR::Factory &f) + FunctionalIRConstruction(Module *module, Factory &f) : factory(f) , simplifier(f) , sig_map(module) @@ -497,7 +495,7 @@ class FunctionalIRConstruction { // - Since wr port j can only have priority over wr port i if j > i, if we do writes in // ascending index order the result will obey the priorty relation. vector read_results; - factory.add_state(mem->cell->name, FunctionalIR::Sort(ceil_log2(mem->size), mem->width)); + factory.add_state(mem->cell->name, Sort(ceil_log2(mem->size), mem->width)); factory.set_initial_state(mem->cell->name, MemContents(mem)); Node node = factory.current_state(mem->cell->name); for (size_t i = 0; i < mem->wr_ports.size(); i++) { @@ -542,7 +540,7 @@ class FunctionalIRConstruction { if (!ff.has_gclk) log_error("The design contains a %s flip-flop at %s. This is not supported by the functional backend. " "Call async2sync or clk2fflogic to avoid this error.\n", log_id(cell->type), log_id(cell)); - factory.add_state(ff.name, FunctionalIR::Sort(ff.width)); + factory.add_state(ff.name, Sort(ff.width)); Node q_value = factory.current_state(ff.name); factory.suggest_name(q_value, ff.name); factory.update_pending(cell_outputs.at({cell, ID(Q)}), q_value); @@ -643,8 +641,8 @@ class FunctionalIRConstruction { } }; -FunctionalIR FunctionalIR::from_module(Module *module) { - FunctionalIR ir; +IR IR::from_module(Module *module) { + IR ir; auto factory = ir.factory(); FunctionalIRConstruction ctor(module, factory); ctor.process_queue(); @@ -653,7 +651,7 @@ FunctionalIR FunctionalIR::from_module(Module *module) { return ir; } -void FunctionalIR::topological_sort() { +void IR::topological_sort() { Graph::SccAdaptor compute_graph_scc(_graph); bool scc = false; std::vector perm; @@ -687,7 +685,7 @@ static IdString merge_name(IdString a, IdString b) { return a; } -void FunctionalIR::forward_buf() { +void IR::forward_buf() { std::vector perm, alias; perm.clear(); @@ -734,7 +732,7 @@ static std::string quote_fmt(const char *fmt) return r; } -void FunctionalTools::Writer::print_impl(const char *fmt, vector> &fns) +void Writer::print_impl(const char *fmt, vector> &fns) { size_t next_index = 0; for(const char *p = fmt; *p != 0; p++) @@ -770,4 +768,5 @@ void FunctionalTools::Writer::print_impl(const char *fmt, vector + * Copyright (C) 2024 Emily Schmidt * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -20,384 +20,571 @@ #ifndef FUNCTIONAL_H #define FUNCTIONAL_H -#include #include "kernel/yosys.h" +#include "kernel/compute_graph.h" +#include "kernel/drivertools.h" +#include "kernel/mem.h" +#include "kernel/utils.h" +USING_YOSYS_NAMESPACE YOSYS_NAMESPACE_BEGIN -template< - typename Fn, // Function type (deduplicated across whole graph) - typename Attr = std::tuple<>, // Call attributes (present in every node) - typename SparseAttr = std::tuple<>, // Sparse call attributes (optional per node) - typename Key = std::tuple<> // Stable keys to refer to nodes -> -struct ComputeGraph -{ - struct Ref; -private: - - // Functions are deduplicated by assigning unique ids - idict functions; - - struct Node { - int fn_index; - int arg_offset; - int arg_count; - Attr attr; - - Node(int fn_index, Attr &&attr, int arg_offset, int arg_count = 0) - : fn_index(fn_index), arg_offset(arg_offset), arg_count(arg_count), attr(std::move(attr)) {} - - Node(int fn_index, Attr const &attr, int arg_offset, int arg_count = 0) - : fn_index(fn_index), arg_offset(arg_offset), arg_count(arg_count), attr(attr) {} - }; - - - std::vector nodes; - std::vector args; - dict keys_; - dict sparse_attrs; - -public: - template - struct BaseRef - { - protected: - friend struct ComputeGraph; - Graph *graph_; - int index_; - BaseRef(Graph *graph, int index) : graph_(graph), index_(index) { - log_assert(index_ >= 0); - check(); - } - - void check() const { log_assert(index_ < graph_->size()); } - - Node const &deref() const { check(); return graph_->nodes[index_]; } - - public: - ComputeGraph const &graph() const { return graph_; } - int index() const { return index_; } - - int size() const { return deref().arg_count; } - - BaseRef arg(int n) const - { - Node const &node = deref(); - log_assert(n >= 0 && n < node.arg_count); - return BaseRef(graph_, graph_->args[node.arg_offset + n]); - } - - std::vector::const_iterator arg_indices_cbegin() const - { - Node const &node = deref(); - return graph_->args.cbegin() + node.arg_offset; - } - - std::vector::const_iterator arg_indices_cend() const - { - Node const &node = deref(); - return graph_->args.cbegin() + node.arg_offset + node.arg_count; - } - - Fn const &function() const { return graph_->functions[deref().fn_index]; } - Attr const &attr() const { return deref().attr; } - - bool has_sparse_attr() const { return graph_->sparse_attrs.count(index_); } - - SparseAttr const &sparse_attr() const - { - auto found = graph_->sparse_attrs.find(index_); - log_assert(found != graph_->sparse_attrs.end()); - return found->second; - } - }; - - using ConstRef = BaseRef; - - struct Ref : public BaseRef - { - private: - friend struct ComputeGraph; - Ref(ComputeGraph *graph, int index) : BaseRef(graph, index) {} - Node &deref() const { this->check(); return this->graph_->nodes[this->index_]; } - - public: - Ref(BaseRef ref) : Ref(ref.graph_, ref.index_) {} - - void set_function(Fn const &function) const - { - deref().fn_index = this->graph_->functions(function); - } - - Attr &attr() const { return deref().attr; } - - void append_arg(ConstRef arg) const - { - log_assert(arg.graph_ == this->graph_); - append_arg(arg.index()); - } - - void append_arg(int arg) const - { - log_assert(arg >= 0 && arg < this->graph_->size()); - Node &node = deref(); - if (node.arg_offset + node.arg_count != GetSize(this->graph_->args)) - move_args(node); - this->graph_->args.push_back(arg); - node.arg_count++; - } - - operator ConstRef() const - { - return ConstRef(this->graph_, this->index_); - } - - SparseAttr &sparse_attr() const - { - return this->graph_->sparse_attrs[this->index_]; - } - - void clear_sparse_attr() const - { - this->graph_->sparse_attrs.erase(this->index_); - } - - void assign_key(Key const &key) const - { - this->graph_->keys_.emplace(key, this->index_); - } - - private: - void move_args(Node &node) const - { - auto &args = this->graph_->args; - int old_offset = node.arg_offset; - node.arg_offset = GetSize(args); - for (int i = 0; i != node.arg_count; ++i) - args.push_back(args[old_offset + i]); - } - - }; - - bool has_key(Key const &key) const - { - return keys_.count(key); - } - - dict const &keys() const - { - return keys_; - } - - ConstRef operator()(Key const &key) const - { - auto it = keys_.find(key); - log_assert(it != keys_.end()); - return (*this)[it->second]; - } - - Ref operator()(Key const &key) - { - auto it = keys_.find(key); - log_assert(it != keys_.end()); - return (*this)[it->second]; - } - - int size() const { return GetSize(nodes); } - - ConstRef operator[](int index) const { return ConstRef(this, index); } - Ref operator[](int index) { return Ref(this, index); } - - Ref add(Fn const &function, Attr &&attr) - { - int index = GetSize(nodes); - int fn_index = functions(function); - nodes.emplace_back(fn_index, std::move(attr), GetSize(args)); - return Ref(this, index); - } - - Ref add(Fn const &function, Attr const &attr) - { - int index = GetSize(nodes); - int fn_index = functions(function); - nodes.emplace_back(fn_index, attr, GetSize(args)); - return Ref(this, index); - } - - template - Ref add(Fn const &function, Attr const &attr, T &&args) - { - Ref added = add(function, attr); - for (auto arg : args) - added.append_arg(arg); - return added; - } - - template - Ref add(Fn const &function, Attr &&attr, T &&args) - { - Ref added = add(function, std::move(attr)); - for (auto arg : args) - added.append_arg(arg); - return added; - } - - Ref add(Fn const &function, Attr const &attr, std::initializer_list args) - { - Ref added = add(function, attr); - for (auto arg : args) - added.append_arg(arg); - return added; - } - - Ref add(Fn const &function, Attr &&attr, std::initializer_list args) - { - Ref added = add(function, std::move(attr)); - for (auto arg : args) - added.append_arg(arg); - return added; - } - - template - Ref add(Fn const &function, Attr const &attr, T begin, T end) - { - Ref added = add(function, attr); - for (; begin != end; ++begin) - added.append_arg(*begin); - return added; - } - - void compact_args() - { - std::vector new_args; - for (auto &node : nodes) - { - int new_offset = GetSize(new_args); - for (int i = 0; i < node.arg_count; i++) - new_args.push_back(args[node.arg_offset + i]); - node.arg_offset = new_offset; - } - std::swap(args, new_args); - } - - void permute(std::vector const &perm) - { - log_assert(perm.size() <= nodes.size()); - std::vector inv_perm; - inv_perm.resize(nodes.size(), -1); - for (int i = 0; i < GetSize(perm); ++i) - { - int j = perm[i]; - log_assert(j >= 0 && j < GetSize(nodes)); - log_assert(inv_perm[j] == -1); - inv_perm[j] = i; - } - permute(perm, inv_perm); - } - - void permute(std::vector const &perm, std::vector const &inv_perm) - { - log_assert(inv_perm.size() == nodes.size()); - std::vector new_nodes; - new_nodes.reserve(perm.size()); - dict new_sparse_attrs; - for (int i : perm) - { - int j = GetSize(new_nodes); - new_nodes.emplace_back(std::move(nodes[i])); - auto found = sparse_attrs.find(i); - if (found != sparse_attrs.end()) - new_sparse_attrs.emplace(j, std::move(found->second)); - } - - std::swap(nodes, new_nodes); - std::swap(sparse_attrs, new_sparse_attrs); - - compact_args(); - for (int &arg : args) - { - log_assert(arg < GetSize(inv_perm)); - log_assert(inv_perm[arg] >= 0); - arg = inv_perm[arg]; - } - - for (auto &key : keys_) - { - log_assert(key.second < GetSize(inv_perm)); - log_assert(inv_perm[key.second] >= 0); - key.second = inv_perm[key.second]; - } - } - - struct SccAdaptor - { - private: - ComputeGraph const &graph_; - std::vector indices_; - public: - SccAdaptor(ComputeGraph const &graph) : graph_(graph) - { - indices_.resize(graph.size(), -1); - } - - - typedef int node_type; - - struct node_enumerator { - private: - friend struct SccAdaptor; - int current, end; - node_enumerator(int current, int end) : current(current), end(end) {} - - public: - - bool finished() const { return current == end; } - node_type next() { - log_assert(!finished()); - node_type result = current; - ++current; - return result; - } - }; - - node_enumerator enumerate_nodes() { - return node_enumerator(0, GetSize(indices_)); - } - - - struct successor_enumerator { - private: - friend struct SccAdaptor; - std::vector::const_iterator current, end; - successor_enumerator(std::vector::const_iterator current, std::vector::const_iterator end) : - current(current), end(end) {} - - public: - bool finished() const { return current == end; } - node_type next() { - log_assert(!finished()); - node_type result = *current; - ++current; - return result; - } - }; - - successor_enumerator enumerate_successors(int index) const { - auto const &ref = graph_[index]; - return successor_enumerator(ref.arg_indices_cbegin(), ref.arg_indices_cend()); - } - - int &dfs_index(node_type const &node) { return indices_[node]; } - - std::vector const &dfs_indices() { return indices_; } - }; - -}; - - +namespace Functional { + // each function is documented with a short pseudocode declaration or definition + // standard C/Verilog operators are used to describe the result + // + // the types used in this are: + // - bit[N]: a bitvector of N bits + // bit[N] can be indicated as signed or unsigned. this is not tracked by the functional backend + // but is meant to indicate how the value is interpreted + // if a bit[N] is marked as neither signed nor unsigned, this means the result should be valid with *either* interpretation + // - memory[N, M]: a memory with N address and M data bits + // - int: C++ int + // - Const[N]: yosys RTLIL::Const (with size() == N) + // - IdString: yosys IdString + // - any: used in documentation to indicate that the type is unconstrained + // + // nodes in the functional backend are either of type bit[N] or memory[N,M] (for some N, M: int) + // additionally, they can carry a constant of type int, Const[N] or IdString + // each node has a 'sort' field that stores the type of the node + // slice, zero_extend, sign_extend use the type field to store out_width + enum class Fn { + // invalid() = known-invalid/shouldn't happen value + // TODO: maybe remove this and use e.g. std::optional instead? + invalid, + // buf(a: any): any = a + // no-op operation + // when constructing the compute graph we generate invalid buf() nodes as a placeholder + // and later insert the argument + buf, + // slice(a: bit[in_width], offset: int, out_width: int): bit[out_width] = a[offset +: out_width] + // required: offset + out_width <= in_width + slice, + // zero_extend(a: unsigned bit[in_width], out_width: int): unsigned bit[out_width] = a (zero extended) + // required: out_width > in_width + zero_extend, + // sign_extend(a: signed bit[in_width], out_width: int): signed bit[out_width] = a (sign extended) + // required: out_width > in_width + sign_extend, + // concat(a: bit[N], b: bit[M]): bit[N+M] = {b, a} (verilog syntax) + // concatenates two bitvectors, with a in the least significant position and b in the more significant position + concat, + // add(a: bit[N], b: bit[N]): bit[N] = a + b + add, + // sub(a: bit[N], b: bit[N]): bit[N] = a - b + sub, + // mul(a: bit[N], b: bit[N]): bit[N] = a * b + mul, + // unsigned_div(a: unsigned bit[N], b: unsigned bit[N]): bit[N] = a / b + unsigned_div, + // unsigned_mod(a: signed bit[N], b: signed bit[N]): bit[N] = a % b + unsigned_mod, + // bitwise_and(a: bit[N], b: bit[N]): bit[N] = a & b + bitwise_and, + // bitwise_or(a: bit[N], b: bit[N]): bit[N] = a | b + bitwise_or, + // bitwise_xor(a: bit[N], b: bit[N]): bit[N] = a ^ b + bitwise_xor, + // bitwise_not(a: bit[N]): bit[N] = ~a + bitwise_not, + // reduce_and(a: bit[N]): bit[1] = &a + reduce_and, + // reduce_or(a: bit[N]): bit[1] = |a + reduce_or, + // reduce_xor(a: bit[N]): bit[1] = ^a + reduce_xor, + // unary_minus(a: bit[N]): bit[N] = -a + unary_minus, + // equal(a: bit[N], b: bit[N]): bit[1] = (a == b) + equal, + // not_equal(a: bit[N], b: bit[N]): bit[1] = (a != b) + not_equal, + // signed_greater_than(a: signed bit[N], b: signed bit[N]): bit[1] = (a > b) + signed_greater_than, + // signed_greater_equal(a: signed bit[N], b: signed bit[N]): bit[1] = (a >= b) + signed_greater_equal, + // unsigned_greater_than(a: unsigned bit[N], b: unsigned bit[N]): bit[1] = (a > b) + unsigned_greater_than, + // unsigned_greater_equal(a: unsigned bit[N], b: unsigned bit[N]): bit[1] = (a >= b) + unsigned_greater_equal, + // logical_shift_left(a: bit[N], b: unsigned bit[M]): bit[N] = a << b + // required: M == clog2(N) + logical_shift_left, + // logical_shift_right(a: unsigned bit[N], b: unsigned bit[M]): unsigned bit[N] = a >> b + // required: M == clog2(N) + logical_shift_right, + // arithmetic_shift_right(a: signed bit[N], b: unsigned bit[M]): signed bit[N] = a >> b + // required: M == clog2(N) + arithmetic_shift_right, + // mux(a: bit[N], b: bit[N], s: bit[1]): bit[N] = s ? b : a + mux, + // constant(a: Const[N]): bit[N] = a + constant, + // input(a: IdString): any + // returns the current value of the input with the specified name + input, + // state(a: IdString): any + // returns the current value of the state variable with the specified name + state, + // memory_read(memory: memory[addr_width, data_width], addr: bit[addr_width]): bit[data_width] = memory[addr] + memory_read, + // memory_write(memory: memory[addr_width, data_width], addr: bit[addr_width], data: bit[data_width]): memory[addr_width, data_width] + // returns a copy of `memory` but with the value at `addr` changed to `data` + memory_write + }; + // returns the name of a Fn value, as a string literal + const char *fn_to_string(Fn); + // Sort represents the sort or type of a node + // currently the only two types are signal/bit and memory + class Sort { + std::variant> _v; + public: + explicit Sort(int width) : _v(width) { } + Sort(int addr_width, int data_width) : _v(std::make_pair(addr_width, data_width)) { } + bool is_signal() const { return _v.index() == 0; } + bool is_memory() const { return _v.index() == 1; } + // returns the width of a bitvector type, errors out for other types + int width() const { return std::get<0>(_v); } + // returns the address width of a bitvector type, errors out for other types + int addr_width() const { return std::get<1>(_v).first; } + // returns the data width of a bitvector type, errors out for other types + int data_width() const { return std::get<1>(_v).second; } + bool operator==(Sort const& other) const { return _v == other._v; } + unsigned int hash() const { return mkhash(_v); } + }; + class Factory; + class Node; + class IR { + friend class Factory; + friend class Node; + // one NodeData is stored per Node, containing the function and non-node arguments + // note that NodeData is deduplicated by ComputeGraph + class NodeData { + Fn _fn; + std::variant< + std::monostate, + RTLIL::Const, + IdString, + int + > _extra; + public: + NodeData() : _fn(Fn::invalid) {} + NodeData(Fn fn) : _fn(fn) {} + template NodeData(Fn fn, T &&extra) : _fn(fn), _extra(std::forward(extra)) {} + Fn fn() const { return _fn; } + const RTLIL::Const &as_const() const { return std::get(_extra); } + IdString as_idstring() const { return std::get(_extra); } + int as_int() const { return std::get(_extra); } + int hash() const { + return mkhash((unsigned int) _fn, mkhash(_extra)); + } + bool operator==(NodeData const &other) const { + return _fn == other._fn && _extra == other._extra; + } + }; + // Attr contains all the information about a note that should not be deduplicated + struct Attr { + Sort sort; + }; + // our specialised version of ComputeGraph + // the sparse_attr IdString stores a naming suggestion, retrieved with name() + // the key is currently used to identify the nodes that represent output and next state values + // the bool is true for next state values + using Graph = ComputeGraph>; + Graph _graph; + dict _input_sorts; + dict _output_sorts; + dict _state_sorts; + dict _initial_state_signal; + dict _initial_state_memory; + public: + static IR from_module(Module *module); + Factory factory(); + int size() const { return _graph.size(); } + Node operator[](int i); + void topological_sort(); + void forward_buf(); + dict inputs() const { return _input_sorts; } + dict outputs() const { return _output_sorts; } + dict state() const { return _state_sorts; } + RTLIL::Const const &get_initial_state_signal(IdString name) { return _initial_state_signal.at(name); } + MemContents const &get_initial_state_memory(IdString name) { return _initial_state_memory.at(name); } + Node get_output_node(IdString name); + Node get_state_next_node(IdString name); + class iterator { + friend class IR; + IR *_ir; + int _index; + iterator(IR *ir, int index) : _ir(ir), _index(index) {} + public: + using iterator_category = std::input_iterator_tag; + using value_type = Node; + using pointer = arrow_proxy; + using reference = Node; + using difference_type = ptrdiff_t; + Node operator*(); + iterator &operator++() { _index++; return *this; } + bool operator!=(iterator const &other) const { return _ir != other._ir || _index != other._index; } + bool operator==(iterator const &other) const { return !(*this != other); } + pointer operator->(); + // TODO: implement operator-> using the arrow_proxy class currently in mem.h + }; + iterator begin() { return iterator(this, 0); } + iterator end() { return iterator(this, _graph.size()); } + }; + // Node is an immutable reference to a FunctionalIR node + class Node { + friend class Factory; + friend class IR; + IR::Graph::ConstRef _ref; + explicit Node(IR::Graph::ConstRef ref) : _ref(ref) { } + explicit operator IR::Graph::ConstRef() { return _ref; } + public: + // the node's index. may change if nodes are added or removed + int id() const { return _ref.index(); } + // a name suggestion for the node, which need not be unique + IdString name() const { + if(_ref.has_sparse_attr()) + return _ref.sparse_attr(); + else + return std::string("\\n") + std::to_string(id()); + } + Fn fn() const { return _ref.function().fn(); } + Sort sort() const { return _ref.attr().sort; } + // returns the width of a bitvector node, errors out for other nodes + int width() const { return sort().width(); } + size_t arg_count() const { return _ref.size(); } + Node arg(int n) const { return Node(_ref.arg(n)); } + // visit calls the appropriate visitor method depending on the type of the node + template auto visit(Visitor v) const + { + // currently templated but could be switched to AbstractVisitor & + switch(_ref.function().fn()) { + case Fn::invalid: log_error("invalid node in visit"); break; + case Fn::buf: return v.buf(*this, arg(0)); break; + case Fn::slice: return v.slice(*this, arg(0), _ref.function().as_int(), sort().width()); break; + case Fn::zero_extend: return v.zero_extend(*this, arg(0), width()); break; + case Fn::sign_extend: return v.sign_extend(*this, arg(0), width()); break; + case Fn::concat: return v.concat(*this, arg(0), arg(1)); break; + case Fn::add: return v.add(*this, arg(0), arg(1)); break; + case Fn::sub: return v.sub(*this, arg(0), arg(1)); break; + case Fn::mul: return v.mul(*this, arg(0), arg(1)); break; + case Fn::unsigned_div: return v.unsigned_div(*this, arg(0), arg(1)); break; + case Fn::unsigned_mod: return v.unsigned_mod(*this, arg(0), arg(1)); break; + case Fn::bitwise_and: return v.bitwise_and(*this, arg(0), arg(1)); break; + case Fn::bitwise_or: return v.bitwise_or(*this, arg(0), arg(1)); break; + case Fn::bitwise_xor: return v.bitwise_xor(*this, arg(0), arg(1)); break; + case Fn::bitwise_not: return v.bitwise_not(*this, arg(0)); break; + case Fn::unary_minus: return v.unary_minus(*this, arg(0)); break; + case Fn::reduce_and: return v.reduce_and(*this, arg(0)); break; + case Fn::reduce_or: return v.reduce_or(*this, arg(0)); break; + case Fn::reduce_xor: return v.reduce_xor(*this, arg(0)); break; + case Fn::equal: return v.equal(*this, arg(0), arg(1)); break; + case Fn::not_equal: return v.not_equal(*this, arg(0), arg(1)); break; + case Fn::signed_greater_than: return v.signed_greater_than(*this, arg(0), arg(1)); break; + case Fn::signed_greater_equal: return v.signed_greater_equal(*this, arg(0), arg(1)); break; + case Fn::unsigned_greater_than: return v.unsigned_greater_than(*this, arg(0), arg(1)); break; + case Fn::unsigned_greater_equal: return v.unsigned_greater_equal(*this, arg(0), arg(1)); break; + case Fn::logical_shift_left: return v.logical_shift_left(*this, arg(0), arg(1)); break; + case Fn::logical_shift_right: return v.logical_shift_right(*this, arg(0), arg(1)); break; + case Fn::arithmetic_shift_right: return v.arithmetic_shift_right(*this, arg(0), arg(1)); break; + case Fn::mux: return v.mux(*this, arg(0), arg(1), arg(2)); break; + case Fn::constant: return v.constant(*this, _ref.function().as_const()); break; + case Fn::input: return v.input(*this, _ref.function().as_idstring()); break; + case Fn::state: return v.state(*this, _ref.function().as_idstring()); break; + case Fn::memory_read: return v.memory_read(*this, arg(0), arg(1)); break; + case Fn::memory_write: return v.memory_write(*this, arg(0), arg(1), arg(2)); break; + } + } + std::string to_string(); + std::string to_string(std::function); + }; + inline Node IR::operator[](int i) { return Node(_graph[i]); } + inline Node IR::get_output_node(IdString name) { return Node(_graph({name, false})); } + inline Node IR::get_state_next_node(IdString name) { return Node(_graph({name, true})); } + inline Node IR::iterator::operator*() { return Node(_ir->_graph[_index]); } + inline arrow_proxy IR::iterator::operator->() { return arrow_proxy(**this); } + // AbstractVisitor provides an abstract base class for visitors + template struct AbstractVisitor { + virtual T buf(Node self, Node n) = 0; + virtual T slice(Node self, Node a, int offset, int out_width) = 0; + virtual T zero_extend(Node self, Node a, int out_width) = 0; + virtual T sign_extend(Node self, Node a, int out_width) = 0; + virtual T concat(Node self, Node a, Node b) = 0; + virtual T add(Node self, Node a, Node b) = 0; + virtual T sub(Node self, Node a, Node b) = 0; + virtual T mul(Node self, Node a, Node b) = 0; + virtual T unsigned_div(Node self, Node a, Node b) = 0; + virtual T unsigned_mod(Node self, Node a, Node b) = 0; + virtual T bitwise_and(Node self, Node a, Node b) = 0; + virtual T bitwise_or(Node self, Node a, Node b) = 0; + virtual T bitwise_xor(Node self, Node a, Node b) = 0; + virtual T bitwise_not(Node self, Node a) = 0; + virtual T unary_minus(Node self, Node a) = 0; + virtual T reduce_and(Node self, Node a) = 0; + virtual T reduce_or(Node self, Node a) = 0; + virtual T reduce_xor(Node self, Node a) = 0; + virtual T equal(Node self, Node a, Node b) = 0; + virtual T not_equal(Node self, Node a, Node b) = 0; + virtual T signed_greater_than(Node self, Node a, Node b) = 0; + virtual T signed_greater_equal(Node self, Node a, Node b) = 0; + virtual T unsigned_greater_than(Node self, Node a, Node b) = 0; + virtual T unsigned_greater_equal(Node self, Node a, Node b) = 0; + virtual T logical_shift_left(Node self, Node a, Node b) = 0; + virtual T logical_shift_right(Node self, Node a, Node b) = 0; + virtual T arithmetic_shift_right(Node self, Node a, Node b) = 0; + virtual T mux(Node self, Node a, Node b, Node s) = 0; + virtual T constant(Node self, RTLIL::Const const & value) = 0; + virtual T input(Node self, IdString name) = 0; + virtual T state(Node self, IdString name) = 0; + virtual T memory_read(Node self, Node mem, Node addr) = 0; + virtual T memory_write(Node self, Node mem, Node addr, Node data) = 0; + }; + // DefaultVisitor provides defaults for all visitor methods which just calls default_handler + template struct DefaultVisitor : public AbstractVisitor { + virtual T default_handler(Node self) = 0; + T buf(Node self, Node) override { return default_handler(self); } + T slice(Node self, Node, int, int) override { return default_handler(self); } + T zero_extend(Node self, Node, int) override { return default_handler(self); } + T sign_extend(Node self, Node, int) override { return default_handler(self); } + T concat(Node self, Node, Node) override { return default_handler(self); } + T add(Node self, Node, Node) override { return default_handler(self); } + T sub(Node self, Node, Node) override { return default_handler(self); } + T mul(Node self, Node, Node) override { return default_handler(self); } + T unsigned_div(Node self, Node, Node) override { return default_handler(self); } + T unsigned_mod(Node self, Node, Node) override { return default_handler(self); } + T bitwise_and(Node self, Node, Node) override { return default_handler(self); } + T bitwise_or(Node self, Node, Node) override { return default_handler(self); } + T bitwise_xor(Node self, Node, Node) override { return default_handler(self); } + T bitwise_not(Node self, Node) override { return default_handler(self); } + T unary_minus(Node self, Node) override { return default_handler(self); } + T reduce_and(Node self, Node) override { return default_handler(self); } + T reduce_or(Node self, Node) override { return default_handler(self); } + T reduce_xor(Node self, Node) override { return default_handler(self); } + T equal(Node self, Node, Node) override { return default_handler(self); } + T not_equal(Node self, Node, Node) override { return default_handler(self); } + T signed_greater_than(Node self, Node, Node) override { return default_handler(self); } + T signed_greater_equal(Node self, Node, Node) override { return default_handler(self); } + T unsigned_greater_than(Node self, Node, Node) override { return default_handler(self); } + T unsigned_greater_equal(Node self, Node, Node) override { return default_handler(self); } + T logical_shift_left(Node self, Node, Node) override { return default_handler(self); } + T logical_shift_right(Node self, Node, Node) override { return default_handler(self); } + T arithmetic_shift_right(Node self, Node, Node) override { return default_handler(self); } + T mux(Node self, Node, Node, Node) override { return default_handler(self); } + T constant(Node self, RTLIL::Const const &) override { return default_handler(self); } + T input(Node self, IdString) override { return default_handler(self); } + T state(Node self, IdString) override { return default_handler(self); } + T memory_read(Node self, Node, Node) override { return default_handler(self); } + T memory_write(Node self, Node, Node, Node) override { return default_handler(self); } + }; + // a factory is used to modify a FunctionalIR. it creates new nodes and allows for some modification of existing nodes. + class Factory { + friend class IR; + IR &_ir; + explicit Factory(IR &ir) : _ir(ir) {} + Node add(IR::NodeData &&fn, Sort &&sort, std::initializer_list args) { + log_assert(!sort.is_signal() || sort.width() > 0); + log_assert(!sort.is_memory() || sort.addr_width() > 0 && sort.data_width() > 0); + IR::Graph::Ref ref = _ir._graph.add(std::move(fn), {std::move(sort)}); + for (auto arg : args) + ref.append_arg(IR::Graph::ConstRef(arg)); + return Node(ref); + } + IR::Graph::Ref mutate(Node n) { + return _ir._graph[n._ref.index()]; + } + void check_basic_binary(Node const &a, Node const &b) { log_assert(a.sort().is_signal() && a.sort() == b.sort()); } + void check_shift(Node const &a, Node const &b) { log_assert(a.sort().is_signal() && b.sort().is_signal() && b.width() == ceil_log2(a.width())); } + void check_unary(Node const &a) { log_assert(a.sort().is_signal()); } + public: + Node slice(Node a, int offset, int out_width) { + log_assert(a.sort().is_signal() && offset + out_width <= a.sort().width()); + if(offset == 0 && out_width == a.width()) + return a; + return add(IR::NodeData(Fn::slice, offset), Sort(out_width), {a}); + } + // extend will either extend or truncate the provided value to reach the desired width + Node extend(Node a, int out_width, bool is_signed) { + int in_width = a.sort().width(); + log_assert(a.sort().is_signal()); + if(in_width == out_width) + return a; + if(in_width > out_width) + return slice(a, 0, out_width); + if(is_signed) + return add(Fn::sign_extend, Sort(out_width), {a}); + else + return add(Fn::zero_extend, Sort(out_width), {a}); + } + Node concat(Node a, Node b) { + log_assert(a.sort().is_signal() && b.sort().is_signal()); + return add(Fn::concat, Sort(a.sort().width() + b.sort().width()), {a, b}); + } + Node add(Node a, Node b) { check_basic_binary(a, b); return add(Fn::add, a.sort(), {a, b}); } + Node sub(Node a, Node b) { check_basic_binary(a, b); return add(Fn::sub, a.sort(), {a, b}); } + Node mul(Node a, Node b) { check_basic_binary(a, b); return add(Fn::mul, a.sort(), {a, b}); } + Node unsigned_div(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_div, a.sort(), {a, b}); } + Node unsigned_mod(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_mod, a.sort(), {a, b}); } + Node bitwise_and(Node a, Node b) { check_basic_binary(a, b); return add(Fn::bitwise_and, a.sort(), {a, b}); } + Node bitwise_or(Node a, Node b) { check_basic_binary(a, b); return add(Fn::bitwise_or, a.sort(), {a, b}); } + Node bitwise_xor(Node a, Node b) { check_basic_binary(a, b); return add(Fn::bitwise_xor, a.sort(), {a, b}); } + Node bitwise_not(Node a) { check_unary(a); return add(Fn::bitwise_not, a.sort(), {a}); } + Node unary_minus(Node a) { check_unary(a); return add(Fn::unary_minus, a.sort(), {a}); } + Node reduce_and(Node a) { + check_unary(a); + if(a.width() == 1) + return a; + return add(Fn::reduce_and, Sort(1), {a}); + } + Node reduce_or(Node a) { + check_unary(a); + if(a.width() == 1) + return a; + return add(Fn::reduce_or, Sort(1), {a}); + } + Node reduce_xor(Node a) { + check_unary(a); + if(a.width() == 1) + return a; + return add(Fn::reduce_xor, Sort(1), {a}); + } + Node equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::equal, Sort(1), {a, b}); } + Node not_equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::not_equal, Sort(1), {a, b}); } + Node signed_greater_than(Node a, Node b) { check_basic_binary(a, b); return add(Fn::signed_greater_than, Sort(1), {a, b}); } + Node signed_greater_equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::signed_greater_equal, Sort(1), {a, b}); } + Node unsigned_greater_than(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_greater_than, Sort(1), {a, b}); } + Node unsigned_greater_equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_greater_equal, Sort(1), {a, b}); } + Node logical_shift_left(Node a, Node b) { check_shift(a, b); return add(Fn::logical_shift_left, a.sort(), {a, b}); } + Node logical_shift_right(Node a, Node b) { check_shift(a, b); return add(Fn::logical_shift_right, a.sort(), {a, b}); } + Node arithmetic_shift_right(Node a, Node b) { check_shift(a, b); return add(Fn::arithmetic_shift_right, a.sort(), {a, b}); } + Node mux(Node a, Node b, Node s) { + log_assert(a.sort().is_signal() && a.sort() == b.sort() && s.sort() == Sort(1)); + return add(Fn::mux, a.sort(), {a, b, s}); + } + Node memory_read(Node mem, Node addr) { + log_assert(mem.sort().is_memory() && addr.sort().is_signal() && mem.sort().addr_width() == addr.sort().width()); + return add(Fn::memory_read, Sort(mem.sort().data_width()), {mem, addr}); + } + Node memory_write(Node mem, Node addr, Node data) { + log_assert(mem.sort().is_memory() && addr.sort().is_signal() && data.sort().is_signal() && + mem.sort().addr_width() == addr.sort().width() && mem.sort().data_width() == data.sort().width()); + return add(Fn::memory_write, mem.sort(), {mem, addr, data}); + } + Node constant(RTLIL::Const value) { + return add(IR::NodeData(Fn::constant, std::move(value)), Sort(value.size()), {}); + } + Node create_pending(int width) { + return add(Fn::buf, Sort(width), {}); + } + void update_pending(Node node, Node value) { + log_assert(node._ref.function() == Fn::buf && node._ref.size() == 0); + log_assert(node.sort() == value.sort()); + mutate(node).append_arg(value._ref); + } + void add_input(IdString name, int width) { + auto [it, inserted] = _ir._input_sorts.emplace(name, Sort(width)); + if (!inserted) log_error("input `%s` was re-defined", name.c_str()); + } + void add_output(IdString name, int width) { + auto [it, inserted] = _ir._output_sorts.emplace(name, Sort(width)); + if (!inserted) log_error("output `%s` was re-defined", name.c_str()); + } + void add_state(IdString name, Sort sort) { + auto [it, inserted] = _ir._state_sorts.emplace(name, sort); + if (!inserted) log_error("state `%s` was re-defined", name.c_str()); + } + Node input(IdString name) { + return add(IR::NodeData(Fn::input, name), Sort(_ir._input_sorts.at(name)), {}); + } + Node current_state(IdString name) { + return add(IR::NodeData(Fn::state, name), Sort(_ir._state_sorts.at(name)), {}); + } + void set_output(IdString output, Node value) { + log_assert(_ir._output_sorts.at(output) == value.sort()); + mutate(value).assign_key({output, false}); + } + void set_initial_state(IdString state, RTLIL::Const value) { + Sort &sort = _ir._state_sorts.at(state); + value.extu(sort.width()); + _ir._initial_state_signal.emplace(state, std::move(value)); + } + void set_initial_state(IdString state, MemContents value) { + log_assert(Sort(value.addr_width(), value.data_width()) == _ir._state_sorts.at(state)); + _ir._initial_state_memory.emplace(state, std::move(value)); + } + void set_next_state(IdString state, Node value) { + log_assert(_ir._state_sorts.at(state) == value.sort()); + mutate(value).assign_key({state, true}); + } + void suggest_name(Node node, IdString name) { + mutate(node).sparse_attr() = name; + } + }; + inline Factory IR::factory() { return Factory(*this); } + template class Scope { + protected: + char substitution_character = '_'; + virtual bool is_character_legal(char) = 0; + private: + pool _used_names; + dict _by_id; + public: + void reserve(std::string name) { + _used_names.insert(std::move(name)); + } + std::string unique_name(IdString suggestion) { + std::string str = RTLIL::unescape_id(suggestion); + for(size_t i = 0; i < str.size(); i++) + if(!is_character_legal(str[i])) + str[i] = substitution_character; + if(_used_names.count(str) == 0) { + _used_names.insert(str); + return str; + } + for (int idx = 0 ; ; idx++){ + std::string suffixed = str + "_" + std::to_string(idx); + if(_used_names.count(suffixed) == 0) { + _used_names.insert(suffixed); + return suffixed; + } + } + } + std::string operator()(Id id, IdString suggestion) { + auto it = _by_id.find(id); + if(it != _by_id.end()) + return it->second; + std::string str = unique_name(suggestion); + _by_id.insert({id, str}); + return str; + } + }; + class Writer { + std::ostream *os; + void print_impl(const char *fmt, vector>& fns); + public: + Writer(std::ostream &os) : os(&os) {} + template Writer& operator <<(T&& arg) { *os << std::forward(arg); return *this; } + template + void print(const char *fmt, Args&&... args) + { + vector> fns { [&]() { *this << args; }... }; + print_impl(fmt, fns); + } + template + void print_with(Fn fn, const char *fmt, Args&&... args) + { + vector> fns { [&]() { + if constexpr (std::is_invocable_v) + *this << fn(args); + else + *this << args; }... + }; + print_impl(fmt, fns); + } + }; + +} YOSYS_NAMESPACE_END - #endif diff --git a/kernel/functionalir.h b/kernel/functionalir.h deleted file mode 100644 index fdbdcbde306..00000000000 --- a/kernel/functionalir.h +++ /dev/null @@ -1,575 +0,0 @@ -/* - * yosys -- Yosys Open SYnthesis Suite - * - * Copyright (C) 2024 Emily Schmidt - * - * 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 FUNCTIONALIR_H -#define FUNCTIONALIR_H - -#include "kernel/yosys.h" -#include "kernel/functional.h" -#include "kernel/drivertools.h" -#include "kernel/mem.h" -#include "kernel/topo_scc.h" - -USING_YOSYS_NAMESPACE -YOSYS_NAMESPACE_BEGIN - -class FunctionalIR { -public: - // each function is documented with a short pseudocode declaration or definition - // standard C/Verilog operators are used to describe the result - // - // the types used in this are: - // - bit[N]: a bitvector of N bits - // bit[N] can be indicated as signed or unsigned. this is not tracked by the functional backend - // but is meant to indicate how the value is interpreted - // if a bit[N] is marked as neither signed nor unsigned, this means the result should be valid with *either* interpretation - // - memory[N, M]: a memory with N address and M data bits - // - int: C++ int - // - Const[N]: yosys RTLIL::Const (with size() == N) - // - IdString: yosys IdString - // - any: used in documentation to indicate that the type is unconstrained - // - // nodes in the functional backend are either of type bit[N] or memory[N,M] (for some N, M: int) - // additionally, they can carry a constant of type int, Const[N] or IdString - // each node has a 'sort' field that stores the type of the node - // slice, zero_extend, sign_extend use the type field to store out_width - enum class Fn { - // invalid() = known-invalid/shouldn't happen value - // TODO: maybe remove this and use e.g. std::optional instead? - invalid, - // buf(a: any): any = a - // no-op operation - // when constructing the compute graph we generate invalid buf() nodes as a placeholder - // and later insert the argument - buf, - // slice(a: bit[in_width], offset: int, out_width: int): bit[out_width] = a[offset +: out_width] - // required: offset + out_width <= in_width - slice, - // zero_extend(a: unsigned bit[in_width], out_width: int): unsigned bit[out_width] = a (zero extended) - // required: out_width > in_width - zero_extend, - // sign_extend(a: signed bit[in_width], out_width: int): signed bit[out_width] = a (sign extended) - // required: out_width > in_width - sign_extend, - // concat(a: bit[N], b: bit[M]): bit[N+M] = {b, a} (verilog syntax) - // concatenates two bitvectors, with a in the least significant position and b in the more significant position - concat, - // add(a: bit[N], b: bit[N]): bit[N] = a + b - add, - // sub(a: bit[N], b: bit[N]): bit[N] = a - b - sub, - // mul(a: bit[N], b: bit[N]): bit[N] = a * b - mul, - // unsigned_div(a: unsigned bit[N], b: unsigned bit[N]): bit[N] = a / b - unsigned_div, - // unsigned_mod(a: signed bit[N], b: signed bit[N]): bit[N] = a % b - unsigned_mod, - // bitwise_and(a: bit[N], b: bit[N]): bit[N] = a & b - bitwise_and, - // bitwise_or(a: bit[N], b: bit[N]): bit[N] = a | b - bitwise_or, - // bitwise_xor(a: bit[N], b: bit[N]): bit[N] = a ^ b - bitwise_xor, - // bitwise_not(a: bit[N]): bit[N] = ~a - bitwise_not, - // reduce_and(a: bit[N]): bit[1] = &a - reduce_and, - // reduce_or(a: bit[N]): bit[1] = |a - reduce_or, - // reduce_xor(a: bit[N]): bit[1] = ^a - reduce_xor, - // unary_minus(a: bit[N]): bit[N] = -a - unary_minus, - // equal(a: bit[N], b: bit[N]): bit[1] = (a == b) - equal, - // not_equal(a: bit[N], b: bit[N]): bit[1] = (a != b) - not_equal, - // signed_greater_than(a: signed bit[N], b: signed bit[N]): bit[1] = (a > b) - signed_greater_than, - // signed_greater_equal(a: signed bit[N], b: signed bit[N]): bit[1] = (a >= b) - signed_greater_equal, - // unsigned_greater_than(a: unsigned bit[N], b: unsigned bit[N]): bit[1] = (a > b) - unsigned_greater_than, - // unsigned_greater_equal(a: unsigned bit[N], b: unsigned bit[N]): bit[1] = (a >= b) - unsigned_greater_equal, - // logical_shift_left(a: bit[N], b: unsigned bit[M]): bit[N] = a << b - // required: M == clog2(N) - logical_shift_left, - // logical_shift_right(a: unsigned bit[N], b: unsigned bit[M]): unsigned bit[N] = a >> b - // required: M == clog2(N) - logical_shift_right, - // arithmetic_shift_right(a: signed bit[N], b: unsigned bit[M]): signed bit[N] = a >> b - // required: M == clog2(N) - arithmetic_shift_right, - // mux(a: bit[N], b: bit[N], s: bit[1]): bit[N] = s ? b : a - mux, - // constant(a: Const[N]): bit[N] = a - constant, - // input(a: IdString): any - // returns the current value of the input with the specified name - input, - // state(a: IdString): any - // returns the current value of the state variable with the specified name - state, - // memory_read(memory: memory[addr_width, data_width], addr: bit[addr_width]): bit[data_width] = memory[addr] - memory_read, - // memory_write(memory: memory[addr_width, data_width], addr: bit[addr_width], data: bit[data_width]): memory[addr_width, data_width] - // returns a copy of `memory` but with the value at `addr` changed to `data` - memory_write - }; - // returns the name of a FunctionalIR::Fn value, as a string literal - static const char *fn_to_string(Fn); - // FunctionalIR::Sort represents the sort or type of a node - // currently the only two types are signal/bit and memory - class Sort { - std::variant> _v; - public: - explicit Sort(int width) : _v(width) { } - Sort(int addr_width, int data_width) : _v(std::make_pair(addr_width, data_width)) { } - bool is_signal() const { return _v.index() == 0; } - bool is_memory() const { return _v.index() == 1; } - // returns the width of a bitvector type, errors out for other types - int width() const { return std::get<0>(_v); } - // returns the address width of a bitvector type, errors out for other types - int addr_width() const { return std::get<1>(_v).first; } - // returns the data width of a bitvector type, errors out for other types - int data_width() const { return std::get<1>(_v).second; } - bool operator==(Sort const& other) const { return _v == other._v; } - unsigned int hash() const { return mkhash(_v); } - }; -private: - // one NodeData is stored per Node, containing the function and non-node arguments - // note that NodeData is deduplicated by ComputeGraph - class NodeData { - Fn _fn; - std::variant< - std::monostate, - RTLIL::Const, - IdString, - int - > _extra; - public: - NodeData() : _fn(Fn::invalid) {} - NodeData(Fn fn) : _fn(fn) {} - template NodeData(Fn fn, T &&extra) : _fn(fn), _extra(std::forward(extra)) {} - Fn fn() const { return _fn; } - const RTLIL::Const &as_const() const { return std::get(_extra); } - IdString as_idstring() const { return std::get(_extra); } - int as_int() const { return std::get(_extra); } - int hash() const { - return mkhash((unsigned int) _fn, mkhash(_extra)); - } - bool operator==(NodeData const &other) const { - return _fn == other._fn && _extra == other._extra; - } - }; - // Attr contains all the information about a note that should not be deduplicated - struct Attr { - Sort sort; - }; - // our specialised version of ComputeGraph - // the sparse_attr IdString stores a naming suggestion, retrieved with name() - // the key is currently used to identify the nodes that represent output and next state values - // the bool is true for next state values - using Graph = ComputeGraph>; - Graph _graph; - dict _input_sorts; - dict _output_sorts; - dict _state_sorts; - dict _initial_state_signal; - dict _initial_state_memory; -public: - class Factory; - // Node is an immutable reference to a FunctionalIR node - class Node { - friend class Factory; - friend class FunctionalIR; - Graph::ConstRef _ref; - explicit Node(Graph::ConstRef ref) : _ref(ref) { } - explicit operator Graph::ConstRef() { return _ref; } - public: - // the node's index. may change if nodes are added or removed - int id() const { return _ref.index(); } - // a name suggestion for the node, which need not be unique - IdString name() const { - if(_ref.has_sparse_attr()) - return _ref.sparse_attr(); - else - return std::string("\\n") + std::to_string(id()); - } - Fn fn() const { return _ref.function().fn(); } - Sort sort() const { return _ref.attr().sort; } - // returns the width of a bitvector node, errors out for other nodes - int width() const { return sort().width(); } - size_t arg_count() const { return _ref.size(); } - Node arg(int n) const { return Node(_ref.arg(n)); } - // visit calls the appropriate visitor method depending on the type of the node - template auto visit(Visitor v) const - { - // currently templated but could be switched to AbstractVisitor & - switch(_ref.function().fn()) { - case Fn::invalid: log_error("invalid node in visit"); break; - case Fn::buf: return v.buf(*this, arg(0)); break; - case Fn::slice: return v.slice(*this, arg(0), _ref.function().as_int(), sort().width()); break; - case Fn::zero_extend: return v.zero_extend(*this, arg(0), width()); break; - case Fn::sign_extend: return v.sign_extend(*this, arg(0), width()); break; - case Fn::concat: return v.concat(*this, arg(0), arg(1)); break; - case Fn::add: return v.add(*this, arg(0), arg(1)); break; - case Fn::sub: return v.sub(*this, arg(0), arg(1)); break; - case Fn::mul: return v.mul(*this, arg(0), arg(1)); break; - case Fn::unsigned_div: return v.unsigned_div(*this, arg(0), arg(1)); break; - case Fn::unsigned_mod: return v.unsigned_mod(*this, arg(0), arg(1)); break; - case Fn::bitwise_and: return v.bitwise_and(*this, arg(0), arg(1)); break; - case Fn::bitwise_or: return v.bitwise_or(*this, arg(0), arg(1)); break; - case Fn::bitwise_xor: return v.bitwise_xor(*this, arg(0), arg(1)); break; - case Fn::bitwise_not: return v.bitwise_not(*this, arg(0)); break; - case Fn::unary_minus: return v.unary_minus(*this, arg(0)); break; - case Fn::reduce_and: return v.reduce_and(*this, arg(0)); break; - case Fn::reduce_or: return v.reduce_or(*this, arg(0)); break; - case Fn::reduce_xor: return v.reduce_xor(*this, arg(0)); break; - case Fn::equal: return v.equal(*this, arg(0), arg(1)); break; - case Fn::not_equal: return v.not_equal(*this, arg(0), arg(1)); break; - case Fn::signed_greater_than: return v.signed_greater_than(*this, arg(0), arg(1)); break; - case Fn::signed_greater_equal: return v.signed_greater_equal(*this, arg(0), arg(1)); break; - case Fn::unsigned_greater_than: return v.unsigned_greater_than(*this, arg(0), arg(1)); break; - case Fn::unsigned_greater_equal: return v.unsigned_greater_equal(*this, arg(0), arg(1)); break; - case Fn::logical_shift_left: return v.logical_shift_left(*this, arg(0), arg(1)); break; - case Fn::logical_shift_right: return v.logical_shift_right(*this, arg(0), arg(1)); break; - case Fn::arithmetic_shift_right: return v.arithmetic_shift_right(*this, arg(0), arg(1)); break; - case Fn::mux: return v.mux(*this, arg(0), arg(1), arg(2)); break; - case Fn::constant: return v.constant(*this, _ref.function().as_const()); break; - case Fn::input: return v.input(*this, _ref.function().as_idstring()); break; - case Fn::state: return v.state(*this, _ref.function().as_idstring()); break; - case Fn::memory_read: return v.memory_read(*this, arg(0), arg(1)); break; - case Fn::memory_write: return v.memory_write(*this, arg(0), arg(1), arg(2)); break; - } - } - std::string to_string(); - std::string to_string(std::function); - }; - // AbstractVisitor provides an abstract base class for visitors - template struct AbstractVisitor { - virtual T buf(Node self, Node n) = 0; - virtual T slice(Node self, Node a, int offset, int out_width) = 0; - virtual T zero_extend(Node self, Node a, int out_width) = 0; - virtual T sign_extend(Node self, Node a, int out_width) = 0; - virtual T concat(Node self, Node a, Node b) = 0; - virtual T add(Node self, Node a, Node b) = 0; - virtual T sub(Node self, Node a, Node b) = 0; - virtual T mul(Node self, Node a, Node b) = 0; - virtual T unsigned_div(Node self, Node a, Node b) = 0; - virtual T unsigned_mod(Node self, Node a, Node b) = 0; - virtual T bitwise_and(Node self, Node a, Node b) = 0; - virtual T bitwise_or(Node self, Node a, Node b) = 0; - virtual T bitwise_xor(Node self, Node a, Node b) = 0; - virtual T bitwise_not(Node self, Node a) = 0; - virtual T unary_minus(Node self, Node a) = 0; - virtual T reduce_and(Node self, Node a) = 0; - virtual T reduce_or(Node self, Node a) = 0; - virtual T reduce_xor(Node self, Node a) = 0; - virtual T equal(Node self, Node a, Node b) = 0; - virtual T not_equal(Node self, Node a, Node b) = 0; - virtual T signed_greater_than(Node self, Node a, Node b) = 0; - virtual T signed_greater_equal(Node self, Node a, Node b) = 0; - virtual T unsigned_greater_than(Node self, Node a, Node b) = 0; - virtual T unsigned_greater_equal(Node self, Node a, Node b) = 0; - virtual T logical_shift_left(Node self, Node a, Node b) = 0; - virtual T logical_shift_right(Node self, Node a, Node b) = 0; - virtual T arithmetic_shift_right(Node self, Node a, Node b) = 0; - virtual T mux(Node self, Node a, Node b, Node s) = 0; - virtual T constant(Node self, RTLIL::Const const & value) = 0; - virtual T input(Node self, IdString name) = 0; - virtual T state(Node self, IdString name) = 0; - virtual T memory_read(Node self, Node mem, Node addr) = 0; - virtual T memory_write(Node self, Node mem, Node addr, Node data) = 0; - }; - // DefaultVisitor provides defaults for all visitor methods which just calls default_handler - template struct DefaultVisitor : public AbstractVisitor { - virtual T default_handler(Node self) = 0; - T buf(Node self, Node) override { return default_handler(self); } - T slice(Node self, Node, int, int) override { return default_handler(self); } - T zero_extend(Node self, Node, int) override { return default_handler(self); } - T sign_extend(Node self, Node, int) override { return default_handler(self); } - T concat(Node self, Node, Node) override { return default_handler(self); } - T add(Node self, Node, Node) override { return default_handler(self); } - T sub(Node self, Node, Node) override { return default_handler(self); } - T mul(Node self, Node, Node) override { return default_handler(self); } - T unsigned_div(Node self, Node, Node) override { return default_handler(self); } - T unsigned_mod(Node self, Node, Node) override { return default_handler(self); } - T bitwise_and(Node self, Node, Node) override { return default_handler(self); } - T bitwise_or(Node self, Node, Node) override { return default_handler(self); } - T bitwise_xor(Node self, Node, Node) override { return default_handler(self); } - T bitwise_not(Node self, Node) override { return default_handler(self); } - T unary_minus(Node self, Node) override { return default_handler(self); } - T reduce_and(Node self, Node) override { return default_handler(self); } - T reduce_or(Node self, Node) override { return default_handler(self); } - T reduce_xor(Node self, Node) override { return default_handler(self); } - T equal(Node self, Node, Node) override { return default_handler(self); } - T not_equal(Node self, Node, Node) override { return default_handler(self); } - T signed_greater_than(Node self, Node, Node) override { return default_handler(self); } - T signed_greater_equal(Node self, Node, Node) override { return default_handler(self); } - T unsigned_greater_than(Node self, Node, Node) override { return default_handler(self); } - T unsigned_greater_equal(Node self, Node, Node) override { return default_handler(self); } - T logical_shift_left(Node self, Node, Node) override { return default_handler(self); } - T logical_shift_right(Node self, Node, Node) override { return default_handler(self); } - T arithmetic_shift_right(Node self, Node, Node) override { return default_handler(self); } - T mux(Node self, Node, Node, Node) override { return default_handler(self); } - T constant(Node self, RTLIL::Const const &) override { return default_handler(self); } - T input(Node self, IdString) override { return default_handler(self); } - T state(Node self, IdString) override { return default_handler(self); } - T memory_read(Node self, Node, Node) override { return default_handler(self); } - T memory_write(Node self, Node, Node, Node) override { return default_handler(self); } - }; - // a factory is used to modify a FunctionalIR. it creates new nodes and allows for some modification of existing nodes. - class Factory { - FunctionalIR &_ir; - friend class FunctionalIR; - explicit Factory(FunctionalIR &ir) : _ir(ir) {} - Node add(NodeData &&fn, Sort &&sort, std::initializer_list args) { - log_assert(!sort.is_signal() || sort.width() > 0); - log_assert(!sort.is_memory() || sort.addr_width() > 0 && sort.data_width() > 0); - Graph::Ref ref = _ir._graph.add(std::move(fn), {std::move(sort)}); - for (auto arg : args) - ref.append_arg(Graph::ConstRef(arg)); - return Node(ref); - } - Graph::Ref mutate(Node n) { - return _ir._graph[n._ref.index()]; - } - void check_basic_binary(Node const &a, Node const &b) { log_assert(a.sort().is_signal() && a.sort() == b.sort()); } - void check_shift(Node const &a, Node const &b) { log_assert(a.sort().is_signal() && b.sort().is_signal() && b.width() == ceil_log2(a.width())); } - void check_unary(Node const &a) { log_assert(a.sort().is_signal()); } - public: - Node slice(Node a, int offset, int out_width) { - log_assert(a.sort().is_signal() && offset + out_width <= a.sort().width()); - if(offset == 0 && out_width == a.width()) - return a; - return add(NodeData(Fn::slice, offset), Sort(out_width), {a}); - } - // extend will either extend or truncate the provided value to reach the desired width - Node extend(Node a, int out_width, bool is_signed) { - int in_width = a.sort().width(); - log_assert(a.sort().is_signal()); - if(in_width == out_width) - return a; - if(in_width > out_width) - return slice(a, 0, out_width); - if(is_signed) - return add(Fn::sign_extend, Sort(out_width), {a}); - else - return add(Fn::zero_extend, Sort(out_width), {a}); - } - Node concat(Node a, Node b) { - log_assert(a.sort().is_signal() && b.sort().is_signal()); - return add(Fn::concat, Sort(a.sort().width() + b.sort().width()), {a, b}); - } - Node add(Node a, Node b) { check_basic_binary(a, b); return add(Fn::add, a.sort(), {a, b}); } - Node sub(Node a, Node b) { check_basic_binary(a, b); return add(Fn::sub, a.sort(), {a, b}); } - Node mul(Node a, Node b) { check_basic_binary(a, b); return add(Fn::mul, a.sort(), {a, b}); } - Node unsigned_div(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_div, a.sort(), {a, b}); } - Node unsigned_mod(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_mod, a.sort(), {a, b}); } - Node bitwise_and(Node a, Node b) { check_basic_binary(a, b); return add(Fn::bitwise_and, a.sort(), {a, b}); } - Node bitwise_or(Node a, Node b) { check_basic_binary(a, b); return add(Fn::bitwise_or, a.sort(), {a, b}); } - Node bitwise_xor(Node a, Node b) { check_basic_binary(a, b); return add(Fn::bitwise_xor, a.sort(), {a, b}); } - Node bitwise_not(Node a) { check_unary(a); return add(Fn::bitwise_not, a.sort(), {a}); } - Node unary_minus(Node a) { check_unary(a); return add(Fn::unary_minus, a.sort(), {a}); } - Node reduce_and(Node a) { - check_unary(a); - if(a.width() == 1) - return a; - return add(Fn::reduce_and, Sort(1), {a}); - } - Node reduce_or(Node a) { - check_unary(a); - if(a.width() == 1) - return a; - return add(Fn::reduce_or, Sort(1), {a}); - } - Node reduce_xor(Node a) { - check_unary(a); - if(a.width() == 1) - return a; - return add(Fn::reduce_xor, Sort(1), {a}); - } - Node equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::equal, Sort(1), {a, b}); } - Node not_equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::not_equal, Sort(1), {a, b}); } - Node signed_greater_than(Node a, Node b) { check_basic_binary(a, b); return add(Fn::signed_greater_than, Sort(1), {a, b}); } - Node signed_greater_equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::signed_greater_equal, Sort(1), {a, b}); } - Node unsigned_greater_than(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_greater_than, Sort(1), {a, b}); } - Node unsigned_greater_equal(Node a, Node b) { check_basic_binary(a, b); return add(Fn::unsigned_greater_equal, Sort(1), {a, b}); } - Node logical_shift_left(Node a, Node b) { check_shift(a, b); return add(Fn::logical_shift_left, a.sort(), {a, b}); } - Node logical_shift_right(Node a, Node b) { check_shift(a, b); return add(Fn::logical_shift_right, a.sort(), {a, b}); } - Node arithmetic_shift_right(Node a, Node b) { check_shift(a, b); return add(Fn::arithmetic_shift_right, a.sort(), {a, b}); } - Node mux(Node a, Node b, Node s) { - log_assert(a.sort().is_signal() && a.sort() == b.sort() && s.sort() == Sort(1)); - return add(Fn::mux, a.sort(), {a, b, s}); - } - Node memory_read(Node mem, Node addr) { - log_assert(mem.sort().is_memory() && addr.sort().is_signal() && mem.sort().addr_width() == addr.sort().width()); - return add(Fn::memory_read, Sort(mem.sort().data_width()), {mem, addr}); - } - Node memory_write(Node mem, Node addr, Node data) { - log_assert(mem.sort().is_memory() && addr.sort().is_signal() && data.sort().is_signal() && - mem.sort().addr_width() == addr.sort().width() && mem.sort().data_width() == data.sort().width()); - return add(Fn::memory_write, mem.sort(), {mem, addr, data}); - } - Node constant(RTLIL::Const value) { - return add(NodeData(Fn::constant, std::move(value)), Sort(value.size()), {}); - } - Node create_pending(int width) { - return add(Fn::buf, Sort(width), {}); - } - void update_pending(Node node, Node value) { - log_assert(node._ref.function() == Fn::buf && node._ref.size() == 0); - log_assert(node.sort() == value.sort()); - mutate(node).append_arg(value._ref); - } - void add_input(IdString name, int width) { - auto [it, inserted] = _ir._input_sorts.emplace(name, Sort(width)); - if (!inserted) log_error("input `%s` was re-defined", name.c_str()); - } - void add_output(IdString name, int width) { - auto [it, inserted] = _ir._output_sorts.emplace(name, Sort(width)); - if (!inserted) log_error("output `%s` was re-defined", name.c_str()); - } - void add_state(IdString name, Sort sort) { - auto [it, inserted] = _ir._state_sorts.emplace(name, sort); - if (!inserted) log_error("state `%s` was re-defined", name.c_str()); - } - Node input(IdString name) { - return add(NodeData(Fn::input, name), Sort(_ir._input_sorts.at(name)), {}); - } - Node current_state(IdString name) { - return add(NodeData(Fn::state, name), Sort(_ir._state_sorts.at(name)), {}); - } - void set_output(IdString output, Node value) { - log_assert(_ir._output_sorts.at(output) == value.sort()); - mutate(value).assign_key({output, false}); - } - void set_initial_state(IdString state, RTLIL::Const value) { - Sort &sort = _ir._state_sorts.at(state); - value.extu(sort.width()); - _ir._initial_state_signal.emplace(state, std::move(value)); - } - void set_initial_state(IdString state, MemContents value) { - log_assert(Sort(value.addr_width(), value.data_width()) == _ir._state_sorts.at(state)); - _ir._initial_state_memory.emplace(state, std::move(value)); - } - void set_next_state(IdString state, Node value) { - log_assert(_ir._state_sorts.at(state) == value.sort()); - mutate(value).assign_key({state, true}); - } - void suggest_name(Node node, IdString name) { - mutate(node).sparse_attr() = name; - } - }; - static FunctionalIR from_module(Module *module); - Factory factory() { return Factory(*this); } - int size() const { return _graph.size(); } - Node operator[](int i) { return Node(_graph[i]); } - void topological_sort(); - void forward_buf(); - dict inputs() const { return _input_sorts; } - dict outputs() const { return _output_sorts; } - dict state() const { return _state_sorts; } - RTLIL::Const const &get_initial_state_signal(IdString name) { return _initial_state_signal.at(name); } - MemContents const &get_initial_state_memory(IdString name) { return _initial_state_memory.at(name); } - Node get_output_node(IdString name) { return Node(_graph({name, false})); } - Node get_state_next_node(IdString name) { return Node(_graph({name, true})); } - class Iterator { - friend class FunctionalIR; - FunctionalIR *_ir; - int _index; - Iterator(FunctionalIR *ir, int index) : _ir(ir), _index(index) {} - public: - Node operator*() { return Node(_ir->_graph[_index]); } - Iterator &operator++() { _index++; return *this; } - bool operator!=(Iterator const &other) const { return _index != other._index; } - }; - Iterator begin() { return Iterator(this, 0); } - Iterator end() { return Iterator(this, _graph.size()); } -}; - -namespace FunctionalTools { - template class Scope { - protected: - char substitution_character = '_'; - virtual bool is_character_legal(char) = 0; - private: - pool _used_names; - dict _by_id; - public: - void reserve(std::string name) { - _used_names.insert(std::move(name)); - } - std::string unique_name(IdString suggestion) { - std::string str = RTLIL::unescape_id(suggestion); - for(size_t i = 0; i < str.size(); i++) - if(!is_character_legal(str[i])) - str[i] = substitution_character; - if(_used_names.count(str) == 0) { - _used_names.insert(str); - return str; - } - for (int idx = 0 ; ; idx++){ - std::string suffixed = str + "_" + std::to_string(idx); - if(_used_names.count(suffixed) == 0) { - _used_names.insert(suffixed); - return suffixed; - } - } - } - std::string operator()(Id id, IdString suggestion) { - auto it = _by_id.find(id); - if(it != _by_id.end()) - return it->second; - std::string str = unique_name(suggestion); - _by_id.insert({id, str}); - return str; - } - }; - class Writer { - std::ostream *os; - void print_impl(const char *fmt, vector>& fns); - public: - Writer(std::ostream &os) : os(&os) {} - template Writer& operator <<(T&& arg) { *os << std::forward(arg); return *this; } - template - void print(const char *fmt, Args&&... args) - { - vector> fns { [&]() { *this << args; }... }; - print_impl(fmt, fns); - } - template - void print_with(Fn fn, const char *fmt, Args&&... args) - { - vector> fns { [&]() { - if constexpr (std::is_invocable_v) - *this << fn(args); - else - *this << args; }... - }; - print_impl(fmt, fns); - } - }; -} - -YOSYS_NAMESPACE_END - -#endif diff --git a/kernel/mem.h b/kernel/mem.h index 4be4b68649e..8c935adc13b 100644 --- a/kernel/mem.h +++ b/kernel/mem.h @@ -22,6 +22,7 @@ #include "kernel/yosys.h" #include "kernel/ffinit.h" +#include "kernel/utils.h" YOSYS_NAMESPACE_BEGIN @@ -224,15 +225,6 @@ struct Mem : RTLIL::AttrObject { Mem(Module *module, IdString memid, int width, int start_offset, int size) : module(module), memid(memid), packed(false), mem(nullptr), cell(nullptr), width(width), start_offset(start_offset), size(size) {} }; -// this class is used for implementing operator-> on iterators that return values rather than references -// it's necessary because in C++ operator-> is called recursively until a raw pointer is obtained -template -struct arrow_proxy { - T v; - explicit arrow_proxy(T const & v) : v(v) {} - T* operator->() { return &v; } -}; - // MemContents efficiently represents the contents of a potentially sparse memory by storing only those segments that are actually defined class MemContents { public: @@ -303,6 +295,7 @@ class MemContents { reference operator *() const { return range(_memory->_data_width, _addr, _memory->_values.at(_addr)); } pointer operator->() const { return arrow_proxy(**this); } bool operator !=(iterator const &other) const { return _memory != other._memory || _addr != other._addr; } + bool operator ==(iterator const &other) const { return !(*this != other); } iterator &operator++(); }; MemContents(int addr_width, int data_width, RTLIL::Const default_value) diff --git a/kernel/utils.h b/kernel/utils.h index 3216c5eb5f0..99f327db4cd 100644 --- a/kernel/utils.h +++ b/kernel/utils.h @@ -253,6 +253,15 @@ template , typename OPS = hash_ops> cla } }; +// this class is used for implementing operator-> on iterators that return values rather than references +// it's necessary because in C++ operator-> is called recursively until a raw pointer is obtained +template +struct arrow_proxy { + T v; + explicit arrow_proxy(T const & v) : v(v) {} + T* operator->() { return &v; } +}; + YOSYS_NAMESPACE_END #endif diff --git a/passes/cmds/example_dt.cc b/passes/cmds/example_dt.cc index 4b836d75b88..aaf07dadddd 100644 --- a/passes/cmds/example_dt.cc +++ b/passes/cmds/example_dt.cc @@ -1,7 +1,7 @@ #include "kernel/yosys.h" #include "kernel/drivertools.h" #include "kernel/topo_scc.h" -#include "kernel/functional.h" +#include "kernel/compute_graph.h" USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN From fbee31080e04ebef3ae99e1dfd876a93060c24d2 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Thu, 25 Jul 2024 12:25:19 +0100 Subject: [PATCH 69/92] add optional header and hashlib implementation for optional --- kernel/hashlib.h | 12 ++++++++++++ kernel/yosys_common.h | 1 + 2 files changed, 13 insertions(+) diff --git a/kernel/hashlib.h b/kernel/hashlib.h index d32db6832fa..157ccf4ce2d 100644 --- a/kernel/hashlib.h +++ b/kernel/hashlib.h @@ -205,6 +205,18 @@ template struct hash_ops> { } }; +template struct hash_ops> { + static inline bool cmp(std::optional a, std::optional b) { + return a == b; + } + static inline unsigned int hash(std::optional a) { + if(a.has_value()) + return mkhash(*a); + else + return 0; + } +}; + inline int hashtable_size(int min_size) { // Primes as generated by https://oeis.org/A175953 diff --git a/kernel/yosys_common.h b/kernel/yosys_common.h index e71e94a78c5..ec1b5aba2ba 100644 --- a/kernel/yosys_common.h +++ b/kernel/yosys_common.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include From 8f774942635a09cd9a30733612d47a92b11fcbd5 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Thu, 25 Jul 2024 12:54:58 +0100 Subject: [PATCH 70/92] silence some warnings --- kernel/drivertools.h | 8 +++++--- kernel/functional.h | 3 ++- kernel/mem.h | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/kernel/drivertools.h b/kernel/drivertools.h index 48c846b5f79..b0e435f400f 100644 --- a/kernel/drivertools.h +++ b/kernel/drivertools.h @@ -388,6 +388,8 @@ struct DriveBit case DriveType::MULTIPLE: inner = multiple_.hash(); break; + default: + log_abort(); } return mkhash((unsigned int)type_, inner); } @@ -412,7 +414,7 @@ struct DriveBit case DriveType::MULTIPLE: return multiple_ == other.multiple_; } - log_assert(false); + log_abort(); } bool operator!=(const DriveBit &other) const @@ -958,7 +960,7 @@ struct DriveChunk case DriveType::MULTIPLE: return multiple_ == other.multiple_; } - log_assert(false); + log_abort(); } bool operator!=(const DriveChunk &other) const @@ -985,7 +987,7 @@ struct DriveChunk case DriveType::MULTIPLE: return multiple_ < other.multiple_; } - log_assert(false); + log_abort(); } DriveType type() const { return type_; } diff --git a/kernel/functional.h b/kernel/functional.h index e0592259c7f..08b7b99ca2a 100644 --- a/kernel/functional.h +++ b/kernel/functional.h @@ -295,6 +295,7 @@ namespace Functional { case Fn::memory_read: return v.memory_read(*this, arg(0), arg(1)); break; case Fn::memory_write: return v.memory_write(*this, arg(0), arg(1), arg(2)); break; } + log_abort(); } std::string to_string(); std::string to_string(std::function); @@ -384,7 +385,7 @@ namespace Functional { explicit Factory(IR &ir) : _ir(ir) {} Node add(IR::NodeData &&fn, Sort &&sort, std::initializer_list args) { log_assert(!sort.is_signal() || sort.width() > 0); - log_assert(!sort.is_memory() || sort.addr_width() > 0 && sort.data_width() > 0); + log_assert(!sort.is_memory() || (sort.addr_width() > 0 && sort.data_width() > 0)); IR::Graph::Ref ref = _ir._graph.add(std::move(fn), {std::move(sort)}); for (auto arg : args) ref.append_arg(IR::Graph::ConstRef(arg)); diff --git a/kernel/mem.h b/kernel/mem.h index 8c935adc13b..4a6a9a431f4 100644 --- a/kernel/mem.h +++ b/kernel/mem.h @@ -318,7 +318,7 @@ class MemContents { // insert multiple words at the given address, overriding any previous assignment. template void insert_range(addr_t addr, Iterator begin, Iterator end) { auto words = end - begin; - log_assert(addr < 1<<_addr_width); log_assert(words <= (1<<_addr_width) - addr); + log_assert(addr < (addr_t)(1<<_addr_width)); log_assert(words <= (addr_t)(1<<_addr_width) - addr); auto range = _reserve_range(addr, addr + words); auto it = _range_data(range, addr); for(; begin != end; ++begin) From 79a1b691ea159230ee5c3938f1e136a25fc896b0 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Thu, 25 Jul 2024 13:11:56 +0100 Subject: [PATCH 71/92] silence some more warnings, undo mistaken addition --- kernel/drivertools.h | 3 +-- kernel/mem.cc | 10 +++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/kernel/drivertools.h b/kernel/drivertools.h index b0e435f400f..8a51b88d9ef 100644 --- a/kernel/drivertools.h +++ b/kernel/drivertools.h @@ -388,8 +388,6 @@ struct DriveBit case DriveType::MULTIPLE: inner = multiple_.hash(); break; - default: - log_abort(); } return mkhash((unsigned int)type_, inner); } @@ -1028,6 +1026,7 @@ struct DriveChunk case DriveType::MULTIPLE: return multiple_.size(); } + log_abort(); } }; diff --git a/kernel/mem.cc b/kernel/mem.cc index 46e4331c5a7..80be52a0eee 100644 --- a/kernel/mem.cc +++ b/kernel/mem.cc @@ -1729,8 +1729,8 @@ void MemContents::check() { log_assert(!it->second.empty()); log_assert(it->second.size() % _data_width == 0); auto end1 = _range_end(it); - log_assert(_range_begin(it) < (1<<_addr_width)); - log_assert(end1 <= (1<<_addr_width)); + log_assert(_range_begin(it) < (addr_t)(1<<_addr_width)); + log_assert(end1 <= (addr_t)(1<<_addr_width)); if(++it == _values.end()) break; // check that ranges neither overlap nor touch @@ -1760,7 +1760,7 @@ bool MemContents::_range_overlaps(std::map::iterator it, a std::map::iterator MemContents::_range_at(addr_t addr) const { // allow addr == 1<<_addr_width (which will just return end()) - log_assert(addr <= 1<<_addr_width); + log_assert(addr <= (addr_t)(1<<_addr_width)); // get the first range with base > addr // (we use const_cast since map::iterators are only passed around internally and not exposed to the user // and using map::iterator in both the const and non-const case simplifies the code a little, @@ -1879,8 +1879,8 @@ std::map::iterator MemContents::_reserve_range(addr_t begi void MemContents::insert_concatenated(addr_t addr, RTLIL::Const const &values) { addr_t words = (values.size() + _data_width - 1) / _data_width; - log_assert(addr < 1<<_addr_width); - log_assert(words <= (1<<_addr_width) - addr); + log_assert(addr < (addr_t)(1<<_addr_width)); + log_assert(words <= (addr_t)(1<<_addr_width) - addr); auto it = _reserve_range(addr, addr + words); auto to_begin = _range_data(it, addr); std::copy(values.bits.begin(), values.bits.end(), to_begin); From 50047d25b3806fb614c983a950de85b09db8fcfc Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Tue, 6 Aug 2024 09:56:28 +0100 Subject: [PATCH 72/92] functional backend: add different types of input/output/state variables --- backends/functional/cxx.cc | 38 +++---- backends/functional/smtlib.cc | 36 +++--- backends/functional/test_generic.cc | 8 +- kernel/functional.cc | 112 +++++++++++++++---- kernel/functional.h | 163 ++++++++++++++++++---------- 5 files changed, 237 insertions(+), 120 deletions(-) diff --git a/backends/functional/cxx.cc b/backends/functional/cxx.cc index a4755e144c3..19740777fb7 100644 --- a/backends/functional/cxx.cc +++ b/backends/functional/cxx.cc @@ -150,8 +150,8 @@ template struct CxxPrintVisitor : public Functional::Abstract void arithmetic_shift_right(Node, Node a, Node b) override { print("{}.arithmetic_shift_right({})", a, b); } void mux(Node, Node a, Node b, Node s) override { print("{2}.any() ? {1} : {0}", a, b, s); } void constant(Node, RTLIL::Const const & value) override { print("{}", cxx_const(value)); } - void input(Node, IdString name) override { print("input.{}", input_struct[name]); } - void state(Node, IdString name) override { print("current_state.{}", state_struct[name]); } + void input(Node, IdString name, IdString type) override { log_assert(type == ID($input)); print("input.{}", input_struct[name]); } + void state(Node, IdString name, IdString type) override { log_assert(type == ID($state)); print("current_state.{}", state_struct[name]); } void memory_read(Node, Node mem, Node addr) override { print("{}.read({})", mem, addr); } void memory_write(Node, Node mem, Node addr, Node data) override { print("{}.write({}, {})", mem, addr, data); } }; @@ -175,12 +175,12 @@ struct CxxModule { output_struct("Outputs"), state_struct("State") { - for (auto [name, sort] : ir.inputs()) - input_struct.insert(name, sort); - for (auto [name, sort] : ir.outputs()) - output_struct.insert(name, sort); - for (auto [name, sort] : ir.state()) - state_struct.insert(name, sort); + for (auto input : ir.inputs()) + input_struct.insert(input->name, input->sort); + for (auto output : ir.outputs()) + output_struct.insert(output->name, output->sort); + for (auto state : ir.states()) + state_struct.insert(state->name, state->sort); module_name = CxxScope().unique_name(module->name); } void write_header(CxxWriter &f) { @@ -197,19 +197,19 @@ struct CxxModule { } void write_initial_def(CxxWriter &f) { f.print("void {0}::initialize({0}::State &state)\n{{\n", module_name); - for (auto [name, sort] : ir.state()) { - if (sort.is_signal()) - f.print("\tstate.{} = {};\n", state_struct[name], cxx_const(ir.get_initial_state_signal(name))); - else if (sort.is_memory()) { + for (auto state : ir.states()) { + if (state->sort.is_signal()) + f.print("\tstate.{} = {};\n", state_struct[state->name], cxx_const(state->initial_value_signal())); + else if (state->sort.is_memory()) { f.print("\t{{\n"); - f.print("\t\tstd::array, {}> mem;\n", sort.data_width(), 1<, {}> mem;\n", state->sort.data_width(), 1<sort.addr_width()); + const auto &contents = state->initial_value_memory(); f.print("\t\tmem.fill({});\n", cxx_const(contents.default_value())); for(auto range : contents) for(auto addr = range.base(); addr < range.limit(); addr++) if(!equal_def(range[addr], contents.default_value())) f.print("\t\tmem[{}] = {};\n", addr, cxx_const(range[addr])); - f.print("\t\tstate.{} = mem;\n", state_struct[name]); + f.print("\t\tstate.{} = mem;\n", state_struct[state->name]); f.print("\t}}\n"); } } @@ -229,10 +229,10 @@ struct CxxModule { node.visit(printVisitor); f.print(";\n"); } - for (auto [name, sort] : ir.state()) - f.print("\tnext_state.{} = {};\n", state_struct[name], node_name(ir.get_state_next_node(name))); - for (auto [name, sort] : ir.outputs()) - f.print("\toutput.{} = {};\n", output_struct[name], node_name(ir.get_output_node(name))); + for (auto state : ir.states()) + f.print("\tnext_state.{} = {};\n", state_struct[state->name], node_name(state->next_value())); + for (auto output : ir.outputs()) + f.print("\toutput.{} = {};\n", output_struct[output->name], node_name(output->value())); f.print("}}\n\n"); } }; diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index 7fd6fe564c2..ec9e6b24279 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -178,8 +178,8 @@ struct SmtPrintVisitor : public Functional::AbstractVisitor { SExpr memory_read(Node, Node mem, Node addr) override { return list("select", n(mem), n(addr)); } SExpr memory_write(Node, Node mem, Node addr, Node data) override { return list("store", n(mem), n(addr), n(data)); } - SExpr input(Node, IdString name) override { return input_struct.access("inputs", name); } - SExpr state(Node, IdString name) override { return state_struct.access("state", name); } + SExpr input(Node, IdString name, IdString type) override { log_assert(type == ID($input)); return input_struct.access("inputs", name); } + SExpr state(Node, IdString name, IdString type) override { log_assert(type == ID($state)); return state_struct.access("state", name); } }; struct SmtModule { @@ -200,12 +200,12 @@ struct SmtModule { , state_struct(scope.unique_name(module->name.str() + "_State"), scope) { scope.reserve(name + "-initial"); - for (const auto &input : ir.inputs()) - input_struct.insert(input.first, input.second); - for (const auto &output : ir.outputs()) - output_struct.insert(output.first, output.second); - for (const auto &state : ir.state()) - state_struct.insert(state.first, state.second); + for (auto input : ir.inputs()) + input_struct.insert(input->name, input->sort); + for (auto output : ir.outputs()) + output_struct.insert(output->name, output->sort); + for (auto state : ir.states()) + state_struct.insert(state->name, state->sort); } void write_eval(SExprWriter &w) @@ -232,8 +232,8 @@ struct SmtModule { w.comment(SmtSort(n.sort()).to_sexpr().to_string(), true); } w.open(list("pair")); - output_struct.write_value(w, [&](IdString name) { return node_to_sexpr(ir.get_output_node(name)); }); - state_struct.write_value(w, [&](IdString name) { return node_to_sexpr(ir.get_state_next_node(name)); }); + output_struct.write_value(w, [&](IdString name) { return node_to_sexpr(ir.output(name).value()); }); + state_struct.write_value(w, [&](IdString name) { return node_to_sexpr(ir.state(name).next_value()); }); w.pop(); } @@ -241,14 +241,14 @@ struct SmtModule { { std::string initial = name + "-initial"; w << list("declare-const", initial, state_struct.name); - for (const auto &[name, sort] : ir.state()) { - if(sort.is_signal()) - w << list("assert", list("=", state_struct.access(initial, name), smt_const(ir.get_initial_state_signal(name)))); - else if(sort.is_memory()) { - auto contents = ir.get_initial_state_memory(name); - for(int i = 0; i < 1<sort.is_signal()) + w << list("assert", list("=", state_struct.access(initial, state->name), smt_const(state->initial_value_signal()))); + else if(state->sort.is_memory()) { + const auto &contents = state->initial_value_memory(); + for(int i = 0; i < 1<sort.addr_width(); i++) { + auto addr = smt_const(RTLIL::Const(i, state->sort.addr_width())); + w << list("assert", list("=", list("select", state_struct.access(initial, state->name), addr), smt_const(contents[i]))); } } } diff --git a/backends/functional/test_generic.cc b/backends/functional/test_generic.cc index 83ea09d8ddb..1617d54c069 100644 --- a/backends/functional/test_generic.cc +++ b/backends/functional/test_generic.cc @@ -142,10 +142,10 @@ struct FunctionalTestGeneric : public Pass auto fir = Functional::IR::from_module(module); for(auto node : fir) std::cout << RTLIL::unescape_id(node.name()) << " = " << node.to_string([](auto n) { return RTLIL::unescape_id(n.name()); }) << "\n"; - for(auto [name, sort] : fir.outputs()) - std::cout << "output " << RTLIL::unescape_id(name) << " = " << RTLIL::unescape_id(fir.get_output_node(name).name()) << "\n"; - for(auto [name, sort] : fir.state()) - std::cout << "state " << RTLIL::unescape_id(name) << " = " << RTLIL::unescape_id(fir.get_state_next_node(name).name()) << "\n"; + for(auto output : fir.all_outputs()) + std::cout << RTLIL::unescape_id(output->type) << " " << RTLIL::unescape_id(output->name) << " = " << RTLIL::unescape_id(output->value().name()) << "\n"; + for(auto state : fir.all_states()) + std::cout << RTLIL::unescape_id(state->type) << " " << RTLIL::unescape_id(state->name) << " = " << RTLIL::unescape_id(state->next_value().name()) << "\n"; } } } FunctionalCxxBackend; diff --git a/kernel/functional.cc b/kernel/functional.cc index ad507187db2..202eaa50221 100644 --- a/kernel/functional.cc +++ b/kernel/functional.cc @@ -65,6 +65,51 @@ const char *fn_to_string(Fn fn) { log_error("fn_to_string: unknown Functional::Fn value %d", (int)fn); } +vector IR::inputs(IdString type) const { + vector ret; + for (const auto &[name, input] : _inputs) + if(input.type == type) + ret.push_back(&input); + return ret; +} + +vector IR::outputs(IdString type) const { + vector ret; + for (const auto &[name, output] : _outputs) + if(output.type == type) + ret.push_back(&output); + return ret; +} + +vector IR::states(IdString type) const { + vector ret; + for (const auto &[name, state] : _states) + if(state.type == type) + ret.push_back(&state); + return ret; +} + +vector IR::all_inputs() const { + vector ret; + for (const auto &[name, input] : _inputs) + ret.push_back(&input); + return ret; +} + +vector IR::all_outputs() const { + vector ret; + for (const auto &[name, output] : _outputs) + ret.push_back(&output); + return ret; +} + +vector IR::all_states() const { + vector ret; + for (const auto &[name, state] : _states) + ret.push_back(&state); + return ret; +} + struct PrintVisitor : DefaultVisitor { std::function np; PrintVisitor(std::function np) : np(np) { } @@ -73,8 +118,8 @@ struct PrintVisitor : DefaultVisitor { std::string zero_extend(Node, Node a, int out_width) override { return "zero_extend(" + np(a) + ", " + std::to_string(out_width) + ")"; } std::string sign_extend(Node, Node a, int out_width) override { return "sign_extend(" + np(a) + ", " + std::to_string(out_width) + ")"; } std::string constant(Node, RTLIL::Const const& value) override { return "constant(" + value.as_string() + ")"; } - std::string input(Node, IdString name) override { return "input(" + name.str() + ")"; } - std::string state(Node, IdString name) override { return "state(" + name.str() + ")"; } + std::string input(Node, IdString name, IdString type) override { return "input(" + name.str() + ", " + type.str() + ")"; } + std::string state(Node, IdString name, IdString type) override { return "state(" + name.str() + ", " + type.str() + ")"; } std::string default_handler(Node self) override { std::string ret = fn_to_string(self.fn()); ret += "("; @@ -199,7 +244,7 @@ class CellSimplifier { return handle_alu(g, factory.bitwise_or(p, g), g.width(), false, ci, factory.constant(Const(State::S0, 1))).at(ID(CO)); } public: - std::variant, Node> handle(IdString cellType, dict parameters, dict inputs) + std::variant, Node> handle(IdString cellName, IdString cellType, dict parameters, dict inputs) { int a_width = parameters.at(ID(A_WIDTH), Const(-1)).as_int(); int b_width = parameters.at(ID(B_WIDTH), Const(-1)).as_int(); @@ -392,6 +437,26 @@ class CellSimplifier { return handle_lcu(inputs.at(ID(P)), inputs.at(ID(G)), inputs.at(ID(CI))); } else if(cellType == ID($alu)) { return handle_alu(inputs.at(ID(A)), inputs.at(ID(B)), y_width, a_signed && b_signed, inputs.at(ID(CI)), inputs.at(ID(BI))); + } else if(cellType.in({ID($assert), ID($assume), ID($live), ID($fair), ID($cover)})) { + Node a = factory.mux(factory.constant(Const(State::S1, 1)), inputs.at(ID(A)), inputs.at(ID(EN))); + auto &output = factory.add_output(cellName, cellType, Sort(1)); + output.set_value(a); + return {}; + } else if(cellType.in({ID($anyconst), ID($allconst), ID($anyseq), ID($allseq)})) { + int width = parameters.at(ID(WIDTH)).as_int(); + auto &input = factory.add_input(cellName, cellType, Sort(width)); + return factory.value(input); + } else if(cellType == ID($initstate)) { + if(factory.ir().has_state(ID($initstate), ID($state))) + return factory.value(factory.ir().state(ID($initstate))); + else { + auto &state = factory.add_state(ID($initstate), ID($state), Sort(1)); + state.set_initial_value(RTLIL::Const(State::S1, 1)); + state.set_next_value(factory.constant(RTLIL::Const(State::S0, 1))); + return factory.value(state); + } + } else if(cellType == ID($check)) { + log_error("The design contains a $check cell `%s'. This is not supported by the functional backend. Call `chformal -lower' to avoid this error.\n", cellName.c_str()); } else { log_error("`%s' cells are not supported by the functional backend\n", cellType.c_str()); } @@ -448,16 +513,15 @@ class FunctionalIRConstruction { { driver_map.add(module); for (auto cell : module->cells()) { - if (cell->type.in(ID($assert), ID($assume), ID($cover), ID($check))) + if (cell->type.in(ID($assert), ID($assume), ID($live), ID($fair), ID($cover), ID($check))) queue.emplace_back(cell); } for (auto wire : module->wires()) { if (wire->port_input) - factory.add_input(wire->name, wire->width); + factory.add_input(wire->name, ID($input), Sort(wire->width)); if (wire->port_output) { - factory.add_output(wire->name, wire->width); - Node value = enqueue(DriveChunk(DriveChunkWire(wire, 0, wire->width))); - factory.set_output(wire->name, value); + auto &output = factory.add_output(wire->name, ID($output), Sort(wire->width)); + output.set_value(enqueue(DriveChunk(DriveChunkWire(wire, 0, wire->width)))); } } memories_vector = Mem::get_all_memories(module); @@ -495,9 +559,9 @@ class FunctionalIRConstruction { // - Since wr port j can only have priority over wr port i if j > i, if we do writes in // ascending index order the result will obey the priorty relation. vector read_results; - factory.add_state(mem->cell->name, Sort(ceil_log2(mem->size), mem->width)); - factory.set_initial_state(mem->cell->name, MemContents(mem)); - Node node = factory.current_state(mem->cell->name); + auto &state = factory.add_state(mem->cell->name, ID($state), Sort(ceil_log2(mem->size), mem->width)); + state.set_initial_value(MemContents(mem)); + Node node = factory.value(state); for (size_t i = 0; i < mem->wr_ports.size(); i++) { const auto &wr = mem->wr_ports[i]; if (wr.clk_enable) @@ -521,7 +585,7 @@ class FunctionalIRConstruction { Node addr = enqueue(driver_map(DriveSpec(rd.addr))); read_results.push_back(factory.memory_read(node, addr)); } - factory.set_next_state(mem->cell->name, node); + state.set_next_value(node); return concatenate_read_results(mem, read_results); } void process_cell(Cell *cell) @@ -540,25 +604,25 @@ class FunctionalIRConstruction { if (!ff.has_gclk) log_error("The design contains a %s flip-flop at %s. This is not supported by the functional backend. " "Call async2sync or clk2fflogic to avoid this error.\n", log_id(cell->type), log_id(cell)); - factory.add_state(ff.name, Sort(ff.width)); - Node q_value = factory.current_state(ff.name); + auto &state = factory.add_state(ff.name, ID($state), Sort(ff.width)); + Node q_value = factory.value(state); factory.suggest_name(q_value, ff.name); factory.update_pending(cell_outputs.at({cell, ID(Q)}), q_value); - factory.set_next_state(ff.name, enqueue(ff.sig_d)); - factory.set_initial_state(ff.name, ff.val_init); + state.set_next_value(enqueue(ff.sig_d)); + state.set_initial_value(ff.val_init); } else { dict connections; IdString output_name; // for the single output case int n_outputs = 0; for(auto const &[name, sigspec] : cell->connections()) { - if(driver_map.celltypes.cell_input(cell->type, name)) + if(driver_map.celltypes.cell_input(cell->type, name) && sigspec.size() > 0) connections.insert({ name, enqueue(DriveChunkPort(cell, {name, sigspec})) }); if(driver_map.celltypes.cell_output(cell->type, name)) { output_name = name; n_outputs++; } } - std::variant, Node> outputs = simplifier.handle(cell->type, cell->parameters, connections); + std::variant, Node> outputs = simplifier.handle(cell->name, cell->type, cell->parameters, connections); if(auto *nodep = std::get_if(&outputs); nodep != nullptr) { log_assert(n_outputs == 1); factory.update_pending(cell_outputs.at({cell, output_name}), *nodep); @@ -591,7 +655,7 @@ class FunctionalIRConstruction { DriveChunkWire wire_chunk = chunk.wire(); if (wire_chunk.is_whole()) { if (wire_chunk.wire->port_input) { - Node node = factory.input(wire_chunk.wire->name); + Node node = factory.value(factory.ir().input(wire_chunk.wire->name)); factory.suggest_name(node, wire_chunk.wire->name); factory.update_pending(pending, node); } else { @@ -668,10 +732,12 @@ void IR::topological_sort() { scc = true; } }); - for(const auto &[name, sort]: _state_sorts) - toposort.process(get_state_next_node(name).id()); - for(const auto &[name, sort]: _output_sorts) - toposort.process(get_output_node(name).id()); + for(const auto &[name, state]: _states) + if(state.has_next_value()) + toposort.process(state.next_value().id()); + for(const auto &[name, output]: _outputs) + if(output.has_value()) + toposort.process(output.value().id()); // any nodes untouched by this point are dead code and will be removed by permute _graph.permute(perm); if(scc) log_error("The design contains combinational loops. This is not supported by the functional backend. " diff --git a/kernel/functional.h b/kernel/functional.h index 08b7b99ca2a..f7ff08228b1 100644 --- a/kernel/functional.h +++ b/kernel/functional.h @@ -152,11 +152,60 @@ namespace Functional { bool operator==(Sort const& other) const { return _v == other._v; } unsigned int hash() const { return mkhash(_v); } }; + class IR; class Factory; class Node; + class IRInput { + friend class Factory; + public: + IdString name; + IdString type; + Sort sort; + private: + IRInput(IR &, IdString name, IdString type, Sort sort) + : name(name), type(type), sort(std::move(sort)) {} + }; + class IROutput { + friend class Factory; + IR &_ir; + public: + IdString name; + IdString type; + Sort sort; + private: + IROutput(IR &ir, IdString name, IdString type, Sort sort) + : _ir(ir), name(name), type(type), sort(std::move(sort)) {} + public: + Node value() const; + bool has_value() const; + void set_value(Node value); + }; + class IRState { + friend class Factory; + IR &_ir; + public: + IdString name; + IdString type; + Sort sort; + private: + std::variant _initial; + IRState(IR &ir, IdString name, IdString type, Sort sort) + : _ir(ir), name(name), type(type), sort(std::move(sort)) {} + public: + Node next_value() const; + bool has_next_value() const; + RTLIL::Const const& initial_value_signal() const { return std::get(_initial); } + MemContents const& initial_value_memory() const { return std::get(_initial); } + void set_next_value(Node value); + void set_initial_value(RTLIL::Const value) { value.extu(sort.width()); _initial = std::move(value); } + void set_initial_value(MemContents value) { log_assert(Sort(value.addr_width(), value.data_width()) == sort); _initial = std::move(value); } + }; class IR { friend class Factory; friend class Node; + friend class IRInput; + friend class IROutput; + friend class IRState; // one NodeData is stored per Node, containing the function and non-node arguments // note that NodeData is deduplicated by ComputeGraph class NodeData { @@ -164,7 +213,7 @@ namespace Functional { std::variant< std::monostate, RTLIL::Const, - IdString, + std::pair, int > _extra; public: @@ -173,7 +222,7 @@ namespace Functional { template NodeData(Fn fn, T &&extra) : _fn(fn), _extra(std::forward(extra)) {} Fn fn() const { return _fn; } const RTLIL::Const &as_const() const { return std::get(_extra); } - IdString as_idstring() const { return std::get(_extra); } + std::pair as_idstring_pair() const { return std::get>(_extra); } int as_int() const { return std::get(_extra); } int hash() const { return mkhash((unsigned int) _fn, mkhash(_extra)); @@ -190,13 +239,12 @@ namespace Functional { // the sparse_attr IdString stores a naming suggestion, retrieved with name() // the key is currently used to identify the nodes that represent output and next state values // the bool is true for next state values - using Graph = ComputeGraph>; + using Graph = ComputeGraph>; Graph _graph; - dict _input_sorts; - dict _output_sorts; - dict _state_sorts; - dict _initial_state_signal; - dict _initial_state_memory; + dict, IRInput> _inputs; + dict, IROutput> _outputs; + dict, IRState> _states; + IR::Graph::Ref mutate(Node n); public: static IR from_module(Module *module); Factory factory(); @@ -204,13 +252,24 @@ namespace Functional { Node operator[](int i); void topological_sort(); void forward_buf(); - dict inputs() const { return _input_sorts; } - dict outputs() const { return _output_sorts; } - dict state() const { return _state_sorts; } - RTLIL::Const const &get_initial_state_signal(IdString name) { return _initial_state_signal.at(name); } - MemContents const &get_initial_state_memory(IdString name) { return _initial_state_memory.at(name); } - Node get_output_node(IdString name); - Node get_state_next_node(IdString name); + IRInput const& input(IdString name, IdString type) const { return _inputs.at({name, type}); } + IRInput const& input(IdString name) const { return input(name, ID($input)); } + IROutput const& output(IdString name, IdString type) const { return _outputs.at({name, type}); } + IROutput const& output(IdString name) const { return output(name, ID($output)); } + IRState const& state(IdString name, IdString type) const { return _states.at({name, type}); } + IRState const& state(IdString name) const { return state(name, ID($state)); } + bool has_input(IdString name, IdString type) const { return _inputs.count({name, type}); } + bool has_output(IdString name, IdString type) const { return _outputs.count({name, type}); } + bool has_state(IdString name, IdString type) const { return _states.count({name, type}); } + vector inputs(IdString type) const; + vector inputs() const { return inputs(ID($input)); } + vector outputs(IdString type) const; + vector outputs() const { return outputs(ID($output)); } + vector states(IdString type) const; + vector states() const { return states(ID($state)); } + vector all_inputs() const; + vector all_outputs() const; + vector all_states() const; class iterator { friend class IR; IR *_ir; @@ -236,6 +295,9 @@ namespace Functional { class Node { friend class Factory; friend class IR; + friend class IRInput; + friend class IROutput; + friend class IRState; IR::Graph::ConstRef _ref; explicit Node(IR::Graph::ConstRef ref) : _ref(ref) { } explicit operator IR::Graph::ConstRef() { return _ref; } @@ -290,8 +352,8 @@ namespace Functional { case Fn::arithmetic_shift_right: return v.arithmetic_shift_right(*this, arg(0), arg(1)); break; case Fn::mux: return v.mux(*this, arg(0), arg(1), arg(2)); break; case Fn::constant: return v.constant(*this, _ref.function().as_const()); break; - case Fn::input: return v.input(*this, _ref.function().as_idstring()); break; - case Fn::state: return v.state(*this, _ref.function().as_idstring()); break; + case Fn::input: return v.input(*this, _ref.function().as_idstring_pair().first, _ref.function().as_idstring_pair().second); break; + case Fn::state: return v.state(*this, _ref.function().as_idstring_pair().first, _ref.function().as_idstring_pair().second); break; case Fn::memory_read: return v.memory_read(*this, arg(0), arg(1)); break; case Fn::memory_write: return v.memory_write(*this, arg(0), arg(1), arg(2)); break; } @@ -300,9 +362,14 @@ namespace Functional { std::string to_string(); std::string to_string(std::function); }; + inline IR::Graph::Ref IR::mutate(Node n) { return _graph[n._ref.index()]; } inline Node IR::operator[](int i) { return Node(_graph[i]); } - inline Node IR::get_output_node(IdString name) { return Node(_graph({name, false})); } - inline Node IR::get_state_next_node(IdString name) { return Node(_graph({name, true})); } + inline Node IROutput::value() const { return Node(_ir._graph({name, type, false})); } + inline bool IROutput::has_value() const { return _ir._graph.has_key({name, type, false}); } + inline void IROutput::set_value(Node value) { log_assert(sort == value.sort()); _ir.mutate(value).assign_key({name, type, false}); } + inline Node IRState::next_value() const { return Node(_ir._graph({name, type, true})); } + inline bool IRState::has_next_value() const { return _ir._graph.has_key({name, type, true}); } + inline void IRState::set_next_value(Node value) { log_assert(sort == value.sort()); _ir.mutate(value).assign_key({name, type, true}); } inline Node IR::iterator::operator*() { return Node(_ir->_graph[_index]); } inline arrow_proxy IR::iterator::operator->() { return arrow_proxy(**this); } // AbstractVisitor provides an abstract base class for visitors @@ -336,8 +403,8 @@ namespace Functional { virtual T arithmetic_shift_right(Node self, Node a, Node b) = 0; virtual T mux(Node self, Node a, Node b, Node s) = 0; virtual T constant(Node self, RTLIL::Const const & value) = 0; - virtual T input(Node self, IdString name) = 0; - virtual T state(Node self, IdString name) = 0; + virtual T input(Node self, IdString name, IdString type) = 0; + virtual T state(Node self, IdString name, IdString type) = 0; virtual T memory_read(Node self, Node mem, Node addr) = 0; virtual T memory_write(Node self, Node mem, Node addr, Node data) = 0; }; @@ -373,8 +440,8 @@ namespace Functional { T arithmetic_shift_right(Node self, Node, Node) override { return default_handler(self); } T mux(Node self, Node, Node, Node) override { return default_handler(self); } T constant(Node self, RTLIL::Const const &) override { return default_handler(self); } - T input(Node self, IdString) override { return default_handler(self); } - T state(Node self, IdString) override { return default_handler(self); } + T input(Node self, IdString, IdString) override { return default_handler(self); } + T state(Node self, IdString, IdString) override { return default_handler(self); } T memory_read(Node self, Node, Node) override { return default_handler(self); } T memory_write(Node self, Node, Node, Node) override { return default_handler(self); } }; @@ -383,7 +450,7 @@ namespace Functional { friend class IR; IR &_ir; explicit Factory(IR &ir) : _ir(ir) {} - Node add(IR::NodeData &&fn, Sort &&sort, std::initializer_list args) { + Node add(IR::NodeData &&fn, Sort const &sort, std::initializer_list args) { log_assert(!sort.is_signal() || sort.width() > 0); log_assert(!sort.is_memory() || (sort.addr_width() > 0 && sort.data_width() > 0)); IR::Graph::Ref ref = _ir._graph.add(std::move(fn), {std::move(sort)}); @@ -391,13 +458,11 @@ namespace Functional { ref.append_arg(IR::Graph::ConstRef(arg)); return Node(ref); } - IR::Graph::Ref mutate(Node n) { - return _ir._graph[n._ref.index()]; - } void check_basic_binary(Node const &a, Node const &b) { log_assert(a.sort().is_signal() && a.sort() == b.sort()); } void check_shift(Node const &a, Node const &b) { log_assert(a.sort().is_signal() && b.sort().is_signal() && b.width() == ceil_log2(a.width())); } void check_unary(Node const &a) { log_assert(a.sort().is_signal()); } public: + IR &ir() { return _ir; } Node slice(Node a, int offset, int out_width) { log_assert(a.sort().is_signal() && offset + out_width <= a.sort().width()); if(offset == 0 && out_width == a.width()) @@ -480,45 +545,31 @@ namespace Functional { void update_pending(Node node, Node value) { log_assert(node._ref.function() == Fn::buf && node._ref.size() == 0); log_assert(node.sort() == value.sort()); - mutate(node).append_arg(value._ref); + _ir.mutate(node).append_arg(value._ref); } - void add_input(IdString name, int width) { - auto [it, inserted] = _ir._input_sorts.emplace(name, Sort(width)); + IRInput &add_input(IdString name, IdString type, Sort sort) { + auto [it, inserted] = _ir._inputs.emplace({name, type}, IRInput(_ir, name, type, std::move(sort))); if (!inserted) log_error("input `%s` was re-defined", name.c_str()); + return it->second; } - void add_output(IdString name, int width) { - auto [it, inserted] = _ir._output_sorts.emplace(name, Sort(width)); + IROutput &add_output(IdString name, IdString type, Sort sort) { + auto [it, inserted] = _ir._outputs.emplace({name, type}, IROutput(_ir, name, type, std::move(sort))); if (!inserted) log_error("output `%s` was re-defined", name.c_str()); + return it->second; } - void add_state(IdString name, Sort sort) { - auto [it, inserted] = _ir._state_sorts.emplace(name, sort); + IRState &add_state(IdString name, IdString type, Sort sort) { + auto [it, inserted] = _ir._states.emplace({name, type}, IRState(_ir, name, type, std::move(sort))); if (!inserted) log_error("state `%s` was re-defined", name.c_str()); + return it->second; } - Node input(IdString name) { - return add(IR::NodeData(Fn::input, name), Sort(_ir._input_sorts.at(name)), {}); - } - Node current_state(IdString name) { - return add(IR::NodeData(Fn::state, name), Sort(_ir._state_sorts.at(name)), {}); - } - void set_output(IdString output, Node value) { - log_assert(_ir._output_sorts.at(output) == value.sort()); - mutate(value).assign_key({output, false}); - } - void set_initial_state(IdString state, RTLIL::Const value) { - Sort &sort = _ir._state_sorts.at(state); - value.extu(sort.width()); - _ir._initial_state_signal.emplace(state, std::move(value)); - } - void set_initial_state(IdString state, MemContents value) { - log_assert(Sort(value.addr_width(), value.data_width()) == _ir._state_sorts.at(state)); - _ir._initial_state_memory.emplace(state, std::move(value)); + Node value(IRInput const& input) { + return add(IR::NodeData(Fn::input, std::pair(input.name, input.type)), input.sort, {}); } - void set_next_state(IdString state, Node value) { - log_assert(_ir._state_sorts.at(state) == value.sort()); - mutate(value).assign_key({state, true}); + Node value(IRState const& state) { + return add(IR::NodeData(Fn::state, std::pair(state.name, state.type)), state.sort, {}); } void suggest_name(Node node, IdString name) { - mutate(node).sparse_attr() = name; + _ir.mutate(node).sparse_attr() = name; } }; inline Factory IR::factory() { return Factory(*this); } From 831da512554aa5adc6875b8894be4a413656b2fb Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Thu, 8 Aug 2024 15:38:24 +0100 Subject: [PATCH 73/92] add picorv test to functional backend --- kernel/functional.cc | 17 +- tests/functional/picorv32.v | 3044 +++++++++++++++++++++++++++ tests/functional/picorv32_tb.v | 62 + tests/functional/rtlil_cells.py | 11 + tests/functional/test_functional.py | 3 + 5 files changed, 3135 insertions(+), 2 deletions(-) create mode 100644 tests/functional/picorv32.v create mode 100644 tests/functional/picorv32_tb.v diff --git a/kernel/functional.cc b/kernel/functional.cc index 202eaa50221..eaae3f09808 100644 --- a/kernel/functional.cc +++ b/kernel/functional.cc @@ -530,6 +530,7 @@ class FunctionalIRConstruction { memories[mem.cell] = &mem; } } +private: Node concatenate_read_results(Mem *mem, vector results) { // sanity check: all read ports concatenated should equal to the RD_DATA port @@ -632,6 +633,17 @@ class FunctionalIRConstruction { } } } + void undriven(const char *name) { + log_error("The design contains an undriven signal %s. This is not supported by the functional backend. " + "Call setundef with appropriate options to avoid this error.\n", name); + } + // we perform this check separately to give better error messages that include the wire or port name + void check_undriven(DriveSpec const& spec, std::string const& name) { + for(auto const &chunk : spec.chunks()) + if(chunk.is_none()) + undriven(name.c_str()); + } +public: void process_queue() { for (; !queue.empty(); queue.pop_front()) { @@ -660,6 +672,7 @@ class FunctionalIRConstruction { factory.update_pending(pending, node); } else { DriveSpec driver = driver_map(DriveSpec(wire_chunk)); + check_undriven(driver, RTLIL::unescape_id(wire_chunk.wire->name)); Node node = enqueue(driver); factory.suggest_name(node, wire_chunk.wire->name); factory.update_pending(pending, node); @@ -677,6 +690,7 @@ class FunctionalIRConstruction { factory.update_pending(pending, node); } else { DriveSpec driver = driver_map(DriveSpec(port_chunk)); + check_undriven(driver, RTLIL::unescape_id(port_chunk.cell->name) + " port " + RTLIL::unescape_id(port_chunk.port)); factory.update_pending(pending, enqueue(driver)); } } else { @@ -692,8 +706,7 @@ class FunctionalIRConstruction { log_error("Signal %s has multiple drivers. This is not supported by the functional backend. " "If tristate drivers are used, call tristate -formal to avoid this error.\n", log_signal(chunk)); } else if (chunk.is_none()) { - log_error("The design contains an undriven signal %s. This is not supported by the functional backend. " - "Call setundef with appropriate options to avoid this error.\n", log_signal(chunk)); + undriven(log_signal(chunk)); } else { log_error("unhandled drivespec: %s\n", log_signal(chunk)); log_abort(); diff --git a/tests/functional/picorv32.v b/tests/functional/picorv32.v new file mode 100644 index 00000000000..cfc7ce0627a --- /dev/null +++ b/tests/functional/picorv32.v @@ -0,0 +1,3044 @@ +/* + * PicoRV32 -- A Small RISC-V (RV32I) Processor Core + * + * Copyright (C) 2015 Claire Xenia Wolf + * + * 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. + * + */ + +/* verilator lint_off WIDTH */ +/* verilator lint_off PINMISSING */ +/* verilator lint_off CASEOVERLAP */ +/* verilator lint_off CASEINCOMPLETE */ + +`timescale 1 ns / 1 ps +// `default_nettype none +// `define DEBUGNETS +// `define DEBUGREGS +// `define DEBUGASM +// `define DEBUG + +`ifdef DEBUG + `define debug(debug_command) debug_command +`else + `define debug(debug_command) +`endif + +`ifdef FORMAL + `define FORMAL_KEEP (* keep *) + `define assert(assert_expr) assert(assert_expr) +`else + `ifdef DEBUGNETS + `define FORMAL_KEEP (* keep *) + `else + `define FORMAL_KEEP + `endif + `define assert(assert_expr) empty_statement +`endif + +// uncomment this for register file in extra module +// `define PICORV32_REGS picorv32_regs + +// this macro can be used to check if the verilog files in your +// design are read in the correct order. +`define PICORV32_V + + +/*************************************************************** + * picorv32 + ***************************************************************/ + +module picorv32 #( + parameter [ 0:0] ENABLE_COUNTERS = 1, + parameter [ 0:0] ENABLE_COUNTERS64 = 1, + parameter [ 0:0] ENABLE_REGS_16_31 = 1, + parameter [ 0:0] ENABLE_REGS_DUALPORT = 1, + parameter [ 0:0] LATCHED_MEM_RDATA = 0, + parameter [ 0:0] TWO_STAGE_SHIFT = 1, + parameter [ 0:0] BARREL_SHIFTER = 0, + parameter [ 0:0] TWO_CYCLE_COMPARE = 0, + parameter [ 0:0] TWO_CYCLE_ALU = 0, + parameter [ 0:0] COMPRESSED_ISA = 0, + parameter [ 0:0] CATCH_MISALIGN = 1, + parameter [ 0:0] CATCH_ILLINSN = 1, + parameter [ 0:0] ENABLE_PCPI = 0, + parameter [ 0:0] ENABLE_MUL = 0, + parameter [ 0:0] ENABLE_FAST_MUL = 0, + parameter [ 0:0] ENABLE_DIV = 0, + parameter [ 0:0] ENABLE_IRQ = 0, + parameter [ 0:0] ENABLE_IRQ_QREGS = 1, + parameter [ 0:0] ENABLE_IRQ_TIMER = 1, + parameter [ 0:0] ENABLE_TRACE = 0, + parameter [ 0:0] REGS_INIT_ZERO = 0, + parameter [31:0] MASKED_IRQ = 32'h 0000_0000, + parameter [31:0] LATCHED_IRQ = 32'h ffff_ffff, + parameter [31:0] PROGADDR_RESET = 32'h 0000_0000, + parameter [31:0] PROGADDR_IRQ = 32'h 0000_0010, + parameter [31:0] STACKADDR = 32'h ffff_ffff +) ( + input clk, resetn, + output reg trap, + + output reg mem_valid, + output reg mem_instr, + input mem_ready, + + output reg [31:0] mem_addr, + output reg [31:0] mem_wdata, + output reg [ 3:0] mem_wstrb, + input [31:0] mem_rdata, + + // Look-Ahead Interface + output mem_la_read, + output mem_la_write, + output [31:0] mem_la_addr, + output reg [31:0] mem_la_wdata, + output reg [ 3:0] mem_la_wstrb, + + // Pico Co-Processor Interface (PCPI) + output reg pcpi_valid, + output reg [31:0] pcpi_insn, + output [31:0] pcpi_rs1, + output [31:0] pcpi_rs2, + input pcpi_wr, + input [31:0] pcpi_rd, + input pcpi_wait, + input pcpi_ready, + + // IRQ Interface + input [31:0] irq, + output reg [31:0] eoi, + +`ifdef RISCV_FORMAL + output reg rvfi_valid, + output reg [63:0] rvfi_order, + output reg [31:0] rvfi_insn, + output reg rvfi_trap, + output reg rvfi_halt, + output reg rvfi_intr, + output reg [ 1:0] rvfi_mode, + output reg [ 1:0] rvfi_ixl, + output reg [ 4:0] rvfi_rs1_addr, + output reg [ 4:0] rvfi_rs2_addr, + output reg [31:0] rvfi_rs1_rdata, + output reg [31:0] rvfi_rs2_rdata, + output reg [ 4:0] rvfi_rd_addr, + output reg [31:0] rvfi_rd_wdata, + output reg [31:0] rvfi_pc_rdata, + output reg [31:0] rvfi_pc_wdata, + output reg [31:0] rvfi_mem_addr, + output reg [ 3:0] rvfi_mem_rmask, + output reg [ 3:0] rvfi_mem_wmask, + output reg [31:0] rvfi_mem_rdata, + output reg [31:0] rvfi_mem_wdata, + + output reg [63:0] rvfi_csr_mcycle_rmask, + output reg [63:0] rvfi_csr_mcycle_wmask, + output reg [63:0] rvfi_csr_mcycle_rdata, + output reg [63:0] rvfi_csr_mcycle_wdata, + + output reg [63:0] rvfi_csr_minstret_rmask, + output reg [63:0] rvfi_csr_minstret_wmask, + output reg [63:0] rvfi_csr_minstret_rdata, + output reg [63:0] rvfi_csr_minstret_wdata, +`endif + + // Trace Interface + output reg trace_valid, + output reg [35:0] trace_data +); + localparam integer irq_timer = 0; + localparam integer irq_ebreak = 1; + localparam integer irq_buserror = 2; + + localparam integer irqregs_offset = ENABLE_REGS_16_31 ? 32 : 16; + localparam integer regfile_size = (ENABLE_REGS_16_31 ? 32 : 16) + 4*ENABLE_IRQ*ENABLE_IRQ_QREGS; + localparam integer regindex_bits = (ENABLE_REGS_16_31 ? 5 : 4) + ENABLE_IRQ*ENABLE_IRQ_QREGS; + + localparam WITH_PCPI = ENABLE_PCPI || ENABLE_MUL || ENABLE_FAST_MUL || ENABLE_DIV; + + localparam [35:0] TRACE_BRANCH = {4'b 0001, 32'b 0}; + localparam [35:0] TRACE_ADDR = {4'b 0010, 32'b 0}; + localparam [35:0] TRACE_IRQ = {4'b 1000, 32'b 0}; + + reg [63:0] count_cycle, count_instr; + reg [31:0] reg_pc, reg_next_pc, reg_op1, reg_op2, reg_out; + reg [4:0] reg_sh; + + reg [31:0] next_insn_opcode; + reg [31:0] dbg_insn_opcode; + reg [31:0] dbg_insn_addr; + + wire dbg_mem_valid = mem_valid; + wire dbg_mem_instr = mem_instr; + wire dbg_mem_ready = mem_ready; + wire [31:0] dbg_mem_addr = mem_addr; + wire [31:0] dbg_mem_wdata = mem_wdata; + wire [ 3:0] dbg_mem_wstrb = mem_wstrb; + wire [31:0] dbg_mem_rdata = mem_rdata; + + assign pcpi_rs1 = reg_op1; + assign pcpi_rs2 = reg_op2; + + wire [31:0] next_pc; + + reg irq_delay; + reg irq_active; + reg [31:0] irq_mask; + reg [31:0] irq_pending; + reg [31:0] timer; + +`ifndef PICORV32_REGS + reg [31:0] cpuregs [0:regfile_size-1]; + + integer i; + initial begin + if (REGS_INIT_ZERO) begin + for (i = 0; i < regfile_size; i = i+1) + cpuregs[i] = 0; + end + end +`endif + + task empty_statement; + // This task is used by the `assert directive in non-formal mode to + // avoid empty statement (which are unsupported by plain Verilog syntax). + begin end + endtask + +`ifdef DEBUGREGS + wire [31:0] dbg_reg_x0 = 0; + wire [31:0] dbg_reg_x1 = cpuregs[1]; + wire [31:0] dbg_reg_x2 = cpuregs[2]; + wire [31:0] dbg_reg_x3 = cpuregs[3]; + wire [31:0] dbg_reg_x4 = cpuregs[4]; + wire [31:0] dbg_reg_x5 = cpuregs[5]; + wire [31:0] dbg_reg_x6 = cpuregs[6]; + wire [31:0] dbg_reg_x7 = cpuregs[7]; + wire [31:0] dbg_reg_x8 = cpuregs[8]; + wire [31:0] dbg_reg_x9 = cpuregs[9]; + wire [31:0] dbg_reg_x10 = cpuregs[10]; + wire [31:0] dbg_reg_x11 = cpuregs[11]; + wire [31:0] dbg_reg_x12 = cpuregs[12]; + wire [31:0] dbg_reg_x13 = cpuregs[13]; + wire [31:0] dbg_reg_x14 = cpuregs[14]; + wire [31:0] dbg_reg_x15 = cpuregs[15]; + wire [31:0] dbg_reg_x16 = cpuregs[16]; + wire [31:0] dbg_reg_x17 = cpuregs[17]; + wire [31:0] dbg_reg_x18 = cpuregs[18]; + wire [31:0] dbg_reg_x19 = cpuregs[19]; + wire [31:0] dbg_reg_x20 = cpuregs[20]; + wire [31:0] dbg_reg_x21 = cpuregs[21]; + wire [31:0] dbg_reg_x22 = cpuregs[22]; + wire [31:0] dbg_reg_x23 = cpuregs[23]; + wire [31:0] dbg_reg_x24 = cpuregs[24]; + wire [31:0] dbg_reg_x25 = cpuregs[25]; + wire [31:0] dbg_reg_x26 = cpuregs[26]; + wire [31:0] dbg_reg_x27 = cpuregs[27]; + wire [31:0] dbg_reg_x28 = cpuregs[28]; + wire [31:0] dbg_reg_x29 = cpuregs[29]; + wire [31:0] dbg_reg_x30 = cpuregs[30]; + wire [31:0] dbg_reg_x31 = cpuregs[31]; +`endif + + // Internal PCPI Cores + + wire pcpi_mul_wr; + wire [31:0] pcpi_mul_rd; + wire pcpi_mul_wait; + wire pcpi_mul_ready; + + wire pcpi_div_wr; + wire [31:0] pcpi_div_rd; + wire pcpi_div_wait; + wire pcpi_div_ready; + + reg pcpi_int_wr; + reg [31:0] pcpi_int_rd; + reg pcpi_int_wait; + reg pcpi_int_ready; + + generate if (ENABLE_FAST_MUL) begin + picorv32_pcpi_fast_mul pcpi_mul ( + .clk (clk ), + .resetn (resetn ), + .pcpi_valid(pcpi_valid ), + .pcpi_insn (pcpi_insn ), + .pcpi_rs1 (pcpi_rs1 ), + .pcpi_rs2 (pcpi_rs2 ), + .pcpi_wr (pcpi_mul_wr ), + .pcpi_rd (pcpi_mul_rd ), + .pcpi_wait (pcpi_mul_wait ), + .pcpi_ready(pcpi_mul_ready ) + ); + end else if (ENABLE_MUL) begin + picorv32_pcpi_mul pcpi_mul ( + .clk (clk ), + .resetn (resetn ), + .pcpi_valid(pcpi_valid ), + .pcpi_insn (pcpi_insn ), + .pcpi_rs1 (pcpi_rs1 ), + .pcpi_rs2 (pcpi_rs2 ), + .pcpi_wr (pcpi_mul_wr ), + .pcpi_rd (pcpi_mul_rd ), + .pcpi_wait (pcpi_mul_wait ), + .pcpi_ready(pcpi_mul_ready ) + ); + end else begin + assign pcpi_mul_wr = 0; + assign pcpi_mul_rd = 32'bx; + assign pcpi_mul_wait = 0; + assign pcpi_mul_ready = 0; + end endgenerate + + generate if (ENABLE_DIV) begin + picorv32_pcpi_div pcpi_div ( + .clk (clk ), + .resetn (resetn ), + .pcpi_valid(pcpi_valid ), + .pcpi_insn (pcpi_insn ), + .pcpi_rs1 (pcpi_rs1 ), + .pcpi_rs2 (pcpi_rs2 ), + .pcpi_wr (pcpi_div_wr ), + .pcpi_rd (pcpi_div_rd ), + .pcpi_wait (pcpi_div_wait ), + .pcpi_ready(pcpi_div_ready ) + ); + end else begin + assign pcpi_div_wr = 0; + assign pcpi_div_rd = 32'bx; + assign pcpi_div_wait = 0; + assign pcpi_div_ready = 0; + end endgenerate + + always @* begin + pcpi_int_wr = 0; + pcpi_int_rd = 32'bx; + pcpi_int_wait = |{ENABLE_PCPI && pcpi_wait, (ENABLE_MUL || ENABLE_FAST_MUL) && pcpi_mul_wait, ENABLE_DIV && pcpi_div_wait}; + pcpi_int_ready = |{ENABLE_PCPI && pcpi_ready, (ENABLE_MUL || ENABLE_FAST_MUL) && pcpi_mul_ready, ENABLE_DIV && pcpi_div_ready}; + + (* parallel_case *) + case (1'b1) + ENABLE_PCPI && pcpi_ready: begin + pcpi_int_wr = ENABLE_PCPI ? pcpi_wr : 0; + pcpi_int_rd = ENABLE_PCPI ? pcpi_rd : 0; + end + (ENABLE_MUL || ENABLE_FAST_MUL) && pcpi_mul_ready: begin + pcpi_int_wr = pcpi_mul_wr; + pcpi_int_rd = pcpi_mul_rd; + end + ENABLE_DIV && pcpi_div_ready: begin + pcpi_int_wr = pcpi_div_wr; + pcpi_int_rd = pcpi_div_rd; + end + endcase + end + + + // Memory Interface + + reg [1:0] mem_state; + reg [1:0] mem_wordsize; + reg [31:0] mem_rdata_word; + reg [31:0] mem_rdata_q; + reg mem_do_prefetch; + reg mem_do_rinst; + reg mem_do_rdata; + reg mem_do_wdata; + + wire mem_xfer; + reg mem_la_secondword, mem_la_firstword_reg, last_mem_valid; + wire mem_la_firstword = COMPRESSED_ISA && (mem_do_prefetch || mem_do_rinst) && next_pc[1] && !mem_la_secondword; + wire mem_la_firstword_xfer = COMPRESSED_ISA && mem_xfer && (!last_mem_valid ? mem_la_firstword : mem_la_firstword_reg); + + reg prefetched_high_word; + reg clear_prefetched_high_word; + reg [15:0] mem_16bit_buffer; + + wire [31:0] mem_rdata_latched_noshuffle; + wire [31:0] mem_rdata_latched; + + wire mem_la_use_prefetched_high_word = COMPRESSED_ISA && mem_la_firstword && prefetched_high_word && !clear_prefetched_high_word; + assign mem_xfer = (mem_valid && mem_ready) || (mem_la_use_prefetched_high_word && mem_do_rinst); + + wire mem_busy = |{mem_do_prefetch, mem_do_rinst, mem_do_rdata, mem_do_wdata}; + wire mem_done = resetn && ((mem_xfer && |mem_state && (mem_do_rinst || mem_do_rdata || mem_do_wdata)) || (&mem_state && mem_do_rinst)) && + (!mem_la_firstword || (~&mem_rdata_latched[1:0] && mem_xfer)); + + assign mem_la_write = resetn && !mem_state && mem_do_wdata; + assign mem_la_read = resetn && ((!mem_la_use_prefetched_high_word && !mem_state && (mem_do_rinst || mem_do_prefetch || mem_do_rdata)) || + (COMPRESSED_ISA && mem_xfer && (!last_mem_valid ? mem_la_firstword : mem_la_firstword_reg) && !mem_la_secondword && &mem_rdata_latched[1:0])); + assign mem_la_addr = (mem_do_prefetch || mem_do_rinst) ? {next_pc[31:2] + mem_la_firstword_xfer, 2'b00} : {reg_op1[31:2], 2'b00}; + + assign mem_rdata_latched_noshuffle = (mem_xfer || LATCHED_MEM_RDATA) ? mem_rdata : mem_rdata_q; + + assign mem_rdata_latched = COMPRESSED_ISA && mem_la_use_prefetched_high_word ? {16'bx, mem_16bit_buffer} : + COMPRESSED_ISA && mem_la_secondword ? {mem_rdata_latched_noshuffle[15:0], mem_16bit_buffer} : + COMPRESSED_ISA && mem_la_firstword ? {16'bx, mem_rdata_latched_noshuffle[31:16]} : mem_rdata_latched_noshuffle; + + always @(posedge clk) begin + if (!resetn) begin + mem_la_firstword_reg <= 0; + last_mem_valid <= 0; + end else begin + if (!last_mem_valid) + mem_la_firstword_reg <= mem_la_firstword; + last_mem_valid <= mem_valid && !mem_ready; + end + end + + always @* begin + (* full_case *) + case (mem_wordsize) + 0: begin + mem_la_wdata = reg_op2; + mem_la_wstrb = 4'b1111; + mem_rdata_word = mem_rdata; + end + 1: begin + mem_la_wdata = {2{reg_op2[15:0]}}; + mem_la_wstrb = reg_op1[1] ? 4'b1100 : 4'b0011; + case (reg_op1[1]) + 1'b0: mem_rdata_word = {16'b0, mem_rdata[15: 0]}; + 1'b1: mem_rdata_word = {16'b0, mem_rdata[31:16]}; + endcase + end + 2: begin + mem_la_wdata = {4{reg_op2[7:0]}}; + mem_la_wstrb = 4'b0001 << reg_op1[1:0]; + case (reg_op1[1:0]) + 2'b00: mem_rdata_word = {24'b0, mem_rdata[ 7: 0]}; + 2'b01: mem_rdata_word = {24'b0, mem_rdata[15: 8]}; + 2'b10: mem_rdata_word = {24'b0, mem_rdata[23:16]}; + 2'b11: mem_rdata_word = {24'b0, mem_rdata[31:24]}; + endcase + end + endcase + end + + always @(posedge clk) begin + if (mem_xfer) begin + mem_rdata_q <= COMPRESSED_ISA ? mem_rdata_latched : mem_rdata; + next_insn_opcode <= COMPRESSED_ISA ? mem_rdata_latched : mem_rdata; + end + + if (COMPRESSED_ISA && mem_done && (mem_do_prefetch || mem_do_rinst)) begin + case (mem_rdata_latched[1:0]) + 2'b00: begin // Quadrant 0 + case (mem_rdata_latched[15:13]) + 3'b000: begin // C.ADDI4SPN + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:20] <= {2'b0, mem_rdata_latched[10:7], mem_rdata_latched[12:11], mem_rdata_latched[5], mem_rdata_latched[6], 2'b00}; + end + 3'b010: begin // C.LW + mem_rdata_q[31:20] <= {5'b0, mem_rdata_latched[5], mem_rdata_latched[12:10], mem_rdata_latched[6], 2'b00}; + mem_rdata_q[14:12] <= 3'b 010; + end + 3'b 110: begin // C.SW + {mem_rdata_q[31:25], mem_rdata_q[11:7]} <= {5'b0, mem_rdata_latched[5], mem_rdata_latched[12:10], mem_rdata_latched[6], 2'b00}; + mem_rdata_q[14:12] <= 3'b 010; + end + endcase + end + 2'b01: begin // Quadrant 1 + case (mem_rdata_latched[15:13]) + 3'b 000: begin // C.ADDI + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:20] <= $signed({mem_rdata_latched[12], mem_rdata_latched[6:2]}); + end + 3'b 010: begin // C.LI + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:20] <= $signed({mem_rdata_latched[12], mem_rdata_latched[6:2]}); + end + 3'b 011: begin + if (mem_rdata_latched[11:7] == 2) begin // C.ADDI16SP + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:20] <= $signed({mem_rdata_latched[12], mem_rdata_latched[4:3], + mem_rdata_latched[5], mem_rdata_latched[2], mem_rdata_latched[6], 4'b 0000}); + end else begin // C.LUI + mem_rdata_q[31:12] <= $signed({mem_rdata_latched[12], mem_rdata_latched[6:2]}); + end + end + 3'b100: begin + if (mem_rdata_latched[11:10] == 2'b00) begin // C.SRLI + mem_rdata_q[31:25] <= 7'b0000000; + mem_rdata_q[14:12] <= 3'b 101; + end + if (mem_rdata_latched[11:10] == 2'b01) begin // C.SRAI + mem_rdata_q[31:25] <= 7'b0100000; + mem_rdata_q[14:12] <= 3'b 101; + end + if (mem_rdata_latched[11:10] == 2'b10) begin // C.ANDI + mem_rdata_q[14:12] <= 3'b111; + mem_rdata_q[31:20] <= $signed({mem_rdata_latched[12], mem_rdata_latched[6:2]}); + end + if (mem_rdata_latched[12:10] == 3'b011) begin // C.SUB, C.XOR, C.OR, C.AND + if (mem_rdata_latched[6:5] == 2'b00) mem_rdata_q[14:12] <= 3'b000; + if (mem_rdata_latched[6:5] == 2'b01) mem_rdata_q[14:12] <= 3'b100; + if (mem_rdata_latched[6:5] == 2'b10) mem_rdata_q[14:12] <= 3'b110; + if (mem_rdata_latched[6:5] == 2'b11) mem_rdata_q[14:12] <= 3'b111; + mem_rdata_q[31:25] <= mem_rdata_latched[6:5] == 2'b00 ? 7'b0100000 : 7'b0000000; + end + end + 3'b 110: begin // C.BEQZ + mem_rdata_q[14:12] <= 3'b000; + { mem_rdata_q[31], mem_rdata_q[7], mem_rdata_q[30:25], mem_rdata_q[11:8] } <= + $signed({mem_rdata_latched[12], mem_rdata_latched[6:5], mem_rdata_latched[2], + mem_rdata_latched[11:10], mem_rdata_latched[4:3]}); + end + 3'b 111: begin // C.BNEZ + mem_rdata_q[14:12] <= 3'b001; + { mem_rdata_q[31], mem_rdata_q[7], mem_rdata_q[30:25], mem_rdata_q[11:8] } <= + $signed({mem_rdata_latched[12], mem_rdata_latched[6:5], mem_rdata_latched[2], + mem_rdata_latched[11:10], mem_rdata_latched[4:3]}); + end + endcase + end + 2'b10: begin // Quadrant 2 + case (mem_rdata_latched[15:13]) + 3'b000: begin // C.SLLI + mem_rdata_q[31:25] <= 7'b0000000; + mem_rdata_q[14:12] <= 3'b 001; + end + 3'b010: begin // C.LWSP + mem_rdata_q[31:20] <= {4'b0, mem_rdata_latched[3:2], mem_rdata_latched[12], mem_rdata_latched[6:4], 2'b00}; + mem_rdata_q[14:12] <= 3'b 010; + end + 3'b100: begin + if (mem_rdata_latched[12] == 0 && mem_rdata_latched[6:2] == 0) begin // C.JR + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:20] <= 12'b0; + end + if (mem_rdata_latched[12] == 0 && mem_rdata_latched[6:2] != 0) begin // C.MV + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:25] <= 7'b0000000; + end + if (mem_rdata_latched[12] != 0 && mem_rdata_latched[11:7] != 0 && mem_rdata_latched[6:2] == 0) begin // C.JALR + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:20] <= 12'b0; + end + if (mem_rdata_latched[12] != 0 && mem_rdata_latched[6:2] != 0) begin // C.ADD + mem_rdata_q[14:12] <= 3'b000; + mem_rdata_q[31:25] <= 7'b0000000; + end + end + 3'b110: begin // C.SWSP + {mem_rdata_q[31:25], mem_rdata_q[11:7]} <= {4'b0, mem_rdata_latched[8:7], mem_rdata_latched[12:9], 2'b00}; + mem_rdata_q[14:12] <= 3'b 010; + end + endcase + end + endcase + end + end + + always @(posedge clk) begin + if (resetn && !trap) begin + if (mem_do_prefetch || mem_do_rinst || mem_do_rdata) + `assert(!mem_do_wdata); + + if (mem_do_prefetch || mem_do_rinst) + `assert(!mem_do_rdata); + + if (mem_do_rdata) + `assert(!mem_do_prefetch && !mem_do_rinst); + + if (mem_do_wdata) + `assert(!(mem_do_prefetch || mem_do_rinst || mem_do_rdata)); + + if (mem_state == 2 || mem_state == 3) + `assert(mem_valid || mem_do_prefetch); + end + end + + always @(posedge clk) begin + if (!resetn || trap) begin + if (!resetn) + mem_state <= 0; + if (!resetn || mem_ready) + mem_valid <= 0; + mem_la_secondword <= 0; + prefetched_high_word <= 0; + end else begin + if (mem_la_read || mem_la_write) begin + mem_addr <= mem_la_addr; + mem_wstrb <= mem_la_wstrb & {4{mem_la_write}}; + end + if (mem_la_write) begin + mem_wdata <= mem_la_wdata; + end + case (mem_state) + 0: begin + if (mem_do_prefetch || mem_do_rinst || mem_do_rdata) begin + mem_valid <= !mem_la_use_prefetched_high_word; + mem_instr <= mem_do_prefetch || mem_do_rinst; + mem_wstrb <= 0; + mem_state <= 1; + end + if (mem_do_wdata) begin + mem_valid <= 1; + mem_instr <= 0; + mem_state <= 2; + end + end + 1: begin + `assert(mem_wstrb == 0); + `assert(mem_do_prefetch || mem_do_rinst || mem_do_rdata); + `assert(mem_valid == !mem_la_use_prefetched_high_word); + `assert(mem_instr == (mem_do_prefetch || mem_do_rinst)); + if (mem_xfer) begin + if (COMPRESSED_ISA && mem_la_read) begin + mem_valid <= 1; + mem_la_secondword <= 1; + if (!mem_la_use_prefetched_high_word) + mem_16bit_buffer <= mem_rdata[31:16]; + end else begin + mem_valid <= 0; + mem_la_secondword <= 0; + if (COMPRESSED_ISA && !mem_do_rdata) begin + if (~&mem_rdata[1:0] || mem_la_secondword) begin + mem_16bit_buffer <= mem_rdata[31:16]; + prefetched_high_word <= 1; + end else begin + prefetched_high_word <= 0; + end + end + mem_state <= mem_do_rinst || mem_do_rdata ? 0 : 3; + end + end + end + 2: begin + `assert(mem_wstrb != 0); + `assert(mem_do_wdata); + if (mem_xfer) begin + mem_valid <= 0; + mem_state <= 0; + end + end + 3: begin + `assert(mem_wstrb == 0); + `assert(mem_do_prefetch); + if (mem_do_rinst) begin + mem_state <= 0; + end + end + endcase + end + + if (clear_prefetched_high_word) + prefetched_high_word <= 0; + end + + + // Instruction Decoder + + reg instr_lui, instr_auipc, instr_jal, instr_jalr; + reg instr_beq, instr_bne, instr_blt, instr_bge, instr_bltu, instr_bgeu; + reg instr_lb, instr_lh, instr_lw, instr_lbu, instr_lhu, instr_sb, instr_sh, instr_sw; + reg instr_addi, instr_slti, instr_sltiu, instr_xori, instr_ori, instr_andi, instr_slli, instr_srli, instr_srai; + reg instr_add, instr_sub, instr_sll, instr_slt, instr_sltu, instr_xor, instr_srl, instr_sra, instr_or, instr_and; + reg instr_rdcycle, instr_rdcycleh, instr_rdinstr, instr_rdinstrh, instr_ecall_ebreak; + reg instr_getq, instr_setq, instr_retirq, instr_maskirq, instr_waitirq, instr_timer; + wire instr_trap; + + reg [regindex_bits-1:0] decoded_rd, decoded_rs1, decoded_rs2; + reg [31:0] decoded_imm, decoded_imm_j; + reg decoder_trigger; + reg decoder_trigger_q; + reg decoder_pseudo_trigger; + reg decoder_pseudo_trigger_q; + reg compressed_instr; + + reg is_lui_auipc_jal; + reg is_lb_lh_lw_lbu_lhu; + reg is_slli_srli_srai; + reg is_jalr_addi_slti_sltiu_xori_ori_andi; + reg is_sb_sh_sw; + reg is_sll_srl_sra; + reg is_lui_auipc_jal_jalr_addi_add_sub; + reg is_slti_blt_slt; + reg is_sltiu_bltu_sltu; + reg is_beq_bne_blt_bge_bltu_bgeu; + reg is_lbu_lhu_lw; + reg is_alu_reg_imm; + reg is_alu_reg_reg; + reg is_compare; + + assign instr_trap = (CATCH_ILLINSN || WITH_PCPI) && !{instr_lui, instr_auipc, instr_jal, instr_jalr, + instr_beq, instr_bne, instr_blt, instr_bge, instr_bltu, instr_bgeu, + instr_lb, instr_lh, instr_lw, instr_lbu, instr_lhu, instr_sb, instr_sh, instr_sw, + instr_addi, instr_slti, instr_sltiu, instr_xori, instr_ori, instr_andi, instr_slli, instr_srli, instr_srai, + instr_add, instr_sub, instr_sll, instr_slt, instr_sltu, instr_xor, instr_srl, instr_sra, instr_or, instr_and, + instr_rdcycle, instr_rdcycleh, instr_rdinstr, instr_rdinstrh, + instr_getq, instr_setq, instr_retirq, instr_maskirq, instr_waitirq, instr_timer}; + + wire is_rdcycle_rdcycleh_rdinstr_rdinstrh; + assign is_rdcycle_rdcycleh_rdinstr_rdinstrh = |{instr_rdcycle, instr_rdcycleh, instr_rdinstr, instr_rdinstrh}; + + reg [63:0] new_ascii_instr; + `FORMAL_KEEP reg [63:0] dbg_ascii_instr; + `FORMAL_KEEP reg [31:0] dbg_insn_imm; + `FORMAL_KEEP reg [4:0] dbg_insn_rs1; + `FORMAL_KEEP reg [4:0] dbg_insn_rs2; + `FORMAL_KEEP reg [4:0] dbg_insn_rd; + `FORMAL_KEEP reg [31:0] dbg_rs1val; + `FORMAL_KEEP reg [31:0] dbg_rs2val; + `FORMAL_KEEP reg dbg_rs1val_valid; + `FORMAL_KEEP reg dbg_rs2val_valid; + + always @* begin + new_ascii_instr = ""; + + if (instr_lui) new_ascii_instr = "lui"; + if (instr_auipc) new_ascii_instr = "auipc"; + if (instr_jal) new_ascii_instr = "jal"; + if (instr_jalr) new_ascii_instr = "jalr"; + + if (instr_beq) new_ascii_instr = "beq"; + if (instr_bne) new_ascii_instr = "bne"; + if (instr_blt) new_ascii_instr = "blt"; + if (instr_bge) new_ascii_instr = "bge"; + if (instr_bltu) new_ascii_instr = "bltu"; + if (instr_bgeu) new_ascii_instr = "bgeu"; + + if (instr_lb) new_ascii_instr = "lb"; + if (instr_lh) new_ascii_instr = "lh"; + if (instr_lw) new_ascii_instr = "lw"; + if (instr_lbu) new_ascii_instr = "lbu"; + if (instr_lhu) new_ascii_instr = "lhu"; + if (instr_sb) new_ascii_instr = "sb"; + if (instr_sh) new_ascii_instr = "sh"; + if (instr_sw) new_ascii_instr = "sw"; + + if (instr_addi) new_ascii_instr = "addi"; + if (instr_slti) new_ascii_instr = "slti"; + if (instr_sltiu) new_ascii_instr = "sltiu"; + if (instr_xori) new_ascii_instr = "xori"; + if (instr_ori) new_ascii_instr = "ori"; + if (instr_andi) new_ascii_instr = "andi"; + if (instr_slli) new_ascii_instr = "slli"; + if (instr_srli) new_ascii_instr = "srli"; + if (instr_srai) new_ascii_instr = "srai"; + + if (instr_add) new_ascii_instr = "add"; + if (instr_sub) new_ascii_instr = "sub"; + if (instr_sll) new_ascii_instr = "sll"; + if (instr_slt) new_ascii_instr = "slt"; + if (instr_sltu) new_ascii_instr = "sltu"; + if (instr_xor) new_ascii_instr = "xor"; + if (instr_srl) new_ascii_instr = "srl"; + if (instr_sra) new_ascii_instr = "sra"; + if (instr_or) new_ascii_instr = "or"; + if (instr_and) new_ascii_instr = "and"; + + if (instr_rdcycle) new_ascii_instr = "rdcycle"; + if (instr_rdcycleh) new_ascii_instr = "rdcycleh"; + if (instr_rdinstr) new_ascii_instr = "rdinstr"; + if (instr_rdinstrh) new_ascii_instr = "rdinstrh"; + + if (instr_getq) new_ascii_instr = "getq"; + if (instr_setq) new_ascii_instr = "setq"; + if (instr_retirq) new_ascii_instr = "retirq"; + if (instr_maskirq) new_ascii_instr = "maskirq"; + if (instr_waitirq) new_ascii_instr = "waitirq"; + if (instr_timer) new_ascii_instr = "timer"; + end + + reg [63:0] q_ascii_instr; + reg [31:0] q_insn_imm; + reg [31:0] q_insn_opcode; + reg [4:0] q_insn_rs1; + reg [4:0] q_insn_rs2; + reg [4:0] q_insn_rd; + reg dbg_next; + + wire launch_next_insn; + reg dbg_valid_insn; + + reg [63:0] cached_ascii_instr; + reg [31:0] cached_insn_imm; + reg [31:0] cached_insn_opcode; + reg [4:0] cached_insn_rs1; + reg [4:0] cached_insn_rs2; + reg [4:0] cached_insn_rd; + + always @(posedge clk) begin + q_ascii_instr <= dbg_ascii_instr; + q_insn_imm <= dbg_insn_imm; + q_insn_opcode <= dbg_insn_opcode; + q_insn_rs1 <= dbg_insn_rs1; + q_insn_rs2 <= dbg_insn_rs2; + q_insn_rd <= dbg_insn_rd; + dbg_next <= launch_next_insn; + + if (!resetn || trap) + dbg_valid_insn <= 0; + else if (launch_next_insn) + dbg_valid_insn <= 1; + + if (decoder_trigger_q) begin + cached_ascii_instr <= new_ascii_instr; + cached_insn_imm <= decoded_imm; + if (&next_insn_opcode[1:0]) + cached_insn_opcode <= next_insn_opcode; + else + cached_insn_opcode <= {16'b0, next_insn_opcode[15:0]}; + cached_insn_rs1 <= decoded_rs1; + cached_insn_rs2 <= decoded_rs2; + cached_insn_rd <= decoded_rd; + end + + if (launch_next_insn) begin + dbg_insn_addr <= next_pc; + end + end + + always @* begin + dbg_ascii_instr = q_ascii_instr; + dbg_insn_imm = q_insn_imm; + dbg_insn_opcode = q_insn_opcode; + dbg_insn_rs1 = q_insn_rs1; + dbg_insn_rs2 = q_insn_rs2; + dbg_insn_rd = q_insn_rd; + + if (dbg_next) begin + if (decoder_pseudo_trigger_q) begin + dbg_ascii_instr = cached_ascii_instr; + dbg_insn_imm = cached_insn_imm; + dbg_insn_opcode = cached_insn_opcode; + dbg_insn_rs1 = cached_insn_rs1; + dbg_insn_rs2 = cached_insn_rs2; + dbg_insn_rd = cached_insn_rd; + end else begin + dbg_ascii_instr = new_ascii_instr; + if (&next_insn_opcode[1:0]) + dbg_insn_opcode = next_insn_opcode; + else + dbg_insn_opcode = {16'b0, next_insn_opcode[15:0]}; + dbg_insn_imm = decoded_imm; + dbg_insn_rs1 = decoded_rs1; + dbg_insn_rs2 = decoded_rs2; + dbg_insn_rd = decoded_rd; + end + end + end + +`ifdef DEBUGASM + always @(posedge clk) begin + if (dbg_next) begin + $display("debugasm %x %x %s", dbg_insn_addr, dbg_insn_opcode, dbg_ascii_instr ? dbg_ascii_instr : "*"); + end + end +`endif + +`ifdef DEBUG + always @(posedge clk) begin + if (dbg_next) begin + if (&dbg_insn_opcode[1:0]) + $display("DECODE: 0x%08x 0x%08x %-0s", dbg_insn_addr, dbg_insn_opcode, dbg_ascii_instr ? dbg_ascii_instr : "UNKNOWN"); + else + $display("DECODE: 0x%08x 0x%04x %-0s", dbg_insn_addr, dbg_insn_opcode[15:0], dbg_ascii_instr ? dbg_ascii_instr : "UNKNOWN"); + end + end +`endif + + always @(posedge clk) begin + is_lui_auipc_jal <= |{instr_lui, instr_auipc, instr_jal}; + is_lui_auipc_jal_jalr_addi_add_sub <= |{instr_lui, instr_auipc, instr_jal, instr_jalr, instr_addi, instr_add, instr_sub}; + is_slti_blt_slt <= |{instr_slti, instr_blt, instr_slt}; + is_sltiu_bltu_sltu <= |{instr_sltiu, instr_bltu, instr_sltu}; + is_lbu_lhu_lw <= |{instr_lbu, instr_lhu, instr_lw}; + is_compare <= |{is_beq_bne_blt_bge_bltu_bgeu, instr_slti, instr_slt, instr_sltiu, instr_sltu}; + + if (mem_do_rinst && mem_done) begin + instr_lui <= mem_rdata_latched[6:0] == 7'b0110111; + instr_auipc <= mem_rdata_latched[6:0] == 7'b0010111; + instr_jal <= mem_rdata_latched[6:0] == 7'b1101111; + instr_jalr <= mem_rdata_latched[6:0] == 7'b1100111 && mem_rdata_latched[14:12] == 3'b000; + instr_retirq <= mem_rdata_latched[6:0] == 7'b0001011 && mem_rdata_latched[31:25] == 7'b0000010 && ENABLE_IRQ; + instr_waitirq <= mem_rdata_latched[6:0] == 7'b0001011 && mem_rdata_latched[31:25] == 7'b0000100 && ENABLE_IRQ; + + is_beq_bne_blt_bge_bltu_bgeu <= mem_rdata_latched[6:0] == 7'b1100011; + is_lb_lh_lw_lbu_lhu <= mem_rdata_latched[6:0] == 7'b0000011; + is_sb_sh_sw <= mem_rdata_latched[6:0] == 7'b0100011; + is_alu_reg_imm <= mem_rdata_latched[6:0] == 7'b0010011; + is_alu_reg_reg <= mem_rdata_latched[6:0] == 7'b0110011; + + { decoded_imm_j[31:20], decoded_imm_j[10:1], decoded_imm_j[11], decoded_imm_j[19:12], decoded_imm_j[0] } <= $signed({mem_rdata_latched[31:12], 1'b0}); + + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= mem_rdata_latched[19:15]; + decoded_rs2 <= mem_rdata_latched[24:20]; + + if (mem_rdata_latched[6:0] == 7'b0001011 && mem_rdata_latched[31:25] == 7'b0000000 && ENABLE_IRQ && ENABLE_IRQ_QREGS) + decoded_rs1[regindex_bits-1] <= 1; // instr_getq + + if (mem_rdata_latched[6:0] == 7'b0001011 && mem_rdata_latched[31:25] == 7'b0000010 && ENABLE_IRQ) + decoded_rs1 <= ENABLE_IRQ_QREGS ? irqregs_offset : 3; // instr_retirq + + compressed_instr <= 0; + if (COMPRESSED_ISA && mem_rdata_latched[1:0] != 2'b11) begin + compressed_instr <= 1; + decoded_rd <= 0; + decoded_rs1 <= 0; + decoded_rs2 <= 0; + + { decoded_imm_j[31:11], decoded_imm_j[4], decoded_imm_j[9:8], decoded_imm_j[10], decoded_imm_j[6], + decoded_imm_j[7], decoded_imm_j[3:1], decoded_imm_j[5], decoded_imm_j[0] } <= $signed({mem_rdata_latched[12:2], 1'b0}); + + case (mem_rdata_latched[1:0]) + 2'b00: begin // Quadrant 0 + case (mem_rdata_latched[15:13]) + 3'b000: begin // C.ADDI4SPN + is_alu_reg_imm <= |mem_rdata_latched[12:5]; + decoded_rs1 <= 2; + decoded_rd <= 8 + mem_rdata_latched[4:2]; + end + 3'b010: begin // C.LW + is_lb_lh_lw_lbu_lhu <= 1; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + decoded_rd <= 8 + mem_rdata_latched[4:2]; + end + 3'b110: begin // C.SW + is_sb_sh_sw <= 1; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + decoded_rs2 <= 8 + mem_rdata_latched[4:2]; + end + endcase + end + 2'b01: begin // Quadrant 1 + case (mem_rdata_latched[15:13]) + 3'b000: begin // C.NOP / C.ADDI + is_alu_reg_imm <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= mem_rdata_latched[11:7]; + end + 3'b001: begin // C.JAL + instr_jal <= 1; + decoded_rd <= 1; + end + 3'b 010: begin // C.LI + is_alu_reg_imm <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= 0; + end + 3'b 011: begin + if (mem_rdata_latched[12] || mem_rdata_latched[6:2]) begin + if (mem_rdata_latched[11:7] == 2) begin // C.ADDI16SP + is_alu_reg_imm <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= mem_rdata_latched[11:7]; + end else begin // C.LUI + instr_lui <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= 0; + end + end + end + 3'b100: begin + if (!mem_rdata_latched[11] && !mem_rdata_latched[12]) begin // C.SRLI, C.SRAI + is_alu_reg_imm <= 1; + decoded_rd <= 8 + mem_rdata_latched[9:7]; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + decoded_rs2 <= {mem_rdata_latched[12], mem_rdata_latched[6:2]}; + end + if (mem_rdata_latched[11:10] == 2'b10) begin // C.ANDI + is_alu_reg_imm <= 1; + decoded_rd <= 8 + mem_rdata_latched[9:7]; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + end + if (mem_rdata_latched[12:10] == 3'b011) begin // C.SUB, C.XOR, C.OR, C.AND + is_alu_reg_reg <= 1; + decoded_rd <= 8 + mem_rdata_latched[9:7]; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + decoded_rs2 <= 8 + mem_rdata_latched[4:2]; + end + end + 3'b101: begin // C.J + instr_jal <= 1; + end + 3'b110: begin // C.BEQZ + is_beq_bne_blt_bge_bltu_bgeu <= 1; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + decoded_rs2 <= 0; + end + 3'b111: begin // C.BNEZ + is_beq_bne_blt_bge_bltu_bgeu <= 1; + decoded_rs1 <= 8 + mem_rdata_latched[9:7]; + decoded_rs2 <= 0; + end + endcase + end + 2'b10: begin // Quadrant 2 + case (mem_rdata_latched[15:13]) + 3'b000: begin // C.SLLI + if (!mem_rdata_latched[12]) begin + is_alu_reg_imm <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= mem_rdata_latched[11:7]; + decoded_rs2 <= {mem_rdata_latched[12], mem_rdata_latched[6:2]}; + end + end + 3'b010: begin // C.LWSP + if (mem_rdata_latched[11:7]) begin + is_lb_lh_lw_lbu_lhu <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= 2; + end + end + 3'b100: begin + if (mem_rdata_latched[12] == 0 && mem_rdata_latched[11:7] != 0 && mem_rdata_latched[6:2] == 0) begin // C.JR + instr_jalr <= 1; + decoded_rd <= 0; + decoded_rs1 <= mem_rdata_latched[11:7]; + end + if (mem_rdata_latched[12] == 0 && mem_rdata_latched[6:2] != 0) begin // C.MV + is_alu_reg_reg <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= 0; + decoded_rs2 <= mem_rdata_latched[6:2]; + end + if (mem_rdata_latched[12] != 0 && mem_rdata_latched[11:7] != 0 && mem_rdata_latched[6:2] == 0) begin // C.JALR + instr_jalr <= 1; + decoded_rd <= 1; + decoded_rs1 <= mem_rdata_latched[11:7]; + end + if (mem_rdata_latched[12] != 0 && mem_rdata_latched[6:2] != 0) begin // C.ADD + is_alu_reg_reg <= 1; + decoded_rd <= mem_rdata_latched[11:7]; + decoded_rs1 <= mem_rdata_latched[11:7]; + decoded_rs2 <= mem_rdata_latched[6:2]; + end + end + 3'b110: begin // C.SWSP + is_sb_sh_sw <= 1; + decoded_rs1 <= 2; + decoded_rs2 <= mem_rdata_latched[6:2]; + end + endcase + end + endcase + end + end + + if (decoder_trigger && !decoder_pseudo_trigger) begin + pcpi_insn <= WITH_PCPI ? mem_rdata_q : 'bx; + + instr_beq <= is_beq_bne_blt_bge_bltu_bgeu && mem_rdata_q[14:12] == 3'b000; + instr_bne <= is_beq_bne_blt_bge_bltu_bgeu && mem_rdata_q[14:12] == 3'b001; + instr_blt <= is_beq_bne_blt_bge_bltu_bgeu && mem_rdata_q[14:12] == 3'b100; + instr_bge <= is_beq_bne_blt_bge_bltu_bgeu && mem_rdata_q[14:12] == 3'b101; + instr_bltu <= is_beq_bne_blt_bge_bltu_bgeu && mem_rdata_q[14:12] == 3'b110; + instr_bgeu <= is_beq_bne_blt_bge_bltu_bgeu && mem_rdata_q[14:12] == 3'b111; + + instr_lb <= is_lb_lh_lw_lbu_lhu && mem_rdata_q[14:12] == 3'b000; + instr_lh <= is_lb_lh_lw_lbu_lhu && mem_rdata_q[14:12] == 3'b001; + instr_lw <= is_lb_lh_lw_lbu_lhu && mem_rdata_q[14:12] == 3'b010; + instr_lbu <= is_lb_lh_lw_lbu_lhu && mem_rdata_q[14:12] == 3'b100; + instr_lhu <= is_lb_lh_lw_lbu_lhu && mem_rdata_q[14:12] == 3'b101; + + instr_sb <= is_sb_sh_sw && mem_rdata_q[14:12] == 3'b000; + instr_sh <= is_sb_sh_sw && mem_rdata_q[14:12] == 3'b001; + instr_sw <= is_sb_sh_sw && mem_rdata_q[14:12] == 3'b010; + + instr_addi <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b000; + instr_slti <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b010; + instr_sltiu <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b011; + instr_xori <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b100; + instr_ori <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b110; + instr_andi <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b111; + + instr_slli <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b001 && mem_rdata_q[31:25] == 7'b0000000; + instr_srli <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0000000; + instr_srai <= is_alu_reg_imm && mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0100000; + + instr_add <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b000 && mem_rdata_q[31:25] == 7'b0000000; + instr_sub <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b000 && mem_rdata_q[31:25] == 7'b0100000; + instr_sll <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b001 && mem_rdata_q[31:25] == 7'b0000000; + instr_slt <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b010 && mem_rdata_q[31:25] == 7'b0000000; + instr_sltu <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b011 && mem_rdata_q[31:25] == 7'b0000000; + instr_xor <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b100 && mem_rdata_q[31:25] == 7'b0000000; + instr_srl <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0000000; + instr_sra <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0100000; + instr_or <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b110 && mem_rdata_q[31:25] == 7'b0000000; + instr_and <= is_alu_reg_reg && mem_rdata_q[14:12] == 3'b111 && mem_rdata_q[31:25] == 7'b0000000; + + instr_rdcycle <= ((mem_rdata_q[6:0] == 7'b1110011 && mem_rdata_q[31:12] == 'b11000000000000000010) || + (mem_rdata_q[6:0] == 7'b1110011 && mem_rdata_q[31:12] == 'b11000000000100000010)) && ENABLE_COUNTERS; + instr_rdcycleh <= ((mem_rdata_q[6:0] == 7'b1110011 && mem_rdata_q[31:12] == 'b11001000000000000010) || + (mem_rdata_q[6:0] == 7'b1110011 && mem_rdata_q[31:12] == 'b11001000000100000010)) && ENABLE_COUNTERS && ENABLE_COUNTERS64; + instr_rdinstr <= (mem_rdata_q[6:0] == 7'b1110011 && mem_rdata_q[31:12] == 'b11000000001000000010) && ENABLE_COUNTERS; + instr_rdinstrh <= (mem_rdata_q[6:0] == 7'b1110011 && mem_rdata_q[31:12] == 'b11001000001000000010) && ENABLE_COUNTERS && ENABLE_COUNTERS64; + + instr_ecall_ebreak <= ((mem_rdata_q[6:0] == 7'b1110011 && !mem_rdata_q[31:21] && !mem_rdata_q[19:7]) || + (COMPRESSED_ISA && mem_rdata_q[15:0] == 16'h9002)); + + instr_getq <= mem_rdata_q[6:0] == 7'b0001011 && mem_rdata_q[31:25] == 7'b0000000 && ENABLE_IRQ && ENABLE_IRQ_QREGS; + instr_setq <= mem_rdata_q[6:0] == 7'b0001011 && mem_rdata_q[31:25] == 7'b0000001 && ENABLE_IRQ && ENABLE_IRQ_QREGS; + instr_maskirq <= mem_rdata_q[6:0] == 7'b0001011 && mem_rdata_q[31:25] == 7'b0000011 && ENABLE_IRQ; + instr_timer <= mem_rdata_q[6:0] == 7'b0001011 && mem_rdata_q[31:25] == 7'b0000101 && ENABLE_IRQ && ENABLE_IRQ_TIMER; + + is_slli_srli_srai <= is_alu_reg_imm && |{ + mem_rdata_q[14:12] == 3'b001 && mem_rdata_q[31:25] == 7'b0000000, + mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0000000, + mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0100000 + }; + + is_jalr_addi_slti_sltiu_xori_ori_andi <= instr_jalr || is_alu_reg_imm && |{ + mem_rdata_q[14:12] == 3'b000, + mem_rdata_q[14:12] == 3'b010, + mem_rdata_q[14:12] == 3'b011, + mem_rdata_q[14:12] == 3'b100, + mem_rdata_q[14:12] == 3'b110, + mem_rdata_q[14:12] == 3'b111 + }; + + is_sll_srl_sra <= is_alu_reg_reg && |{ + mem_rdata_q[14:12] == 3'b001 && mem_rdata_q[31:25] == 7'b0000000, + mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0000000, + mem_rdata_q[14:12] == 3'b101 && mem_rdata_q[31:25] == 7'b0100000 + }; + + is_lui_auipc_jal_jalr_addi_add_sub <= 0; + is_compare <= 0; + + (* parallel_case *) + case (1'b1) + instr_jal: + decoded_imm <= decoded_imm_j; + |{instr_lui, instr_auipc}: + decoded_imm <= mem_rdata_q[31:12] << 12; + |{instr_jalr, is_lb_lh_lw_lbu_lhu, is_alu_reg_imm}: + decoded_imm <= $signed(mem_rdata_q[31:20]); + is_beq_bne_blt_bge_bltu_bgeu: + decoded_imm <= $signed({mem_rdata_q[31], mem_rdata_q[7], mem_rdata_q[30:25], mem_rdata_q[11:8], 1'b0}); + is_sb_sh_sw: + decoded_imm <= $signed({mem_rdata_q[31:25], mem_rdata_q[11:7]}); + default: + decoded_imm <= 1'bx; + endcase + end + + if (!resetn) begin + is_beq_bne_blt_bge_bltu_bgeu <= 0; + is_compare <= 0; + + instr_beq <= 0; + instr_bne <= 0; + instr_blt <= 0; + instr_bge <= 0; + instr_bltu <= 0; + instr_bgeu <= 0; + + instr_addi <= 0; + instr_slti <= 0; + instr_sltiu <= 0; + instr_xori <= 0; + instr_ori <= 0; + instr_andi <= 0; + + instr_add <= 0; + instr_sub <= 0; + instr_sll <= 0; + instr_slt <= 0; + instr_sltu <= 0; + instr_xor <= 0; + instr_srl <= 0; + instr_sra <= 0; + instr_or <= 0; + instr_and <= 0; + end + end + + + // Main State Machine + + localparam cpu_state_trap = 8'b10000000; + localparam cpu_state_fetch = 8'b01000000; + localparam cpu_state_ld_rs1 = 8'b00100000; + localparam cpu_state_ld_rs2 = 8'b00010000; + localparam cpu_state_exec = 8'b00001000; + localparam cpu_state_shift = 8'b00000100; + localparam cpu_state_stmem = 8'b00000010; + localparam cpu_state_ldmem = 8'b00000001; + + reg [7:0] cpu_state; + reg [1:0] irq_state; + + `FORMAL_KEEP reg [127:0] dbg_ascii_state; + + always @* begin + dbg_ascii_state = ""; + if (cpu_state == cpu_state_trap) dbg_ascii_state = "trap"; + if (cpu_state == cpu_state_fetch) dbg_ascii_state = "fetch"; + if (cpu_state == cpu_state_ld_rs1) dbg_ascii_state = "ld_rs1"; + if (cpu_state == cpu_state_ld_rs2) dbg_ascii_state = "ld_rs2"; + if (cpu_state == cpu_state_exec) dbg_ascii_state = "exec"; + if (cpu_state == cpu_state_shift) dbg_ascii_state = "shift"; + if (cpu_state == cpu_state_stmem) dbg_ascii_state = "stmem"; + if (cpu_state == cpu_state_ldmem) dbg_ascii_state = "ldmem"; + end + + reg set_mem_do_rinst; + reg set_mem_do_rdata; + reg set_mem_do_wdata; + + reg latched_store; + reg latched_stalu; + reg latched_branch; + reg latched_compr; + reg latched_trace; + reg latched_is_lu; + reg latched_is_lh; + reg latched_is_lb; + reg [regindex_bits-1:0] latched_rd; + + reg [31:0] current_pc; + assign next_pc = latched_store && latched_branch ? reg_out & ~1 : reg_next_pc; + + reg [3:0] pcpi_timeout_counter; + reg pcpi_timeout; + + reg [31:0] next_irq_pending; + reg do_waitirq; + + reg [31:0] alu_out, alu_out_q; + reg alu_out_0, alu_out_0_q; + reg alu_wait, alu_wait_2; + + reg [31:0] alu_add_sub; + reg [31:0] alu_shl, alu_shr; + reg alu_eq, alu_ltu, alu_lts; + + generate if (TWO_CYCLE_ALU) begin + always @(posedge clk) begin + alu_add_sub <= instr_sub ? reg_op1 - reg_op2 : reg_op1 + reg_op2; + alu_eq <= reg_op1 == reg_op2; + alu_lts <= $signed(reg_op1) < $signed(reg_op2); + alu_ltu <= reg_op1 < reg_op2; + alu_shl <= reg_op1 << reg_op2[4:0]; + alu_shr <= $signed({instr_sra || instr_srai ? reg_op1[31] : 1'b0, reg_op1}) >>> reg_op2[4:0]; + end + end else begin + always @* begin + alu_add_sub = instr_sub ? reg_op1 - reg_op2 : reg_op1 + reg_op2; + alu_eq = reg_op1 == reg_op2; + alu_lts = $signed(reg_op1) < $signed(reg_op2); + alu_ltu = reg_op1 < reg_op2; + alu_shl = reg_op1 << reg_op2[4:0]; + alu_shr = $signed({instr_sra || instr_srai ? reg_op1[31] : 1'b0, reg_op1}) >>> reg_op2[4:0]; + end + end endgenerate + + always @* begin + alu_out_0 = 'bx; + (* parallel_case, full_case *) + case (1'b1) + instr_beq: + alu_out_0 = alu_eq; + instr_bne: + alu_out_0 = !alu_eq; + instr_bge: + alu_out_0 = !alu_lts; + instr_bgeu: + alu_out_0 = !alu_ltu; + is_slti_blt_slt && (!TWO_CYCLE_COMPARE || !{instr_beq,instr_bne,instr_bge,instr_bgeu}): + alu_out_0 = alu_lts; + is_sltiu_bltu_sltu && (!TWO_CYCLE_COMPARE || !{instr_beq,instr_bne,instr_bge,instr_bgeu}): + alu_out_0 = alu_ltu; + endcase + + alu_out = 'bx; + (* parallel_case, full_case *) + case (1'b1) + is_lui_auipc_jal_jalr_addi_add_sub: + alu_out = alu_add_sub; + is_compare: + alu_out = alu_out_0; + instr_xori || instr_xor: + alu_out = reg_op1 ^ reg_op2; + instr_ori || instr_or: + alu_out = reg_op1 | reg_op2; + instr_andi || instr_and: + alu_out = reg_op1 & reg_op2; + BARREL_SHIFTER && (instr_sll || instr_slli): + alu_out = alu_shl; + BARREL_SHIFTER && (instr_srl || instr_srli || instr_sra || instr_srai): + alu_out = alu_shr; + endcase + +`ifdef RISCV_FORMAL_BLACKBOX_ALU + alu_out_0 = $anyseq; + alu_out = $anyseq; +`endif + end + + reg clear_prefetched_high_word_q; + always @(posedge clk) clear_prefetched_high_word_q <= clear_prefetched_high_word; + + always @* begin + clear_prefetched_high_word = clear_prefetched_high_word_q; + if (!prefetched_high_word) + clear_prefetched_high_word = 0; + if (latched_branch || irq_state || !resetn) + clear_prefetched_high_word = COMPRESSED_ISA; + end + + reg cpuregs_write; + reg [31:0] cpuregs_wrdata; + reg [31:0] cpuregs_rs1; + reg [31:0] cpuregs_rs2; + reg [regindex_bits-1:0] decoded_rs; + + always @* begin + cpuregs_write = 0; + cpuregs_wrdata = 'bx; + + if (cpu_state == cpu_state_fetch) begin + (* parallel_case *) + case (1'b1) + latched_branch: begin + cpuregs_wrdata = reg_pc + (latched_compr ? 2 : 4); + cpuregs_write = 1; + end + latched_store && !latched_branch: begin + cpuregs_wrdata = latched_stalu ? alu_out_q : reg_out; + cpuregs_write = 1; + end + ENABLE_IRQ && irq_state[0]: begin + cpuregs_wrdata = reg_next_pc | latched_compr; + cpuregs_write = 1; + end + ENABLE_IRQ && irq_state[1]: begin + cpuregs_wrdata = irq_pending & ~irq_mask; + cpuregs_write = 1; + end + endcase + end + end + +`ifndef PICORV32_REGS + always @(posedge clk) begin + if (resetn && cpuregs_write && latched_rd) +`ifdef PICORV32_TESTBUG_001 + cpuregs[latched_rd ^ 1] <= cpuregs_wrdata; +`elsif PICORV32_TESTBUG_002 + cpuregs[latched_rd] <= cpuregs_wrdata ^ 1; +`else + cpuregs[latched_rd] <= cpuregs_wrdata; +`endif + end + + always @* begin + decoded_rs = 'bx; + if (ENABLE_REGS_DUALPORT) begin +`ifndef RISCV_FORMAL_BLACKBOX_REGS + cpuregs_rs1 = decoded_rs1 ? cpuregs[decoded_rs1] : 0; + cpuregs_rs2 = decoded_rs2 ? cpuregs[decoded_rs2] : 0; +`else + cpuregs_rs1 = decoded_rs1 ? $anyseq : 0; + cpuregs_rs2 = decoded_rs2 ? $anyseq : 0; +`endif + end else begin + decoded_rs = (cpu_state == cpu_state_ld_rs2) ? decoded_rs2 : decoded_rs1; +`ifndef RISCV_FORMAL_BLACKBOX_REGS + cpuregs_rs1 = decoded_rs ? cpuregs[decoded_rs] : 0; +`else + cpuregs_rs1 = decoded_rs ? $anyseq : 0; +`endif + cpuregs_rs2 = cpuregs_rs1; + end + end +`else + wire[31:0] cpuregs_rdata1; + wire[31:0] cpuregs_rdata2; + + wire [5:0] cpuregs_waddr = latched_rd; + wire [5:0] cpuregs_raddr1 = ENABLE_REGS_DUALPORT ? decoded_rs1 : decoded_rs; + wire [5:0] cpuregs_raddr2 = ENABLE_REGS_DUALPORT ? decoded_rs2 : 0; + + `PICORV32_REGS cpuregs ( + .clk(clk), + .wen(resetn && cpuregs_write && latched_rd), + .waddr(cpuregs_waddr), + .raddr1(cpuregs_raddr1), + .raddr2(cpuregs_raddr2), + .wdata(cpuregs_wrdata), + .rdata1(cpuregs_rdata1), + .rdata2(cpuregs_rdata2) + ); + + always @* begin + decoded_rs = 'bx; + if (ENABLE_REGS_DUALPORT) begin + cpuregs_rs1 = decoded_rs1 ? cpuregs_rdata1 : 0; + cpuregs_rs2 = decoded_rs2 ? cpuregs_rdata2 : 0; + end else begin + decoded_rs = (cpu_state == cpu_state_ld_rs2) ? decoded_rs2 : decoded_rs1; + cpuregs_rs1 = decoded_rs ? cpuregs_rdata1 : 0; + cpuregs_rs2 = cpuregs_rs1; + end + end +`endif + + assign launch_next_insn = cpu_state == cpu_state_fetch && decoder_trigger && (!ENABLE_IRQ || irq_delay || irq_active || !(irq_pending & ~irq_mask)); + + always @(posedge clk) begin + trap <= 0; + reg_sh <= 'bx; + reg_out <= 'bx; + set_mem_do_rinst = 0; + set_mem_do_rdata = 0; + set_mem_do_wdata = 0; + + alu_out_0_q <= alu_out_0; + alu_out_q <= alu_out; + + alu_wait <= 0; + alu_wait_2 <= 0; + + if (launch_next_insn) begin + dbg_rs1val <= 'bx; + dbg_rs2val <= 'bx; + dbg_rs1val_valid <= 0; + dbg_rs2val_valid <= 0; + end + + if (WITH_PCPI && CATCH_ILLINSN) begin + if (resetn && pcpi_valid && !pcpi_int_wait) begin + if (pcpi_timeout_counter) + pcpi_timeout_counter <= pcpi_timeout_counter - 1; + end else + pcpi_timeout_counter <= ~0; + pcpi_timeout <= !pcpi_timeout_counter; + end + + if (ENABLE_COUNTERS) begin + count_cycle <= resetn ? count_cycle + 1 : 0; + if (!ENABLE_COUNTERS64) count_cycle[63:32] <= 0; + end else begin + count_cycle <= 'bx; + count_instr <= 'bx; + end + + next_irq_pending = ENABLE_IRQ ? irq_pending & LATCHED_IRQ : 'bx; + + if (ENABLE_IRQ && ENABLE_IRQ_TIMER && timer) begin + timer <= timer - 1; + end + + decoder_trigger <= mem_do_rinst && mem_done; + decoder_trigger_q <= decoder_trigger; + decoder_pseudo_trigger <= 0; + decoder_pseudo_trigger_q <= decoder_pseudo_trigger; + do_waitirq <= 0; + + trace_valid <= 0; + + if (!ENABLE_TRACE) + trace_data <= 'bx; + + if (!resetn) begin + reg_pc <= PROGADDR_RESET; + reg_next_pc <= PROGADDR_RESET; + if (ENABLE_COUNTERS) + count_instr <= 0; + latched_store <= 0; + latched_stalu <= 0; + latched_branch <= 0; + latched_trace <= 0; + latched_is_lu <= 0; + latched_is_lh <= 0; + latched_is_lb <= 0; + pcpi_valid <= 0; + pcpi_timeout <= 0; + irq_active <= 0; + irq_delay <= 0; + irq_mask <= ~0; + next_irq_pending = 0; + irq_state <= 0; + eoi <= 0; + timer <= 0; + if (~STACKADDR) begin + latched_store <= 1; + latched_rd <= 2; + reg_out <= STACKADDR; + end + cpu_state <= cpu_state_fetch; + end else + (* parallel_case, full_case *) + case (cpu_state) + cpu_state_trap: begin + trap <= 1; + end + + cpu_state_fetch: begin + mem_do_rinst <= !decoder_trigger && !do_waitirq; + mem_wordsize <= 0; + + current_pc = reg_next_pc; + + (* parallel_case *) + case (1'b1) + latched_branch: begin + current_pc = latched_store ? (latched_stalu ? alu_out_q : reg_out) & ~1 : reg_next_pc; + `debug($display("ST_RD: %2d 0x%08x, BRANCH 0x%08x", latched_rd, reg_pc + (latched_compr ? 2 : 4), current_pc);) + end + latched_store && !latched_branch: begin + `debug($display("ST_RD: %2d 0x%08x", latched_rd, latched_stalu ? alu_out_q : reg_out);) + end + ENABLE_IRQ && irq_state[0]: begin + current_pc = PROGADDR_IRQ; + irq_active <= 1; + mem_do_rinst <= 1; + end + ENABLE_IRQ && irq_state[1]: begin + eoi <= irq_pending & ~irq_mask; + next_irq_pending = next_irq_pending & irq_mask; + end + endcase + + if (ENABLE_TRACE && latched_trace) begin + latched_trace <= 0; + trace_valid <= 1; + if (latched_branch) + trace_data <= (irq_active ? TRACE_IRQ : 0) | TRACE_BRANCH | (current_pc & 32'hfffffffe); + else + trace_data <= (irq_active ? TRACE_IRQ : 0) | (latched_stalu ? alu_out_q : reg_out); + end + + reg_pc <= current_pc; + reg_next_pc <= current_pc; + + latched_store <= 0; + latched_stalu <= 0; + latched_branch <= 0; + latched_is_lu <= 0; + latched_is_lh <= 0; + latched_is_lb <= 0; + latched_rd <= decoded_rd; + latched_compr <= compressed_instr; + + if (ENABLE_IRQ && ((decoder_trigger && !irq_active && !irq_delay && |(irq_pending & ~irq_mask)) || irq_state)) begin + irq_state <= + irq_state == 2'b00 ? 2'b01 : + irq_state == 2'b01 ? 2'b10 : 2'b00; + latched_compr <= latched_compr; + if (ENABLE_IRQ_QREGS) + latched_rd <= irqregs_offset | irq_state[0]; + else + latched_rd <= irq_state[0] ? 4 : 3; + end else + if (ENABLE_IRQ && (decoder_trigger || do_waitirq) && instr_waitirq) begin + if (irq_pending) begin + latched_store <= 1; + reg_out <= irq_pending; + reg_next_pc <= current_pc + (compressed_instr ? 2 : 4); + mem_do_rinst <= 1; + end else + do_waitirq <= 1; + end else + if (decoder_trigger) begin + `debug($display("-- %-0t", $time);) + irq_delay <= irq_active; + reg_next_pc <= current_pc + (compressed_instr ? 2 : 4); + if (ENABLE_TRACE) + latched_trace <= 1; + if (ENABLE_COUNTERS) begin + count_instr <= count_instr + 1; + if (!ENABLE_COUNTERS64) count_instr[63:32] <= 0; + end + if (instr_jal) begin + mem_do_rinst <= 1; + reg_next_pc <= current_pc + decoded_imm_j; + latched_branch <= 1; + end else begin + mem_do_rinst <= 0; + mem_do_prefetch <= !instr_jalr && !instr_retirq; + cpu_state <= cpu_state_ld_rs1; + end + end + end + + cpu_state_ld_rs1: begin + reg_op1 <= 'bx; + reg_op2 <= 'bx; + + (* parallel_case *) + case (1'b1) + (CATCH_ILLINSN || WITH_PCPI) && instr_trap: begin + if (WITH_PCPI) begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_op1 <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + if (ENABLE_REGS_DUALPORT) begin + pcpi_valid <= 1; + `debug($display("LD_RS2: %2d 0x%08x", decoded_rs2, cpuregs_rs2);) + reg_sh <= cpuregs_rs2; + reg_op2 <= cpuregs_rs2; + dbg_rs2val <= cpuregs_rs2; + dbg_rs2val_valid <= 1; + if (pcpi_int_ready) begin + mem_do_rinst <= 1; + pcpi_valid <= 0; + reg_out <= pcpi_int_rd; + latched_store <= pcpi_int_wr; + cpu_state <= cpu_state_fetch; + end else + if (CATCH_ILLINSN && (pcpi_timeout || instr_ecall_ebreak)) begin + pcpi_valid <= 0; + `debug($display("EBREAK OR UNSUPPORTED INSN AT 0x%08x", reg_pc);) + if (ENABLE_IRQ && !irq_mask[irq_ebreak] && !irq_active) begin + next_irq_pending[irq_ebreak] = 1; + cpu_state <= cpu_state_fetch; + end else + cpu_state <= cpu_state_trap; + end + end else begin + cpu_state <= cpu_state_ld_rs2; + end + end else begin + `debug($display("EBREAK OR UNSUPPORTED INSN AT 0x%08x", reg_pc);) + if (ENABLE_IRQ && !irq_mask[irq_ebreak] && !irq_active) begin + next_irq_pending[irq_ebreak] = 1; + cpu_state <= cpu_state_fetch; + end else + cpu_state <= cpu_state_trap; + end + end + ENABLE_COUNTERS && is_rdcycle_rdcycleh_rdinstr_rdinstrh: begin + (* parallel_case, full_case *) + case (1'b1) + instr_rdcycle: + reg_out <= count_cycle[31:0]; + instr_rdcycleh && ENABLE_COUNTERS64: + reg_out <= count_cycle[63:32]; + instr_rdinstr: + reg_out <= count_instr[31:0]; + instr_rdinstrh && ENABLE_COUNTERS64: + reg_out <= count_instr[63:32]; + endcase + latched_store <= 1; + cpu_state <= cpu_state_fetch; + end + is_lui_auipc_jal: begin + reg_op1 <= instr_lui ? 0 : reg_pc; + reg_op2 <= decoded_imm; + if (TWO_CYCLE_ALU) + alu_wait <= 1; + else + mem_do_rinst <= mem_do_prefetch; + cpu_state <= cpu_state_exec; + end + ENABLE_IRQ && ENABLE_IRQ_QREGS && instr_getq: begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_out <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + latched_store <= 1; + cpu_state <= cpu_state_fetch; + end + ENABLE_IRQ && ENABLE_IRQ_QREGS && instr_setq: begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_out <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + latched_rd <= latched_rd | irqregs_offset; + latched_store <= 1; + cpu_state <= cpu_state_fetch; + end + ENABLE_IRQ && instr_retirq: begin + eoi <= 0; + irq_active <= 0; + latched_branch <= 1; + latched_store <= 1; + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_out <= CATCH_MISALIGN ? (cpuregs_rs1 & 32'h fffffffe) : cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + cpu_state <= cpu_state_fetch; + end + ENABLE_IRQ && instr_maskirq: begin + latched_store <= 1; + reg_out <= irq_mask; + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + irq_mask <= cpuregs_rs1 | MASKED_IRQ; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + cpu_state <= cpu_state_fetch; + end + ENABLE_IRQ && ENABLE_IRQ_TIMER && instr_timer: begin + latched_store <= 1; + reg_out <= timer; + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + timer <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + cpu_state <= cpu_state_fetch; + end + is_lb_lh_lw_lbu_lhu && !instr_trap: begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_op1 <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + cpu_state <= cpu_state_ldmem; + mem_do_rinst <= 1; + end + is_slli_srli_srai && !BARREL_SHIFTER: begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_op1 <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + reg_sh <= decoded_rs2; + cpu_state <= cpu_state_shift; + end + is_jalr_addi_slti_sltiu_xori_ori_andi, is_slli_srli_srai && BARREL_SHIFTER: begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_op1 <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + reg_op2 <= is_slli_srli_srai && BARREL_SHIFTER ? decoded_rs2 : decoded_imm; + if (TWO_CYCLE_ALU) + alu_wait <= 1; + else + mem_do_rinst <= mem_do_prefetch; + cpu_state <= cpu_state_exec; + end + default: begin + `debug($display("LD_RS1: %2d 0x%08x", decoded_rs1, cpuregs_rs1);) + reg_op1 <= cpuregs_rs1; + dbg_rs1val <= cpuregs_rs1; + dbg_rs1val_valid <= 1; + if (ENABLE_REGS_DUALPORT) begin + `debug($display("LD_RS2: %2d 0x%08x", decoded_rs2, cpuregs_rs2);) + reg_sh <= cpuregs_rs2; + reg_op2 <= cpuregs_rs2; + dbg_rs2val <= cpuregs_rs2; + dbg_rs2val_valid <= 1; + (* parallel_case *) + case (1'b1) + is_sb_sh_sw: begin + cpu_state <= cpu_state_stmem; + mem_do_rinst <= 1; + end + is_sll_srl_sra && !BARREL_SHIFTER: begin + cpu_state <= cpu_state_shift; + end + default: begin + if (TWO_CYCLE_ALU || (TWO_CYCLE_COMPARE && is_beq_bne_blt_bge_bltu_bgeu)) begin + alu_wait_2 <= TWO_CYCLE_ALU && (TWO_CYCLE_COMPARE && is_beq_bne_blt_bge_bltu_bgeu); + alu_wait <= 1; + end else + mem_do_rinst <= mem_do_prefetch; + cpu_state <= cpu_state_exec; + end + endcase + end else + cpu_state <= cpu_state_ld_rs2; + end + endcase + end + + cpu_state_ld_rs2: begin + `debug($display("LD_RS2: %2d 0x%08x", decoded_rs2, cpuregs_rs2);) + reg_sh <= cpuregs_rs2; + reg_op2 <= cpuregs_rs2; + dbg_rs2val <= cpuregs_rs2; + dbg_rs2val_valid <= 1; + + (* parallel_case *) + case (1'b1) + WITH_PCPI && instr_trap: begin + pcpi_valid <= 1; + if (pcpi_int_ready) begin + mem_do_rinst <= 1; + pcpi_valid <= 0; + reg_out <= pcpi_int_rd; + latched_store <= pcpi_int_wr; + cpu_state <= cpu_state_fetch; + end else + if (CATCH_ILLINSN && (pcpi_timeout || instr_ecall_ebreak)) begin + pcpi_valid <= 0; + `debug($display("EBREAK OR UNSUPPORTED INSN AT 0x%08x", reg_pc);) + if (ENABLE_IRQ && !irq_mask[irq_ebreak] && !irq_active) begin + next_irq_pending[irq_ebreak] = 1; + cpu_state <= cpu_state_fetch; + end else + cpu_state <= cpu_state_trap; + end + end + is_sb_sh_sw: begin + cpu_state <= cpu_state_stmem; + mem_do_rinst <= 1; + end + is_sll_srl_sra && !BARREL_SHIFTER: begin + cpu_state <= cpu_state_shift; + end + default: begin + if (TWO_CYCLE_ALU || (TWO_CYCLE_COMPARE && is_beq_bne_blt_bge_bltu_bgeu)) begin + alu_wait_2 <= TWO_CYCLE_ALU && (TWO_CYCLE_COMPARE && is_beq_bne_blt_bge_bltu_bgeu); + alu_wait <= 1; + end else + mem_do_rinst <= mem_do_prefetch; + cpu_state <= cpu_state_exec; + end + endcase + end + + cpu_state_exec: begin + reg_out <= reg_pc + decoded_imm; + if ((TWO_CYCLE_ALU || TWO_CYCLE_COMPARE) && (alu_wait || alu_wait_2)) begin + mem_do_rinst <= mem_do_prefetch && !alu_wait_2; + alu_wait <= alu_wait_2; + end else + if (is_beq_bne_blt_bge_bltu_bgeu) begin + latched_rd <= 0; + latched_store <= TWO_CYCLE_COMPARE ? alu_out_0_q : alu_out_0; + latched_branch <= TWO_CYCLE_COMPARE ? alu_out_0_q : alu_out_0; + if (mem_done) + cpu_state <= cpu_state_fetch; + if (TWO_CYCLE_COMPARE ? alu_out_0_q : alu_out_0) begin + decoder_trigger <= 0; + set_mem_do_rinst = 1; + end + end else begin + latched_branch <= instr_jalr; + latched_store <= 1; + latched_stalu <= 1; + cpu_state <= cpu_state_fetch; + end + end + + cpu_state_shift: begin + latched_store <= 1; + if (reg_sh == 0) begin + reg_out <= reg_op1; + mem_do_rinst <= mem_do_prefetch; + cpu_state <= cpu_state_fetch; + end else if (TWO_STAGE_SHIFT && reg_sh >= 4) begin + (* parallel_case, full_case *) + case (1'b1) + instr_slli || instr_sll: reg_op1 <= reg_op1 << 4; + instr_srli || instr_srl: reg_op1 <= reg_op1 >> 4; + instr_srai || instr_sra: reg_op1 <= $signed(reg_op1) >>> 4; + endcase + reg_sh <= reg_sh - 4; + end else begin + (* parallel_case, full_case *) + case (1'b1) + instr_slli || instr_sll: reg_op1 <= reg_op1 << 1; + instr_srli || instr_srl: reg_op1 <= reg_op1 >> 1; + instr_srai || instr_sra: reg_op1 <= $signed(reg_op1) >>> 1; + endcase + reg_sh <= reg_sh - 1; + end + end + + cpu_state_stmem: begin + if (ENABLE_TRACE) + reg_out <= reg_op2; + if (!mem_do_prefetch || mem_done) begin + if (!mem_do_wdata) begin + (* parallel_case, full_case *) + case (1'b1) + instr_sb: mem_wordsize <= 2; + instr_sh: mem_wordsize <= 1; + instr_sw: mem_wordsize <= 0; + endcase + if (ENABLE_TRACE) begin + trace_valid <= 1; + trace_data <= (irq_active ? TRACE_IRQ : 0) | TRACE_ADDR | ((reg_op1 + decoded_imm) & 32'hffffffff); + end + reg_op1 <= reg_op1 + decoded_imm; + set_mem_do_wdata = 1; + end + if (!mem_do_prefetch && mem_done) begin + cpu_state <= cpu_state_fetch; + decoder_trigger <= 1; + decoder_pseudo_trigger <= 1; + end + end + end + + cpu_state_ldmem: begin + latched_store <= 1; + if (!mem_do_prefetch || mem_done) begin + if (!mem_do_rdata) begin + (* parallel_case, full_case *) + case (1'b1) + instr_lb || instr_lbu: mem_wordsize <= 2; + instr_lh || instr_lhu: mem_wordsize <= 1; + instr_lw: mem_wordsize <= 0; + endcase + latched_is_lu <= is_lbu_lhu_lw; + latched_is_lh <= instr_lh; + latched_is_lb <= instr_lb; + if (ENABLE_TRACE) begin + trace_valid <= 1; + trace_data <= (irq_active ? TRACE_IRQ : 0) | TRACE_ADDR | ((reg_op1 + decoded_imm) & 32'hffffffff); + end + reg_op1 <= reg_op1 + decoded_imm; + set_mem_do_rdata = 1; + end + if (!mem_do_prefetch && mem_done) begin + (* parallel_case, full_case *) + case (1'b1) + latched_is_lu: reg_out <= mem_rdata_word; + latched_is_lh: reg_out <= $signed(mem_rdata_word[15:0]); + latched_is_lb: reg_out <= $signed(mem_rdata_word[7:0]); + endcase + decoder_trigger <= 1; + decoder_pseudo_trigger <= 1; + cpu_state <= cpu_state_fetch; + end + end + end + endcase + + if (ENABLE_IRQ) begin + next_irq_pending = next_irq_pending | irq; + if(ENABLE_IRQ_TIMER && timer) + if (timer - 1 == 0) + next_irq_pending[irq_timer] = 1; + end + + if (CATCH_MISALIGN && resetn && (mem_do_rdata || mem_do_wdata)) begin + if (mem_wordsize == 0 && reg_op1[1:0] != 0) begin + `debug($display("MISALIGNED WORD: 0x%08x", reg_op1);) + if (ENABLE_IRQ && !irq_mask[irq_buserror] && !irq_active) begin + next_irq_pending[irq_buserror] = 1; + end else + cpu_state <= cpu_state_trap; + end + if (mem_wordsize == 1 && reg_op1[0] != 0) begin + `debug($display("MISALIGNED HALFWORD: 0x%08x", reg_op1);) + if (ENABLE_IRQ && !irq_mask[irq_buserror] && !irq_active) begin + next_irq_pending[irq_buserror] = 1; + end else + cpu_state <= cpu_state_trap; + end + end + if (CATCH_MISALIGN && resetn && mem_do_rinst && (COMPRESSED_ISA ? reg_pc[0] : |reg_pc[1:0])) begin + `debug($display("MISALIGNED INSTRUCTION: 0x%08x", reg_pc);) + if (ENABLE_IRQ && !irq_mask[irq_buserror] && !irq_active) begin + next_irq_pending[irq_buserror] = 1; + end else + cpu_state <= cpu_state_trap; + end + if (!CATCH_ILLINSN && decoder_trigger_q && !decoder_pseudo_trigger_q && instr_ecall_ebreak) begin + cpu_state <= cpu_state_trap; + end + + if (!resetn || mem_done) begin + mem_do_prefetch <= 0; + mem_do_rinst <= 0; + mem_do_rdata <= 0; + mem_do_wdata <= 0; + end + + if (set_mem_do_rinst) + mem_do_rinst <= 1; + if (set_mem_do_rdata) + mem_do_rdata <= 1; + if (set_mem_do_wdata) + mem_do_wdata <= 1; + + irq_pending <= next_irq_pending & ~MASKED_IRQ; + + if (!CATCH_MISALIGN) begin + if (COMPRESSED_ISA) begin + reg_pc[0] <= 0; + reg_next_pc[0] <= 0; + end else begin + reg_pc[1:0] <= 0; + reg_next_pc[1:0] <= 0; + end + end + current_pc = 'bx; + end + +`ifdef RISCV_FORMAL + reg dbg_irq_call; + reg dbg_irq_enter; + reg [31:0] dbg_irq_ret; + always @(posedge clk) begin + rvfi_valid <= resetn && (launch_next_insn || trap) && dbg_valid_insn; + rvfi_order <= resetn ? rvfi_order + rvfi_valid : 0; + + rvfi_insn <= dbg_insn_opcode; + rvfi_rs1_addr <= dbg_rs1val_valid ? dbg_insn_rs1 : 0; + rvfi_rs2_addr <= dbg_rs2val_valid ? dbg_insn_rs2 : 0; + rvfi_pc_rdata <= dbg_insn_addr; + rvfi_rs1_rdata <= dbg_rs1val_valid ? dbg_rs1val : 0; + rvfi_rs2_rdata <= dbg_rs2val_valid ? dbg_rs2val : 0; + rvfi_trap <= trap; + rvfi_halt <= trap; + rvfi_intr <= dbg_irq_enter; + rvfi_mode <= 3; + rvfi_ixl <= 1; + + if (!resetn) begin + dbg_irq_call <= 0; + dbg_irq_enter <= 0; + end else + if (rvfi_valid) begin + dbg_irq_call <= 0; + dbg_irq_enter <= dbg_irq_call; + end else + if (irq_state == 1) begin + dbg_irq_call <= 1; + dbg_irq_ret <= next_pc; + end + + if (!resetn) begin + rvfi_rd_addr <= 0; + rvfi_rd_wdata <= 0; + end else + if (cpuregs_write && !irq_state) begin +`ifdef PICORV32_TESTBUG_003 + rvfi_rd_addr <= latched_rd ^ 1; +`else + rvfi_rd_addr <= latched_rd; +`endif +`ifdef PICORV32_TESTBUG_004 + rvfi_rd_wdata <= latched_rd ? cpuregs_wrdata ^ 1 : 0; +`else + rvfi_rd_wdata <= latched_rd ? cpuregs_wrdata : 0; +`endif + end else + if (rvfi_valid) begin + rvfi_rd_addr <= 0; + rvfi_rd_wdata <= 0; + end + + casez (dbg_insn_opcode) + 32'b 0000000_?????_000??_???_?????_0001011: begin // getq + rvfi_rs1_addr <= 0; + rvfi_rs1_rdata <= 0; + end + 32'b 0000001_?????_?????_???_000??_0001011: begin // setq + rvfi_rd_addr <= 0; + rvfi_rd_wdata <= 0; + end + 32'b 0000010_?????_00000_???_00000_0001011: begin // retirq + rvfi_rs1_addr <= 0; + rvfi_rs1_rdata <= 0; + end + endcase + + if (!dbg_irq_call) begin + if (dbg_mem_instr) begin + rvfi_mem_addr <= 0; + rvfi_mem_rmask <= 0; + rvfi_mem_wmask <= 0; + rvfi_mem_rdata <= 0; + rvfi_mem_wdata <= 0; + end else + if (dbg_mem_valid && dbg_mem_ready) begin + rvfi_mem_addr <= dbg_mem_addr; + rvfi_mem_rmask <= dbg_mem_wstrb ? 0 : ~0; + rvfi_mem_wmask <= dbg_mem_wstrb; + rvfi_mem_rdata <= dbg_mem_rdata; + rvfi_mem_wdata <= dbg_mem_wdata; + end + end + end + + always @* begin +`ifdef PICORV32_TESTBUG_005 + rvfi_pc_wdata = (dbg_irq_call ? dbg_irq_ret : dbg_insn_addr) ^ 4; +`else + rvfi_pc_wdata = dbg_irq_call ? dbg_irq_ret : dbg_insn_addr; +`endif + + rvfi_csr_mcycle_rmask = 0; + rvfi_csr_mcycle_wmask = 0; + rvfi_csr_mcycle_rdata = 0; + rvfi_csr_mcycle_wdata = 0; + + rvfi_csr_minstret_rmask = 0; + rvfi_csr_minstret_wmask = 0; + rvfi_csr_minstret_rdata = 0; + rvfi_csr_minstret_wdata = 0; + + if (rvfi_valid && rvfi_insn[6:0] == 7'b 1110011 && rvfi_insn[13:12] == 3'b010) begin + if (rvfi_insn[31:20] == 12'h C00) begin + rvfi_csr_mcycle_rmask = 64'h 0000_0000_FFFF_FFFF; + rvfi_csr_mcycle_rdata = {32'h 0000_0000, rvfi_rd_wdata}; + end + if (rvfi_insn[31:20] == 12'h C80) begin + rvfi_csr_mcycle_rmask = 64'h FFFF_FFFF_0000_0000; + rvfi_csr_mcycle_rdata = {rvfi_rd_wdata, 32'h 0000_0000}; + end + if (rvfi_insn[31:20] == 12'h C02) begin + rvfi_csr_minstret_rmask = 64'h 0000_0000_FFFF_FFFF; + rvfi_csr_minstret_rdata = {32'h 0000_0000, rvfi_rd_wdata}; + end + if (rvfi_insn[31:20] == 12'h C82) begin + rvfi_csr_minstret_rmask = 64'h FFFF_FFFF_0000_0000; + rvfi_csr_minstret_rdata = {rvfi_rd_wdata, 32'h 0000_0000}; + end + end + end +`endif + + // Formal Verification +`ifdef FORMAL + reg [3:0] last_mem_nowait; + always @(posedge clk) + last_mem_nowait <= {last_mem_nowait, mem_ready || !mem_valid}; + + // stall the memory interface for max 4 cycles + restrict property (|last_mem_nowait || mem_ready || !mem_valid); + + // resetn low in first cycle, after that resetn high + restrict property (resetn != $initstate); + + // this just makes it much easier to read traces. uncomment as needed. + // assume property (mem_valid || !mem_ready); + + reg ok; + always @* begin + if (resetn) begin + // instruction fetches are read-only + if (mem_valid && mem_instr) + assert (mem_wstrb == 0); + + // cpu_state must be valid + ok = 0; + if (cpu_state == cpu_state_trap) ok = 1; + if (cpu_state == cpu_state_fetch) ok = 1; + if (cpu_state == cpu_state_ld_rs1) ok = 1; + if (cpu_state == cpu_state_ld_rs2) ok = !ENABLE_REGS_DUALPORT; + if (cpu_state == cpu_state_exec) ok = 1; + if (cpu_state == cpu_state_shift) ok = 1; + if (cpu_state == cpu_state_stmem) ok = 1; + if (cpu_state == cpu_state_ldmem) ok = 1; + assert (ok); + end + end + + reg last_mem_la_read = 0; + reg last_mem_la_write = 0; + reg [31:0] last_mem_la_addr; + reg [31:0] last_mem_la_wdata; + reg [3:0] last_mem_la_wstrb = 0; + + always @(posedge clk) begin + last_mem_la_read <= mem_la_read; + last_mem_la_write <= mem_la_write; + last_mem_la_addr <= mem_la_addr; + last_mem_la_wdata <= mem_la_wdata; + last_mem_la_wstrb <= mem_la_wstrb; + + if (last_mem_la_read) begin + assert(mem_valid); + assert(mem_addr == last_mem_la_addr); + assert(mem_wstrb == 0); + end + if (last_mem_la_write) begin + assert(mem_valid); + assert(mem_addr == last_mem_la_addr); + assert(mem_wdata == last_mem_la_wdata); + assert(mem_wstrb == last_mem_la_wstrb); + end + if (mem_la_read || mem_la_write) begin + assert(!mem_valid || mem_ready); + end + end +`endif +endmodule + +// This is a simple example implementation of PICORV32_REGS. +// Use the PICORV32_REGS mechanism if you want to use custom +// memory resources to implement the processor register file. +// Note that your implementation must match the requirements of +// the PicoRV32 configuration. (e.g. QREGS, etc) +module picorv32_regs ( + input clk, wen, + input [5:0] waddr, + input [5:0] raddr1, + input [5:0] raddr2, + input [31:0] wdata, + output [31:0] rdata1, + output [31:0] rdata2 +); + reg [31:0] regs [0:30]; + + always @(posedge clk) + if (wen) regs[~waddr[4:0]] <= wdata; + + assign rdata1 = regs[~raddr1[4:0]]; + assign rdata2 = regs[~raddr2[4:0]]; +endmodule + + +/*************************************************************** + * picorv32_pcpi_mul + ***************************************************************/ + +module picorv32_pcpi_mul #( + parameter STEPS_AT_ONCE = 1, + parameter CARRY_CHAIN = 4 +) ( + input clk, resetn, + + input pcpi_valid, + input [31:0] pcpi_insn, + input [31:0] pcpi_rs1, + input [31:0] pcpi_rs2, + output reg pcpi_wr, + output reg [31:0] pcpi_rd, + output reg pcpi_wait, + output reg pcpi_ready +); + reg instr_mul, instr_mulh, instr_mulhsu, instr_mulhu; + wire instr_any_mul = |{instr_mul, instr_mulh, instr_mulhsu, instr_mulhu}; + wire instr_any_mulh = |{instr_mulh, instr_mulhsu, instr_mulhu}; + wire instr_rs1_signed = |{instr_mulh, instr_mulhsu}; + wire instr_rs2_signed = |{instr_mulh}; + + reg pcpi_wait_q; + wire mul_start = pcpi_wait && !pcpi_wait_q; + + always @(posedge clk) begin + instr_mul <= 0; + instr_mulh <= 0; + instr_mulhsu <= 0; + instr_mulhu <= 0; + + if (resetn && pcpi_valid && pcpi_insn[6:0] == 7'b0110011 && pcpi_insn[31:25] == 7'b0000001) begin + case (pcpi_insn[14:12]) + 3'b000: instr_mul <= 1; + 3'b001: instr_mulh <= 1; + 3'b010: instr_mulhsu <= 1; + 3'b011: instr_mulhu <= 1; + endcase + end + + pcpi_wait <= instr_any_mul; + pcpi_wait_q <= pcpi_wait; + end + + reg [63:0] rs1, rs2, rd, rdx; + reg [63:0] next_rs1, next_rs2, this_rs2; + reg [63:0] next_rd, next_rdx, next_rdt; + reg [6:0] mul_counter; + reg mul_waiting; + reg mul_finish; + integer i, j; + + // carry save accumulator + always @* begin + next_rd = rd; + next_rdx = rdx; + next_rs1 = rs1; + next_rs2 = rs2; + + for (i = 0; i < STEPS_AT_ONCE; i=i+1) begin + this_rs2 = next_rs1[0] ? next_rs2 : 0; + if (CARRY_CHAIN == 0) begin + next_rdt = next_rd ^ next_rdx ^ this_rs2; + next_rdx = ((next_rd & next_rdx) | (next_rd & this_rs2) | (next_rdx & this_rs2)) << 1; + next_rd = next_rdt; + end else begin + next_rdt = 0; + for (j = 0; j < 64; j = j + CARRY_CHAIN) + {next_rdt[j+CARRY_CHAIN-1], next_rd[j +: CARRY_CHAIN]} = + next_rd[j +: CARRY_CHAIN] + next_rdx[j +: CARRY_CHAIN] + this_rs2[j +: CARRY_CHAIN]; + next_rdx = next_rdt << 1; + end + next_rs1 = next_rs1 >> 1; + next_rs2 = next_rs2 << 1; + end + end + + always @(posedge clk) begin + mul_finish <= 0; + if (!resetn) begin + mul_waiting <= 1; + end else + if (mul_waiting) begin + if (instr_rs1_signed) + rs1 <= $signed(pcpi_rs1); + else + rs1 <= $unsigned(pcpi_rs1); + + if (instr_rs2_signed) + rs2 <= $signed(pcpi_rs2); + else + rs2 <= $unsigned(pcpi_rs2); + + rd <= 0; + rdx <= 0; + mul_counter <= (instr_any_mulh ? 63 - STEPS_AT_ONCE : 31 - STEPS_AT_ONCE); + mul_waiting <= !mul_start; + end else begin + rd <= next_rd; + rdx <= next_rdx; + rs1 <= next_rs1; + rs2 <= next_rs2; + + mul_counter <= mul_counter - STEPS_AT_ONCE; + if (mul_counter[6]) begin + mul_finish <= 1; + mul_waiting <= 1; + end + end + end + + always @(posedge clk) begin + pcpi_wr <= 0; + pcpi_ready <= 0; + if (mul_finish && resetn) begin + pcpi_wr <= 1; + pcpi_ready <= 1; + pcpi_rd <= instr_any_mulh ? rd >> 32 : rd; + end + end +endmodule + +module picorv32_pcpi_fast_mul #( + parameter EXTRA_MUL_FFS = 0, + parameter EXTRA_INSN_FFS = 0, + parameter MUL_CLKGATE = 0 +) ( + input clk, resetn, + + input pcpi_valid, + input [31:0] pcpi_insn, + input [31:0] pcpi_rs1, + input [31:0] pcpi_rs2, + output pcpi_wr, + output [31:0] pcpi_rd, + output pcpi_wait, + output pcpi_ready +); + reg instr_mul, instr_mulh, instr_mulhsu, instr_mulhu; + wire instr_any_mul = |{instr_mul, instr_mulh, instr_mulhsu, instr_mulhu}; + wire instr_any_mulh = |{instr_mulh, instr_mulhsu, instr_mulhu}; + wire instr_rs1_signed = |{instr_mulh, instr_mulhsu}; + wire instr_rs2_signed = |{instr_mulh}; + + reg shift_out; + reg [3:0] active; + reg [32:0] rs1, rs2, rs1_q, rs2_q; + reg [63:0] rd, rd_q; + + wire pcpi_insn_valid = pcpi_valid && pcpi_insn[6:0] == 7'b0110011 && pcpi_insn[31:25] == 7'b0000001; + reg pcpi_insn_valid_q; + + always @* begin + instr_mul = 0; + instr_mulh = 0; + instr_mulhsu = 0; + instr_mulhu = 0; + + if (resetn && (EXTRA_INSN_FFS ? pcpi_insn_valid_q : pcpi_insn_valid)) begin + case (pcpi_insn[14:12]) + 3'b000: instr_mul = 1; + 3'b001: instr_mulh = 1; + 3'b010: instr_mulhsu = 1; + 3'b011: instr_mulhu = 1; + endcase + end + end + + always @(posedge clk) begin + pcpi_insn_valid_q <= pcpi_insn_valid; + if (!MUL_CLKGATE || active[0]) begin + rs1_q <= rs1; + rs2_q <= rs2; + end + if (!MUL_CLKGATE || active[1]) begin + rd <= $signed(EXTRA_MUL_FFS ? rs1_q : rs1) * $signed(EXTRA_MUL_FFS ? rs2_q : rs2); + end + if (!MUL_CLKGATE || active[2]) begin + rd_q <= rd; + end + end + + always @(posedge clk) begin + if (instr_any_mul && !(EXTRA_MUL_FFS ? active[3:0] : active[1:0])) begin + if (instr_rs1_signed) + rs1 <= $signed(pcpi_rs1); + else + rs1 <= $unsigned(pcpi_rs1); + + if (instr_rs2_signed) + rs2 <= $signed(pcpi_rs2); + else + rs2 <= $unsigned(pcpi_rs2); + active[0] <= 1; + end else begin + active[0] <= 0; + end + + active[3:1] <= active; + shift_out <= instr_any_mulh; + + if (!resetn) + active <= 0; + end + + assign pcpi_wr = active[EXTRA_MUL_FFS ? 3 : 1]; + assign pcpi_wait = 0; + assign pcpi_ready = active[EXTRA_MUL_FFS ? 3 : 1]; +`ifdef RISCV_FORMAL_ALTOPS + assign pcpi_rd = + instr_mul ? (pcpi_rs1 + pcpi_rs2) ^ 32'h5876063e : + instr_mulh ? (pcpi_rs1 + pcpi_rs2) ^ 32'hf6583fb7 : + instr_mulhsu ? (pcpi_rs1 - pcpi_rs2) ^ 32'hecfbe137 : + instr_mulhu ? (pcpi_rs1 + pcpi_rs2) ^ 32'h949ce5e8 : 1'bx; +`else + assign pcpi_rd = shift_out ? (EXTRA_MUL_FFS ? rd_q : rd) >> 32 : (EXTRA_MUL_FFS ? rd_q : rd); +`endif +endmodule + + +/*************************************************************** + * picorv32_pcpi_div + ***************************************************************/ + +module picorv32_pcpi_div ( + input clk, resetn, + + input pcpi_valid, + input [31:0] pcpi_insn, + input [31:0] pcpi_rs1, + input [31:0] pcpi_rs2, + output reg pcpi_wr, + output reg [31:0] pcpi_rd, + output reg pcpi_wait, + output reg pcpi_ready +); + reg instr_div, instr_divu, instr_rem, instr_remu; + wire instr_any_div_rem = |{instr_div, instr_divu, instr_rem, instr_remu}; + + reg pcpi_wait_q; + wire start = pcpi_wait && !pcpi_wait_q; + + always @(posedge clk) begin + instr_div <= 0; + instr_divu <= 0; + instr_rem <= 0; + instr_remu <= 0; + + if (resetn && pcpi_valid && !pcpi_ready && pcpi_insn[6:0] == 7'b0110011 && pcpi_insn[31:25] == 7'b0000001) begin + case (pcpi_insn[14:12]) + 3'b100: instr_div <= 1; + 3'b101: instr_divu <= 1; + 3'b110: instr_rem <= 1; + 3'b111: instr_remu <= 1; + endcase + end + + pcpi_wait <= instr_any_div_rem && resetn; + pcpi_wait_q <= pcpi_wait && resetn; + end + + reg [31:0] dividend; + reg [62:0] divisor; + reg [31:0] quotient; + reg [31:0] quotient_msk; + reg running; + reg outsign; + + always @(posedge clk) begin + pcpi_ready <= 0; + pcpi_wr <= 0; + pcpi_rd <= 'bx; + + if (!resetn) begin + running <= 0; + end else + if (start) begin + running <= 1; + dividend <= (instr_div || instr_rem) && pcpi_rs1[31] ? -pcpi_rs1 : pcpi_rs1; + divisor <= ((instr_div || instr_rem) && pcpi_rs2[31] ? -pcpi_rs2 : pcpi_rs2) << 31; + outsign <= (instr_div && (pcpi_rs1[31] != pcpi_rs2[31]) && |pcpi_rs2) || (instr_rem && pcpi_rs1[31]); + quotient <= 0; + quotient_msk <= 1 << 31; + end else + if (!quotient_msk && running) begin + running <= 0; + pcpi_ready <= 1; + pcpi_wr <= 1; +`ifdef RISCV_FORMAL_ALTOPS + case (1) + instr_div: pcpi_rd <= (pcpi_rs1 - pcpi_rs2) ^ 32'h7f8529ec; + instr_divu: pcpi_rd <= (pcpi_rs1 - pcpi_rs2) ^ 32'h10e8fd70; + instr_rem: pcpi_rd <= (pcpi_rs1 - pcpi_rs2) ^ 32'h8da68fa5; + instr_remu: pcpi_rd <= (pcpi_rs1 - pcpi_rs2) ^ 32'h3138d0e1; + endcase +`else + if (instr_div || instr_divu) + pcpi_rd <= outsign ? -quotient : quotient; + else + pcpi_rd <= outsign ? -dividend : dividend; +`endif + end else begin + if (divisor <= dividend) begin + dividend <= dividend - divisor; + quotient <= quotient | quotient_msk; + end + divisor <= divisor >> 1; +`ifdef RISCV_FORMAL_ALTOPS + quotient_msk <= quotient_msk >> 5; +`else + quotient_msk <= quotient_msk >> 1; +`endif + end + end +endmodule + + +/*************************************************************** + * picorv32_axi + ***************************************************************/ + +module picorv32_axi #( + parameter [ 0:0] ENABLE_COUNTERS = 1, + parameter [ 0:0] ENABLE_COUNTERS64 = 1, + parameter [ 0:0] ENABLE_REGS_16_31 = 1, + parameter [ 0:0] ENABLE_REGS_DUALPORT = 1, + parameter [ 0:0] TWO_STAGE_SHIFT = 1, + parameter [ 0:0] BARREL_SHIFTER = 0, + parameter [ 0:0] TWO_CYCLE_COMPARE = 0, + parameter [ 0:0] TWO_CYCLE_ALU = 0, + parameter [ 0:0] COMPRESSED_ISA = 0, + parameter [ 0:0] CATCH_MISALIGN = 1, + parameter [ 0:0] CATCH_ILLINSN = 1, + parameter [ 0:0] ENABLE_PCPI = 0, + parameter [ 0:0] ENABLE_MUL = 0, + parameter [ 0:0] ENABLE_FAST_MUL = 0, + parameter [ 0:0] ENABLE_DIV = 0, + parameter [ 0:0] ENABLE_IRQ = 0, + parameter [ 0:0] ENABLE_IRQ_QREGS = 1, + parameter [ 0:0] ENABLE_IRQ_TIMER = 1, + parameter [ 0:0] ENABLE_TRACE = 0, + parameter [ 0:0] REGS_INIT_ZERO = 0, + parameter [31:0] MASKED_IRQ = 32'h 0000_0000, + parameter [31:0] LATCHED_IRQ = 32'h ffff_ffff, + parameter [31:0] PROGADDR_RESET = 32'h 0000_0000, + parameter [31:0] PROGADDR_IRQ = 32'h 0000_0010, + parameter [31:0] STACKADDR = 32'h ffff_ffff +) ( + input clk, resetn, + output trap, + + // AXI4-lite master memory interface + + output mem_axi_awvalid, + input mem_axi_awready, + output [31:0] mem_axi_awaddr, + output [ 2:0] mem_axi_awprot, + + output mem_axi_wvalid, + input mem_axi_wready, + output [31:0] mem_axi_wdata, + output [ 3:0] mem_axi_wstrb, + + input mem_axi_bvalid, + output mem_axi_bready, + + output mem_axi_arvalid, + input mem_axi_arready, + output [31:0] mem_axi_araddr, + output [ 2:0] mem_axi_arprot, + + input mem_axi_rvalid, + output mem_axi_rready, + input [31:0] mem_axi_rdata, + + // Pico Co-Processor Interface (PCPI) + output pcpi_valid, + output [31:0] pcpi_insn, + output [31:0] pcpi_rs1, + output [31:0] pcpi_rs2, + input pcpi_wr, + input [31:0] pcpi_rd, + input pcpi_wait, + input pcpi_ready, + + // IRQ interface + input [31:0] irq, + output [31:0] eoi, + +`ifdef RISCV_FORMAL + output rvfi_valid, + output [63:0] rvfi_order, + output [31:0] rvfi_insn, + output rvfi_trap, + output rvfi_halt, + output rvfi_intr, + output [ 4:0] rvfi_rs1_addr, + output [ 4:0] rvfi_rs2_addr, + output [31:0] rvfi_rs1_rdata, + output [31:0] rvfi_rs2_rdata, + output [ 4:0] rvfi_rd_addr, + output [31:0] rvfi_rd_wdata, + output [31:0] rvfi_pc_rdata, + output [31:0] rvfi_pc_wdata, + output [31:0] rvfi_mem_addr, + output [ 3:0] rvfi_mem_rmask, + output [ 3:0] rvfi_mem_wmask, + output [31:0] rvfi_mem_rdata, + output [31:0] rvfi_mem_wdata, +`endif + + // Trace Interface + output trace_valid, + output [35:0] trace_data +); + wire mem_valid; + wire [31:0] mem_addr; + wire [31:0] mem_wdata; + wire [ 3:0] mem_wstrb; + wire mem_instr; + wire mem_ready; + wire [31:0] mem_rdata; + + picorv32_axi_adapter axi_adapter ( + .clk (clk ), + .resetn (resetn ), + .mem_axi_awvalid(mem_axi_awvalid), + .mem_axi_awready(mem_axi_awready), + .mem_axi_awaddr (mem_axi_awaddr ), + .mem_axi_awprot (mem_axi_awprot ), + .mem_axi_wvalid (mem_axi_wvalid ), + .mem_axi_wready (mem_axi_wready ), + .mem_axi_wdata (mem_axi_wdata ), + .mem_axi_wstrb (mem_axi_wstrb ), + .mem_axi_bvalid (mem_axi_bvalid ), + .mem_axi_bready (mem_axi_bready ), + .mem_axi_arvalid(mem_axi_arvalid), + .mem_axi_arready(mem_axi_arready), + .mem_axi_araddr (mem_axi_araddr ), + .mem_axi_arprot (mem_axi_arprot ), + .mem_axi_rvalid (mem_axi_rvalid ), + .mem_axi_rready (mem_axi_rready ), + .mem_axi_rdata (mem_axi_rdata ), + .mem_valid (mem_valid ), + .mem_instr (mem_instr ), + .mem_ready (mem_ready ), + .mem_addr (mem_addr ), + .mem_wdata (mem_wdata ), + .mem_wstrb (mem_wstrb ), + .mem_rdata (mem_rdata ) + ); + + picorv32 #( + .ENABLE_COUNTERS (ENABLE_COUNTERS ), + .ENABLE_COUNTERS64 (ENABLE_COUNTERS64 ), + .ENABLE_REGS_16_31 (ENABLE_REGS_16_31 ), + .ENABLE_REGS_DUALPORT(ENABLE_REGS_DUALPORT), + .TWO_STAGE_SHIFT (TWO_STAGE_SHIFT ), + .BARREL_SHIFTER (BARREL_SHIFTER ), + .TWO_CYCLE_COMPARE (TWO_CYCLE_COMPARE ), + .TWO_CYCLE_ALU (TWO_CYCLE_ALU ), + .COMPRESSED_ISA (COMPRESSED_ISA ), + .CATCH_MISALIGN (CATCH_MISALIGN ), + .CATCH_ILLINSN (CATCH_ILLINSN ), + .ENABLE_PCPI (ENABLE_PCPI ), + .ENABLE_MUL (ENABLE_MUL ), + .ENABLE_FAST_MUL (ENABLE_FAST_MUL ), + .ENABLE_DIV (ENABLE_DIV ), + .ENABLE_IRQ (ENABLE_IRQ ), + .ENABLE_IRQ_QREGS (ENABLE_IRQ_QREGS ), + .ENABLE_IRQ_TIMER (ENABLE_IRQ_TIMER ), + .ENABLE_TRACE (ENABLE_TRACE ), + .REGS_INIT_ZERO (REGS_INIT_ZERO ), + .MASKED_IRQ (MASKED_IRQ ), + .LATCHED_IRQ (LATCHED_IRQ ), + .PROGADDR_RESET (PROGADDR_RESET ), + .PROGADDR_IRQ (PROGADDR_IRQ ), + .STACKADDR (STACKADDR ) + ) picorv32_core ( + .clk (clk ), + .resetn (resetn), + .trap (trap ), + + .mem_valid(mem_valid), + .mem_addr (mem_addr ), + .mem_wdata(mem_wdata), + .mem_wstrb(mem_wstrb), + .mem_instr(mem_instr), + .mem_ready(mem_ready), + .mem_rdata(mem_rdata), + + .pcpi_valid(pcpi_valid), + .pcpi_insn (pcpi_insn ), + .pcpi_rs1 (pcpi_rs1 ), + .pcpi_rs2 (pcpi_rs2 ), + .pcpi_wr (pcpi_wr ), + .pcpi_rd (pcpi_rd ), + .pcpi_wait (pcpi_wait ), + .pcpi_ready(pcpi_ready), + + .irq(irq), + .eoi(eoi), + +`ifdef RISCV_FORMAL + .rvfi_valid (rvfi_valid ), + .rvfi_order (rvfi_order ), + .rvfi_insn (rvfi_insn ), + .rvfi_trap (rvfi_trap ), + .rvfi_halt (rvfi_halt ), + .rvfi_intr (rvfi_intr ), + .rvfi_rs1_addr (rvfi_rs1_addr ), + .rvfi_rs2_addr (rvfi_rs2_addr ), + .rvfi_rs1_rdata(rvfi_rs1_rdata), + .rvfi_rs2_rdata(rvfi_rs2_rdata), + .rvfi_rd_addr (rvfi_rd_addr ), + .rvfi_rd_wdata (rvfi_rd_wdata ), + .rvfi_pc_rdata (rvfi_pc_rdata ), + .rvfi_pc_wdata (rvfi_pc_wdata ), + .rvfi_mem_addr (rvfi_mem_addr ), + .rvfi_mem_rmask(rvfi_mem_rmask), + .rvfi_mem_wmask(rvfi_mem_wmask), + .rvfi_mem_rdata(rvfi_mem_rdata), + .rvfi_mem_wdata(rvfi_mem_wdata), +`endif + + .trace_valid(trace_valid), + .trace_data (trace_data) + ); +endmodule + + +/*************************************************************** + * picorv32_axi_adapter + ***************************************************************/ + +module picorv32_axi_adapter ( + input clk, resetn, + + // AXI4-lite master memory interface + + output mem_axi_awvalid, + input mem_axi_awready, + output [31:0] mem_axi_awaddr, + output [ 2:0] mem_axi_awprot, + + output mem_axi_wvalid, + input mem_axi_wready, + output [31:0] mem_axi_wdata, + output [ 3:0] mem_axi_wstrb, + + input mem_axi_bvalid, + output mem_axi_bready, + + output mem_axi_arvalid, + input mem_axi_arready, + output [31:0] mem_axi_araddr, + output [ 2:0] mem_axi_arprot, + + input mem_axi_rvalid, + output mem_axi_rready, + input [31:0] mem_axi_rdata, + + // Native PicoRV32 memory interface + + input mem_valid, + input mem_instr, + output mem_ready, + input [31:0] mem_addr, + input [31:0] mem_wdata, + input [ 3:0] mem_wstrb, + output [31:0] mem_rdata +); + reg ack_awvalid; + reg ack_arvalid; + reg ack_wvalid; + reg xfer_done; + + assign mem_axi_awvalid = mem_valid && |mem_wstrb && !ack_awvalid; + assign mem_axi_awaddr = mem_addr; + assign mem_axi_awprot = 0; + + assign mem_axi_arvalid = mem_valid && !mem_wstrb && !ack_arvalid; + assign mem_axi_araddr = mem_addr; + assign mem_axi_arprot = mem_instr ? 3'b100 : 3'b000; + + assign mem_axi_wvalid = mem_valid && |mem_wstrb && !ack_wvalid; + assign mem_axi_wdata = mem_wdata; + assign mem_axi_wstrb = mem_wstrb; + + assign mem_ready = mem_axi_bvalid || mem_axi_rvalid; + assign mem_axi_bready = mem_valid && |mem_wstrb; + assign mem_axi_rready = mem_valid && !mem_wstrb; + assign mem_rdata = mem_axi_rdata; + + always @(posedge clk) begin + if (!resetn) begin + ack_awvalid <= 0; + end else begin + xfer_done <= mem_valid && mem_ready; + if (mem_axi_awready && mem_axi_awvalid) + ack_awvalid <= 1; + if (mem_axi_arready && mem_axi_arvalid) + ack_arvalid <= 1; + if (mem_axi_wready && mem_axi_wvalid) + ack_wvalid <= 1; + if (xfer_done || !mem_valid) begin + ack_awvalid <= 0; + ack_arvalid <= 0; + ack_wvalid <= 0; + end + end + end +endmodule + + +/*************************************************************** + * picorv32_wb + ***************************************************************/ + +module picorv32_wb #( + parameter [ 0:0] ENABLE_COUNTERS = 1, + parameter [ 0:0] ENABLE_COUNTERS64 = 1, + parameter [ 0:0] ENABLE_REGS_16_31 = 1, + parameter [ 0:0] ENABLE_REGS_DUALPORT = 1, + parameter [ 0:0] TWO_STAGE_SHIFT = 1, + parameter [ 0:0] BARREL_SHIFTER = 0, + parameter [ 0:0] TWO_CYCLE_COMPARE = 0, + parameter [ 0:0] TWO_CYCLE_ALU = 0, + parameter [ 0:0] COMPRESSED_ISA = 0, + parameter [ 0:0] CATCH_MISALIGN = 1, + parameter [ 0:0] CATCH_ILLINSN = 1, + parameter [ 0:0] ENABLE_PCPI = 0, + parameter [ 0:0] ENABLE_MUL = 0, + parameter [ 0:0] ENABLE_FAST_MUL = 0, + parameter [ 0:0] ENABLE_DIV = 0, + parameter [ 0:0] ENABLE_IRQ = 0, + parameter [ 0:0] ENABLE_IRQ_QREGS = 1, + parameter [ 0:0] ENABLE_IRQ_TIMER = 1, + parameter [ 0:0] ENABLE_TRACE = 0, + parameter [ 0:0] REGS_INIT_ZERO = 0, + parameter [31:0] MASKED_IRQ = 32'h 0000_0000, + parameter [31:0] LATCHED_IRQ = 32'h ffff_ffff, + parameter [31:0] PROGADDR_RESET = 32'h 0000_0000, + parameter [31:0] PROGADDR_IRQ = 32'h 0000_0010, + parameter [31:0] STACKADDR = 32'h ffff_ffff +) ( + output trap, + + // Wishbone interfaces + input wb_rst_i, + input wb_clk_i, + + output reg [31:0] wbm_adr_o, + output reg [31:0] wbm_dat_o, + input [31:0] wbm_dat_i, + output reg wbm_we_o, + output reg [3:0] wbm_sel_o, + output reg wbm_stb_o, + input wbm_ack_i, + output reg wbm_cyc_o, + + // Pico Co-Processor Interface (PCPI) + output pcpi_valid, + output [31:0] pcpi_insn, + output [31:0] pcpi_rs1, + output [31:0] pcpi_rs2, + input pcpi_wr, + input [31:0] pcpi_rd, + input pcpi_wait, + input pcpi_ready, + + // IRQ interface + input [31:0] irq, + output [31:0] eoi, + +`ifdef RISCV_FORMAL + output rvfi_valid, + output [63:0] rvfi_order, + output [31:0] rvfi_insn, + output rvfi_trap, + output rvfi_halt, + output rvfi_intr, + output [ 4:0] rvfi_rs1_addr, + output [ 4:0] rvfi_rs2_addr, + output [31:0] rvfi_rs1_rdata, + output [31:0] rvfi_rs2_rdata, + output [ 4:0] rvfi_rd_addr, + output [31:0] rvfi_rd_wdata, + output [31:0] rvfi_pc_rdata, + output [31:0] rvfi_pc_wdata, + output [31:0] rvfi_mem_addr, + output [ 3:0] rvfi_mem_rmask, + output [ 3:0] rvfi_mem_wmask, + output [31:0] rvfi_mem_rdata, + output [31:0] rvfi_mem_wdata, +`endif + + // Trace Interface + output trace_valid, + output [35:0] trace_data, + + output mem_instr +); + wire mem_valid; + wire [31:0] mem_addr; + wire [31:0] mem_wdata; + wire [ 3:0] mem_wstrb; + reg mem_ready; + reg [31:0] mem_rdata; + + wire clk; + wire resetn; + + assign clk = wb_clk_i; + assign resetn = ~wb_rst_i; + + picorv32 #( + .ENABLE_COUNTERS (ENABLE_COUNTERS ), + .ENABLE_COUNTERS64 (ENABLE_COUNTERS64 ), + .ENABLE_REGS_16_31 (ENABLE_REGS_16_31 ), + .ENABLE_REGS_DUALPORT(ENABLE_REGS_DUALPORT), + .TWO_STAGE_SHIFT (TWO_STAGE_SHIFT ), + .BARREL_SHIFTER (BARREL_SHIFTER ), + .TWO_CYCLE_COMPARE (TWO_CYCLE_COMPARE ), + .TWO_CYCLE_ALU (TWO_CYCLE_ALU ), + .COMPRESSED_ISA (COMPRESSED_ISA ), + .CATCH_MISALIGN (CATCH_MISALIGN ), + .CATCH_ILLINSN (CATCH_ILLINSN ), + .ENABLE_PCPI (ENABLE_PCPI ), + .ENABLE_MUL (ENABLE_MUL ), + .ENABLE_FAST_MUL (ENABLE_FAST_MUL ), + .ENABLE_DIV (ENABLE_DIV ), + .ENABLE_IRQ (ENABLE_IRQ ), + .ENABLE_IRQ_QREGS (ENABLE_IRQ_QREGS ), + .ENABLE_IRQ_TIMER (ENABLE_IRQ_TIMER ), + .ENABLE_TRACE (ENABLE_TRACE ), + .REGS_INIT_ZERO (REGS_INIT_ZERO ), + .MASKED_IRQ (MASKED_IRQ ), + .LATCHED_IRQ (LATCHED_IRQ ), + .PROGADDR_RESET (PROGADDR_RESET ), + .PROGADDR_IRQ (PROGADDR_IRQ ), + .STACKADDR (STACKADDR ) + ) picorv32_core ( + .clk (clk ), + .resetn (resetn), + .trap (trap ), + + .mem_valid(mem_valid), + .mem_addr (mem_addr ), + .mem_wdata(mem_wdata), + .mem_wstrb(mem_wstrb), + .mem_instr(mem_instr), + .mem_ready(mem_ready), + .mem_rdata(mem_rdata), + + .pcpi_valid(pcpi_valid), + .pcpi_insn (pcpi_insn ), + .pcpi_rs1 (pcpi_rs1 ), + .pcpi_rs2 (pcpi_rs2 ), + .pcpi_wr (pcpi_wr ), + .pcpi_rd (pcpi_rd ), + .pcpi_wait (pcpi_wait ), + .pcpi_ready(pcpi_ready), + + .irq(irq), + .eoi(eoi), + +`ifdef RISCV_FORMAL + .rvfi_valid (rvfi_valid ), + .rvfi_order (rvfi_order ), + .rvfi_insn (rvfi_insn ), + .rvfi_trap (rvfi_trap ), + .rvfi_halt (rvfi_halt ), + .rvfi_intr (rvfi_intr ), + .rvfi_rs1_addr (rvfi_rs1_addr ), + .rvfi_rs2_addr (rvfi_rs2_addr ), + .rvfi_rs1_rdata(rvfi_rs1_rdata), + .rvfi_rs2_rdata(rvfi_rs2_rdata), + .rvfi_rd_addr (rvfi_rd_addr ), + .rvfi_rd_wdata (rvfi_rd_wdata ), + .rvfi_pc_rdata (rvfi_pc_rdata ), + .rvfi_pc_wdata (rvfi_pc_wdata ), + .rvfi_mem_addr (rvfi_mem_addr ), + .rvfi_mem_rmask(rvfi_mem_rmask), + .rvfi_mem_wmask(rvfi_mem_wmask), + .rvfi_mem_rdata(rvfi_mem_rdata), + .rvfi_mem_wdata(rvfi_mem_wdata), +`endif + + .trace_valid(trace_valid), + .trace_data (trace_data) + ); + + localparam IDLE = 2'b00; + localparam WBSTART = 2'b01; + localparam WBEND = 2'b10; + + reg [1:0] state; + + wire we; + assign we = (mem_wstrb[0] | mem_wstrb[1] | mem_wstrb[2] | mem_wstrb[3]); + + always @(posedge wb_clk_i) begin + if (wb_rst_i) begin + wbm_adr_o <= 0; + wbm_dat_o <= 0; + wbm_we_o <= 0; + wbm_sel_o <= 0; + wbm_stb_o <= 0; + wbm_cyc_o <= 0; + state <= IDLE; + end else begin + case (state) + IDLE: begin + if (mem_valid) begin + wbm_adr_o <= mem_addr; + wbm_dat_o <= mem_wdata; + wbm_we_o <= we; + wbm_sel_o <= mem_wstrb; + + wbm_stb_o <= 1'b1; + wbm_cyc_o <= 1'b1; + state <= WBSTART; + end else begin + mem_ready <= 1'b0; + + wbm_stb_o <= 1'b0; + wbm_cyc_o <= 1'b0; + wbm_we_o <= 1'b0; + end + end + WBSTART:begin + if (wbm_ack_i) begin + mem_rdata <= wbm_dat_i; + mem_ready <= 1'b1; + + state <= WBEND; + + wbm_stb_o <= 1'b0; + wbm_cyc_o <= 1'b0; + wbm_we_o <= 1'b0; + end + end + WBEND: begin + mem_ready <= 1'b0; + + state <= IDLE; + end + default: + state <= IDLE; + endcase + end + end +endmodule diff --git a/tests/functional/picorv32_tb.v b/tests/functional/picorv32_tb.v new file mode 100644 index 00000000000..1404c2d8c27 --- /dev/null +++ b/tests/functional/picorv32_tb.v @@ -0,0 +1,62 @@ +module gold( + input wire clk, + output reg resetn, + output wire trap, + output wire mem_valid, + output wire mem_instr, + output wire mem_ready, + output wire [31:0] mem_addr, + output wire [31:0] mem_wdata, + output wire [31:0] mem_wstrb, + output wire [31:0] mem_rdata, +); + + initial resetn = 1'b0; + always @(posedge clk) resetn <= 1'b1; + + reg [31:0] rom[0:15]; + + initial begin + rom[0] = 32'h00200093; + rom[1] = 32'h00200113; + rom[2] = 32'h00111863; + rom[3] = 32'h00102023; + rom[4] = 32'h00108093; + rom[5] = 32'hfe0008e3; + rom[6] = 32'h00008193; + rom[7] = 32'h402181b3; + rom[8] = 32'hfe304ee3; + rom[9] = 32'hfe0186e3; + rom[10] = 32'h00110113; + rom[11] = 32'hfc000ee3; + end + + assign mem_ready = 1'b1; + assign mem_rdata = rom[mem_addr[5:2]]; + + wire pcpi_wr = 1'b0; + wire [31:0] pcpi_rd = 32'b0; + wire pcpi_wait = 1'b0; + wire pcpi_ready = 1'b0; + + wire [31:0] irq = 32'b0; + + picorv32 picorv32_i( + .clk(clk), + .resetn(resetn), + .trap(trap), + .mem_valid(mem_valid), + .mem_instr(mem_instr), + .mem_ready(mem_ready), + .mem_addr(mem_addr), + .mem_wdata(mem_wdata), + .mem_wstrb(mem_wstrb), + .mem_rdata(mem_rdata), + .pcpi_wr(pcpi_wr), + .pcpi_rd(pcpi_rd), + .pcpi_wait(pcpi_wait), + .pcpi_ready(pcpi_ready), + .irq(irq) + ); + +endmodule diff --git a/tests/functional/rtlil_cells.py b/tests/functional/rtlil_cells.py index ed867b695b4..964d81ddf50 100644 --- a/tests/functional/rtlil_cells.py +++ b/tests/functional/rtlil_cells.py @@ -233,6 +233,16 @@ def write_rtlil_file(self, path, parameters): endmodule""".format(parameters['DATA_WIDTH'] - 1, parameters['ADDR_WIDTH'] - 1, 2**parameters['ADDR_WIDTH'] - 1)) yosys_synth(verilog_file, path) +class PicorvCell(BaseCell): + def __init__(self): + super().__init__("picorv", [], {}, {}, [()]) + self.smt_max_steps = 50 # z3 is too slow for more steps + def write_rtlil_file(self, path, parameters): + from test_functional import yosys, base_path, quote + tb_file = base_path / 'tests/functional/picorv32_tb.v' + cpu_file = base_path / 'tests/functional/picorv32.v' + yosys(f"read_verilog {quote(tb_file)} {quote(cpu_file)}; prep -top gold; flatten; write_rtlil {quote(path)}") + binary_widths = [ # try to cover extending A operand, extending B operand, extending/truncating result (16, 32, 48, True, True), @@ -353,6 +363,7 @@ def write_rtlil_file(self, path, parameters): # ("original_tag", ["A", "Y"]), # ("future_ff", ["A", "Y"]), # ("scopeinfo", []), + PicorvCell() ] def generate_test_cases(per_cell, rnd): diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index 7bfe19fc494..107ca6862e5 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -63,6 +63,9 @@ def test_smt(cell, parameters, tmp_path, num_steps, rnd): vcd_functional_file = tmp_path / 'functional.vcd' vcd_yosys_sim_file = tmp_path / 'yosys.vcd' + if hasattr(cell, 'smt_max_steps'): + num_steps = min(num_steps, cell.smt_max_steps) + cell.write_rtlil_file(rtlil_file, parameters) yosys(f"read_rtlil {quote(rtlil_file)} ; clk2fflogic ; write_functional_smt2 {quote(smt_file)}") run(['z3', smt_file]) # check if output is valid smtlib before continuing From f456761e88886053c503393fb2356afa45ef750f Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Tue, 13 Aug 2024 14:04:04 +0100 Subject: [PATCH 74/92] add sandia copyright notice to the functional backend --- backends/functional/cxx.cc | 1 + backends/functional/cxx_runtime/sim.h | 1 + backends/functional/smtlib.cc | 1 + backends/functional/test_generic.cc | 1 + kernel/functional.cc | 1 + kernel/functional.h | 1 + 6 files changed, 6 insertions(+) diff --git a/backends/functional/cxx.cc b/backends/functional/cxx.cc index 19740777fb7..282eb8f673c 100644 --- a/backends/functional/cxx.cc +++ b/backends/functional/cxx.cc @@ -2,6 +2,7 @@ * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2024 Emily Schmidt + * Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above diff --git a/backends/functional/cxx_runtime/sim.h b/backends/functional/cxx_runtime/sim.h index c50bc2cd818..941f067b930 100644 --- a/backends/functional/cxx_runtime/sim.h +++ b/backends/functional/cxx_runtime/sim.h @@ -2,6 +2,7 @@ * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2024 Emily Schmidt + * Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index ec9e6b24279..4e176c2fd3e 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -2,6 +2,7 @@ * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2024 Emily Schmidt + * Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above diff --git a/backends/functional/test_generic.cc b/backends/functional/test_generic.cc index 1617d54c069..aab2260f177 100644 --- a/backends/functional/test_generic.cc +++ b/backends/functional/test_generic.cc @@ -2,6 +2,7 @@ * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2024 Emily Schmidt + * Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above diff --git a/kernel/functional.cc b/kernel/functional.cc index eaae3f09808..1ece145e59f 100644 --- a/kernel/functional.cc +++ b/kernel/functional.cc @@ -2,6 +2,7 @@ * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2024 Emily Schmidt + * Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above diff --git a/kernel/functional.h b/kernel/functional.h index f7ff08228b1..88a48dba9ec 100644 --- a/kernel/functional.h +++ b/kernel/functional.h @@ -2,6 +2,7 @@ * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2024 Emily Schmidt + * Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above From 9b5e81b13fbc2e4947ad591bd49971a278082247 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Thu, 22 Aug 2024 10:40:56 +0100 Subject: [PATCH 75/92] drivertools: fix C++20 "incomplete type" error by moving constructors below other definitions --- kernel/drivertools.h | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/kernel/drivertools.h b/kernel/drivertools.h index 8a51b88d9ef..079701c35cc 100644 --- a/kernel/drivertools.h +++ b/kernel/drivertools.h @@ -146,11 +146,8 @@ struct DriveBitMultiple pool multiple_; public: - DriveBitMultiple() {} - DriveBitMultiple(DriveBit const &single) - { - multiple_.emplace(single); - } + DriveBitMultiple(); + DriveBitMultiple(DriveBit const &single); pool const &multiple() const { return multiple_; } @@ -467,6 +464,11 @@ struct DriveBit }; +inline DriveBitMultiple::DriveBitMultiple() {} +inline DriveBitMultiple::DriveBitMultiple(DriveBit const &single) +{ + multiple_.emplace(single); +} struct DriveChunkWire { @@ -626,10 +628,7 @@ struct DriveChunkMultiple public: pool const &multiple() const { return multiple_; } - DriveChunkMultiple(DriveBitMultiple const &bit) : width_(1) { - for (auto const &bit : bit.multiple()) - multiple_.emplace(bit); - } + DriveChunkMultiple(DriveBitMultiple const &bit); int size() const { return width_; } @@ -1030,6 +1029,13 @@ struct DriveChunk } }; +inline DriveChunkMultiple::DriveChunkMultiple(DriveBitMultiple const &bit) + : width_(1) +{ + for (auto const &bit : bit.multiple()) + multiple_.emplace(bit); +} + struct DriveSpec { private: From 761eff594f0d21f73af3cfc4d9d9c3fe08d61edc Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Thu, 22 Aug 2024 11:13:58 +0100 Subject: [PATCH 76/92] functional backend: missing includes for stl containers --- kernel/functional.cc | 1 + tests/functional/vcd_harness.cc | 1 + 2 files changed, 2 insertions(+) diff --git a/kernel/functional.cc b/kernel/functional.cc index 1ece145e59f..11f030eae14 100644 --- a/kernel/functional.cc +++ b/kernel/functional.cc @@ -22,6 +22,7 @@ #include "kernel/topo_scc.h" #include "ff.h" #include "ffinit.h" +#include YOSYS_NAMESPACE_BEGIN namespace Functional { diff --git a/tests/functional/vcd_harness.cc b/tests/functional/vcd_harness.cc index 7232172348a..30238429b6e 100644 --- a/tests/functional/vcd_harness.cc +++ b/tests/functional/vcd_harness.cc @@ -4,6 +4,7 @@ #include #include #include +#include #include "my_module_functional_cxx.cc" From 459e6b913a95beed390b650fef787a466b582332 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Tue, 27 Aug 2024 11:11:02 +0100 Subject: [PATCH 77/92] add functional ir documentation --- .../extending_yosys/functional_ir.rst | 42 +++++++++++++++++++ kernel/functional.h | 1 - 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 docs/source/yosys_internals/extending_yosys/functional_ir.rst diff --git a/docs/source/yosys_internals/extending_yosys/functional_ir.rst b/docs/source/yosys_internals/extending_yosys/functional_ir.rst new file mode 100644 index 00000000000..c0794519df3 --- /dev/null +++ b/docs/source/yosys_internals/extending_yosys/functional_ir.rst @@ -0,0 +1,42 @@ +Writing a new backend using FunctionalIR +=========================================== + +To simplify the writing of backends for functional languages or similar targets, Yosys provides an alternative intermediate representation called FunctionalIR which maps more directly on those targets. + +FunctionalIR represents the design as a function `(inputs, current_state) -> (outputs, next_state)`. +This function is broken down into a series of assignments to variables. +Those assignments only use simple operations, like a simple addition. +Unlike RTLIL cells there is no support for automatically extending operands, every sign and zero extension has to be encoded as a separate operation. + +Like SSA form, each variable is assigned to exactly once. +We can thus treat variables and assignments as equivalent and, since this is a graph-like representation, those variables are also called "nodes". +Unlike RTLIL's cells and wires representation, this representation is strictly ordered (topologically sorted) with definitions preceding their use. + +Nodes are strongly typed, the two types (also called "sorts") available are +- `bit[n]` for an `n`-bit bitvector, and +- `memory[n,m]` for an immutable array of `2**n` values of type `bit[m]`. + +In terms of actual code, Yosys provides a class `Functional::IR` that represents a design in FunctionalIR. +`Functional::IR::from_module` generates an instance from an RTLIL module. +The entire design is stored as a whole in an internal data structure. +To access the design, the `Functional::Node` class provides a reference to a particular node in the design. +The `Functional::IR` class supports the syntax `for(auto node : ir)` to iterate over every node. + +`Functional::IR` also keeps track of inputs, outputs and "states" (registers and memories). +They all have a name (equal to their name in RTLIL), a sort and a "type". +The "type" field usually remains as the default value `$input`, `$output` or `$state`, however some RTLIL cells such as `$assert` or `$anyseq` generate auxiliary inputs/outputs/states that are given a different type to distinguish them from ordinary RTLIL inputs/outputs/states. +- To access an individual input/output/"state", use `ir.input(name, type)`, `ir.output(name, type)` or `ir.state(name, type)`. `type` defaults to the default type. +- To iterate over all inputs/outputs/"states" of a certain "type", methods `ir.inputs`, `ir.outputs`, `ir.states` are provided. Their argument defaults to the default types mentioned. +- To iterate over inputs/outputs/"states" of any "type", use `ir.all_inputs`, `ir.all_outputs` and `ir.all_states`. +- Outputs have a node that indicate the value of the output, this can be retrieved via `output.value()`. +- States have a node that indicate the next value of the state, this can be retrieved via `state.next_value()`. + They also have an initial value that is accessed as either `state.initial_value_signal()` or `state.initial_value_memory()`, depending on their sort. + +Each node has a "function", which defines its operation (for a complete list of functions and a specification of their operation, see `functional.h`). +Functions are represented as an enum `Functional::Fn` and the function field can be accessed as `node.fn()`. +Since the most common operation is a switch over the function that also accesses the arguments, the `Node` class provides a method `visit` that implements the visitor pattern. +For example, for an addition node `node` with arguments `n1` and `n2`, `node.visit(visitor)` would call `visitor.add(node, n1, n2)`. +Thus typically one would implement class with a method for every function. +Visitors should inherit from either `Functional::AbstractVisitor` or `Functional::DefaultVisitor`. +The former will produce a compiler error if a case is unhandled, the latter will call `default_handler(node)` instead. +Visitor methods should be marked as `override` to provide compiler errors if the arguments are wrong. \ No newline at end of file diff --git a/kernel/functional.h b/kernel/functional.h index 88a48dba9ec..bc7b86ac15c 100644 --- a/kernel/functional.h +++ b/kernel/functional.h @@ -287,7 +287,6 @@ namespace Functional { bool operator!=(iterator const &other) const { return _ir != other._ir || _index != other._index; } bool operator==(iterator const &other) const { return !(*this != other); } pointer operator->(); - // TODO: implement operator-> using the arrow_proxy class currently in mem.h }; iterator begin() { return iterator(this, 0); } iterator end() { return iterator(this, _graph.size()); } From b428bf4600774b49de780e1dae7ff00f6564e72f Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Tue, 27 Aug 2024 13:10:34 +0100 Subject: [PATCH 78/92] functional backends: identifiers in c++/smtlib may not start with digits --- backends/functional/cxx.cc | 4 ++-- backends/functional/smtlib.cc | 4 ++-- kernel/functional.h | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/backends/functional/cxx.cc b/backends/functional/cxx.cc index 282eb8f673c..de7737a420e 100644 --- a/backends/functional/cxx.cc +++ b/backends/functional/cxx.cc @@ -48,8 +48,8 @@ template struct CxxScope : public Functional::Scope { for(const char **p = reserved_keywords; *p != nullptr; p++) this->reserve(*p); } - bool is_character_legal(char c) override { - return isascii(c) && (isalnum(c) || c == '_' || c == '$'); + bool is_character_legal(char c, int index) override { + return isascii(c) && (isalpha(c) || (isdigit(c) && index > 0) || c == '_' || c == '$'); } }; diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index 4e176c2fd3e..ae6ae40ca4b 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -48,8 +48,8 @@ struct SmtScope : public Functional::Scope { for(const char **p = reserved_keywords; *p != nullptr; p++) reserve(*p); } - bool is_character_legal(char c) override { - return isascii(c) && (isalnum(c) || strchr("~!@$%^&*_-+=<>.?/", c)); + bool is_character_legal(char c, int index) override { + return isascii(c) && (isalpha(c) || (isdigit(c) && index > 0) || strchr("~!@$%^&*_-+=<>.?/", c)); } }; diff --git a/kernel/functional.h b/kernel/functional.h index bc7b86ac15c..2afdd7fc339 100644 --- a/kernel/functional.h +++ b/kernel/functional.h @@ -576,7 +576,7 @@ namespace Functional { template class Scope { protected: char substitution_character = '_'; - virtual bool is_character_legal(char) = 0; + virtual bool is_character_legal(char, int) = 0; private: pool _used_names; dict _by_id; @@ -587,7 +587,7 @@ namespace Functional { std::string unique_name(IdString suggestion) { std::string str = RTLIL::unescape_id(suggestion); for(size_t i = 0; i < str.size(); i++) - if(!is_character_legal(str[i])) + if(!is_character_legal(str[i], i)) str[i] = substitution_character; if(_used_names.count(str) == 0) { _used_names.insert(str); From 27efed27c2ed94bd9778f6b4fc9640c2ebd1bf5b Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Wed, 28 Aug 2024 11:28:24 +0100 Subject: [PATCH 79/92] functional backend: more documentation --- .../extending_yosys/functional_ir.rst | 65 +++++++++++++++---- 1 file changed, 54 insertions(+), 11 deletions(-) diff --git a/docs/source/yosys_internals/extending_yosys/functional_ir.rst b/docs/source/yosys_internals/extending_yosys/functional_ir.rst index c0794519df3..887d44a6ec1 100644 --- a/docs/source/yosys_internals/extending_yosys/functional_ir.rst +++ b/docs/source/yosys_internals/extending_yosys/functional_ir.rst @@ -5,16 +5,17 @@ To simplify the writing of backends for functional languages or similar targets, FunctionalIR represents the design as a function `(inputs, current_state) -> (outputs, next_state)`. This function is broken down into a series of assignments to variables. -Those assignments only use simple operations, like a simple addition. -Unlike RTLIL cells there is no support for automatically extending operands, every sign and zero extension has to be encoded as a separate operation. +Each assignment is a simple operation, such as an addition. +Complex operations are broken up into multiple steps. +For example, an RTLIL addition will be translated into a sign/zero extension of the inputs, followed by an addition. Like SSA form, each variable is assigned to exactly once. We can thus treat variables and assignments as equivalent and, since this is a graph-like representation, those variables are also called "nodes". Unlike RTLIL's cells and wires representation, this representation is strictly ordered (topologically sorted) with definitions preceding their use. -Nodes are strongly typed, the two types (also called "sorts") available are +Every node has a "sort" (the FunctionalIR term for what might otherwise be called a "type"). The sorts available are - `bit[n]` for an `n`-bit bitvector, and -- `memory[n,m]` for an immutable array of `2**n` values of type `bit[m]`. +- `memory[n,m]` for an immutable array of `2**n` values of sort `bit[m]`. In terms of actual code, Yosys provides a class `Functional::IR` that represents a design in FunctionalIR. `Functional::IR::from_module` generates an instance from an RTLIL module. @@ -22,12 +23,14 @@ The entire design is stored as a whole in an internal data structure. To access the design, the `Functional::Node` class provides a reference to a particular node in the design. The `Functional::IR` class supports the syntax `for(auto node : ir)` to iterate over every node. -`Functional::IR` also keeps track of inputs, outputs and "states" (registers and memories). -They all have a name (equal to their name in RTLIL), a sort and a "type". +`Functional::IR` also keeps track of inputs, outputs and states. +By a "state" we mean a pair of a "current state" input and a "next state" output. +One such pair is created for every register and for every memory. +Every input, output and state has a name (equal to their name in RTLIL), a sort and a "type". The "type" field usually remains as the default value `$input`, `$output` or `$state`, however some RTLIL cells such as `$assert` or `$anyseq` generate auxiliary inputs/outputs/states that are given a different type to distinguish them from ordinary RTLIL inputs/outputs/states. -- To access an individual input/output/"state", use `ir.input(name, type)`, `ir.output(name, type)` or `ir.state(name, type)`. `type` defaults to the default type. -- To iterate over all inputs/outputs/"states" of a certain "type", methods `ir.inputs`, `ir.outputs`, `ir.states` are provided. Their argument defaults to the default types mentioned. -- To iterate over inputs/outputs/"states" of any "type", use `ir.all_inputs`, `ir.all_outputs` and `ir.all_states`. +- To access an individual input/output/state, use `ir.input(name, type)`, `ir.output(name, type)` or `ir.state(name, type)`. `type` defaults to the default type. +- To iterate over all inputs/outputs/states of a certain "type", methods `ir.inputs`, `ir.outputs`, `ir.states` are provided. Their argument defaults to the default types mentioned. +- To iterate over inputs/outputs/states of any "type", use `ir.all_inputs`, `ir.all_outputs` and `ir.all_states`. - Outputs have a node that indicate the value of the output, this can be retrieved via `output.value()`. - States have a node that indicate the next value of the state, this can be retrieved via `state.next_value()`. They also have an initial value that is accessed as either `state.initial_value_signal()` or `state.initial_value_memory()`, depending on their sort. @@ -36,7 +39,47 @@ Each node has a "function", which defines its operation (for a complete list of Functions are represented as an enum `Functional::Fn` and the function field can be accessed as `node.fn()`. Since the most common operation is a switch over the function that also accesses the arguments, the `Node` class provides a method `visit` that implements the visitor pattern. For example, for an addition node `node` with arguments `n1` and `n2`, `node.visit(visitor)` would call `visitor.add(node, n1, n2)`. -Thus typically one would implement class with a method for every function. +Thus typically one would implement a class with a method for every function. Visitors should inherit from either `Functional::AbstractVisitor` or `Functional::DefaultVisitor`. The former will produce a compiler error if a case is unhandled, the latter will call `default_handler(node)` instead. -Visitor methods should be marked as `override` to provide compiler errors if the arguments are wrong. \ No newline at end of file +Visitor methods should be marked as `override` to provide compiler errors if the arguments are wrong. + +Utility classes +----------------- + +`functional.h` also provides utility classes that are independent of the main FunctionalIR representation but are likely to be useful for backends. + +`Functional::Writer` provides a simple formatting class that wraps a `std::ostream` and provides the following methods: +- `writer << value` wraps `os << value`. +- `writer.print(fmt, value0, value1, value2, ...)` replaces `{0}`, `{1}`, `{2}`, etc in the string `fmt` with `value0`, `value1`, `value2`, resp. + Each value is formatted using `os << value`. + It is also possible to write `{}` to refer to one past the last index, i.e. `{1} {} {} {7} {}` is equivalent to `{1} {2} {3} {7} {8}`. +- `writer.print_with(fn, fmt, value0, value1, value2, ...)` functions much the same as `print` but it uses `os << fn(value)` to print each value and falls back to `os << value` if `fn(value)` is not legal. + +`Functional::Scope` keeps track of variable names in a target language. +It is used to translate between different sets of legal characters and to avoid accidentally re-defining identifiers. +Users should derive a class from `Scope` and supply the following: +- `Scope` takes a template argument that specifies a type that's used to uniquely distinguish variables. + Typically this would be `int` (if variables are used for `Functional::IR` nodes) or `IdString`. +- The derived class should provide a constructor that calls `reserve` for every reserved word in the target language. +- A method `bool is_legal_character(char c, int index)` has to be provided that returns `true` iff `c` is legal in an identifier at position `index`. +Given an instance `scope` of the derived class, the following methods are then available: +- `scope.reserve(std::string name)` marks the given name as being in-use +- `scope.unique_name(IdString suggestion)` generates a previously unused name and attempts to make it similar to `suggestion`. +- `scope(Id id, IdString suggestion)` functions similar to `unique_name`, except that multiple calls with the same `id` are guaranteed to retrieve the same name (independent of `suggestion`). + +`sexpr.h` provides classes that represent and pretty-print s-expressions. +S-expressions can be constructed with `SExpr::list`, for example `SExpr expr = SExpr::list("add", "x", SExpr::list("mul", "y", "z"))` represents `(add x (mul y z))` +(by adding `using SExprUtil::list` to the top of the file, `list` can be used as shorthand for `SExpr::list`). +For prettyprinting, `SExprWriter` wraps an `std::ostream` and provides the following methods: +- `writer << sexpr` writes the provided expression to the output, breaking long lines and adding appropriate indentation. +- `writer.open(sexpr)` is similar to `writer << sexpr` but will omit the last closing parenthesis. + Further arguments can then be added separately with `<<` or `open`. + This allows for printing large s-expressions without needing the construct the whole expression in memory first. +- `writer.open(sexpr, false)` is similar to `writer.open(sexpr)` but further arguments will not be indented. + This is used to avoid unlimited indentation on structures with unlimited nesting. +- `writer.close(n = 1)` closes the last `n` open s-expressions. +- `writer.push()` and `writer.pop()` are used to automatically close s-expressions. + `writer.pop()` closes all s-expressions opened since the last call to `writer.push()`. +- `writer.comment(string)` writes a comment on a separate-line. + `writer.comment(string, true)` appends a comment to the last printed s-expression. \ No newline at end of file From 4eeb8d326acbd2c3d4fcdc4cef798abb1a2e0728 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Wed, 28 Aug 2024 12:39:41 +0100 Subject: [PATCH 80/92] functional backend: rename "type" to either "kind" or "sort" to make the terminology consistent --- backends/functional/cxx.cc | 4 +- backends/functional/smtlib.cc | 4 +- backends/functional/test_generic.cc | 4 +- .../extending_yosys/functional_ir.rst | 10 +-- kernel/functional.cc | 16 ++-- kernel/functional.h | 88 +++++++++---------- 6 files changed, 63 insertions(+), 63 deletions(-) diff --git a/backends/functional/cxx.cc b/backends/functional/cxx.cc index de7737a420e..b8b25df8b12 100644 --- a/backends/functional/cxx.cc +++ b/backends/functional/cxx.cc @@ -151,8 +151,8 @@ template struct CxxPrintVisitor : public Functional::Abstract void arithmetic_shift_right(Node, Node a, Node b) override { print("{}.arithmetic_shift_right({})", a, b); } void mux(Node, Node a, Node b, Node s) override { print("{2}.any() ? {1} : {0}", a, b, s); } void constant(Node, RTLIL::Const const & value) override { print("{}", cxx_const(value)); } - void input(Node, IdString name, IdString type) override { log_assert(type == ID($input)); print("input.{}", input_struct[name]); } - void state(Node, IdString name, IdString type) override { log_assert(type == ID($state)); print("current_state.{}", state_struct[name]); } + void input(Node, IdString name, IdString kind) override { log_assert(kind == ID($input)); print("input.{}", input_struct[name]); } + void state(Node, IdString name, IdString kind) override { log_assert(kind == ID($state)); print("current_state.{}", state_struct[name]); } void memory_read(Node, Node mem, Node addr) override { print("{}.read({})", mem, addr); } void memory_write(Node, Node mem, Node addr, Node data) override { print("{}.write({}, {})", mem, addr, data); } }; diff --git a/backends/functional/smtlib.cc b/backends/functional/smtlib.cc index ae6ae40ca4b..3eacf407cf2 100644 --- a/backends/functional/smtlib.cc +++ b/backends/functional/smtlib.cc @@ -179,8 +179,8 @@ struct SmtPrintVisitor : public Functional::AbstractVisitor { SExpr memory_read(Node, Node mem, Node addr) override { return list("select", n(mem), n(addr)); } SExpr memory_write(Node, Node mem, Node addr, Node data) override { return list("store", n(mem), n(addr), n(data)); } - SExpr input(Node, IdString name, IdString type) override { log_assert(type == ID($input)); return input_struct.access("inputs", name); } - SExpr state(Node, IdString name, IdString type) override { log_assert(type == ID($state)); return state_struct.access("state", name); } + SExpr input(Node, IdString name, IdString kind) override { log_assert(kind == ID($input)); return input_struct.access("inputs", name); } + SExpr state(Node, IdString name, IdString kind) override { log_assert(kind == ID($state)); return state_struct.access("state", name); } }; struct SmtModule { diff --git a/backends/functional/test_generic.cc b/backends/functional/test_generic.cc index aab2260f177..10a7a50f018 100644 --- a/backends/functional/test_generic.cc +++ b/backends/functional/test_generic.cc @@ -144,9 +144,9 @@ struct FunctionalTestGeneric : public Pass for(auto node : fir) std::cout << RTLIL::unescape_id(node.name()) << " = " << node.to_string([](auto n) { return RTLIL::unescape_id(n.name()); }) << "\n"; for(auto output : fir.all_outputs()) - std::cout << RTLIL::unescape_id(output->type) << " " << RTLIL::unescape_id(output->name) << " = " << RTLIL::unescape_id(output->value().name()) << "\n"; + std::cout << RTLIL::unescape_id(output->kind) << " " << RTLIL::unescape_id(output->name) << " = " << RTLIL::unescape_id(output->value().name()) << "\n"; for(auto state : fir.all_states()) - std::cout << RTLIL::unescape_id(state->type) << " " << RTLIL::unescape_id(state->name) << " = " << RTLIL::unescape_id(state->next_value().name()) << "\n"; + std::cout << RTLIL::unescape_id(state->kind) << " " << RTLIL::unescape_id(state->name) << " = " << RTLIL::unescape_id(state->next_value().name()) << "\n"; } } } FunctionalCxxBackend; diff --git a/docs/source/yosys_internals/extending_yosys/functional_ir.rst b/docs/source/yosys_internals/extending_yosys/functional_ir.rst index 887d44a6ec1..38d00055627 100644 --- a/docs/source/yosys_internals/extending_yosys/functional_ir.rst +++ b/docs/source/yosys_internals/extending_yosys/functional_ir.rst @@ -26,11 +26,11 @@ The `Functional::IR` class supports the syntax `for(auto node : ir)` to iterate `Functional::IR` also keeps track of inputs, outputs and states. By a "state" we mean a pair of a "current state" input and a "next state" output. One such pair is created for every register and for every memory. -Every input, output and state has a name (equal to their name in RTLIL), a sort and a "type". -The "type" field usually remains as the default value `$input`, `$output` or `$state`, however some RTLIL cells such as `$assert` or `$anyseq` generate auxiliary inputs/outputs/states that are given a different type to distinguish them from ordinary RTLIL inputs/outputs/states. -- To access an individual input/output/state, use `ir.input(name, type)`, `ir.output(name, type)` or `ir.state(name, type)`. `type` defaults to the default type. -- To iterate over all inputs/outputs/states of a certain "type", methods `ir.inputs`, `ir.outputs`, `ir.states` are provided. Their argument defaults to the default types mentioned. -- To iterate over inputs/outputs/states of any "type", use `ir.all_inputs`, `ir.all_outputs` and `ir.all_states`. +Every input, output and state has a name (equal to their name in RTLIL), a sort and a kind. +The kind field usually remains as the default value `$input`, `$output` or `$state`, however some RTLIL cells such as `$assert` or `$anyseq` generate auxiliary inputs/outputs/states that are given a different kind to distinguish them from ordinary RTLIL inputs/outputs/states. +- To access an individual input/output/state, use `ir.input(name, kind)`, `ir.output(name, kind)` or `ir.state(name, kind)`. `kind` defaults to the default kind. +- To iterate over all inputs/outputs/states of a certain kind, methods `ir.inputs`, `ir.outputs`, `ir.states` are provided. Their argument defaults to the default kinds mentioned. +- To iterate over inputs/outputs/states of any kind, use `ir.all_inputs`, `ir.all_outputs` and `ir.all_states`. - Outputs have a node that indicate the value of the output, this can be retrieved via `output.value()`. - States have a node that indicate the next value of the state, this can be retrieved via `state.next_value()`. They also have an initial value that is accessed as either `state.initial_value_signal()` or `state.initial_value_memory()`, depending on their sort. diff --git a/kernel/functional.cc b/kernel/functional.cc index 11f030eae14..c270a2bb631 100644 --- a/kernel/functional.cc +++ b/kernel/functional.cc @@ -67,26 +67,26 @@ const char *fn_to_string(Fn fn) { log_error("fn_to_string: unknown Functional::Fn value %d", (int)fn); } -vector IR::inputs(IdString type) const { +vector IR::inputs(IdString kind) const { vector ret; for (const auto &[name, input] : _inputs) - if(input.type == type) + if(input.kind == kind) ret.push_back(&input); return ret; } -vector IR::outputs(IdString type) const { +vector IR::outputs(IdString kind) const { vector ret; for (const auto &[name, output] : _outputs) - if(output.type == type) + if(output.kind == kind) ret.push_back(&output); return ret; } -vector IR::states(IdString type) const { +vector IR::states(IdString kind) const { vector ret; for (const auto &[name, state] : _states) - if(state.type == type) + if(state.kind == kind) ret.push_back(&state); return ret; } @@ -120,8 +120,8 @@ struct PrintVisitor : DefaultVisitor { std::string zero_extend(Node, Node a, int out_width) override { return "zero_extend(" + np(a) + ", " + std::to_string(out_width) + ")"; } std::string sign_extend(Node, Node a, int out_width) override { return "sign_extend(" + np(a) + ", " + std::to_string(out_width) + ")"; } std::string constant(Node, RTLIL::Const const& value) override { return "constant(" + value.as_string() + ")"; } - std::string input(Node, IdString name, IdString type) override { return "input(" + name.str() + ", " + type.str() + ")"; } - std::string state(Node, IdString name, IdString type) override { return "state(" + name.str() + ", " + type.str() + ")"; } + std::string input(Node, IdString name, IdString kind) override { return "input(" + name.str() + ", " + kind.str() + ")"; } + std::string state(Node, IdString name, IdString kind) override { return "state(" + name.str() + ", " + kind.str() + ")"; } std::string default_handler(Node self) override { std::string ret = fn_to_string(self.fn()); ret += "("; diff --git a/kernel/functional.h b/kernel/functional.h index 2afdd7fc339..f1fb2767f6f 100644 --- a/kernel/functional.h +++ b/kernel/functional.h @@ -34,7 +34,7 @@ namespace Functional { // each function is documented with a short pseudocode declaration or definition // standard C/Verilog operators are used to describe the result // - // the types used in this are: + // the sorts used in this are: // - bit[N]: a bitvector of N bits // bit[N] can be indicated as signed or unsigned. this is not tracked by the functional backend // but is meant to indicate how the value is interpreted @@ -43,12 +43,12 @@ namespace Functional { // - int: C++ int // - Const[N]: yosys RTLIL::Const (with size() == N) // - IdString: yosys IdString - // - any: used in documentation to indicate that the type is unconstrained + // - any: used in documentation to indicate that the sort is unconstrained // - // nodes in the functional backend are either of type bit[N] or memory[N,M] (for some N, M: int) - // additionally, they can carry a constant of type int, Const[N] or IdString - // each node has a 'sort' field that stores the type of the node - // slice, zero_extend, sign_extend use the type field to store out_width + // nodes in the functional backend are either of sort bit[N] or memory[N,M] (for some N, M: int) + // additionally, they can carry a constant of sort int, Const[N] or IdString + // each node has a 'sort' field that stores the sort of the node + // slice, zero_extend, sign_extend use the sort field to store out_width enum class Fn { // invalid() = known-invalid/shouldn't happen value // TODO: maybe remove this and use e.g. std::optional instead? @@ -136,7 +136,7 @@ namespace Functional { // returns the name of a Fn value, as a string literal const char *fn_to_string(Fn); // Sort represents the sort or type of a node - // currently the only two types are signal/bit and memory + // currently the only two sorts are signal/bit and memory class Sort { std::variant> _v; public: @@ -144,11 +144,11 @@ namespace Functional { Sort(int addr_width, int data_width) : _v(std::make_pair(addr_width, data_width)) { } bool is_signal() const { return _v.index() == 0; } bool is_memory() const { return _v.index() == 1; } - // returns the width of a bitvector type, errors out for other types + // returns the width of a bitvector sort, errors out for other sorts int width() const { return std::get<0>(_v); } - // returns the address width of a bitvector type, errors out for other types + // returns the address width of a bitvector sort, errors out for other sorts int addr_width() const { return std::get<1>(_v).first; } - // returns the data width of a bitvector type, errors out for other types + // returns the data width of a bitvector sort, errors out for other sorts int data_width() const { return std::get<1>(_v).second; } bool operator==(Sort const& other) const { return _v == other._v; } unsigned int hash() const { return mkhash(_v); } @@ -160,22 +160,22 @@ namespace Functional { friend class Factory; public: IdString name; - IdString type; + IdString kind; Sort sort; private: - IRInput(IR &, IdString name, IdString type, Sort sort) - : name(name), type(type), sort(std::move(sort)) {} + IRInput(IR &, IdString name, IdString kind, Sort sort) + : name(name), kind(kind), sort(std::move(sort)) {} }; class IROutput { friend class Factory; IR &_ir; public: IdString name; - IdString type; + IdString kind; Sort sort; private: - IROutput(IR &ir, IdString name, IdString type, Sort sort) - : _ir(ir), name(name), type(type), sort(std::move(sort)) {} + IROutput(IR &ir, IdString name, IdString kind, Sort sort) + : _ir(ir), name(name), kind(kind), sort(std::move(sort)) {} public: Node value() const; bool has_value() const; @@ -186,12 +186,12 @@ namespace Functional { IR &_ir; public: IdString name; - IdString type; + IdString kind; Sort sort; private: std::variant _initial; - IRState(IR &ir, IdString name, IdString type, Sort sort) - : _ir(ir), name(name), type(type), sort(std::move(sort)) {} + IRState(IR &ir, IdString name, IdString kind, Sort sort) + : _ir(ir), name(name), kind(kind), sort(std::move(sort)) {} public: Node next_value() const; bool has_next_value() const; @@ -253,20 +253,20 @@ namespace Functional { Node operator[](int i); void topological_sort(); void forward_buf(); - IRInput const& input(IdString name, IdString type) const { return _inputs.at({name, type}); } + IRInput const& input(IdString name, IdString kind) const { return _inputs.at({name, kind}); } IRInput const& input(IdString name) const { return input(name, ID($input)); } - IROutput const& output(IdString name, IdString type) const { return _outputs.at({name, type}); } + IROutput const& output(IdString name, IdString kind) const { return _outputs.at({name, kind}); } IROutput const& output(IdString name) const { return output(name, ID($output)); } - IRState const& state(IdString name, IdString type) const { return _states.at({name, type}); } + IRState const& state(IdString name, IdString kind) const { return _states.at({name, kind}); } IRState const& state(IdString name) const { return state(name, ID($state)); } - bool has_input(IdString name, IdString type) const { return _inputs.count({name, type}); } - bool has_output(IdString name, IdString type) const { return _outputs.count({name, type}); } - bool has_state(IdString name, IdString type) const { return _states.count({name, type}); } - vector inputs(IdString type) const; + bool has_input(IdString name, IdString kind) const { return _inputs.count({name, kind}); } + bool has_output(IdString name, IdString kind) const { return _outputs.count({name, kind}); } + bool has_state(IdString name, IdString kind) const { return _states.count({name, kind}); } + vector inputs(IdString kind) const; vector inputs() const { return inputs(ID($input)); } - vector outputs(IdString type) const; + vector outputs(IdString kind) const; vector outputs() const { return outputs(ID($output)); } - vector states(IdString type) const; + vector states(IdString kind) const; vector states() const { return states(ID($state)); } vector all_inputs() const; vector all_outputs() const; @@ -364,12 +364,12 @@ namespace Functional { }; inline IR::Graph::Ref IR::mutate(Node n) { return _graph[n._ref.index()]; } inline Node IR::operator[](int i) { return Node(_graph[i]); } - inline Node IROutput::value() const { return Node(_ir._graph({name, type, false})); } - inline bool IROutput::has_value() const { return _ir._graph.has_key({name, type, false}); } - inline void IROutput::set_value(Node value) { log_assert(sort == value.sort()); _ir.mutate(value).assign_key({name, type, false}); } - inline Node IRState::next_value() const { return Node(_ir._graph({name, type, true})); } - inline bool IRState::has_next_value() const { return _ir._graph.has_key({name, type, true}); } - inline void IRState::set_next_value(Node value) { log_assert(sort == value.sort()); _ir.mutate(value).assign_key({name, type, true}); } + inline Node IROutput::value() const { return Node(_ir._graph({name, kind, false})); } + inline bool IROutput::has_value() const { return _ir._graph.has_key({name, kind, false}); } + inline void IROutput::set_value(Node value) { log_assert(sort == value.sort()); _ir.mutate(value).assign_key({name, kind, false}); } + inline Node IRState::next_value() const { return Node(_ir._graph({name, kind, true})); } + inline bool IRState::has_next_value() const { return _ir._graph.has_key({name, kind, true}); } + inline void IRState::set_next_value(Node value) { log_assert(sort == value.sort()); _ir.mutate(value).assign_key({name, kind, true}); } inline Node IR::iterator::operator*() { return Node(_ir->_graph[_index]); } inline arrow_proxy IR::iterator::operator->() { return arrow_proxy(**this); } // AbstractVisitor provides an abstract base class for visitors @@ -403,8 +403,8 @@ namespace Functional { virtual T arithmetic_shift_right(Node self, Node a, Node b) = 0; virtual T mux(Node self, Node a, Node b, Node s) = 0; virtual T constant(Node self, RTLIL::Const const & value) = 0; - virtual T input(Node self, IdString name, IdString type) = 0; - virtual T state(Node self, IdString name, IdString type) = 0; + virtual T input(Node self, IdString name, IdString kind) = 0; + virtual T state(Node self, IdString name, IdString kind) = 0; virtual T memory_read(Node self, Node mem, Node addr) = 0; virtual T memory_write(Node self, Node mem, Node addr, Node data) = 0; }; @@ -547,26 +547,26 @@ namespace Functional { log_assert(node.sort() == value.sort()); _ir.mutate(node).append_arg(value._ref); } - IRInput &add_input(IdString name, IdString type, Sort sort) { - auto [it, inserted] = _ir._inputs.emplace({name, type}, IRInput(_ir, name, type, std::move(sort))); + IRInput &add_input(IdString name, IdString kind, Sort sort) { + auto [it, inserted] = _ir._inputs.emplace({name, kind}, IRInput(_ir, name, kind, std::move(sort))); if (!inserted) log_error("input `%s` was re-defined", name.c_str()); return it->second; } - IROutput &add_output(IdString name, IdString type, Sort sort) { - auto [it, inserted] = _ir._outputs.emplace({name, type}, IROutput(_ir, name, type, std::move(sort))); + IROutput &add_output(IdString name, IdString kind, Sort sort) { + auto [it, inserted] = _ir._outputs.emplace({name, kind}, IROutput(_ir, name, kind, std::move(sort))); if (!inserted) log_error("output `%s` was re-defined", name.c_str()); return it->second; } - IRState &add_state(IdString name, IdString type, Sort sort) { - auto [it, inserted] = _ir._states.emplace({name, type}, IRState(_ir, name, type, std::move(sort))); + IRState &add_state(IdString name, IdString kind, Sort sort) { + auto [it, inserted] = _ir._states.emplace({name, kind}, IRState(_ir, name, kind, std::move(sort))); if (!inserted) log_error("state `%s` was re-defined", name.c_str()); return it->second; } Node value(IRInput const& input) { - return add(IR::NodeData(Fn::input, std::pair(input.name, input.type)), input.sort, {}); + return add(IR::NodeData(Fn::input, std::pair(input.name, input.kind)), input.sort, {}); } Node value(IRState const& state) { - return add(IR::NodeData(Fn::state, std::pair(state.name, state.type)), state.sort, {}); + return add(IR::NodeData(Fn::state, std::pair(state.name, state.kind)), state.sort, {}); } void suggest_name(Node node, IdString name) { _ir.mutate(node).sparse_attr() = name; From 2b8db94aa01394a3d25576439e5492cb52beab9d Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Thu, 29 Aug 2024 13:14:18 +0100 Subject: [PATCH 81/92] functional backend: add test to verify test_generic --- Makefile | 5 +++-- tests/functional/test_functional.py | 8 +++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index e61948cb4f1..fbe09904263 100644 --- a/Makefile +++ b/Makefile @@ -1052,9 +1052,10 @@ clean_coverage: find . -name "*.gcda" -type f -delete coverage_functional: - rm -rf coverage.info coverage_html + rm -rf coverage.info coverage2.info coverage_html lcov --capture -d backends/functional --no-external -o coverage.info - genhtml coverage.info --output-directory coverage_html + lcov --capture -d kernel --include kernel/functional.cc --include kernel/functional.h --include kernel/sexpr.cc --include kernel/sexpr.h --include kernel/compute_graph.h --no-external -o coverage2.info + genhtml coverage.info coverage2.info --output-directory coverage_html qtcreator: echo "$(CXXFLAGS)" | grep -o '\-D[^ ]*' | tr ' ' '\n' | sed 's/-D/#define /' | sed 's/=/ /'> qtcreator.config diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index 107ca6862e5..55e4acc3340 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -70,4 +70,10 @@ def test_smt(cell, parameters, tmp_path, num_steps, rnd): yosys(f"read_rtlil {quote(rtlil_file)} ; clk2fflogic ; write_functional_smt2 {quote(smt_file)}") run(['z3', smt_file]) # check if output is valid smtlib before continuing smt_vcd.simulate_smt(smt_file, vcd_functional_file, num_steps, rnd(cell.name + "-smt")) - yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file, getattr(cell, 'sim_preprocessing', '')) \ No newline at end of file + yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file, getattr(cell, 'sim_preprocessing', '')) + +def test_print_graph(tmp_path): + tb_file = base_path / 'tests/functional/picorv32_tb.v' + cpu_file = base_path / 'tests/functional/picorv32.v' + # currently we only check that we can print the graph without getting an error, not that it prints anything sensibl + yosys(f"read_verilog {quote(tb_file)} {quote(cpu_file)}; prep -top gold; flatten; clk2fflogic; test_generic") From 8b29629ca9873e7caff091223170e33014598aed Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Fri, 23 Aug 2024 09:05:44 +1200 Subject: [PATCH 82/92] smtr: Fork smtlib for rosette --- backends/functional/Makefile.inc | 1 + backends/functional/smtlib_rosette.cc | 303 ++++++++++++++++++++++++++ 2 files changed, 304 insertions(+) create mode 100644 backends/functional/smtlib_rosette.cc diff --git a/backends/functional/Makefile.inc b/backends/functional/Makefile.inc index 13c962c99ff..16d1c054273 100644 --- a/backends/functional/Makefile.inc +++ b/backends/functional/Makefile.inc @@ -1,3 +1,4 @@ OBJS += backends/functional/cxx.o OBJS += backends/functional/smtlib.o +OBJS += backends/functional/smtlib_rosette.o OBJS += backends/functional/test_generic.o diff --git a/backends/functional/smtlib_rosette.cc b/backends/functional/smtlib_rosette.cc new file mode 100644 index 00000000000..601f311af96 --- /dev/null +++ b/backends/functional/smtlib_rosette.cc @@ -0,0 +1,303 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2024 Emily Schmidt + * Copyright (C) 2024 National Technology and Engineering Solutions of Sandia, LLC + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/functional.h" +#include "kernel/yosys.h" +#include "kernel/sexpr.h" +#include + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +using SExprUtil::list; + +const char *reserved_keywords[] = { + // reserved keywords from the racket spec + "struct", "lambda", "values", "extract", "concat", "bv", "let", "define", "cons", "list", "read", "write", + "stream", "error", "raise", "exit", "for", "for/list", "begin", "when", "unless", "module", "require", + "provide", "apply", "if", "cond", "even", "odd", "any", "and", "or", "match", "match-define", "values", + + // reserved for our own purposes + "inputs", "state", "name", + nullptr +}; + +struct SmtrScope : public Functional::Scope { + SmtrScope() { + for(const char **p = reserved_keywords; *p != nullptr; p++) + reserve(*p); + } + bool is_character_legal(char c, int index) override { + return isascii(c) && (isalpha(c) || (isdigit(c) && index > 0) || strchr("~!@$%^&*_-+=<>.?/", c)); + } +}; + +struct SmtrSort { + Functional::Sort sort; + SmtrSort(Functional::Sort sort) : sort(sort) {} + SExpr to_sexpr() const { + if(sort.is_memory()) { + return list("list", list("bitvector", sort.addr_width()), list("bitvector", sort.data_width())); + } else if(sort.is_signal()) { + return list("bitvector", sort.width()); + } else { + log_error("unknown sort"); + } + } +}; + +class SmtrStruct { + struct Field { + SmtrSort sort; + std::string accessor; + std::string name; + }; + idict field_names; + vector fields; + SmtrScope &scope; +public: + std::string name; + SmtrStruct(std::string name, SmtrScope &scope) : scope(scope), name(name) {} + void insert(IdString field_name, SmtrSort sort) { + field_names(field_name); + auto base_name = RTLIL::unescape_id(field_name); + auto accessor = name + "-" + base_name; + scope.reserve(accessor); + fields.emplace_back(Field{sort, accessor, base_name}); + } + void write_definition(SExprWriter &w) { + vector field_list; + for(const auto &field : fields) { + field_list.emplace_back(field.name); + } + w.push(); + w.open(list("struct", name, field_list, "#:transparent")); + if (field_names.size()) { + for (const auto &field : fields) { + auto bv_type = field.sort.to_sexpr(); + w.comment(field.name + " " + bv_type.to_string()); + } + } + w.pop(); + } + template void write_value(SExprWriter &w, Fn fn) { + w.open(list(name)); + for(auto field_name : field_names) { + w << fn(field_name); + w.comment(RTLIL::unescape_id(field_name), true); + } + w.close(); + } + SExpr access(SExpr record, IdString name) { + size_t i = field_names.at(name); + return list(fields[i].accessor, std::move(record)); + } +}; + +std::string smt_const(RTLIL::Const const &c) { + std::string s = "#b"; + for(int i = c.size(); i-- > 0; ) + s += c[i] == State::S1 ? '1' : '0'; + return s; +} + +struct SmtrPrintVisitor : public Functional::AbstractVisitor { + using Node = Functional::Node; + std::function n; + SmtrStruct &input_struct; + SmtrStruct &state_struct; + + SmtrPrintVisitor(SmtrStruct &input_struct, SmtrStruct &state_struct) : input_struct(input_struct), state_struct(state_struct) {} + + SExpr from_bool(SExpr &&arg) { + return list("bool->bitvector", std::move(arg)); + } + SExpr to_bool(SExpr &&arg) { + return list("bitvector->bool", std::move(arg)); + } + SExpr to_list(SExpr &&arg) { + return list("bitvector->bits", std::move(arg)); + } + + SExpr buf(Node, Node a) override { return n(a); } + SExpr slice(Node, Node a, int offset, int out_width) override { return list("extract", offset + out_width - 1, offset, n(a)); } + SExpr zero_extend(Node, Node a, int out_width) override { return list("zero-extend", n(a), list("bitvector", out_width)); } + SExpr sign_extend(Node, Node a, int out_width) override { return list("sign-extend", n(a), list("bitvector", out_width)); } + SExpr concat(Node, Node a, Node b) override { return list("concat", n(b), n(a)); } + SExpr add(Node, Node a, Node b) override { return list("bvadd", n(a), n(b)); } + SExpr sub(Node, Node a, Node b) override { return list("bvsub", n(a), n(b)); } + SExpr mul(Node, Node a, Node b) override { return list("bvmul", n(a), n(b)); } + SExpr unsigned_div(Node, Node a, Node b) override { return list("bvudiv", n(a), n(b)); } + SExpr unsigned_mod(Node, Node a, Node b) override { return list("bvurem", n(a), n(b)); } + SExpr bitwise_and(Node, Node a, Node b) override { return list("bvand", n(a), n(b)); } + SExpr bitwise_or(Node, Node a, Node b) override { return list("bvor", n(a), n(b)); } + SExpr bitwise_xor(Node, Node a, Node b) override { return list("bvxor", n(a), n(b)); } + SExpr bitwise_not(Node, Node a) override { return list("bvnot", n(a)); } + SExpr unary_minus(Node, Node a) override { return list("bvneg", n(a)); } + SExpr reduce_and(Node, Node a) override { return list("apply", "bvand", to_list(n(a))); } + SExpr reduce_or(Node, Node a) override { return list("apply", "bvor", to_list(n(a))); } + SExpr reduce_xor(Node, Node a) override { return list("apply", "bvxor", to_list(n(a))); } + SExpr equal(Node, Node a, Node b) override { return from_bool(list("bveq", n(a), n(b))); } + SExpr not_equal(Node, Node a, Node b) override { return from_bool(list("not", list("bveq", n(a), n(b)))); } + SExpr signed_greater_than(Node, Node a, Node b) override { return from_bool(list("bvsgt", n(a), n(b))); } + SExpr signed_greater_equal(Node, Node a, Node b) override { return from_bool(list("bvsge", n(a), n(b))); } + SExpr unsigned_greater_than(Node, Node a, Node b) override { return from_bool(list("bvugt", n(a), n(b))); } + SExpr unsigned_greater_equal(Node, Node a, Node b) override { return from_bool(list("bvuge", n(a), n(b))); } + + SExpr extend(SExpr &&a, int in_width, int out_width) { + if(in_width < out_width) + return list("zero-extend", std::move(a), list("bitvector", out_width)); + else + return std::move(a); + } + SExpr logical_shift_left(Node, Node a, Node b) override { return list("bvshl", n(a), extend(n(b), b.width(), a.width())); } + SExpr logical_shift_right(Node, Node a, Node b) override { return list("bvlshr", n(a), extend(n(b), b.width(), a.width())); } + SExpr arithmetic_shift_right(Node, Node a, Node b) override { return list("bvashr", n(a), extend(n(b), b.width(), a.width())); } + SExpr mux(Node, Node a, Node b, Node s) override { return list("if", to_bool(n(s)), n(b), n(a)); } + SExpr constant(Node, RTLIL::Const const& value) override { return list("bv", smt_const(value), value.size()); } + SExpr memory_read(Node, Node mem, Node addr) override { return list("list-ref-bv", n(mem), n(addr)); } + SExpr memory_write(Node, Node mem, Node addr, Node data) override { return list("list-set-bv", n(mem), n(addr), n(data)); } + + SExpr input(Node, IdString name, IdString kind) override { log_assert(kind == ID($input)); return input_struct.access("inputs", name); } + SExpr state(Node, IdString name, IdString kind) override { log_assert(kind == ID($state)); return state_struct.access("state", name); } +}; + +struct SmtrModule { + Functional::IR ir; + SmtrScope scope; + std::string name; + + SmtrStruct input_struct; + SmtrStruct output_struct; + SmtrStruct state_struct; + + SmtrModule(Module *module) + : ir(Functional::IR::from_module(module)) + , scope() + , name(scope.unique_name(module->name)) + , input_struct(scope.unique_name(module->name.str() + "_Inputs"), scope) + , output_struct(scope.unique_name(module->name.str() + "_Outputs"), scope) + , state_struct(scope.unique_name(module->name.str() + "_State"), scope) + { + scope.reserve(name + "_initial"); + for (auto input : ir.inputs()) + input_struct.insert(input->name, input->sort); + for (auto output : ir.outputs()) + output_struct.insert(output->name, output->sort); + for (auto state : ir.states()) + state_struct.insert(state->name, state->sort); + } + + void write(std::ostream &out) + { + SExprWriter w(out); + + input_struct.write_definition(w); + output_struct.write_definition(w); + state_struct.write_definition(w); + + w.push(); + w.open(list("define", list(name, "inputs", "state"))); + auto inlined = [&](Functional::Node n) { + return n.fn() == Functional::Fn::constant; + }; + SmtrPrintVisitor visitor(input_struct, state_struct); + auto node_to_sexpr = [&](Functional::Node n) -> SExpr { + if(inlined(n)) + return n.visit(visitor); + else + return scope(n.id(), n.name()); + }; + visitor.n = node_to_sexpr; + for(auto n : ir) + if(!inlined(n)) { + w.open(list("let", list(list(node_to_sexpr(n), n.visit(visitor)))), false); + w.comment(SmtrSort(n.sort()).to_sexpr().to_string(), true); + } + w.open(list("cons")); + output_struct.write_value(w, [&](IdString name) { return node_to_sexpr(ir.output(name).value()); }); + state_struct.write_value(w, [&](IdString name) { return node_to_sexpr(ir.state(name).next_value()); }); + w.pop(); + + w.push(); + auto initial = name + "_initial"; + w.open(list("define", initial)); + w.open(list(state_struct.name)); + for (auto state : ir.states()) { + if (state->sort.is_signal()) + w << list("bv", smt_const(state->initial_value_signal()), state->sort.width()); + else if (state->sort.is_memory()) { + const auto &contents = state->initial_value_memory(); + w.open(list("list")); + for(int i = 0; i < 1<sort.addr_width(); i++) { + w << list("bv", smt_const(contents[i]), state->sort.data_width()); + } + w.close(); + } + } + w.pop(); + } +}; + +struct FunctionalSmtrBackend : public Backend { + FunctionalSmtrBackend() : Backend("functional_rosette", "Generate Rosette compatible Racket from Functional IR") {} + + void help() override { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" write_functional_rosette [options] [selection] [filename]\n"); + log("\n"); + log("Functional Rosette Backend.\n"); + log("\n"); + log(" -provides\n"); + log(" include 'provide' statement(s) for loading output as a module\n"); + log("\n"); + } + + void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) override + { + auto provides = false; + + log_header(design, "Executing Functional Rosette Backend.\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) + { + if (args[argidx] == "-provides") + provides = true; + else + break; + } + extra_args(f, filename, args, argidx); + + *f << "#lang rosette\n"; + if (provides) { + *f << "(provide (all-defined-out))\n"; + } + + for (auto module : design->selected_modules()) { + log("Processing module `%s`.\n", module->name.c_str()); + SmtrModule smtr(module); + smtr.write(*f); + } + } +} FunctionalSmtrBackend; + +PRIVATE_NAMESPACE_END From 7fe9157df2f38c871cdb912cd2150ea701574358 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Fri, 23 Aug 2024 09:05:44 +1200 Subject: [PATCH 83/92] smtr: Add rkt to functional tests --- tests/functional/.gitignore | 1 + tests/functional/rkt_vcd.py | 115 ++++++++++++++++++++++++++++ tests/functional/test_functional.py | 13 ++++ 3 files changed, 129 insertions(+) create mode 100644 tests/functional/rkt_vcd.py diff --git a/tests/functional/.gitignore b/tests/functional/.gitignore index 1ffe9926ef6..f0c4cf14e45 100644 --- a/tests/functional/.gitignore +++ b/tests/functional/.gitignore @@ -3,3 +3,4 @@ my_module_functional_cxx.cc vcd_harness *.vcd *.smt2 +*.rkt diff --git a/tests/functional/rkt_vcd.py b/tests/functional/rkt_vcd.py new file mode 100644 index 00000000000..1b2cf31e384 --- /dev/null +++ b/tests/functional/rkt_vcd.py @@ -0,0 +1,115 @@ +from __future__ import annotations +from pathlib import Path +import re +import subprocess +from typing import Dict, List, Tuple +from random import Random + +vcd_version = "Yosys/tests/functional/rkt_vcd.py" +StepList = List[Tuple[int, str]] +SignalStepMap = Dict[str, StepList] +SignalWidthMap = Dict[str, int] + +def write_vcd(filename: Path, signals: SignalStepMap, timescale='1 ns', date='today'): + with open(filename, 'w') as f: + # Write the header + f.write(f"$date\n {date}\n$end\n") + f.write(f"$timescale {timescale} $end\n") + + # Declare signals + f.write("$scope module gold $end\n") + for signal_name, changes in signals.items(): + signal_size = len(changes[0][1]) + f.write(f"$var wire {signal_size - 1} {signal_name} {signal_name} $end\n") + f.write("$upscope $end\n") + f.write("$enddefinitions $end\n") + + # Collect all unique timestamps + timestamps = sorted(set(time for changes in signals.values() for time, _ in changes)) + + # Write initial values + f.write("#0\n") + for signal_name, changes in signals.items(): + for time, value in changes: + if time == 0: + f.write(f"{value} {signal_name}\n") + + # Write value changes + for time in timestamps: + if time != 0: + f.write(f"#{time}\n") + for signal_name, changes in signals.items(): + for change_time, value in changes: + if change_time == time: + f.write(f"{value} {signal_name}\n") + +def simulate_rosette(rkt_file_path: Path, vcd_path: Path, num_steps: int, rnd: Random): + signals: dict[str, list[str]] = {} + inputs: SignalWidthMap = {} + outputs: SignalWidthMap = {} + + current_struct_name: str = "" + with open(rkt_file_path, 'r') as rkt_file: + for line in rkt_file: + m = re.search(r'gold_(Inputs|Outputs|State)', line) + if m: + current_struct_name = m.group(1) + if current_struct_name == "State": break + elif not current_struct_name: continue # skip lines before structs + m = re.search(r'; (.+?)\b \(bitvector (\d+)\)', line) + if not m: continue # skip non matching lines (probably closing the struct) + signal = m.group(1) + width = int(m.group(2)) + if current_struct_name == "Inputs": + inputs[signal] = width + elif current_struct_name == "Outputs": + outputs[signal] = width + + for signal, width in inputs.items(): + step_list: list[int] = [] + for step in range(num_steps): + value = rnd.getrandbits(width) + binary_string = format(value, '0{}b'.format(width)) + step_list.append(binary_string) + signals[signal] = step_list + + test_rkt_file_path = rkt_file_path.with_suffix('.tst.rkt') + with open(test_rkt_file_path, 'w') as test_rkt_file: + test_rkt_file.writelines([ + '#lang rosette\n', + f'(require "{rkt_file_path.name}")\n', + ]) + + for step in range(num_steps): + this_step = f"step_{step}" + value_list: list[str] = [] + for signal, width in inputs.items(): + value = signals[signal][step] + value_list.append(f"(bv #b{value} {width})") + gold_Inputs = f"(gold_Inputs {' '.join(value_list)})" + gold_State = f"(cdr step_{step-1})" if step else "gold_initial" + test_rkt_file.write(f"(define {this_step} (gold {gold_Inputs} {gold_State})) (car {this_step})\n") + + cmd = ["racket", test_rkt_file_path] + status = subprocess.run(cmd, capture_output=True) + assert status.returncode == 0, f"{cmd[0]} failed" + + for signal in outputs.keys(): + signals[signal] = [] + + for line in status.stdout.decode().splitlines(): + m = re.match(r'\(gold_Outputs( \(bv \S+ \d+\))+\)', line) + assert m, f"Incomplete output definition {line!r}" + for output, (value, width) in zip(outputs.keys(), re.findall(r'\(bv (\S+) (\d+)\)', line)): + assert isinstance(value, str), f"Bad value {value!r}" + assert value.startswith(('#b', '#x')), f"Non-binary value {value!r}" + assert int(width) == outputs[output], f"Width mismatch for output {output!r} (got {width}, expected {outputs[output]})" + int_value = int(value[2:], 16 if value.startswith('#x') else 2) + binary_string = format(int_value, '0{}b'.format(width)) + signals[output].append(binary_string) + + vcd_signals: SignalStepMap = {} + for signal, steps in signals.items(): + vcd_signals[signal] = [(time, f"b{value}") for time, value in enumerate(steps)] + + write_vcd(vcd_path, vcd_signals) diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index 55e4acc3340..3b006676a14 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -72,6 +72,19 @@ def test_smt(cell, parameters, tmp_path, num_steps, rnd): smt_vcd.simulate_smt(smt_file, vcd_functional_file, num_steps, rnd(cell.name + "-smt")) yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file, getattr(cell, 'sim_preprocessing', '')) +def test_rkt(cell, parameters, tmp_path, num_steps, rnd): + import rkt_vcd + + rtlil_file = tmp_path / 'rtlil.il' + rkt_file = tmp_path / 'smtlib.rkt' + vcd_functional_file = tmp_path / 'functional.vcd' + vcd_yosys_sim_file = tmp_path / 'yosys.vcd' + + cell.write_rtlil_file(rtlil_file, parameters) + yosys(f"read_rtlil {quote(rtlil_file)} ; clk2fflogic ; write_functional_rosette -provides {quote(rkt_file)}") + rkt_vcd.simulate_rosette(rkt_file, vcd_functional_file, num_steps, rnd(cell.name + "-rkt")) + yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file, getattr(cell, 'sim_preprocessing', '')) + def test_print_graph(tmp_path): tb_file = base_path / 'tests/functional/picorv32_tb.v' cpu_file = base_path / 'tests/functional/picorv32.v' From 07b69080191c2112e817334d313e5e6d944d3a66 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Fri, 30 Aug 2024 09:56:08 +1200 Subject: [PATCH 84/92] smtr: Use rosette/safe --- backends/functional/smtlib_rosette.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/functional/smtlib_rosette.cc b/backends/functional/smtlib_rosette.cc index 601f311af96..f2395374799 100644 --- a/backends/functional/smtlib_rosette.cc +++ b/backends/functional/smtlib_rosette.cc @@ -287,7 +287,7 @@ struct FunctionalSmtrBackend : public Backend { } extra_args(f, filename, args, argidx); - *f << "#lang rosette\n"; + *f << "#lang rosette/safe\n"; if (provides) { *f << "(provide (all-defined-out))\n"; } From 5a29b3e1724e45b6d7afe30f3ebb3e7546f8845e Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Fri, 30 Aug 2024 10:44:35 +1200 Subject: [PATCH 85/92] smtr: More sanitization --- backends/functional/smtlib_rosette.cc | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/backends/functional/smtlib_rosette.cc b/backends/functional/smtlib_rosette.cc index f2395374799..4e60576946b 100644 --- a/backends/functional/smtlib_rosette.cc +++ b/backends/functional/smtlib_rosette.cc @@ -31,8 +31,12 @@ using SExprUtil::list; const char *reserved_keywords[] = { // reserved keywords from the racket spec "struct", "lambda", "values", "extract", "concat", "bv", "let", "define", "cons", "list", "read", "write", - "stream", "error", "raise", "exit", "for", "for/list", "begin", "when", "unless", "module", "require", - "provide", "apply", "if", "cond", "even", "odd", "any", "and", "or", "match", "match-define", "values", + "stream", "error", "raise", "exit", "for", "begin", "when", "unless", "module", "require", "provide", "apply", + "if", "cond", "even", "odd", "any", "and", "or", "match", "command-line", "ffi-lib", "thread", "kill", "sync", + "future", "touch", "subprocess", "make-custodian", "custodian-shutdown-all", "current-custodian", "make", "tcp", + "connect", "prepare", "malloc", "free", "_fun", "_cprocedure", "build", "path", "file", "peek", "bytes", + "flush", "with", "lexer", "parser", "syntax", "interface", "send", "make-object", "new", "instantiate", + "define-generics", "set", // reserved for our own purposes "inputs", "state", "name", @@ -45,7 +49,7 @@ struct SmtrScope : public Functional::Scope { reserve(*p); } bool is_character_legal(char c, int index) override { - return isascii(c) && (isalpha(c) || (isdigit(c) && index > 0) || strchr("~!@$%^&*_-+=<>.?/", c)); + return isascii(c) && (isalpha(c) || (isdigit(c) && index > 0) || strchr("@$%^&_+=.", c)); } }; From a2abbcb33f3ae1c2287b4b2a3b9e419494ab8aa0 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Tue, 3 Sep 2024 22:05:17 +1200 Subject: [PATCH 86/92] smtr: Use scope.unique_name --- backends/functional/smtlib_rosette.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backends/functional/smtlib_rosette.cc b/backends/functional/smtlib_rosette.cc index 4e60576946b..ff47f5706d1 100644 --- a/backends/functional/smtlib_rosette.cc +++ b/backends/functional/smtlib_rosette.cc @@ -81,7 +81,7 @@ class SmtrStruct { SmtrStruct(std::string name, SmtrScope &scope) : scope(scope), name(name) {} void insert(IdString field_name, SmtrSort sort) { field_names(field_name); - auto base_name = RTLIL::unescape_id(field_name); + auto base_name = scope.unique_name("\\" + RTLIL::unescape_id(field_name)); auto accessor = name + "-" + base_name; scope.reserve(accessor); fields.emplace_back(Field{sort, accessor, base_name}); From d6c5e13bf31043173adf5667b7edc7d9940293db Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Tue, 3 Sep 2024 22:23:41 +1200 Subject: [PATCH 87/92] smtr: Structs have local scope Also unique_name can take field_name directly. --- backends/functional/smtlib_rosette.cc | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/backends/functional/smtlib_rosette.cc b/backends/functional/smtlib_rosette.cc index ff47f5706d1..ea14da85428 100644 --- a/backends/functional/smtlib_rosette.cc +++ b/backends/functional/smtlib_rosette.cc @@ -75,15 +75,16 @@ class SmtrStruct { }; idict field_names; vector fields; - SmtrScope &scope; + SmtrScope &global_scope; + SmtrScope local_scope; public: std::string name; - SmtrStruct(std::string name, SmtrScope &scope) : scope(scope), name(name) {} + SmtrStruct(std::string name, SmtrScope &scope) : global_scope(scope), local_scope(), name(name) {} void insert(IdString field_name, SmtrSort sort) { field_names(field_name); - auto base_name = scope.unique_name("\\" + RTLIL::unescape_id(field_name)); + auto base_name = local_scope.unique_name(field_name); auto accessor = name + "-" + base_name; - scope.reserve(accessor); + global_scope.reserve(accessor); fields.emplace_back(Field{sort, accessor, base_name}); } void write_definition(SExprWriter &w) { From 75ed6d38fc20f490d0b8adcacf6bd26c5e1a8613 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Tue, 3 Sep 2024 14:21:56 +0100 Subject: [PATCH 88/92] fix rst formatting in functional_ir.rst --- .../extending_yosys/functional_ir.rst | 115 ++++++++++-------- 1 file changed, 61 insertions(+), 54 deletions(-) diff --git a/docs/source/yosys_internals/extending_yosys/functional_ir.rst b/docs/source/yosys_internals/extending_yosys/functional_ir.rst index 38d00055627..29aaf398c28 100644 --- a/docs/source/yosys_internals/extending_yosys/functional_ir.rst +++ b/docs/source/yosys_internals/extending_yosys/functional_ir.rst @@ -3,7 +3,7 @@ Writing a new backend using FunctionalIR To simplify the writing of backends for functional languages or similar targets, Yosys provides an alternative intermediate representation called FunctionalIR which maps more directly on those targets. -FunctionalIR represents the design as a function `(inputs, current_state) -> (outputs, next_state)`. +FunctionalIR represents the design as a function ``(inputs, current_state) -> (outputs, next_state)``. This function is broken down into a series of assignments to variables. Each assignment is a simple operation, such as an addition. Complex operations are broken up into multiple steps. @@ -14,72 +14,79 @@ We can thus treat variables and assignments as equivalent and, since this is a g Unlike RTLIL's cells and wires representation, this representation is strictly ordered (topologically sorted) with definitions preceding their use. Every node has a "sort" (the FunctionalIR term for what might otherwise be called a "type"). The sorts available are -- `bit[n]` for an `n`-bit bitvector, and -- `memory[n,m]` for an immutable array of `2**n` values of sort `bit[m]`. -In terms of actual code, Yosys provides a class `Functional::IR` that represents a design in FunctionalIR. -`Functional::IR::from_module` generates an instance from an RTLIL module. +- ``bit[n]`` for an ``n``-bit bitvector, and +- ``memory[n,m]`` for an immutable array of ``2**n`` values of sort ``bit[m]``. + +In terms of actual code, Yosys provides a class ``Functional::IR`` that represents a design in FunctionalIR. +``Functional::IR::from_module`` generates an instance from an RTLIL module. The entire design is stored as a whole in an internal data structure. -To access the design, the `Functional::Node` class provides a reference to a particular node in the design. -The `Functional::IR` class supports the syntax `for(auto node : ir)` to iterate over every node. +To access the design, the ``Functional::Node`` class provides a reference to a particular node in the design. +The ``Functional::IR`` class supports the syntax ``for(auto node : ir)`` to iterate over every node. -`Functional::IR` also keeps track of inputs, outputs and states. +``Functional::IR`` also keeps track of inputs, outputs and states. By a "state" we mean a pair of a "current state" input and a "next state" output. One such pair is created for every register and for every memory. Every input, output and state has a name (equal to their name in RTLIL), a sort and a kind. -The kind field usually remains as the default value `$input`, `$output` or `$state`, however some RTLIL cells such as `$assert` or `$anyseq` generate auxiliary inputs/outputs/states that are given a different kind to distinguish them from ordinary RTLIL inputs/outputs/states. -- To access an individual input/output/state, use `ir.input(name, kind)`, `ir.output(name, kind)` or `ir.state(name, kind)`. `kind` defaults to the default kind. -- To iterate over all inputs/outputs/states of a certain kind, methods `ir.inputs`, `ir.outputs`, `ir.states` are provided. Their argument defaults to the default kinds mentioned. -- To iterate over inputs/outputs/states of any kind, use `ir.all_inputs`, `ir.all_outputs` and `ir.all_states`. -- Outputs have a node that indicate the value of the output, this can be retrieved via `output.value()`. -- States have a node that indicate the next value of the state, this can be retrieved via `state.next_value()`. - They also have an initial value that is accessed as either `state.initial_value_signal()` or `state.initial_value_memory()`, depending on their sort. - -Each node has a "function", which defines its operation (for a complete list of functions and a specification of their operation, see `functional.h`). -Functions are represented as an enum `Functional::Fn` and the function field can be accessed as `node.fn()`. -Since the most common operation is a switch over the function that also accesses the arguments, the `Node` class provides a method `visit` that implements the visitor pattern. -For example, for an addition node `node` with arguments `n1` and `n2`, `node.visit(visitor)` would call `visitor.add(node, n1, n2)`. +The kind field usually remains as the default value ``$input``, ``$output`` or ``$state``, however some RTLIL cells such as ``$assert`` or ``$anyseq`` generate auxiliary inputs/outputs/states that are given a different kind to distinguish them from ordinary RTLIL inputs/outputs/states. + +- To access an individual input/output/state, use ``ir.input(name, kind)``, ``ir.output(name, kind)`` or ``ir.state(name, kind)``. ``kind`` defaults to the default kind. +- To iterate over all inputs/outputs/states of a certain kind, methods ``ir.inputs``, ``ir.outputs``, ``ir.states`` are provided. Their argument defaults to the default kinds mentioned. +- To iterate over inputs/outputs/states of any kind, use ``ir.all_inputs``, ``ir.all_outputs`` and ``ir.all_states``. +- Outputs have a node that indicate the value of the output, this can be retrieved via ``output.value()``. +- States have a node that indicate the next value of the state, this can be retrieved via ``state.next_value()``. + They also have an initial value that is accessed as either ``state.initial_value_signal()`` or ``state.initial_value_memory()``, depending on their sort. + +Each node has a "function", which defines its operation (for a complete list of functions and a specification of their operation, see ``functional.h``). +Functions are represented as an enum ``Functional::Fn`` and the function field can be accessed as ``node.fn()``. +Since the most common operation is a switch over the function that also accesses the arguments, the ``Node`` class provides a method ``visit`` that implements the visitor pattern. +For example, for an addition node ``node`` with arguments ``n1`` and ``n2``, ``node.visit(visitor)`` would call ``visitor.add(node, n1, n2)``. Thus typically one would implement a class with a method for every function. -Visitors should inherit from either `Functional::AbstractVisitor` or `Functional::DefaultVisitor`. -The former will produce a compiler error if a case is unhandled, the latter will call `default_handler(node)` instead. -Visitor methods should be marked as `override` to provide compiler errors if the arguments are wrong. +Visitors should inherit from either ``Functional::AbstractVisitor`` or ``Functional::DefaultVisitor``. +The former will produce a compiler error if a case is unhandled, the latter will call ``default_handler(node)`` instead. +Visitor methods should be marked as ``override`` to provide compiler errors if the arguments are wrong. Utility classes ----------------- -`functional.h` also provides utility classes that are independent of the main FunctionalIR representation but are likely to be useful for backends. +``functional.h`` also provides utility classes that are independent of the main FunctionalIR representation but are likely to be useful for backends. -`Functional::Writer` provides a simple formatting class that wraps a `std::ostream` and provides the following methods: -- `writer << value` wraps `os << value`. -- `writer.print(fmt, value0, value1, value2, ...)` replaces `{0}`, `{1}`, `{2}`, etc in the string `fmt` with `value0`, `value1`, `value2`, resp. - Each value is formatted using `os << value`. - It is also possible to write `{}` to refer to one past the last index, i.e. `{1} {} {} {7} {}` is equivalent to `{1} {2} {3} {7} {8}`. -- `writer.print_with(fn, fmt, value0, value1, value2, ...)` functions much the same as `print` but it uses `os << fn(value)` to print each value and falls back to `os << value` if `fn(value)` is not legal. +``Functional::Writer`` provides a simple formatting class that wraps a ``std::ostream`` and provides the following methods: -`Functional::Scope` keeps track of variable names in a target language. +- ``writer << value`` wraps ``os << value``. +- ``writer.print(fmt, value0, value1, value2, ...)`` replaces ``{0}``, ``{1}``, ``{2}``, etc in the string ``fmt`` with ``value0``, ``value1``, ``value2``, resp. + Each value is formatted using ``os << value``. + It is also possible to write ``{}`` to refer to one past the last index, i.e. ``{1} {} {} {7} {}`` is equivalent to ``{1} {2} {3} {7} {8}``. +- ``writer.print_with(fn, fmt, value0, value1, value2, ...)`` functions much the same as ``print`` but it uses ``os << fn(value)`` to print each value and falls back to ``os << value`` if ``fn(value)`` is not legal. + +``Functional::Scope`` keeps track of variable names in a target language. It is used to translate between different sets of legal characters and to avoid accidentally re-defining identifiers. -Users should derive a class from `Scope` and supply the following: -- `Scope` takes a template argument that specifies a type that's used to uniquely distinguish variables. - Typically this would be `int` (if variables are used for `Functional::IR` nodes) or `IdString`. -- The derived class should provide a constructor that calls `reserve` for every reserved word in the target language. -- A method `bool is_legal_character(char c, int index)` has to be provided that returns `true` iff `c` is legal in an identifier at position `index`. -Given an instance `scope` of the derived class, the following methods are then available: -- `scope.reserve(std::string name)` marks the given name as being in-use -- `scope.unique_name(IdString suggestion)` generates a previously unused name and attempts to make it similar to `suggestion`. -- `scope(Id id, IdString suggestion)` functions similar to `unique_name`, except that multiple calls with the same `id` are guaranteed to retrieve the same name (independent of `suggestion`). - -`sexpr.h` provides classes that represent and pretty-print s-expressions. -S-expressions can be constructed with `SExpr::list`, for example `SExpr expr = SExpr::list("add", "x", SExpr::list("mul", "y", "z"))` represents `(add x (mul y z))` -(by adding `using SExprUtil::list` to the top of the file, `list` can be used as shorthand for `SExpr::list`). -For prettyprinting, `SExprWriter` wraps an `std::ostream` and provides the following methods: -- `writer << sexpr` writes the provided expression to the output, breaking long lines and adding appropriate indentation. -- `writer.open(sexpr)` is similar to `writer << sexpr` but will omit the last closing parenthesis. - Further arguments can then be added separately with `<<` or `open`. +Users should derive a class from ``Scope`` and supply the following: + +- ``Scope`` takes a template argument that specifies a type that's used to uniquely distinguish variables. + Typically this would be ``int`` (if variables are used for ``Functional::IR`` nodes) or ``IdString``. +- The derived class should provide a constructor that calls ``reserve`` for every reserved word in the target language. +- A method ``bool is_legal_character(char c, int index)`` has to be provided that returns ``true`` iff ``c`` is legal in an identifier at position ``index``. + +Given an instance ``scope`` of the derived class, the following methods are then available: + +- ``scope.reserve(std::string name)`` marks the given name as being in-use +- ``scope.unique_name(IdString suggestion)`` generates a previously unused name and attempts to make it similar to ``suggestion``. +- ``scope(Id id, IdString suggestion)`` functions similar to ``unique_name``, except that multiple calls with the same ``id`` are guaranteed to retrieve the same name (independent of ``suggestion``). + +``sexpr.h`` provides classes that represent and pretty-print s-expressions. +S-expressions can be constructed with ``SExpr::list``, for example ``SExpr expr = SExpr::list("add", "x", SExpr::list("mul", "y", "z"))`` represents ``(add x (mul y z))`` +(by adding ``using SExprUtil::list`` to the top of the file, ``list`` can be used as shorthand for ``SExpr::list``). +For prettyprinting, ``SExprWriter`` wraps an ``std::ostream`` and provides the following methods: + +- ``writer << sexpr`` writes the provided expression to the output, breaking long lines and adding appropriate indentation. +- ``writer.open(sexpr)`` is similar to ``writer << sexpr`` but will omit the last closing parenthesis. + Further arguments can then be added separately with ``<<`` or ``open``. This allows for printing large s-expressions without needing the construct the whole expression in memory first. -- `writer.open(sexpr, false)` is similar to `writer.open(sexpr)` but further arguments will not be indented. +- ``writer.open(sexpr, false)`` is similar to ``writer.open(sexpr)`` but further arguments will not be indented. This is used to avoid unlimited indentation on structures with unlimited nesting. -- `writer.close(n = 1)` closes the last `n` open s-expressions. -- `writer.push()` and `writer.pop()` are used to automatically close s-expressions. - `writer.pop()` closes all s-expressions opened since the last call to `writer.push()`. -- `writer.comment(string)` writes a comment on a separate-line. - `writer.comment(string, true)` appends a comment to the last printed s-expression. \ No newline at end of file +- ``writer.close(n = 1)`` closes the last ``n`` open s-expressions. +- ``writer.push()`` and ``writer.pop()`` are used to automatically close s-expressions. + ``writer.pop()`` closes all s-expressions opened since the last call to ``writer.push()``. +- ``writer.comment(string)`` writes a comment on a separate-line. + ``writer.comment(string, true)`` appends a comment to the last printed s-expression. \ No newline at end of file From 7de8be14516bc059d2029859b7a5469a322548e7 Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Tue, 3 Sep 2024 14:30:56 +0100 Subject: [PATCH 89/92] functional_ir.rst: fix typo, document SExprWriter::flush --- .../yosys_internals/extending_yosys/functional_ir.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/source/yosys_internals/extending_yosys/functional_ir.rst b/docs/source/yosys_internals/extending_yosys/functional_ir.rst index 29aaf398c28..a763e050889 100644 --- a/docs/source/yosys_internals/extending_yosys/functional_ir.rst +++ b/docs/source/yosys_internals/extending_yosys/functional_ir.rst @@ -82,11 +82,13 @@ For prettyprinting, ``SExprWriter`` wraps an ``std::ostream`` and provides the f - ``writer << sexpr`` writes the provided expression to the output, breaking long lines and adding appropriate indentation. - ``writer.open(sexpr)`` is similar to ``writer << sexpr`` but will omit the last closing parenthesis. Further arguments can then be added separately with ``<<`` or ``open``. - This allows for printing large s-expressions without needing the construct the whole expression in memory first. + This allows for printing large s-expressions without needing to construct the whole expression in memory first. - ``writer.open(sexpr, false)`` is similar to ``writer.open(sexpr)`` but further arguments will not be indented. This is used to avoid unlimited indentation on structures with unlimited nesting. - ``writer.close(n = 1)`` closes the last ``n`` open s-expressions. - ``writer.push()`` and ``writer.pop()`` are used to automatically close s-expressions. ``writer.pop()`` closes all s-expressions opened since the last call to ``writer.push()``. - ``writer.comment(string)`` writes a comment on a separate-line. - ``writer.comment(string, true)`` appends a comment to the last printed s-expression. \ No newline at end of file + ``writer.comment(string, true)`` appends a comment to the last printed s-expression. +- ``writer.flush()`` flushes any buffering and should be called before any direct access to the underlying ``std::ostream``. It does not close unclosed parentheses. +- The destructor calls ``flush`` but also closes all unclosed parentheses. From 5a476a8d29c541d421d409a48a81f642de30b80e Mon Sep 17 00:00:00 2001 From: Emily Schmidt Date: Wed, 4 Sep 2024 10:30:08 +0100 Subject: [PATCH 90/92] functional tests: run from make tests but not smtlib/rkt tests --- Makefile | 1 + tests/functional/conftest.py | 6 +++++- tests/functional/run-test.sh | 2 +- tests/functional/test_functional.py | 2 ++ 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index fbe09904263..2a4f4f58758 100644 --- a/Makefile +++ b/Makefile @@ -891,6 +891,7 @@ endif +cd tests/xprop && bash run-test.sh $(SEEDOPT) +cd tests/fmt && bash run-test.sh +cd tests/cxxrtl && bash run-test.sh + +cd tests/functional && bash run-test.sh @echo "" @echo " Passed \"make test\"." @echo "" diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py index 12db7f1d09b..a9fbb3c5947 100644 --- a/tests/functional/conftest.py +++ b/tests/functional/conftest.py @@ -4,6 +4,10 @@ random_seed = random.getrandbits(32) +def pytest_configure(config): + config.addinivalue_line("markers", "smt: test uses smtlib/z3") + config.addinivalue_line("markers", "rkt: test uses racket/rosette") + def pytest_addoption(parser): parser.addoption("--per-cell", type=int, default=None, help="run only N tests per cell") parser.addoption("--steps", type=int, default=1000, help="run each test for N steps") @@ -27,4 +31,4 @@ def pytest_generate_tests(metafunc): seed1 = metafunc.config.getoption("seed") rnd = lambda seed2: random.Random('{}-{}'.format(seed1, seed2)) names, cases = generate_test_cases(per_cell, rnd) - metafunc.parametrize("cell,parameters", cases, ids=names) \ No newline at end of file + metafunc.parametrize("cell,parameters", cases, ids=names) diff --git a/tests/functional/run-test.sh b/tests/functional/run-test.sh index b9a0595f663..9f70462eed9 100755 --- a/tests/functional/run-test.sh +++ b/tests/functional/run-test.sh @@ -1,2 +1,2 @@ #!/usr/bin/env bash -pytest -v "$@" +pytest -v -m "not smt and not rkt" "$@" diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index 3b006676a14..7a09966d897 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -55,6 +55,7 @@ def test_cxx(cell, parameters, tmp_path, num_steps, rnd): run([str(vcdharness_exe_file.resolve()), str(vcd_functional_file), str(num_steps), str(seed)]) yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file, getattr(cell, 'sim_preprocessing', '')) +@pytest.mark.smt def test_smt(cell, parameters, tmp_path, num_steps, rnd): import smt_vcd @@ -72,6 +73,7 @@ def test_smt(cell, parameters, tmp_path, num_steps, rnd): smt_vcd.simulate_smt(smt_file, vcd_functional_file, num_steps, rnd(cell.name + "-smt")) yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file, getattr(cell, 'sim_preprocessing', '')) +@pytest.mark.rkt def test_rkt(cell, parameters, tmp_path, num_steps, rnd): import rkt_vcd From 21494d1f06457c2ec99669c06bf42c8bf9ee92d5 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Thu, 5 Sep 2024 11:43:31 +1200 Subject: [PATCH 91/92] Makefile: Update coverage_functional Note sure if this is the best way to do it, but it works? --- Makefile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 2a4f4f58758..f377072fb25 100644 --- a/Makefile +++ b/Makefile @@ -1052,11 +1052,12 @@ coverage: clean_coverage: find . -name "*.gcda" -type f -delete +FUNC_KERNEL := functional.cc functional.h sexpr.cc sexpr.h compute_graph.h +FUNC_INCLUDES := $(addprefix --include *,functional/* $(FUNC_KERNEL)) coverage_functional: - rm -rf coverage.info coverage2.info coverage_html - lcov --capture -d backends/functional --no-external -o coverage.info - lcov --capture -d kernel --include kernel/functional.cc --include kernel/functional.h --include kernel/sexpr.cc --include kernel/sexpr.h --include kernel/compute_graph.h --no-external -o coverage2.info - genhtml coverage.info coverage2.info --output-directory coverage_html + rm -rf coverage.info coverage_html + lcov --capture -d backends/functional -d kernel $(FUNC_INCLUDES) --no-external -o coverage.info + genhtml coverage.info --output-directory coverage_html qtcreator: echo "$(CXXFLAGS)" | grep -o '\-D[^ ]*' | tr ' ' '\n' | sed 's/-D/#define /' | sed 's/=/ /'> qtcreator.config From fc10a6eee2d12c18212fbe7ccabd6094408afd30 Mon Sep 17 00:00:00 2001 From: Miodrag Milanovic Date: Fri, 6 Sep 2024 08:47:43 +0200 Subject: [PATCH 92/92] Run functional tests on private runner only --- .github/workflows/test-verific.yml | 1 + Makefile | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/test-verific.yml b/.github/workflows/test-verific.yml index 627a70d4794..024f8cddcb1 100644 --- a/.github/workflows/test-verific.yml +++ b/.github/workflows/test-verific.yml @@ -41,6 +41,7 @@ jobs: echo "ENABLE_VERIFIC_LIBERTY := 1" >> Makefile.conf echo "ENABLE_VERIFIC_YOSYSHQ_EXTENSIONS := 1" >> Makefile.conf echo "ENABLE_CCACHE := 1" >> Makefile.conf + echo "ENABLE_FUNCTIONAL_TESTS := 1" >> Makefile.conf make -j${{ env.procs }} ENABLE_LTO=1 - name: Install Yosys diff --git a/Makefile b/Makefile index f377072fb25..c3d572a3686 100644 --- a/Makefile +++ b/Makefile @@ -38,6 +38,7 @@ ENABLE_LTO := 0 ENABLE_CCACHE := 0 # sccache is not always a drop-in replacement for ccache in practice ENABLE_SCCACHE := 0 +ENABLE_FUNCTIONAL_TESTS := 0 LINK_CURSES := 0 LINK_TERMCAP := 0 LINK_ABC := 0 @@ -891,7 +892,9 @@ endif +cd tests/xprop && bash run-test.sh $(SEEDOPT) +cd tests/fmt && bash run-test.sh +cd tests/cxxrtl && bash run-test.sh +ifeq ($(ENABLE_FUNCTIONAL_TESTS),1) +cd tests/functional && bash run-test.sh +endif @echo "" @echo " Passed \"make test\"." @echo ""