rtlil_frontend: fix twine ordering

This commit is contained in:
Emil J. Tywoniak 2026-06-20 18:16:47 +02:00
parent 1b7daaae2e
commit 84829cb11d
1 changed files with 73 additions and 26 deletions

View File

@ -65,6 +65,20 @@ struct RTLILFrontendWorker {
dict<size_t, TwineRef> twine_remap;
std::vector<TwineRef> twine_parser_holds;
// Raw twines-block node descriptors keyed by file-local id. The block may
// list a node before its children (the writer emits in pool-index order,
// and free-list slot reuse can place a parent at a lower index than a
// child), so descriptors are collected first and materialized on demand in
// dependency order via materialize_file_twine.
struct TwineDesc {
enum Kind { Leaf, Suffix, Concat } kind;
std::string text;
size_t parent = 0;
std::vector<size_t> children;
bool materializing = false;
};
dict<size_t, TwineDesc> twine_descs;
template <typename... Args>
[[noreturn]]
void error(FmtString<TypeIdentity<Args>...> fmt, const Args &... args)
@ -570,20 +584,6 @@ struct RTLILFrontendWorker {
}
}
// Parses a `twines` ... `end` block. Builds twine_remap so subsequent
// cell/wire src "@N" references (which use file-local ids) translate
// to ids in design->twines. The destination pool may already be
// non-empty (multi-file load) — interned/concated nodes are dedup'd
// against the existing pool by the pool itself.
// Static ids are universal: a file id below STATIC_TWINE_END denotes the
// same prepopulated twine in every pool, so it maps to itself and is
// never emitted in the `twines` block.
// Resolve a numeric content id from the twines block (a node's own id, or
// a suffix/concat child). Statics are universal — an id below
// STATIC_TWINE_END denotes the same prepopulated twine in every pool;
// locals are remapped per file. Publicity (for $pub@/$priv@ handles)
// travels separately and is reapplied here. Object-use statics never reach
// this path: they are written as escaped names and interned by content.
TwineRef resolve_file_twine(size_t id, bool is_public = false)
{
TwineRef base;
@ -592,14 +592,52 @@ struct RTLILFrontendWorker {
} else {
auto it = twine_remap.find(id);
if (it == twine_remap.end())
error("Unknown twine reference @%zu at line %d", id, line_num);
base = it->second;
base = materialize_file_twine(id);
else
base = it->second;
}
return twine_tag(base, is_public);
}
// Parse a "$pub@N"/"$priv@N" twine handle into a resolved, retagged ref.
// Returns nullopt (consuming nothing) if the next token is not a handle.
// Tolerates nodes listed out of dependency order
TwineRef materialize_file_twine(size_t id)
{
if (id < STATIC_TWINE_END)
return TwineRef(id);
auto rit = twine_remap.find(id);
if (rit != twine_remap.end())
return rit->second;
auto dit = twine_descs.find(id);
if (dit == twine_descs.end())
error("Unknown twine reference @%zu at line %d", id, line_num);
TwineDesc &desc = dit->second;
if (desc.materializing)
error("Cyclic twine reference @%zu at line %d", id, line_num);
desc.materializing = true;
TwineRef ref;
switch (desc.kind) {
case TwineDesc::Leaf:
ref = design->twines.add(Twine{desc.text});
break;
case TwineDesc::Suffix:
ref = design->twines.add(Twine{Twine::Suffix{
materialize_file_twine(desc.parent), desc.text}});
break;
case TwineDesc::Concat: {
std::vector<TwineRef> children;
children.reserve(desc.children.size());
for (size_t c : desc.children)
children.push_back(materialize_file_twine(c));
ref = design->twines.add(Twine{children});
break;
}
}
desc.materializing = false;
twine_remap[id] = ref;
return ref;
}
// Parse a "$pub@N"/"$priv@N" twine handle into a resolved, retagged ref
std::optional<TwineRef> try_parse_twine_handle()
{
bool is_public;
@ -632,6 +670,10 @@ struct RTLILFrontendWorker {
return *t;
}
// Parse a `twines` ... `end` block into per-file node descriptors, then
// intern them all into design->twines. The destination pool may already
// hold twines (multi-file load); it dedups by content. Static ids are
// universal and never appear in the block.
void parse_twines()
{
expect_eol();
@ -640,30 +682,35 @@ struct RTLILFrontendWorker {
break;
if (try_parse_keyword("leaf")) {
size_t file_id = parse_integer();
std::string text = parse_string();
TwineDesc &desc = twine_descs[file_id];
desc.kind = TwineDesc::Leaf;
desc.text = parse_string();
expect_eol();
twine_remap[file_id] = design->twines.add(Twine{text});
continue;
}
if (try_parse_keyword("suffix")) {
size_t file_id = parse_integer();
size_t file_parent = parse_integer();
std::string tail = parse_string();
TwineDesc &desc = twine_descs[file_id];
desc.kind = TwineDesc::Suffix;
desc.parent = parse_integer();
desc.text = parse_string();
expect_eol();
twine_remap[file_id] = design->twines.add(Twine{Twine::Suffix{resolve_file_twine(file_parent), tail}});
continue;
}
if (try_parse_keyword("concat")) {
size_t file_id = parse_integer();
std::vector<TwineRef> children;
TwineDesc &desc = twine_descs[file_id];
desc.kind = TwineDesc::Concat;
while (!try_parse_eol())
children.push_back(resolve_file_twine(parse_integer()));
twine_remap[file_id] = design->twines.add(Twine{children});
desc.children.push_back(parse_integer());
continue;
}
error("Expected `leaf`, `suffix` or `concat` inside twines block, got `%s'.",
error_token());
}
for (auto &it : twine_descs)
materialize_file_twine(it.first);
twine_descs.clear();
expect_eol();
}