From b4af536f6896bc3a625f65f25b2d85d8deb139a3 Mon Sep 17 00:00:00 2001 From: Jannis Harder Date: Wed, 10 Apr 2024 13:26:03 +0200 Subject: [PATCH] drivertools: WIP --- Makefile | 2 + kernel/drivertools.cc | 949 ++++++++++++++++++++++++++ kernel/drivertools.h | 1316 +++++++++++++++++++++++++++++++++++++ kernel/log.cc | 10 + kernel/log.h | 2 + kernel/rtlil.cc | 14 + kernel/rtlil.h | 1 + kernel/topo_scc.h | 328 +++++++++ passes/cmds/Makefile.inc | 1 + passes/cmds/example_dt.cc | 140 ++++ 10 files changed, 2763 insertions(+) create mode 100644 kernel/drivertools.cc create mode 100644 kernel/drivertools.h create mode 100644 kernel/topo_scc.h create mode 100644 passes/cmds/example_dt.cc diff --git a/Makefile b/Makefile index 6d81b627f03..c6213a3e305 100644 --- a/Makefile +++ b/Makefile @@ -607,6 +607,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)) @@ -647,6 +648,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/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..f0dda51a8b8 --- /dev/null +++ b/kernel/drivertools.cc @@ -0,0 +1,949 @@ +/* + * 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) + connect_directed_merge(a_id, b_id); + else if (b_mode == BitMode::DRIVEN_UNIQUE) + connect_directed_merge(b_id, a_id); + // If either bit requires a driven value and may have multiple drivers, + // store a directed connection from the other bit. + else if (a_mode == BitMode::DRIVEN) + connect_directed_buffer(a_id, b_id); + else if (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_undriven() 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: + 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(); + 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(); + 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_undriven() 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 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) diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 8781b6a8946..4713b84a255 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -3733,6 +3733,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; } 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 diff --git a/passes/cmds/Makefile.inc b/passes/cmds/Makefile.inc index d7e572462b0..caea266c397 100644 --- a/passes/cmds/Makefile.inc +++ b/passes/cmds/Makefile.inc @@ -48,3 +48,4 @@ OBJS += passes/cmds/clean_zerowidth.o OBJS += passes/cmds/xprop.o OBJS += passes/cmds/dft_tag.o OBJS += passes/cmds/future.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