Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wrapcell: Optionally track unused outputs #4799

Merged
merged 4 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 158 additions & 26 deletions passes/cmds/wrapcell.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,37 @@
*/
#include "kernel/yosys.h"
#include "kernel/celltypes.h"
#include "kernel/sigtools.h"
#include "backends/rtlil/rtlil_backend.h"

USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN

std::optional<std::string> format(std::string fmt, const dict<IdString, Const> &parameters)
bool has_fmt_field(std::string fmt, std::string field_name)
{
auto it = fmt.begin();
while (it != fmt.end()) {
if (*it == '{') {
it++;
auto beg = it;
while (it != fmt.end() && *it != '}') it++;
if (it == fmt.end())
return false;

if (std::string(beg, it) == field_name)
return true;
}
it++;
}
return false;
}

struct ContextData {
std::string unused_outputs;
};

std::optional<std::string> format(std::string fmt, const dict<IdString, Const> &parameters,
povik marked this conversation as resolved.
Show resolved Hide resolved
const ContextData &context)
{
std::stringstream result;

Expand All @@ -38,13 +63,19 @@ std::optional<std::string> format(std::string fmt, const dict<IdString, Const> &
return {};
}

auto id = RTLIL::escape_id(std::string(beg, it));
if (!parameters.count(id)) {
log("Parameter %s referenced in format string '%s' not found\n", log_id(id), fmt.c_str());
return {};
}
std::string param_name = {beg, it};

RTLIL_BACKEND::dump_const(result, parameters.at(id));
if (param_name == "%unused") {
result << context.unused_outputs;
} else {
auto id = RTLIL::escape_id(std::string(beg, it));
if (!parameters.count(id)) {
log("Parameter %s referenced in format string '%s' not found\n", log_id(id), fmt.c_str());
return {};
}

RTLIL_BACKEND::dump_const(result, parameters.at(id));
}
} else {
result << *it;
}
Expand All @@ -54,6 +85,45 @@ std::optional<std::string> format(std::string fmt, const dict<IdString, Const> &
return {result.str()};
}

struct Chunk {
IdString port;
int base, len;

Chunk(IdString id, int base, int len)
: port(id), base(base), len(len) {}

IdString format(Cell *cell)
{
if (len == cell->getPort(port).size())
return port;
else if (len == 1)
return stringf("%s[%d]", port.c_str(), base);
else
return stringf("%s[%d:%d]", port.c_str(), base + len - 1, base);
}

SigSpec sample(Cell *cell)
{
return cell->getPort(port).extract(base, len);
}
};

// Joins contiguous runs of bits into a 'Chunk'
std::vector<Chunk> collect_chunks(std::vector<std::pair<IdString, int>> bits)
povik marked this conversation as resolved.
Show resolved Hide resolved
{
std::vector<Chunk> ret;
std::sort(bits.begin(), bits.end());
for (auto it = bits.begin(); it != bits.end();) {
auto sep = it + 1;
for (; sep != bits.end() &&
sep->first == it->first &&
sep->second == (sep - 1)->second + 1; sep++);
ret.emplace_back(it->first, it->second, sep - it);
it = sep;
}
return ret;
}

