From 7542146fc52b8ac09b1319a0bae97ed048905aa3 Mon Sep 17 00:00:00 2001 From: "N. Engelhardt" Date: Wed, 28 Jun 2023 17:48:20 +0200 Subject: [PATCH 1/3] memory_libmap: print message about attributes forcing ram kind --- passes/memory/memory_libmap.cc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/passes/memory/memory_libmap.cc b/passes/memory/memory_libmap.cc index 9e147b0bf..aee07c607 100644 --- a/passes/memory/memory_libmap.cc +++ b/passes/memory/memory_libmap.cc @@ -431,6 +431,7 @@ void MemMapping::determine_style() { style = ""; if (mem.get_bool_attribute(ID::lram)) { kind = RamKind::Huge; + log("found attribute 'lram' on memory %s.%s, forced mapping to huge RAM\n", log_id(mem.module->name), log_id(mem.memid)); return; } for (auto attr: {ID::ram_block, ID::rom_block, ID::ram_style, ID::rom_style, ID::ramstyle, ID::romstyle, ID::syn_ramstyle, ID::syn_romstyle}) { @@ -438,6 +439,7 @@ void MemMapping::determine_style() { Const val = mem.attributes.at(attr); if (val == 1) { kind = RamKind::NotLogic; + log("found attribute '%s = 1' on memory %s.%s, disabled mapping to FF\n", log_id(attr), log_id(mem.module->name), log_id(mem.memid)); return; } std::string val_s = val.decode_string(); @@ -450,15 +452,20 @@ void MemMapping::determine_style() { // Nothing. } else if (val_s == "logic" || val_s == "registers") { kind = RamKind::Logic; + log("found attribute '%s = %s' on memory %s.%s, forced mapping to FF\n", log_id(attr), val_s.c_str(), log_id(mem.module->name), log_id(mem.memid)); } else if (val_s == "distributed") { kind = RamKind::Distributed; + log("found attribute '%s = %s' on memory %s.%s, forced mapping to distributed RAM\n", log_id(attr), val_s.c_str(), log_id(mem.module->name), log_id(mem.memid)); } else if (val_s == "block" || val_s == "block_ram" || val_s == "ebr") { kind = RamKind::Block; + log("found attribute '%s = %s' on memory %s.%s, forced mapping to block RAM\n", log_id(attr), val_s.c_str(), log_id(mem.module->name), log_id(mem.memid)); } else if (val_s == "huge" || val_s == "ultra") { kind = RamKind::Huge; + log("found attribute '%s = %s' on memory %s.%s, forced mapping to huge RAM\n", log_id(attr), val_s.c_str(), log_id(mem.module->name), log_id(mem.memid)); } else { kind = RamKind::NotLogic; style = val_s; + log("found attribute '%s = %s' on memory %s.%s, forced mapping to %s RAM\n", log_id(attr), val_s.c_str(), log_id(mem.module->name), log_id(mem.memid), val_s.c_str()); } return; } From a6be7b475192f285e23b7ee94465b004513aa150 Mon Sep 17 00:00:00 2001 From: "N. Engelhardt" Date: Thu, 29 Jun 2023 14:02:08 +0200 Subject: [PATCH 2/3] memory_libmap: add debug messages for some reasons for rejecting mappings --- passes/memory/memory_libmap.cc | 37 ++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/passes/memory/memory_libmap.cc b/passes/memory/memory_libmap.cc index aee07c607..0496a4029 100644 --- a/passes/memory/memory_libmap.cc +++ b/passes/memory/memory_libmap.cc @@ -201,8 +201,10 @@ struct MemMapping { continue; if (!check_init(rdef)) continue; - if (rdef.prune_rom && mem.wr_ports.empty()) + 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)); continue; + } MemConfig cfg; cfg.def = &rdef; for (auto &cdef: rdef.shared_clocks) { @@ -476,18 +478,26 @@ 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) + 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)); return false; + } // Memory is mappable entirely to soft logic iff all its write ports are in the same clock domain. if (mem.wr_ports.empty()) return true; for (auto &port: mem.wr_ports) { - if (!port.clk_enable) + 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)); return false; - if (port.clk != mem.wr_ports[0].clk) + } + 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)); return false; - if (port.clk_polarity != mem.wr_ports[0].clk_polarity) + } + 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)); return false; + } } return true; } @@ -499,14 +509,21 @@ bool MemMapping::check_ram_kind(const Ram &ram) { if (ram.kind == kind) return true; if (kind == RamKind::Auto || kind == RamKind::NotLogic) { - if (ram.kind == RamKind::Distributed && opts.no_auto_distributed) + 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)); return false; - if (ram.kind == RamKind::Block && opts.no_auto_block) + } + 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)); return false; - if (ram.kind == RamKind::Huge && opts.no_auto_huge) + } + 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)); 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)); return false; } @@ -517,6 +534,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)); return false; } @@ -536,8 +554,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)); 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)); return !has_one; default: return true; @@ -577,6 +597,7 @@ void MemMapping::assign_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)); return; } MemConfigs new_cfgs; From 57de249881fbad885195b9e99d2bd0547895a5b9 Mon Sep 17 00:00:00 2001 From: "N. Engelhardt" Date: Thu, 6 Jul 2023 18:54:32 +0200 Subject: [PATCH 3/3] memory_libmap: print additional debug messages when no valid mapping is found --- passes/memory/memory_libmap.cc | 160 ++++++++++++++++++++++++++------- 1 file changed, 128 insertions(+), 32 deletions(-) diff --git a/passes/memory/memory_libmap.cc b/passes/memory/memory_libmap.cc index 0496a4029..6e5a806fd 100644 --- a/passes/memory/memory_libmap.cc +++ b/passes/memory/memory_libmap.cc @@ -183,6 +183,7 @@ struct MemMapping { dict, bool> wr_implies_rd_cache; dict, bool> wr_excludes_rd_cache; dict, 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 &cells, const PortVariant &pdef, const char *name, int wpidx, int rpidx, const std::vector &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; }