mirror of
https://github.com/verilator/verilator.git
synced 2026-10-07 02:23:19 +02:00
The loops modelling the SystemVerilog scheduling regions are no longer generated. They now live in 'VerilatedEvalLoop' in the runtime library. The generated model holds one as a member, passing itself to it, and exposes each evaluation entry point to it as a pure virtual method on VerilatedModel. The model's 'eval' and 'eval_step' remain the top level entry points, and are backward compatible. V3Sched no longer emits '_eval' or '_eval_settle', etc.. Instead every evaluation entry point called from the runtime is enumerated by 'VEval', Scheduling creates all entry points, for all scheduling regions, even if they are empty, and the runtime eval loop calls everything unconditionally. If regions are empty, this is simply a call to an empty function. This will hurt performance on very small models, but should not be noticeable on anything meaningful, so it is likely best to keep to reduce complexity. A scheduling entry points evaluate a single iteration and returns whether it did any work, they are effectively the previous `_eval_phase_*` functions.
1133 lines
48 KiB
C++
1133 lines
48 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//*************************************************************************
|
|
// DESCRIPTION: Verilator: Verilator Control Files (.vlt) handling
|
|
//
|
|
// Code available from: https://verilator.org
|
|
//
|
|
//*************************************************************************
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify it
|
|
// under the terms of either the GNU Lesser General Public License Version 3
|
|
// or the Perl Artistic License Version 2.0.
|
|
// SPDX-FileCopyrightText: 2010-2026 Wilson Snyder
|
|
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
|
//
|
|
//*************************************************************************
|
|
|
|
#include "V3PchAstMT.h"
|
|
|
|
#include "V3Control.h"
|
|
|
|
#include "V3InstrCount.h"
|
|
#include "V3String.h"
|
|
|
|
#include <memory>
|
|
#include <set>
|
|
#include <unordered_map>
|
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
//######################################################################
|
|
// Resolve wildcards in files, modules, ftasks or variables
|
|
|
|
// Template for a class that serves as a map for entities that can be specified
|
|
// as wildcards and are accessed by a resolved name. It rebuilds a name lookup
|
|
// cache of resolved entities. Entities stored in this container need an update
|
|
// function that takes a reference of this type to join multiple entities into one.
|
|
template <typename T>
|
|
class V3ControlWildcardResolver final {
|
|
mutable V3Mutex m_mutex; // protects members
|
|
// Pattern strings (wildcard, or simple name) to entities
|
|
std::map<const std::string, T> m_mapPatterns VL_GUARDED_BY(m_mutex);
|
|
// Resolved strings to converged entities - nullptr, iff none of the patterns applies
|
|
std::map<const std::string, std::unique_ptr<T>> m_mapResolved VL_GUARDED_BY(m_mutex);
|
|
|
|
public:
|
|
V3ControlWildcardResolver() = default;
|
|
~V3ControlWildcardResolver() = default;
|
|
|
|
// Update this resolved file's item map by inserting other's (wildcarded filename's) items
|
|
void update(const V3ControlWildcardResolver& other) VL_MT_SAFE_EXCLUDES(m_mutex)
|
|
VL_EXCLUDES(other.m_mutex) {
|
|
const V3LockGuard lock{m_mutex};
|
|
const V3LockGuard otherLock{other.m_mutex};
|
|
// Clear the resolved cache, as 'other' might add new patterns that need to be applied as
|
|
// well.
|
|
m_mapResolved.clear();
|
|
for (const auto& itr : other.m_mapPatterns) m_mapPatterns[itr.first].update(itr.second);
|
|
}
|
|
|
|
// Access and create a pattern entry
|
|
T& at(const string& name) VL_MT_SAFE_EXCLUDES(m_mutex) {
|
|
const V3LockGuard lock{m_mutex};
|
|
// We might be adding a new entry under this, so clear the cache.
|
|
m_mapResolved.clear();
|
|
return m_mapPatterns[name];
|
|
}
|
|
// Access an entity and resolve patterns that match it
|
|
T* resolve(const string& name) VL_MT_SAFE_EXCLUDES(m_mutex) {
|
|
const V3LockGuard lock{m_mutex};
|
|
// Lookup if it was resolved before, typically not
|
|
const auto pair = m_mapResolved.emplace(name, nullptr);
|
|
std::unique_ptr<T>& entryr = pair.first->second;
|
|
// Resolve entry when first requested, cache the result
|
|
if (pair.second) {
|
|
// Inserted: update the entity with all matches in the patterns
|
|
for (const auto& patEnt : m_mapPatterns) {
|
|
if (VString::wildmatch(name, patEnt.first)) {
|
|
if (!entryr) entryr.reset(new T{});
|
|
entryr->update(patEnt.second);
|
|
}
|
|
}
|
|
// Perform final actions that needed all updates completed
|
|
if (entryr) entryr->updateFinalize();
|
|
}
|
|
return entryr.get();
|
|
}
|
|
};
|
|
|
|
// List of attributes for variables
|
|
class V3ControlVar final {
|
|
std::vector<VAttrType> m_attrs; // The list of attributes
|
|
public:
|
|
// Add new attribugte
|
|
void add(VAttrType attr) { m_attrs.emplace_back(attr); }
|
|
// Update from other by copying all attributes
|
|
void update(const V3ControlVar& other) {
|
|
m_attrs.reserve(m_attrs.size() + other.m_attrs.size());
|
|
m_attrs.insert(m_attrs.end(), other.m_attrs.begin(), other.m_attrs.end());
|
|
}
|
|
void updateFinalize() {}
|
|
// Apply all attributes to the variable
|
|
void apply(AstVar* varp) const {
|
|
for (const VAttrType attr : m_attrs) {
|
|
varp->addAttrsp(new AstAttrOf{varp->fileline(), attr});
|
|
}
|
|
}
|
|
};
|
|
|
|
using V3ControlVarResolver = V3ControlWildcardResolver<V3ControlVar>;
|
|
|
|
//======================================================================
|
|
|
|
class WildcardContents final {
|
|
// Not mutex protected, current calling from V3Control::waive is protected by error's mutex
|
|
// MEMBERS
|
|
std::map<const std::string, bool> m_mapPatterns; // Pattern match results
|
|
std::deque<string> m_lines; // Source text lines
|
|
|
|
// METHODS
|
|
static WildcardContents& s() { // Singleton
|
|
static WildcardContents s_s;
|
|
return s_s;
|
|
}
|
|
void clearCacheImp() { m_mapPatterns.clear(); }
|
|
void pushTextImp(const string& text) {
|
|
// Similar code in VFileContent::pushText()
|
|
// Any leftover text is stored on largest line (might be "")
|
|
const string leftover = m_lines.back() + text;
|
|
m_lines.pop_back();
|
|
|
|
// Insert line-by-line
|
|
string::size_type line_start = 0;
|
|
while (true) {
|
|
const string::size_type line_end = leftover.find('\n', line_start);
|
|
if (line_end != string::npos) {
|
|
const string oneline(leftover, line_start, line_end - line_start + 1);
|
|
if (oneline.size() > 1) m_lines.push_back(oneline); // Keeps newline
|
|
UINFO(9, "Push[+" << (m_lines.size() - 1) << "]: " << oneline);
|
|
line_start = line_end + 1;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
// Keep leftover for next time
|
|
m_lines.emplace_back(string(leftover, line_start)); // Might be ""
|
|
clearCacheImp();
|
|
}
|
|
|
|
bool resolveUncachedImp(const string& contentsRegexp) {
|
|
for (const string& i : m_lines) {
|
|
if (VString::wildmatch(i, contentsRegexp)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
bool resolveCachedImp(const string& contentsRegexp) {
|
|
// Lookup if it was resolved before, typically is
|
|
if (contentsRegexp.empty()) return true;
|
|
const auto pair = m_mapPatterns.emplace(contentsRegexp, false);
|
|
bool& entryr = pair.first->second;
|
|
// Resolve entry when first requested, cache the result
|
|
if (pair.second) entryr = resolveUncachedImp(contentsRegexp);
|
|
return entryr;
|
|
}
|
|
|
|
public:
|
|
WildcardContents() {
|
|
m_lines.emplace_back(""); // start with no leftover
|
|
}
|
|
~WildcardContents() = default;
|
|
// Return true iff contentsRegexp in parsed contents
|
|
static bool resolve(const string& contentsRegexp) {
|
|
return s().resolveCachedImp(contentsRegexp);
|
|
}
|
|
// Add arbitrary text (need not be line-by-line)
|
|
static void pushText(const string& text) { s().pushTextImp(text); }
|
|
};
|
|
|
|
//######################################################################
|
|
// Function or task: Have variables and properties
|
|
|
|
class V3ControlFTask final {
|
|
V3ControlVarResolver m_params; // Parameters in function/task
|
|
V3ControlVarResolver m_ports; // Ports in function/task
|
|
V3ControlVarResolver m_vars; // Variables in function/task
|
|
bool m_noinline = false; // Don't inline function/task
|
|
bool m_public = false; // Public function/task
|
|
|
|
public:
|
|
V3ControlFTask() = default;
|
|
void update(const V3ControlFTask& f) {
|
|
// Don't overwrite true with false
|
|
if (f.m_noinline) m_noinline = true;
|
|
if (f.m_public) m_public = true;
|
|
m_params.update(f.m_params);
|
|
m_ports.update(f.m_ports);
|
|
m_vars.update(f.m_vars);
|
|
}
|
|
void updateFinalize() {}
|
|
|
|
V3ControlVarResolver& params() { return m_params; }
|
|
V3ControlVarResolver& ports() { return m_ports; }
|
|
V3ControlVarResolver& vars() { return m_vars; }
|
|
|
|
void setNoInline(bool set) { m_noinline = set; }
|
|
void setPublic(bool set) { m_public = set; }
|
|
|
|
void apply(AstNodeFTask* ftaskp) const {
|
|
if (m_noinline)
|
|
ftaskp->addStmtsp(new AstPragma{ftaskp->fileline(), VPragmaType::NO_INLINE_TASK});
|
|
if (m_public)
|
|
ftaskp->addStmtsp(new AstPragma{ftaskp->fileline(), VPragmaType::PUBLIC_TASK});
|
|
}
|
|
};
|
|
|
|
using V3ControlFTaskResolver = V3ControlWildcardResolver<V3ControlFTask>;
|
|
|
|
//######################################################################
|
|
// Modules have tasks, variables, named blocks and properties
|
|
|
|
class V3ControlModule final {
|
|
V3ControlFTaskResolver m_tasks; // Functions/tasks in module
|
|
V3ControlVarResolver m_params; // Parameters in module
|
|
V3ControlVarResolver m_ports; // Ports in module
|
|
V3ControlVarResolver m_vars; // Variables in module
|
|
V3Control::FsmRegisterWrapper m_fsmRegisterWrapper; // FSM wrapper descriptor
|
|
std::unordered_set<std::string> m_coverageOffBlocks; // List of block names for coverage_off
|
|
std::set<VPragmaType> m_modPragmas; // List of Pragmas for modules
|
|
bool m_hasFsmRegisterWrapper = false; // Whether descriptor has been set
|
|
bool m_inline = false; // Whether to force the inline
|
|
bool m_inlineValue = false; // The inline value (on/off)
|
|
|
|
public:
|
|
V3ControlModule() = default;
|
|
|
|
void update(const V3ControlModule& m) {
|
|
m_tasks.update(m.m_tasks);
|
|
m_params.update(m.m_params);
|
|
m_ports.update(m.m_ports);
|
|
m_vars.update(m.m_vars);
|
|
if (m.m_hasFsmRegisterWrapper) {
|
|
m_fsmRegisterWrapper = m.m_fsmRegisterWrapper;
|
|
m_hasFsmRegisterWrapper = true;
|
|
}
|
|
for (const string& i : m.m_coverageOffBlocks) m_coverageOffBlocks.insert(i);
|
|
if (!m_inline) {
|
|
m_inline = m.m_inline;
|
|
m_inlineValue = m.m_inlineValue;
|
|
}
|
|
for (auto it = m.m_modPragmas.cbegin(); it != m.m_modPragmas.cend(); ++it) {
|
|
m_modPragmas.insert(*it);
|
|
}
|
|
}
|
|
void updateFinalize() {}
|
|
|
|
V3ControlFTaskResolver& ftasks() { return m_tasks; }
|
|
V3ControlVarResolver& params() { return m_params; }
|
|
V3ControlVarResolver& ports() { return m_ports; }
|
|
V3ControlVarResolver& vars() { return m_vars; }
|
|
|
|
void addCoverageBlockOff(const string& name) { m_coverageOffBlocks.insert(name); }
|
|
void setInline(bool set) {
|
|
m_inline = true;
|
|
m_inlineValue = set;
|
|
}
|
|
void setFsmRegisterWrapper(FileLine* fl, const V3Control::FsmRegisterWrapper& desc) {
|
|
if (m_hasFsmRegisterWrapper) {
|
|
fl->v3warn(BADVLTPRAGMA, "Duplicate fsm_register_wrapper descriptor for module "
|
|
<< AstNode::prettyNameQ(desc.moduleName)
|
|
<< "; replacing previous descriptor");
|
|
}
|
|
m_fsmRegisterWrapper = desc;
|
|
m_hasFsmRegisterWrapper = true;
|
|
}
|
|
const V3Control::FsmRegisterWrapper* fsmRegisterWrapperp() const {
|
|
return m_hasFsmRegisterWrapper ? &m_fsmRegisterWrapper : nullptr;
|
|
}
|
|
void addModulePragma(VPragmaType pragma) { m_modPragmas.insert(pragma); }
|
|
|
|
void apply(AstNodeModule* modp) {
|
|
if (m_inline) {
|
|
const VPragmaType type
|
|
= m_inlineValue ? VPragmaType::INLINE_MODULE : VPragmaType::NO_INLINE_MODULE;
|
|
AstNode* const nodep = new AstPragma{modp->fileline(), type};
|
|
modp->addStmtsp(nodep);
|
|
}
|
|
for (const auto& itr : m_modPragmas) {
|
|
// Catch hier param modules to mark their attributes before they are
|
|
// flagged dead in LinkDot.
|
|
if (itr == VPragmaType::HIER_PARAMS) modp->hierParams(true);
|
|
AstNode* const nodep = new AstPragma{modp->fileline(), itr};
|
|
modp->addStmtsp(nodep);
|
|
}
|
|
}
|
|
|
|
void applyBlock(AstGenBlock* nodep) {
|
|
const VPragmaType pragma = VPragmaType::COVERAGE_BLOCK_OFF;
|
|
if (!nodep->unnamed()) {
|
|
for (const string& i : m_coverageOffBlocks) {
|
|
if (VString::wildmatch(nodep->prettyDehashOrigOrName(), i)) {
|
|
nodep->addItemsp(new AstPragma{nodep->fileline(), pragma});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void applyBlock(AstNodeBlock* nodep) {
|
|
const VPragmaType pragma = VPragmaType::COVERAGE_BLOCK_OFF;
|
|
FileLine* const flp = nodep->fileline();
|
|
if (!nodep->unnamed()) {
|
|
for (const string& i : m_coverageOffBlocks) {
|
|
if (VString::wildmatch(nodep->prettyDehashOrigOrName(), i)) {
|
|
nodep->addStmtsp(new AstStmtPragma{flp, new AstPragma{flp, pragma}});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
using V3ControlModuleResolver = V3ControlWildcardResolver<V3ControlModule>;
|
|
|
|
//######################################################################
|
|
// Files have:
|
|
// - Line ignores (lint/coverage/tracing on/off)
|
|
// - Line attributes: Attributes attached to lines
|
|
|
|
// lint/coverage/tracing on/off
|
|
class V3ControlIgnoresLine final {
|
|
public:
|
|
const int m_lineMin; // Minimum line number to make change at
|
|
const int m_lineMax; // Maximum line number to make change at (inclusive)
|
|
const V3ErrorCode m_code; // Error code
|
|
const bool m_on; // True to enable message
|
|
V3ControlIgnoresLine(V3ErrorCode code, int linemin, int linemax, bool on)
|
|
: m_lineMin{linemin}
|
|
, m_lineMax{linemax}
|
|
, m_code{code}
|
|
, m_on{on} {}
|
|
~V3ControlIgnoresLine() = default;
|
|
bool everyLine() const { return m_lineMin == 0 && m_lineMax == 0; }
|
|
bool inRange(int lineno) const {
|
|
return (lineno >= m_lineMin && lineno <= m_lineMax) || everyLine();
|
|
}
|
|
};
|
|
std::ostream& operator<<(std::ostream& os, const V3ControlIgnoresLine& rhs) {
|
|
return os << rhs.m_lineMin << "-" << rhs.m_lineMax << ", " << rhs.m_code << ", " << rhs.m_on;
|
|
}
|
|
|
|
// Ignore line settings, index is parse order
|
|
// Single global deque to avoid copying when update()
|
|
static std::vector<V3ControlIgnoresLine> controlIgnLines;
|
|
|
|
using IgnIndices = std::vector<uint32_t>; // List of {s_ignLines indces}
|
|
|
|
class VIntervalTree final {
|
|
struct Entry final {
|
|
// Classic centered interval tree, referencing an index to controlIgnLines
|
|
int m_center; // Line number of central point
|
|
std::unique_ptr<Entry> m_leftp; // Entry with all its rules/entries < this center
|
|
std::unique_ptr<Entry> m_rightp; // Entry with all its rules/entries > this center
|
|
IgnIndices m_byMin; // Rules overlapping center sorted by min line number
|
|
IgnIndices m_byMax; // Rules overlapping center sorted by max line number
|
|
explicit Entry(int center)
|
|
: m_center{center} {}
|
|
};
|
|
|
|
std::unique_ptr<Entry> m_rootp; // Root of interval tree
|
|
IgnIndices m_everyLines; // All-line disables
|
|
|
|
private:
|
|
std::unique_ptr<Entry> buildTree(const IgnIndices& points) {
|
|
if (points.empty()) return nullptr;
|
|
|
|
int minval = std::numeric_limits<int>::max();
|
|
int maxval = std::numeric_limits<int>::min();
|
|
for (const auto& it : points) {
|
|
const V3ControlIgnoresLine& cign = controlIgnLines[it];
|
|
if (cign.everyLine()) {
|
|
m_everyLines.emplace_back(it);
|
|
continue;
|
|
}
|
|
minval = std::min(minval, cign.m_lineMin);
|
|
maxval = std::max(maxval, cign.m_lineMax);
|
|
}
|
|
|
|
const int center = minval + (maxval - minval) / 2;
|
|
auto entp = std::unique_ptr<Entry>(new Entry{center});
|
|
|
|
IgnIndices leftPoints;
|
|
IgnIndices rightPoints;
|
|
for (const auto& it : points) {
|
|
const V3ControlIgnoresLine& cign = controlIgnLines[it];
|
|
if (cign.everyLine()) continue;
|
|
// UINFO(9, "Inserting for center " << center << " point " << it);
|
|
if (cign.m_lineMax < center)
|
|
leftPoints.emplace_back(it);
|
|
else if (cign.m_lineMin > center)
|
|
rightPoints.emplace_back(it);
|
|
else {
|
|
entp->m_byMin.push_back(it);
|
|
entp->m_byMax.push_back(it);
|
|
}
|
|
}
|
|
|
|
std::sort(entp->m_byMin.begin(), entp->m_byMin.end(), [](uint32_t a, uint32_t b) {
|
|
return controlIgnLines[a].m_lineMin < controlIgnLines[b].m_lineMin;
|
|
});
|
|
std::sort(entp->m_byMax.begin(), entp->m_byMax.end(), [](uint32_t a, uint32_t b) {
|
|
return controlIgnLines[a].m_lineMax > controlIgnLines[b].m_lineMax;
|
|
});
|
|
|
|
entp->m_leftp = buildTree(leftPoints);
|
|
entp->m_rightp = buildTree(rightPoints);
|
|
return entp;
|
|
}
|
|
|
|
void findTree(int lineno, Entry* entp, IgnIndices& resultsr, int& nextChanger) const {
|
|
// UINFO(9, "Find " << lineno << " center " << entp->m_center);
|
|
if (lineno < entp->m_center) {
|
|
nextChanger = std::min(nextChanger, entp->m_center);
|
|
for (const auto& it : entp->m_byMin) {
|
|
const V3ControlIgnoresLine& cign = controlIgnLines[it];
|
|
if (cign.m_lineMin <= lineno) {
|
|
resultsr.emplace_back(it);
|
|
} else {
|
|
nextChanger = std::min(nextChanger, cign.m_lineMin);
|
|
break;
|
|
}
|
|
}
|
|
if (entp->m_leftp) findTree(lineno, entp->m_leftp.get(), resultsr, nextChanger);
|
|
} else if (lineno > entp->m_center) {
|
|
for (const auto& it : entp->m_byMax) {
|
|
const V3ControlIgnoresLine& cign = controlIgnLines[it];
|
|
if (cign.m_lineMax >= lineno) {
|
|
nextChanger = std::min(nextChanger, cign.m_lineMax + 1);
|
|
resultsr.emplace_back(it);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (entp->m_rightp) findTree(lineno, entp->m_rightp.get(), resultsr, nextChanger);
|
|
} else {
|
|
for (const auto& it : entp->m_byMin) {
|
|
const V3ControlIgnoresLine& cign = controlIgnLines[it];
|
|
resultsr.emplace_back(it);
|
|
if (cign.m_lineMax >= lineno)
|
|
nextChanger = std::min(nextChanger, cign.m_lineMax + 1);
|
|
}
|
|
if (entp->m_leftp) findTree(lineno, entp->m_leftp.get(), resultsr, nextChanger);
|
|
if (entp->m_rightp) findTree(lineno, entp->m_rightp.get(), resultsr, nextChanger);
|
|
}
|
|
}
|
|
|
|
public:
|
|
// CONSTRUCTORS
|
|
VIntervalTree() = default;
|
|
~VIntervalTree() = default;
|
|
// METHODS
|
|
void clear() { m_rootp = nullptr; }
|
|
void build(const IgnIndices& points) {
|
|
clear();
|
|
m_rootp = buildTree(points);
|
|
}
|
|
void find(int lineno, IgnIndices& resultsr, int& nextChanger) const {
|
|
resultsr = m_everyLines;
|
|
nextChanger = std::numeric_limits<int>::max();
|
|
|
|
if (m_rootp) findTree(lineno, m_rootp.get(), resultsr, nextChanger);
|
|
// Sort indices, so can process in parse order
|
|
std::sort(resultsr.begin(), resultsr.end());
|
|
}
|
|
|
|
static void selfTest() {
|
|
// 0 10 20 30 40 50
|
|
// i0: . 10-10 . . . .
|
|
// i1: . . 20-20 . . .
|
|
// i2: . . . . 40-40 .
|
|
// i3: . 10------------30 . .
|
|
// i4: . . 20------------40 .
|
|
// i5:(below) 0---------------------------------------0
|
|
IgnIndices data;
|
|
std::vector<std::pair<int, int>> points
|
|
= {{10, 10}, {20, 20}, {40, 40}, {10, 30}, {20, 40}};
|
|
for (const auto& it : points) {
|
|
controlIgnLines.emplace_back(
|
|
V3ControlIgnoresLine{V3ErrorCode::I_LINT, it.first, it.second, true});
|
|
data.emplace_back(static_cast<uint32_t>(controlIgnLines.size() - 1));
|
|
}
|
|
VIntervalTree tree;
|
|
tree.build(data);
|
|
|
|
IgnIndices results;
|
|
int nextChange = 0;
|
|
tree.find(0, results, nextChange);
|
|
UASSERT_SELFTEST(const size_t, results.size(), 0);
|
|
UASSERT_SELFTEST(const int, nextChange, 10);
|
|
tree.find(10, results, nextChange);
|
|
UASSERT_SELFTEST(const size_t, results.size(), 2);
|
|
UASSERT_SELFTEST(const int, results[0], 0);
|
|
UASSERT_SELFTEST(const int, results[1], 3);
|
|
UASSERT_SELFTEST(const int, nextChange, 11);
|
|
tree.find(11, results, nextChange);
|
|
UASSERT_SELFTEST(const size_t, results.size(), 1);
|
|
UASSERT_SELFTEST(const int, results[0], 3);
|
|
UASSERT_SELFTEST(const int, nextChange, 15); // Center, or would be 20
|
|
tree.find(20, results, nextChange);
|
|
UASSERT_SELFTEST(const size_t, results.size(), 3);
|
|
UASSERT_SELFTEST(const int, results[0], 1);
|
|
UASSERT_SELFTEST(const int, results[1], 3);
|
|
UASSERT_SELFTEST(const int, results[2], 4);
|
|
UASSERT_SELFTEST(const int, nextChange, 21);
|
|
tree.find(21, results, nextChange);
|
|
UASSERT_SELFTEST(const size_t, results.size(), 2);
|
|
UASSERT_SELFTEST(const int, results[0], 3);
|
|
UASSERT_SELFTEST(const int, results[1], 4);
|
|
UASSERT_SELFTEST(const int, nextChange, 25); // Center, or would be 30
|
|
tree.find(30, results, nextChange);
|
|
UASSERT_SELFTEST(const size_t, results.size(), 2);
|
|
UASSERT_SELFTEST(const int, results[0], 3);
|
|
UASSERT_SELFTEST(const int, results[1], 4);
|
|
UASSERT_SELFTEST(const int, nextChange, 31);
|
|
tree.find(40, results, nextChange);
|
|
UASSERT_SELFTEST(const size_t, results.size(), 2);
|
|
UASSERT_SELFTEST(const int, results[0], 2);
|
|
UASSERT_SELFTEST(const int, results[1], 4);
|
|
UASSERT_SELFTEST(const int, nextChange, 41);
|
|
tree.find(41, results, nextChange);
|
|
UASSERT_SELFTEST(const size_t, results.size(), 0);
|
|
UASSERT_SELFTEST(const int, nextChange, std::numeric_limits<int>::max());
|
|
//
|
|
points = {{0, 0}};
|
|
for (const auto& it : points) {
|
|
controlIgnLines.emplace_back(
|
|
V3ControlIgnoresLine{V3ErrorCode::I_LINT, it.first, it.second, true});
|
|
data.emplace_back(static_cast<uint32_t>(controlIgnLines.size() - 1));
|
|
}
|
|
tree.build(data);
|
|
//
|
|
tree.find(50, results, nextChange);
|
|
UASSERT_SELFTEST(const size_t, results.size(), 1);
|
|
UASSERT_SELFTEST(const int, results[0], 5);
|
|
}
|
|
};
|
|
|
|
// Some attributes are attached to entities of the occur on a fileline
|
|
// and multiple attributes can be attached to a line
|
|
using V3ControlLineAttribute = std::bitset<VPragmaType::_ENUM_SIZE>;
|
|
|
|
class WaiverSetting final {
|
|
public:
|
|
V3ErrorCode m_code; // Error code
|
|
string m_contents; // --contents regexp
|
|
string m_match; // --match regexp
|
|
WaiverSetting(V3ErrorCode code, const string& contents, const string& match)
|
|
: m_code{code}
|
|
, m_contents{contents}
|
|
, m_match{match} {}
|
|
~WaiverSetting() = default;
|
|
WaiverSetting& operator=(const WaiverSetting& rhs) = default;
|
|
};
|
|
|
|
// File entity
|
|
class V3ControlFile final {
|
|
using LineAttrMap = std::map<int, V3ControlLineAttribute>; // Map line->bitset of attributes
|
|
using Waivers = std::vector<WaiverSetting>; // List of {code,wildcard string}
|
|
|
|
LineAttrMap m_lineAttrs; // Attributes to line mapping
|
|
IgnIndices m_ignIndices; // s_ignLines that apply to this specific file
|
|
Waivers m_waivers; // Waive messages
|
|
VIntervalTree m_intervalTree; // Tree of indices to IgnLines
|
|
|
|
struct {
|
|
int filenameno = -1; // Last filename
|
|
int lineno = -1; // Last linenumber
|
|
int nextChange = -1; // Line number of next change
|
|
} m_lastIgnore; // Last ignore line run
|
|
|
|
// Match a given line and attribute to the map, line 0 is any
|
|
bool lineMatch(int lineno, VPragmaType type) {
|
|
if (m_lineAttrs.find(0) != m_lineAttrs.end() && m_lineAttrs[0][type]) return true;
|
|
if (m_lineAttrs.find(lineno) == m_lineAttrs.end()) return false;
|
|
return m_lineAttrs[lineno][type];
|
|
}
|
|
|
|
public:
|
|
V3ControlFile() = default;
|
|
void update(const V3ControlFile& file) {
|
|
// Copy in all attributes and waivers
|
|
for (const auto& itr : file.m_lineAttrs) m_lineAttrs[itr.first] |= itr.second;
|
|
m_waivers.reserve(m_waivers.size() + file.m_waivers.size());
|
|
m_waivers.insert(m_waivers.end(), file.m_waivers.begin(), file.m_waivers.end());
|
|
// Copy in all ignore references
|
|
m_ignIndices.reserve(m_ignIndices.size() + file.m_ignIndices.size());
|
|
m_ignIndices.insert(m_ignIndices.end(), file.m_ignIndices.begin(),
|
|
file.m_ignIndices.end());
|
|
// updateFinalize() will soon build tree
|
|
}
|
|
void updateFinalize() { m_intervalTree.build(m_ignIndices); }
|
|
void addLineAttribute(int lineno, VPragmaType attr) { m_lineAttrs[lineno].set(attr); }
|
|
void addIgnore(V3ErrorCode code, int min, int max, bool on) {
|
|
controlIgnLines.emplace_back(V3ControlIgnoresLine{code, min, max, on});
|
|
m_ignIndices.emplace_back(static_cast<uint32_t>(controlIgnLines.size() - 1));
|
|
m_lastIgnore.nextChange = -1;
|
|
}
|
|
void addIgnoreMatch(V3ErrorCode code, const string& contents, const string& match) {
|
|
// Since Verilator 5.031 the error message compared has context, so
|
|
// allow old rules to still match using a final '*'
|
|
string newMatch = match;
|
|
if (newMatch.empty() || newMatch.back() != '*') newMatch += '*';
|
|
m_waivers.emplace_back(code, contents, newMatch);
|
|
}
|
|
|
|
void applyBlock(AstGenBlock* nodep) {
|
|
// Apply to block at this line
|
|
const VPragmaType pragma = VPragmaType::COVERAGE_BLOCK_OFF;
|
|
if (lineMatch(nodep->fileline()->lineno(), pragma)) {
|
|
nodep->addItemsp(new AstPragma{nodep->fileline(), pragma});
|
|
}
|
|
}
|
|
void applyBlock(AstNodeBlock* nodep) {
|
|
// Apply to block at this line
|
|
const VPragmaType pragma = VPragmaType::COVERAGE_BLOCK_OFF;
|
|
FileLine* const flp = nodep->fileline();
|
|
if (lineMatch(flp->lineno(), pragma)) {
|
|
nodep->addStmtsp(new AstStmtPragma{flp, new AstPragma{flp, pragma}});
|
|
}
|
|
}
|
|
void applyCase(AstCase* nodep) {
|
|
// Apply to this case at this line
|
|
const int lineno = nodep->fileline()->lineno();
|
|
if (lineMatch(lineno, VPragmaType::FULL_CASE)) nodep->fullPragma(true);
|
|
if (lineMatch(lineno, VPragmaType::PARALLEL_CASE)) nodep->parallelPragma(true);
|
|
}
|
|
void applyIgnores(FileLine* filelinep) {
|
|
// HOT routine, called each parsed token line of this filename
|
|
if (filelinep->filenameno() == m_lastIgnore.filenameno
|
|
&& filelinep->lineno() == m_lastIgnore.lineno)
|
|
return; // Short circuit, no change, no debug
|
|
// For speed, compute line number of next potential change, and skip if before then
|
|
if (filelinep->filenameno() == m_lastIgnore.filenameno
|
|
&& filelinep->lineno() < m_lastIgnore.nextChange) {
|
|
m_lastIgnore.lineno = filelinep->lineno();
|
|
UINFO(9, " ApplyIgnores for " << filelinep->ascii() << " (no change predicted)");
|
|
return;
|
|
}
|
|
UINFO(9, " ApplyIgnores for " << filelinep->ascii());
|
|
m_lastIgnore.filenameno = filelinep->filenameno();
|
|
m_lastIgnore.lineno = filelinep->lineno();
|
|
m_lastIgnore.nextChange = std::numeric_limits<int>::max();
|
|
|
|
// Find all on/offs. Unlike lint pragmas we calculate the entire
|
|
// bitset of errors to enable, instead of individual enable/disable changes.
|
|
IgnIndices results;
|
|
m_intervalTree.find(filelinep->lineno(), results /*ref*/, m_lastIgnore.nextChange /*ref*/);
|
|
// Process all on/offs for lines up to and including the current line
|
|
VErrorBitSet bitset{VErrorBitSet::AllOnes{}};
|
|
for (const auto& it : results) {
|
|
const V3ControlIgnoresLine& cign = controlIgnLines[it];
|
|
UASSERT(cign.inRange(filelinep->lineno()),
|
|
"Interval tree returned lines outside range");
|
|
UINFO(9, " Hit " << cign);
|
|
cign.m_code.forDelegateCodes(
|
|
[&](V3ErrorCode subcode) { bitset.set(subcode, cign.m_on); });
|
|
}
|
|
|
|
filelinep->warnSetCtrlBitSet(bitset);
|
|
m_lastIgnore.nextChange = std::max(m_lastIgnore.nextChange, filelinep->lineno() + 1);
|
|
UINFO(9, " AppliedIgnores " << filelinep << " next@ln " << std::dec
|
|
<< m_lastIgnore.nextChange
|
|
<< " Messages: " << bitset.ascii());
|
|
}
|
|
bool waive(V3ErrorCode code, const string& message) {
|
|
if (code.hardError()) return false;
|
|
for (const auto& itr : m_waivers) {
|
|
if ((code.isUnder(itr.m_code) || (itr.m_code == V3ErrorCode::I_LINT))
|
|
&& VString::wildmatch(message, itr.m_match)
|
|
&& WildcardContents::resolve(itr.m_contents)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
using V3ControlFileResolver = V3ControlWildcardResolver<V3ControlFile>;
|
|
|
|
//######################################################################
|
|
// ScopeTrace tracking
|
|
|
|
class V3ControlScopeTraceEntry final {
|
|
public:
|
|
const string m_scope; // Scope or regexp to match
|
|
const bool m_on; // True to enable message
|
|
int m_levels = 0; // # levels, 0 = all, 1 = only this, ...
|
|
// CONSTRUCTORS
|
|
V3ControlScopeTraceEntry(const string& scope, bool on, int levels)
|
|
: m_scope{scope}
|
|
, m_on{on}
|
|
, m_levels{levels} {}
|
|
~V3ControlScopeTraceEntry() = default;
|
|
bool operator<(const V3ControlScopeTraceEntry& other) const {
|
|
if (m_on < other.m_on) return true;
|
|
if (m_on > other.m_on) return false;
|
|
if (m_levels < other.m_levels) return true;
|
|
if (m_levels > other.m_levels) return false;
|
|
return m_scope < other.m_scope;
|
|
}
|
|
};
|
|
|
|
// Tracks what matches are known to hit against V3ControlScopeTraceEntries
|
|
class V3ControlScopeTraceEntryMatch final {
|
|
public:
|
|
const V3ControlScopeTraceEntry* m_entryp;
|
|
const string m_scopepart;
|
|
V3ControlScopeTraceEntryMatch(const V3ControlScopeTraceEntry* entryp, const string& scopepart)
|
|
: m_entryp{entryp}
|
|
, m_scopepart{scopepart} {}
|
|
bool operator<(const V3ControlScopeTraceEntryMatch& other) const {
|
|
if (m_entryp < other.m_entryp) return true;
|
|
if (m_entryp > other.m_entryp) return false;
|
|
return m_scopepart < other.m_scopepart;
|
|
}
|
|
};
|
|
|
|
class V3ControlScopeTraceResolver final {
|
|
std::vector<V3ControlScopeTraceEntry> m_entries; // User specified on/offs and levels
|
|
std::map<V3ControlScopeTraceEntryMatch, bool> m_matchCache; // Matching entries for speed
|
|
|
|
public:
|
|
void addScopeTraceOn(bool on, const string& scope, int levels) {
|
|
UINFO(9, "addScopeTraceOn " << on << " '" << scope << "' " << " levels=" << levels);
|
|
m_entries.emplace_back(V3ControlScopeTraceEntry{scope, on, levels});
|
|
m_matchCache.clear();
|
|
}
|
|
|
|
bool getEntryMatch(const V3ControlScopeTraceEntry* entp, const string& scopepart) {
|
|
// Return if a entry matches the scopepart, with memoization
|
|
const V3ControlScopeTraceEntryMatch key{entp, scopepart};
|
|
const auto pair = m_matchCache.emplace(key, false);
|
|
if (pair.second) pair.first->second = VString::wildmatch(scopepart, entp->m_scope);
|
|
return pair.first->second;
|
|
}
|
|
|
|
bool getScopeTraceOn(const string& scope) {
|
|
// Apply in the order the user provided them, so they can choose on/off preferences
|
|
int maxLevel = 1;
|
|
for (const auto& ch : scope) {
|
|
if (ch == '.') ++maxLevel;
|
|
}
|
|
UINFO(9, "getScopeTraceOn " << scope << " maxLevel=" << maxLevel);
|
|
|
|
bool enabled = true;
|
|
for (const auto& ent : m_entries) {
|
|
// We apply shortest match first for each rule component
|
|
// (Otherwise the levels would be useless as "--scope top* --levels 1" would
|
|
// always match at every scopepart, and we wouldn't know how to count levels)
|
|
int partLevel = 1;
|
|
for (string::size_type partEnd = 0; true;) {
|
|
partEnd = scope.find('.', partEnd + 1);
|
|
if (partEnd == string::npos) partEnd = scope.length();
|
|
const std::string scopepart = scope.substr(0, partEnd);
|
|
if (getEntryMatch(&ent, scopepart)) {
|
|
const bool levelMatch
|
|
= !ent.m_levels || (ent.m_levels >= maxLevel - partLevel);
|
|
if (levelMatch) enabled = ent.m_on;
|
|
UINFO(9, "getScopeTraceOn-part " << scope << " enabled=" << enabled
|
|
<< " @ lev=" << partLevel
|
|
<< (levelMatch ? "[match]" : "[miss]")
|
|
<< " from scopepart=" << scopepart);
|
|
break;
|
|
}
|
|
if (partEnd == scope.length()) break;
|
|
++partLevel;
|
|
}
|
|
}
|
|
return enabled;
|
|
}
|
|
};
|
|
|
|
//######################################################################
|
|
// Resolve modules and files in the design
|
|
|
|
class V3ControlResolverHierWorkerEntry final {
|
|
const int m_workers;
|
|
FileLine* const m_flp;
|
|
|
|
public:
|
|
explicit V3ControlResolverHierWorkerEntry(int workers, FileLine* flp)
|
|
: m_workers{workers}
|
|
, m_flp{flp} {}
|
|
int workers() const { return m_workers; }
|
|
FileLine* flp() const { return m_flp; }
|
|
};
|
|
|
|
class V3ControlResolver final {
|
|
enum ProfileDataMode : uint8_t { NONE = 0, MTASK = 1, HIER_DPI = 2 };
|
|
V3ControlModuleResolver m_modules; // Access to module names (with wildcards)
|
|
V3ControlFileResolver m_files; // Access to file names (with wildcards)
|
|
V3ControlScopeTraceResolver m_scopeTraces; // Regexp to trace enables
|
|
std::unordered_map<string, std::unordered_map<string, uint64_t>>
|
|
m_profileData; // Access to profile_data records
|
|
uint8_t m_mode = NONE;
|
|
std::unordered_map<string, V3ControlResolverHierWorkerEntry> m_hierWorkers;
|
|
FileLine* m_profileFileLine = nullptr;
|
|
|
|
V3ControlResolver() = default;
|
|
~V3ControlResolver() = default;
|
|
|
|
public:
|
|
static V3ControlResolver& s() {
|
|
static V3ControlResolver s_singleton;
|
|
return s_singleton;
|
|
}
|
|
V3ControlModuleResolver& modules() { return m_modules; }
|
|
V3ControlFileResolver& files() { return m_files; }
|
|
V3ControlScopeTraceResolver& scopeTraces() { return m_scopeTraces; }
|
|
|
|
void addProfileData(FileLine* fl, const string& hierDpi, uint64_t cost) {
|
|
// Empty key for hierarchical DPI wrapper costs.
|
|
addProfileData(fl, hierDpi, "", cost, HIER_DPI);
|
|
}
|
|
void addProfileData(FileLine* fl, const string& model, const string& key, uint64_t cost,
|
|
ProfileDataMode mode = MTASK) {
|
|
if (!m_profileFileLine) m_profileFileLine = fl;
|
|
if (cost == 0) cost = 1; // Cost 0 means delete (or no data)
|
|
if (mode == MTASK) {
|
|
m_profileData[model][key] += cost;
|
|
} else if (mode == HIER_DPI) {
|
|
m_profileData[model][key] = std::max(m_profileData[model][key], cost);
|
|
}
|
|
m_mode |= mode;
|
|
}
|
|
bool containsMTaskProfileData() const { return m_mode & MTASK; }
|
|
uint64_t getProfileData(const string& hierDpi) const {
|
|
// Empty key for hierarchical DPI wrapper costs.
|
|
return getProfileData(hierDpi, "");
|
|
}
|
|
void addHierWorkers(FileLine* flp, const string& model, int workers) {
|
|
m_hierWorkers.emplace(std::piecewise_construct, std::forward_as_tuple(model),
|
|
std::forward_as_tuple(workers, flp));
|
|
}
|
|
int getHierWorkers(const string& model) const {
|
|
const auto mit = m_hierWorkers.find(model);
|
|
// Assign a single worker if no specified.
|
|
return mit != m_hierWorkers.cend() ? mit->second.workers() : 0;
|
|
}
|
|
FileLine* getHierWorkersFileLine(const string& model) const {
|
|
const auto mit = m_hierWorkers.find(model);
|
|
return mit != m_hierWorkers.cend() ? mit->second.flp() : v3Global.rootp()->fileline();
|
|
}
|
|
uint64_t getProfileData(const string& model, const string& key) const {
|
|
const auto mit = m_profileData.find(model);
|
|
if (mit == m_profileData.cend()) return 0;
|
|
const auto it = mit->second.find(key);
|
|
if (it == mit->second.cend()) return 0;
|
|
return it->second;
|
|
}
|
|
FileLine* getProfileDataFileLine() const { return m_profileFileLine; } // Maybe null
|
|
static uint64_t getCurrentHierBlockCost() {
|
|
if (uint64_t cost = V3Control::getProfileData(v3Global.opt.prefix())) {
|
|
UINFO(9, "Fetching cost from profile info: " << cost);
|
|
return cost;
|
|
}
|
|
|
|
// Without profiling data, sum the regions evaluated on each time step
|
|
const AstNetlist* const netlistp = v3Global.rootp();
|
|
uint64_t cost = 0;
|
|
for (int i = 0; i < VEval::_ENUM_END; ++i) {
|
|
const VEval eval{i};
|
|
if (eval.slow()) continue;
|
|
cost += V3InstrCount::count(netlistp->evalFuncp(eval), false);
|
|
}
|
|
UINFO(9, "Evaluating cost: " << cost);
|
|
return cost;
|
|
}
|
|
};
|
|
|
|
//######################################################################
|
|
// V3Control
|
|
|
|
void V3Control::addCaseFull(const string& filename, int lineno) {
|
|
V3ControlFile& file = V3ControlResolver::s().files().at(filename);
|
|
file.addLineAttribute(lineno, VPragmaType::FULL_CASE);
|
|
}
|
|
void V3Control::addCaseParallel(const string& filename, int lineno) {
|
|
V3ControlFile& file = V3ControlResolver::s().files().at(filename);
|
|
file.addLineAttribute(lineno, VPragmaType::PARALLEL_CASE);
|
|
}
|
|
void V3Control::addCoverageBlockOff(const string& filename, int lineno) {
|
|
V3ControlFile& file = V3ControlResolver::s().files().at(filename);
|
|
file.addLineAttribute(lineno, VPragmaType::COVERAGE_BLOCK_OFF);
|
|
}
|
|
void V3Control::addCoverageBlockOff(const string& module, const string& blockname) {
|
|
V3ControlResolver::s().modules().at(module).addCoverageBlockOff(blockname);
|
|
}
|
|
|
|
void V3Control::addHierWorkers(FileLine* fl, const string& model, int workers) {
|
|
V3ControlResolver::s().addHierWorkers(fl, model, workers);
|
|
}
|
|
|
|
void V3Control::addFsmRegisterWrapper(FileLine* fl, const string& module, const string& d,
|
|
const string& q, const string& clock, const string& reset,
|
|
const string& resetValue) {
|
|
string missing;
|
|
if (module.empty()) missing += "-module";
|
|
if (d.empty()) missing = VString::dot(missing, ", ", "-d");
|
|
if (q.empty()) missing = VString::dot(missing, ", ", "-q");
|
|
if (clock.empty()) missing = VString::dot(missing, ", ", "-clock");
|
|
if (!missing.empty()) {
|
|
fl->v3error("fsm_register_wrapper missing " << missing);
|
|
return;
|
|
}
|
|
|
|
const FsmRegisterWrapper desc{module, d, q, clock, reset, resetValue};
|
|
V3ControlResolver::s().modules().at(module).setFsmRegisterWrapper(fl, desc);
|
|
}
|
|
|
|
void V3Control::addIgnore(V3ErrorCode code, bool on, const string& filename, int min, int max) {
|
|
UINFO(9, "addIgnore " << code << " " << min << "-" << max << " fn=" << filename);
|
|
if (filename == "*") { // For "lint_off/lint_on [--rule x]"
|
|
FileLine::globalWarnOff(code, !on);
|
|
} else { // For "lint_off/lint_on [--rule x] --file y [-lines min[-max]]"
|
|
V3ControlResolver::s().files().at(filename).addIgnore(code, min, max, on);
|
|
}
|
|
}
|
|
void V3Control::addIgnoreMatch(V3ErrorCode code, const string& filename, const string& contents,
|
|
const string& match) {
|
|
// For "lint_off --rule x --file y --match z", no support for lint_on
|
|
V3ControlResolver::s().files().at(filename).addIgnoreMatch(code, contents, match);
|
|
}
|
|
|
|
void V3Control::addInline(FileLine* fl, const string& module, const string& ftask, bool on) {
|
|
if (ftask.empty()) {
|
|
V3ControlResolver::s().modules().at(module).setInline(on);
|
|
} else {
|
|
if (!on) {
|
|
fl->v3warn(E_UNSUPPORTED, "Unsupported: no_inline for tasks");
|
|
} else {
|
|
V3ControlResolver::s().modules().at(module).ftasks().at(ftask).setNoInline(on);
|
|
}
|
|
}
|
|
}
|
|
|
|
void V3Control::addModulePragma(const string& module, VPragmaType pragma) {
|
|
V3ControlResolver::s().modules().at(module).addModulePragma(pragma);
|
|
}
|
|
|
|
void V3Control::addProfileData(FileLine* fl, const string& hierDpi, uint64_t cost) {
|
|
V3ControlResolver::s().addProfileData(fl, hierDpi, cost);
|
|
}
|
|
void V3Control::addProfileData(FileLine* fl, const string& model, const string& key,
|
|
uint64_t cost) {
|
|
V3ControlResolver::s().addProfileData(fl, model, key, cost);
|
|
}
|
|
|
|
void V3Control::addScopeTraceOn(bool on, const string& scope, int levels) {
|
|
V3ControlResolver::s().scopeTraces().addScopeTraceOn(on, scope, levels);
|
|
}
|
|
|
|
void V3Control::addVarAttr(FileLine* fl, const string& module, const string& ftask,
|
|
VarSpecKind kind, const string& pattern, VAttrType attr,
|
|
AstSenTree* sensep) {
|
|
if (sensep) {
|
|
FileLine* const flp = sensep->fileline();
|
|
// Historical, not actually needed, only parsed for compatibility, delete it
|
|
VL_DO_DANGLING(sensep->deleteTree(), sensep);
|
|
// Used to be only accepted on public_flat_rw
|
|
if (attr != VAttrType::VAR_PUBLIC_FLAT_RW) {
|
|
flp->v3error("sensitivity not expected for attribute");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (kind != VarSpecKind::VAR) {
|
|
switch (attr) {
|
|
case VAttrType::VAR_PUBLIC_FLAT:
|
|
case VAttrType::VAR_PUBLIC_FLAT_RD:
|
|
case VAttrType::VAR_PUBLIC_FLAT_RW: break;
|
|
default:
|
|
fl->v3error("'"s + attr.ascii() + "' attribute does not accept -param/-port");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Semantics: Most of the attributes operate on signals
|
|
if (pattern.empty()) {
|
|
if (attr == VAttrType::VAR_PUBLIC) {
|
|
if (ftask.empty()) {
|
|
// public module, this is the only exception from var here
|
|
V3ControlResolver::s().modules().at(module).addModulePragma(
|
|
VPragmaType::PUBLIC_MODULE);
|
|
} else {
|
|
V3ControlResolver::s().modules().at(module).ftasks().at(ftask).setPublic(true);
|
|
}
|
|
} else {
|
|
fl->v3error("missing -var");
|
|
}
|
|
} else {
|
|
if (attr == VAttrType::VAR_FORCEABLE) {
|
|
if (module.empty()) {
|
|
fl->v3error("forceable missing -module");
|
|
} else if (!ftask.empty()) {
|
|
fl->v3error("Signals inside functions/tasks cannot be marked forceable");
|
|
} else {
|
|
V3ControlResolver::s().modules().at(module).vars().at(pattern).add(attr);
|
|
}
|
|
} else {
|
|
V3ControlModule& mod = V3ControlResolver::s().modules().at(module);
|
|
V3ControlVar& controlVar = [&]() -> V3ControlVar& {
|
|
if (ftask.empty()) {
|
|
if (kind == VarSpecKind::PARAM) return mod.params().at(pattern);
|
|
if (kind == VarSpecKind::PORT) return mod.ports().at(pattern);
|
|
UASSERT_OBJ(kind == VarSpecKind::VAR, fl, "Unexpected VarSpecKind");
|
|
return mod.vars().at(pattern);
|
|
}
|
|
if (kind == VarSpecKind::PARAM) return mod.ftasks().at(ftask).params().at(pattern);
|
|
if (kind == VarSpecKind::PORT) return mod.ftasks().at(ftask).ports().at(pattern);
|
|
UASSERT_OBJ(kind == VarSpecKind::VAR, fl, "Unexpected VarSpecKind");
|
|
return mod.ftasks().at(ftask).vars().at(pattern);
|
|
}();
|
|
controlVar.add(attr);
|
|
}
|
|
}
|
|
}
|
|
|
|
void V3Control::applyCase(AstCase* nodep) {
|
|
const string& filename = nodep->fileline()->filename();
|
|
V3ControlFile* const filep = V3ControlResolver::s().files().resolve(filename);
|
|
if (filep) filep->applyCase(nodep);
|
|
}
|
|
|
|
void V3Control::applyCoverageBlock(AstNodeModule* modulep, AstBegin* nodep) {
|
|
const string& filename = nodep->fileline()->filename();
|
|
V3ControlFile* const filep = V3ControlResolver::s().files().resolve(filename);
|
|
if (filep) filep->applyBlock(nodep);
|
|
const string& modname = modulep->prettyDehashOrigOrName();
|
|
V3ControlModule* const modp = V3ControlResolver::s().modules().resolve(modname);
|
|
if (modp) modp->applyBlock(nodep);
|
|
}
|
|
void V3Control::applyCoverageBlock(AstNodeModule* modulep, AstGenBlock* nodep) {
|
|
const string& filename = nodep->fileline()->filename();
|
|
V3ControlFile* const filep = V3ControlResolver::s().files().resolve(filename);
|
|
if (filep) filep->applyBlock(nodep);
|
|
const string& modname = modulep->prettyDehashOrigOrName();
|
|
V3ControlModule* const modp = V3ControlResolver::s().modules().resolve(modname);
|
|
if (modp) modp->applyBlock(nodep);
|
|
}
|
|
|
|
void V3Control::applyIgnores(FileLine* filelinep) {
|
|
const string& filename = filelinep->filename();
|
|
V3ControlFile* const filep = V3ControlResolver::s().files().resolve(filename);
|
|
if (filep) filep->applyIgnores(filelinep);
|
|
}
|
|
|
|
void V3Control::applyModule(AstNodeModule* modulep) {
|
|
const string& modname = modulep->prettyDehashOrigOrName();
|
|
V3ControlModule* const modp = V3ControlResolver::s().modules().resolve(modname);
|
|
if (modp) modp->apply(modulep);
|
|
}
|
|
|
|
void V3Control::applyFTask(AstNodeModule* modulep, AstNodeFTask* ftaskp) {
|
|
const string& modname = modulep->prettyDehashOrigOrName();
|
|
V3ControlModule* const modp = V3ControlResolver::s().modules().resolve(modname);
|
|
if (!modp) return;
|
|
const V3ControlFTask* const ftp = modp->ftasks().resolve(ftaskp->prettyDehashOrigOrName());
|
|
if (ftp) ftp->apply(ftaskp);
|
|
}
|
|
|
|
template <typename T_Resolver>
|
|
static void resolveThenApply(T_Resolver& resolver, AstVar* varp) {
|
|
const std::string name = varp->prettyDehashOrigOrName();
|
|
if (const V3ControlVar* const vp = resolver.vars().resolve(name)) vp->apply(varp);
|
|
if (varp->isParam()) {
|
|
if (const V3ControlVar* const vp = resolver.params().resolve(name)) vp->apply(varp);
|
|
}
|
|
if (varp->isIO()) {
|
|
if (const V3ControlVar* const vp = resolver.ports().resolve(name)) vp->apply(varp);
|
|
}
|
|
}
|
|
|
|
void V3Control::applyVarAttr(const AstNodeModule* modulep, const AstNodeFTask* ftaskp,
|
|
AstVar* varp) {
|
|
V3ControlModule* const modp
|
|
= V3ControlResolver::s().modules().resolve(modulep->prettyDehashOrigOrName());
|
|
if (!modp) return;
|
|
if (ftaskp) {
|
|
V3ControlFTask* const ftp = modp->ftasks().resolve(ftaskp->prettyDehashOrigOrName());
|
|
if (!ftp) return;
|
|
resolveThenApply(*ftp, varp);
|
|
} else {
|
|
resolveThenApply(*modp, varp);
|
|
}
|
|
}
|
|
|
|
int V3Control::getHierWorkers(const string& model) {
|
|
return V3ControlResolver::s().getHierWorkers(model);
|
|
}
|
|
FileLine* V3Control::getHierWorkersFileLine(const string& model) {
|
|
return V3ControlResolver::s().getHierWorkersFileLine(model);
|
|
}
|
|
const V3Control::FsmRegisterWrapper* V3Control::getFsmRegisterWrapper(const string& module) {
|
|
V3ControlModule* const modp = V3ControlResolver::s().modules().resolve(module);
|
|
return modp ? modp->fsmRegisterWrapperp() : nullptr;
|
|
}
|
|
uint64_t V3Control::getProfileData(const string& hierDpi) {
|
|
return V3ControlResolver::s().getProfileData(hierDpi);
|
|
}
|
|
uint64_t V3Control::getProfileData(const string& model, const string& key) {
|
|
return V3ControlResolver::s().getProfileData(model, key);
|
|
}
|
|
FileLine* V3Control::getProfileDataFileLine() {
|
|
return V3ControlResolver::s().getProfileDataFileLine();
|
|
}
|
|
bool V3Control::getScopeTraceOn(const string& scope) {
|
|
return V3ControlResolver::s().scopeTraces().getScopeTraceOn(scope);
|
|
}
|
|
|
|
void V3Control::contentsPushText(const string& text) { WildcardContents::pushText(text); }
|
|
|
|
bool V3Control::containsMTaskProfileData() {
|
|
return V3ControlResolver::s().containsMTaskProfileData();
|
|
}
|
|
uint64_t V3Control::getCurrentHierBlockCost() {
|
|
return V3ControlResolver::s().getCurrentHierBlockCost();
|
|
}
|
|
|
|
bool V3Control::waive(const FileLine* filelinep, V3ErrorCode code, const string& message) {
|
|
V3ControlFile* const filep = V3ControlResolver::s().files().resolve(filelinep->filename());
|
|
if (!filep) return false;
|
|
return filep->waive(code, message);
|
|
}
|
|
|
|
void V3Control::selfTest() { VIntervalTree::selfTest(); }
|