memory_libmap: print additional debug messages when no valid mapping is found

This commit is contained in:
N. Engelhardt 2023-07-06 18:54:32 +02:00
parent a6be7b4751
commit 57de249881
1 changed files with 128 additions and 32 deletions

View File

@ -183,6 +183,7 @@ struct MemMapping {
dict<std::pair<int, int>, bool> wr_implies_rd_cache;
dict<std::pair<int, int>, bool> wr_excludes_rd_cache;
dict<std::pair<int, int>, bool> wr_excludes_srst_cache;
std::string rejected_cfg_debug_msgs;
MemMapping(MapWorker &worker, Mem &mem, const Library &lib, const PassOptions &opts) : worker(worker), qcsat(worker.modwalker), mem(mem), lib(lib), opts(opts) {
determine_style();
@ -202,7 +203,7 @@ struct MemMapping {
if (!check_init(rdef))
continue;
if (rdef.prune_rom && mem.wr_ports.empty()) {
log_debug("memory %s.%s: rejecting mapping to %s: ROM not allowed\n", log_id(mem.module->name), log_id(mem.memid), log_id(rdef.id));
log_debug("memory %s.%s: rejecting mapping to %s: ROM mapping disabled (prune_rom set)\n", log_id(mem.module->name), log_id(mem.memid), log_id(rdef.id));
continue;
}
MemConfig cfg;
@ -311,6 +312,59 @@ struct MemMapping {
void prune_post_geom();
void emit_port(const MemConfig &cfg, std::vector<Cell*> &cells, const PortVariant &pdef, const char *name, int wpidx, int rpidx, const std::vector<int> &hw_addr_swizzle);
void emit(const MemConfig &cfg);
void log_reject(std::string message){
if(ys_debug(1)) {
rejected_cfg_debug_msgs += message;
rejected_cfg_debug_msgs += "\n";
}
}
void log_reject(const Ram &ram, std::string message) {
if(ys_debug(1)) {
rejected_cfg_debug_msgs += stringf("can't map to to %s: ", log_id(ram.id));
rejected_cfg_debug_msgs += message;
rejected_cfg_debug_msgs += "\n";
}
}
void log_reject(const Ram &ram, const PortGroup &pg, std::string message) {
if(ys_debug(1)) {
rejected_cfg_debug_msgs += stringf("can't map to port group [");
bool first = true;
for (std::string portname : pg.names){
if (!first) rejected_cfg_debug_msgs += ", ";
rejected_cfg_debug_msgs += portname;
first = false;
}
rejected_cfg_debug_msgs += stringf("] of %s: ", log_id(ram.id));
rejected_cfg_debug_msgs += message;
rejected_cfg_debug_msgs += "\n";
}
}
void log_reject(const Ram &ram, const PortGroup &pg, int pvi, std::string message) {
if(ys_debug(1)) {
rejected_cfg_debug_msgs += stringf("can't map to option selection [");
bool first = true;
for(auto opt : pg.variants[pvi].options){
if (!first) rejected_cfg_debug_msgs += ", ";
rejected_cfg_debug_msgs += opt.first;
rejected_cfg_debug_msgs += stringf(" = %s", log_const(opt.second));
first = false;
}
rejected_cfg_debug_msgs += "] of port group [";
first = true;
for (std::string portname : pg.names){
if (!first) rejected_cfg_debug_msgs += ", ";
rejected_cfg_debug_msgs += portname;
first = false;
}
rejected_cfg_debug_msgs += stringf("] of %s: ", log_id(ram.id));
rejected_cfg_debug_msgs += message;
rejected_cfg_debug_msgs += "\n";
}
}
};
void MemMapping::dump_configs(int stage) {
@ -479,7 +533,7 @@ void MemMapping::determine_style() {
// Determine whether the memory can be mapped entirely to soft logic.
bool MemMapping::determine_logic_ok() {
if (kind != RamKind::Auto && kind != RamKind::Logic) {
log_debug("memory %s.%s: rejecting mapping to logic: RAM kind conflicts with attribute\n", log_id(mem.module->name), log_id(mem.memid));
log_reject("can't map to logic: RAM kind conflicts with attribute");
return false;
}
// Memory is mappable entirely to soft logic iff all its write ports are in the same clock domain.
@ -487,15 +541,15 @@ bool MemMapping::determine_logic_ok() {
return true;
for (auto &port: mem.wr_ports) {
if (!port.clk_enable){
log_debug("memory %s.%s: rejecting mapping to logic: unclocked port\n", log_id(mem.module->name), log_id(mem.memid));
log_reject("can't map to logic: unclocked port");
return false;
}
if (port.clk != mem.wr_ports[0].clk) {
log_debug("memory %s.%s: rejecting mapping to logic: ports have different write clock domains\n", log_id(mem.module->name), log_id(mem.memid));
log_reject("can't map to logic: ports have different write clock domains");
return false;
}
if (port.clk_polarity != mem.wr_ports[0].clk_polarity) {
log_debug("memory %s.%s: rejecting mapping to logic: ports have different write clock polarity\n", log_id(mem.module->name), log_id(mem.memid));
log_reject("can't map to logic: ports have different write clock polarity");
return false;
}
}
@ -510,20 +564,20 @@ bool MemMapping::check_ram_kind(const Ram &ram) {
return true;
if (kind == RamKind::Auto || kind == RamKind::NotLogic) {
if (ram.kind == RamKind::Distributed && opts.no_auto_distributed) {
log_debug("memory %s.%s: rejecting mapping to %s: option -no-auto-distributed given\n", log_id(mem.module->name), log_id(mem.memid), log_id(ram.id));
log_reject(ram, "option -no-auto-distributed given");
return false;
}
if (ram.kind == RamKind::Block && opts.no_auto_block) {
log_debug("memory %s.%s: rejecting mapping to %s: option -no-auto-block given\n", log_id(mem.module->name), log_id(mem.memid), log_id(ram.id));
log_reject(ram, "option -no-auto-block given");
return false;
}
if (ram.kind == RamKind::Huge && opts.no_auto_huge) {
log_debug("memory %s.%s: rejecting mapping to %s: option -no-auto-huge given\n", log_id(mem.module->name), log_id(mem.memid), log_id(ram.id));
log_reject(ram, "option -no-auto-huge given");
return false;
}
return true;
}
log_debug("memory %s.%s: rejecting mapping to %s: RAM kind conflicts with attribute\n", log_id(mem.module->name), log_id(mem.memid), log_id(ram.id));
log_reject(ram, "RAM kind conflicts with attribute");
return false;
}
@ -534,7 +588,7 @@ bool MemMapping::check_ram_style(const Ram &ram) {
for (auto &s: ram.style)
if (s == style)
return true;
log_debug("memory %s.%s: rejecting mapping to %s: RAM style conflicts with attribute\n", log_id(mem.module->name), log_id(mem.memid), log_id(ram.id));
log_reject(ram, "RAM style conflicts with attribute");
return false;
}
@ -554,10 +608,10 @@ bool MemMapping::check_init(const Ram &ram) {
switch (ram.init) {
case MemoryInitKind::None:
if(has_nonx) log_debug("memory %s.%s: rejecting mapping to %s: does not support initialization\n", log_id(mem.module->name), log_id(mem.memid), log_id(ram.id));
if(has_nonx) log_reject(ram, "does not support initialization");
return !has_nonx;
case MemoryInitKind::Zero:
if(has_one) log_debug("memory %s.%s: rejecting mapping to %s: does not support non-zero initialization\n", log_id(mem.module->name), log_id(mem.memid), log_id(ram.id));
if(has_one) log_reject(ram, "does not support non-zero initialization");
return !has_one;
default:
return true;
@ -593,11 +647,12 @@ bool apply_clock(MemConfig &cfg, const PortVariant &def, SigBit clk, bool clk_po
// Perform write port assignment, validating clock options as we go.
void MemMapping::assign_wr_ports() {
log_reject(stringf("Assigning write ports... (candidate configs: %lu)", cfgs.size()));
for (auto &port: mem.wr_ports) {
if (!port.clk_enable) {
// Async write ports not supported.
cfgs.clear();
log_debug("memory %s.%s: rejecting mapping: async write port\n", log_id(mem.module->name), log_id(mem.memid));
log_reject("can't map at all: async write port");
return;
}
MemConfigs new_cfgs;
@ -609,21 +664,27 @@ void MemMapping::assign_wr_ports() {
for (auto &oport: cfg.wr_ports)
if (oport.port_group == pgi)
used++;
if (used >= GetSize(pg.names))
if (used >= GetSize(pg.names)) {
log_reject(*cfg.def, pg, "not enough unassigned ports remaining");
continue;
}
for (int pvi = 0; pvi < GetSize(pg.variants); pvi++) {
auto &def = pg.variants[pvi];
// Make sure the target is a write port.
if (def.kind == PortKind::Ar || def.kind == PortKind::Sr)
if (def.kind == PortKind::Ar || def.kind == PortKind::Sr) {
log_reject(*cfg.def, pg, pvi, "not a write port");
continue;
}
MemConfig new_cfg = cfg;
WrPortConfig pcfg;
pcfg.rd_port = -1;
pcfg.port_group = pgi;
pcfg.port_variant = pvi;
pcfg.def = &def;
if (!apply_clock(new_cfg, def, port.clk, port.clk_polarity))
if (!apply_clock(new_cfg, def, port.clk, port.clk_polarity)) {
log_reject(*cfg.def, pg, pvi, "incompatible clock polarity");
continue;
}
new_cfg.wr_ports.push_back(pcfg);
new_cfgs.push_back(new_cfg);
}
@ -635,6 +696,7 @@ void MemMapping::assign_wr_ports() {
// Perform read port assignment, validating clock and rden options as we go.
void MemMapping::assign_rd_ports() {
log_reject(stringf("Assigning read ports... (candidate configs: %lu)", cfgs.size()));
for (int pidx = 0; pidx < GetSize(mem.rd_ports); pidx++) {
auto &port = mem.rd_ports[pidx];
MemConfigs new_cfgs;
@ -649,17 +711,23 @@ void MemMapping::assign_rd_ports() {
for (auto &oport: cfg.wr_ports)
if (oport.port_group == pgi)
used++;
if (used >= GetSize(pg.names))
if (used >= GetSize(pg.names)) {
log_reject(*cfg.def, pg, "not enough unassigned ports remaining");
continue;
}
for (int pvi = 0; pvi < GetSize(pg.variants); pvi++) {
auto &def = pg.variants[pvi];
// Make sure the target is a read port.
if (def.kind == PortKind::Sw)
if (def.kind == PortKind::Sw) {
log_reject(*cfg.def, pg, pvi, "not a read port");
continue;
}
// If mapping an async port, accept only async defs.
if (!port.clk_enable) {
if (def.kind == PortKind::Sr || def.kind == PortKind::Srsw)
if (def.kind == PortKind::Sr || def.kind == PortKind::Srsw) {
log_reject(*cfg.def, pg, pvi, "not an asynchronous read port");
continue;
}
}
MemConfig new_cfg = cfg;
RdPortConfig pcfg;
@ -669,8 +737,10 @@ void MemMapping::assign_rd_ports() {
pcfg.def = &def;
if (def.kind == PortKind::Sr || def.kind == PortKind::Srsw) {
pcfg.emu_sync = false;
if (!apply_clock(new_cfg, def, port.clk, port.clk_polarity))
if (!apply_clock(new_cfg, def, port.clk, port.clk_polarity)) {
log_reject(*cfg.def, pg, pvi, "incompatible clock polarity");
continue;
}
// Decide if rden is usable.
if (port.en != State::S1) {
if (def.clk_en) {
@ -692,22 +762,34 @@ void MemMapping::assign_rd_ports() {
auto &wpcfg = cfg.wr_ports[wpidx];
auto &def = *wpcfg.def;
// Make sure the write port is not yet shared.
if (wpcfg.rd_port != -1)
if (wpcfg.rd_port != -1) {
log_reject(stringf("can't share write port %d: already shared by a different read port", wpidx));
continue;
}
// Make sure the target is a read port.
if (def.kind == PortKind::Sw)
if (def.kind == PortKind::Sw) {
log_reject(stringf("can't share write port %d: not a read-write port", wpidx));
continue;
}
// Validate address compatibility.
if (!addr_compatible(wpidx, pidx))
if (!addr_compatible(wpidx, pidx)) {
log_reject(stringf("can't share write port %d: addresses are not compatible", wpidx));
continue;
}
// Validate clock compatibility, if needed.
if (def.kind == PortKind::Srsw) {
if (!port.clk_enable)
if (!port.clk_enable) {
log_reject(stringf("can't share write port %d: incompatible enable", wpidx));
continue;
if (port.clk != wport.clk)
}
if (port.clk != wport.clk) {
log_reject(stringf("can't share write port %d: different clock signal", wpidx));
continue;
if (port.clk_polarity != wport.clk_polarity)
}
if (port.clk_polarity != wport.clk_polarity) {
log_reject(stringf("can't share write port %d: incompatible clock polarity", wpidx));
continue;
}
}
// Okay, let's fill it in.
MemConfig new_cfg = cfg;
@ -724,8 +806,10 @@ void MemMapping::assign_rd_ports() {
bool col_x = port.collision_x_mask[wpidx];
if (def.rdwr == RdWrKind::NoChange) {
if (!get_wr_excludes_rd(wpidx, pidx)) {
if (!trans && !col_x)
if (!trans && !col_x) {
log_reject(stringf("can't share write port %d: conflict in simultaneous read and write operations", wpidx));
continue;
}
if (trans)
pcfg.emu_trans.push_back(wpidx);
new_cfg.wr_ports[wpidx].force_uniform = true;
@ -738,8 +822,10 @@ void MemMapping::assign_rd_ports() {
}
}
} else {
if (!col_x && !trans && def.rdwr != RdWrKind::Old)
if (!col_x && !trans && def.rdwr != RdWrKind::Old) {
log_reject(stringf("can't share write port %d: simultaneous read and write operations should result in new value but port reads old", wpidx));
continue;
}
if (trans) {
if (def.rdwr != RdWrKind::New && def.rdwr != RdWrKind::NewOnly)
pcfg.emu_trans.push_back(wpidx);
@ -771,6 +857,7 @@ void MemMapping::assign_rd_ports() {
// Validate transparency restrictions, determine where to add soft transparency logic.
void MemMapping::handle_trans() {
log_reject(stringf("Handling transparency... (candidate configs: %lu)", cfgs.size()));
if (mem.emulate_read_first_ok()) {
MemConfigs new_cfgs;
for (auto &cfg: cfgs) {
@ -829,15 +916,21 @@ void MemMapping::handle_trans() {
bool found = false;
for (auto &tdef: wpcfg.def->wrtrans) {
// Check if the target matches.
if (tdef.target_kind == WrTransTargetKind::Group && rpcfg.port_group != tdef.target_group)
if (tdef.target_kind == WrTransTargetKind::Group && rpcfg.port_group != tdef.target_group) {
log_reject(*cfg.def, stringf("transparency with target port group %d not supported", tdef.target_group));
continue;
}
// Check if the transparency kind is acceptable.
if (transparent) {
if (tdef.kind == WrTransKind::Old)
if (tdef.kind == WrTransKind::Old) {
log_reject(*cfg.def, stringf("target %d has wrong transparency kind: new value required", tdef.target_group));
continue;
}
} else {
if (tdef.kind != WrTransKind::Old)
if (tdef.kind != WrTransKind::Old) {
log_reject(*cfg.def, stringf("target %d has wrong transparency kind: old value required", tdef.target_group));
continue;
}
}
// Okay, we can use this cap.
new_cfgs.push_back(cfg);
@ -2101,8 +2194,11 @@ struct MemoryLibMapPass : public Pass {
int idx = -1;
int best = map.logic_cost;
if (!map.logic_ok) {
if (map.cfgs.empty())
if (map.cfgs.empty()) {
log_debug("Rejected candidates for mapping memory %s.%s:\n", log_id(module->name), log_id(mem.memid));
log_debug("%s", map.rejected_cfg_debug_msgs.c_str());
log_error("no valid mapping found for memory %s.%s\n", log_id(module->name), log_id(mem.memid));
}
idx = 0;
best = map.cfgs[0].cost;
}