struct WrapcellPass : Pass {
WrapcellPass() : Pass("wrapcell", "wrap individual cells into new modules") {}

Expand All @@ -68,6 +138,10 @@ struct WrapcellPass : Pass {
log("parameter values as specified in curly brackets. If the named module already\n");
log("exists, it is reused.\n");
log("\n");
log("If the template contains the special string '{%%unused}', the command tracks\n");
log("unused output ports -- specialized wrapper modules will be generated per every\n");
log("distinct set of unused port bits as appearing on any selected cell.\n");
log("\n");
log(" -setattr <attribute-name>\n");
log(" set the given boolean attribute on each created wrapper module\n");
log("\n");
Expand Down Expand Up @@ -114,43 +188,89 @@ struct WrapcellPass : Pass {
CellTypes ct;
ct.setup();

bool tracking_unused = has_fmt_field(name_fmt, "%unused");

for (auto module : d->selected_modules()) {
SigPool unused;

for (auto wire : module->wires())
if (wire->has_attribute(ID::unused_bits)) {
std::string str = wire->get_string_attribute(ID::unused_bits);
for (auto it = str.begin(); it != str.end();) {
auto sep = it;
for (; sep != str.end() && *sep != ' '; sep++);
unused.add(SigBit(wire, std::stoi(std::string(it, sep))));
for (it = sep; it != str.end() && *it == ' '; it++);
}
}

for (auto cell : module->selected_cells()) {
std::optional<std::string> unescaped_name = format(name_fmt, cell->parameters);
Module *subm;
Cell *subcell;

if (!ct.cell_known(cell->type))
log_error("Non-internal cell type '%s' on cell '%s' in module '%s' unsupported\n",
log_id(cell->type), log_id(cell), log_id(module));

std::vector<std::pair<IdString, int>> unused_outputs, used_outputs;
for (auto conn : cell->connections()) {
if (ct.cell_output(cell->type, conn.first))
for (int i = 0; i < conn.second.size(); i++) {
if (tracking_unused && unused.check(conn.second[i]))
unused_outputs.emplace_back(conn.first, i);
else
used_outputs.emplace_back(conn.first, i);
}
}

ContextData context;
if (!unused_outputs.empty()) {
context.unused_outputs += "_unused";
for (auto chunk : collect_chunks(unused_outputs))
context.unused_outputs += "_" + RTLIL::unescape_id(chunk.format(cell));
}

std::optional<std::string> unescaped_name = format(name_fmt, cell->parameters, context);
if (!unescaped_name)
log_error("Formatting error when processing cell '%s' in module '%s'\n",
log_id(cell), log_id(module));

IdString name = RTLIL::escape_id(unescaped_name.value());
if (d->module(name))
goto replace_cell;

if (d->module(name)) {
cell->type = name;
cell->parameters.clear();
continue;
subm = d->addModule(name);
subcell = subm->addCell("$1", cell->type);
for (auto conn : cell->connections()) {
if (ct.cell_output(cell->type, conn.first)) {
// Insert marker bits as placehodlers which need to be replaced
subcell->setPort(conn.first, SigSpec(RTLIL::Sm, conn.second.size()));
povik marked this conversation as resolved.
Show resolved Hide resolved
} else {
Wire *w = subm->addWire(conn.first, conn.second.size());
w->port_input = true;
subcell->setPort(conn.first, w);
}
}

if (!ct.cell_known(cell->type))
log_error("Non-internal cell type '%s' on cell '%s' in module '%s' unsupported\n",
log_id(cell->type), log_id(cell), log_id(module));
for (auto chunk : collect_chunks(used_outputs)) {
Wire *w = subm->addWire(chunk.format(cell), chunk.len);
w->port_output = true;
subcell->connections_[chunk.port].replace(chunk.base, w);
}

Module *subm = d->addModule(name);
Cell *subcell = subm->addCell("$1", cell->type);
for (auto conn : cell->connections()) {
Wire *w = subm->addWire(conn.first, conn.second.size());
if (ct.cell_output(cell->type, w->name))
w->port_output = true;
else
w->port_input = true;
subcell->setPort(conn.first, w);
for (auto chunk : collect_chunks(unused_outputs)) {
widlarizer marked this conversation as resolved.
Show resolved Hide resolved
Wire *w = subm->addWire(chunk.format(cell), chunk.len);
subcell->connections_[chunk.port].replace(chunk.base, w);
}

subcell->parameters = cell->parameters;
subm->fixup_ports();

for (auto rule : attributes) {
if (rule.value_fmt.empty()) {
subm->set_bool_attribute(rule.name);
} else {
std::optional<std::string> value = format(rule.value_fmt, cell->parameters);
std::optional<std::string> value = format(rule.value_fmt, cell->parameters, context);

if (!value)
log_error("Formatting error when processing cell '%s' in module '%s'\n",
Expand All @@ -160,8 +280,20 @@ struct WrapcellPass : Pass {
}
}

cell->type = name;
replace_cell:
cell->parameters.clear();

dict<IdString, SigSpec> new_connections;

for (auto conn : cell->connections())
if (!ct.cell_output(cell->type, conn.first))
new_connections[conn.first] = conn.second;

for (auto chunk : collect_chunks(used_outputs))
new_connections[chunk.format(cell)] = chunk.sample(cell);

cell->type = name;
cell->connections_ = new_connections;
}
}
}
Expand Down
32 changes: 32 additions & 0 deletions tests/various/wrapcell.ys
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,40 @@ EOF

wreduce
wrapcell -setattr foo -formatattr bar w{Y_WIDTH} -name OR_{A_WIDTH}_{B_WIDTH}_{Y_WIDTH}
check -assert
select -assert-count 2 top/t:OR_2_3_3
select -assert-count 1 top/t:OR_3_4_4
select -assert-none top/t:OR_2_3_3 top/t:OR_3_4_4 %% top/t:* %D
select -assert-mod-count 2 OR_2_3_3 OR_3_4_4
select -assert-mod-count 2 A:bar=w3 A:bar=w4

design -reset
read_verilog <<EOF
module top(
input [1:0] a,
input [2:0] b,
output [2:0] y,
input [2:0] a2,
input [3:0] b2,
output [3:0] y2,
input [1:0] a3,
input [2:0] b3,
output [2:0] y3
);
assign y = a | (*keep*) b;
assign y2 = a2 | (*keep*) b2;
wire [2:0] y3_ = a3 | (*keep*) b3;
assign y3 = {y3_[2], y3_[0]};
endmodule
EOF

opt_clean
wreduce
wrapcell -setattr foo -formatattr bar w{Y_WIDTH} -name OR_{A_WIDTH}_{B_WIDTH}_{Y_WIDTH}{%unused}
check -assert
select -assert-count 1 top/t:OR_2_3_3
select -assert-count 1 top/t:OR_2_3_3_unused_Y[1]
select -assert-count 1 top/t:OR_3_4_4
select -assert-none top/t:OR_2_3_3 top/t:OR_3_4_4 top/t:OR_2_3_3_unused_Y[1] %% top/t:* %D
select -assert-mod-count 2 OR_2_3_3 OR_3_4_4
select -assert-mod-count 3 A:bar=w3 A:bar=w4
Loading