mirror of
https://github.com/verilator/verilator.git
synced 2026-10-06 10:03:44 +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.
164 lines
6.8 KiB
C++
164 lines
6.8 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//*************************************************************************
|
|
// DESCRIPTION: Verilator: Block code ordering
|
|
//
|
|
// 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: 2003-2026 Wilson Snyder
|
|
// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
|
|
//
|
|
//*************************************************************************
|
|
// V3Order's Transformations:
|
|
//
|
|
// Compute near optimal scheduling of always/wire statements
|
|
// Make a graph of the entire netlist
|
|
//
|
|
// For seq logic
|
|
// Add logic_sensitive_vertex for this list of SenItems
|
|
// Add edge for each sensitive_var->logic_sensitive_vertex
|
|
// For AlwaysPre's
|
|
// Add vertex for this logic
|
|
// Add edge logic_sensitive_vertex->logic_vertex
|
|
// Add edge logic_consumed_var_PREVAR->logic_vertex
|
|
// Add edge logic_vertex->logic_generated_var (same as if comb)
|
|
// Add edge logic_vertex->generated_var_PREORDER
|
|
// Cutable dependency to attempt to order dlyed
|
|
// assignments to avoid saving state, thus we prefer
|
|
// a <= b ... As the opposite order would
|
|
// b <= c ... require the old value of b.
|
|
// Add edge consumed_var_POST->logic_vertex
|
|
// This prevents a consumer of the "early" value to be
|
|
// scheduled after we've changed to the next-cycle value
|
|
// For Logic
|
|
// Add vertex for this logic
|
|
// Add edge logic_sensitive_vertex->logic_vertex
|
|
// Add edge logic_generated_var_PREORDER->logic_vertex
|
|
// This ensures the AlwaysPre gets scheduled before this logic
|
|
// Add edge logic_vertex->consumed_var_PREVAR
|
|
// Add edge logic_vertex->consumed_var_POSTVAR
|
|
// Add edge logic_vertex->logic_generated_var (same as if comb)
|
|
// For AlwaysPost's
|
|
// Add vertex for this logic
|
|
// Add edge logic_sensitive_vertex->logic_vertex
|
|
// Add edge logic_consumed_var->logic_vertex (same as if comb)
|
|
// Add edge logic_vertex->logic_generated_var (same as if comb)
|
|
// Add edge consumed_var_POST->logic_vertex (same as if comb)
|
|
//
|
|
// For comb logic
|
|
// For comb logic
|
|
// Add vertex for this logic
|
|
// Add edge logic_consumed_var->logic_vertex
|
|
// Add edge logic_vertex->logic_generated_var
|
|
// Mark it cutable, as circular logic may require
|
|
// the generated signal to become a primary input again.
|
|
//
|
|
//
|
|
//
|
|
// Rank the graph starting at INPUTS (see V3Graph)
|
|
//
|
|
// Visit the graph's logic vertices in ranked order
|
|
// For all logic vertices with all inputs already ordered
|
|
// Make ordered block for this module
|
|
// For all ^^ in same domain
|
|
// Move logic to ordered activation
|
|
// When we have no more choices, we move to the next module
|
|
// and make a new block. Add that new activation block to the list of calls to make.
|
|
//
|
|
//*************************************************************************
|
|
|
|
#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT
|
|
|
|
#include "V3OrderInternal.h"
|
|
#include "V3Sched.h"
|
|
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
|
|
|
void V3Order::orderOrderGraph(OrderGraph& graph, const std::string& tag) {
|
|
// Dump data
|
|
if (dumpGraphLevel()) graph.dumpDotFilePrefixed(tag + "_orderg_pre");
|
|
|
|
// Break cycles. Note that the OrderGraph only contains cuttable cycles
|
|
// (soft constraints). Actual logic loops must have been eliminated by
|
|
// the introduction of Hybid sensitivity expressions, before invoking
|
|
// ordering (e.g. in V3SchedAcyclic).
|
|
graph.acyclic(&V3GraphEdge::followAlwaysTrue);
|
|
if (dumpGraphLevel()) graph.dumpDotFilePrefixed(tag + "_orderg_acyc");
|
|
|
|
// Assign ranks so we know what to follow, then sort vertices and edges by that ordering
|
|
graph.order();
|
|
if (dumpGraphLevel()) graph.dumpDotFilePrefixed(tag + "_orderg_order");
|
|
}
|
|
|
|
//######################################################################
|
|
|
|
AstCFunc* V3Order::order(AstNetlist* netlistp, //
|
|
const std::vector<V3Sched::LogicByScope*>& logic, //
|
|
const V3Order::TrigToSenMap& trigToSen,
|
|
const string& tag, //
|
|
bool parallel, //
|
|
bool slow, //
|
|
const ExternalDomainsProvider& externalDomains) {
|
|
// Build the OrderGraph
|
|
const std::unique_ptr<OrderGraph> graph
|
|
= buildOrderGraph(netlistp, logic, trigToSen, parallel);
|
|
// Order it
|
|
orderOrderGraph(*graph, tag);
|
|
// Assign sensitivity domains to combinational logic
|
|
processDomains(netlistp, *graph, tag, externalDomains);
|
|
// Build the move graph
|
|
OrderMoveDomScope::clear();
|
|
const std::unique_ptr<OrderMoveGraph> moveGraphp = OrderMoveGraph::build(*graph, trigToSen);
|
|
if (dumpGraphLevel() >= 9) moveGraphp->dumpDotFilePrefixed(tag + "_ordermv");
|
|
|
|
// The ordered statements, if there are any
|
|
AstNodeStmt* stmtsp = nullptr;
|
|
if (!moveGraphp->empty()) {
|
|
if (parallel) {
|
|
stmtsp = createParallel(*moveGraphp, tag, slow);
|
|
} else {
|
|
stmtsp = createSerial(*moveGraphp, tag, slow);
|
|
}
|
|
// Should have consumed all vertices
|
|
UASSERT(moveGraphp->empty(), "Unconsumed vertices remain in OrderMoveGraph");
|
|
}
|
|
OrderMoveDomScope::clear();
|
|
|
|
// Dump data
|
|
if (dumpGraphLevel()) graph->dumpDotFilePrefixed(tag + "_orderg_done");
|
|
|
|
// Dispose of the remnants of the inputs
|
|
for (auto* const lbsp : logic) lbsp->deleteActives();
|
|
|
|
// If there is no resulting logic, then don't create an empty function
|
|
if (!stmtsp) return nullptr;
|
|
|
|
// Create the result function
|
|
FileLine* const flp = netlistp->fileline();
|
|
AstCFunc* const funcp = [&]() {
|
|
AstScope* const scopeTopp = netlistp->topScopep()->scopep();
|
|
AstCFunc* const resp = new AstCFunc{flp, "_eval_body__" + tag, scopeTopp, ""};
|
|
resp->dontCombine(true);
|
|
resp->isStatic(false);
|
|
resp->isLoose(true);
|
|
resp->slow(slow);
|
|
resp->isConst(false);
|
|
resp->declPrivate(true);
|
|
scopeTopp->addBlocksp(resp);
|
|
return resp;
|
|
}();
|
|
|
|
// Assemble the body
|
|
funcp->addStmtsp(stmtsp);
|
|
|
|
// Done
|
|
return funcp;
|
|
}
|