Merge remote-tracking branch 'parallax/master'
Signed-off-by: Matt Liberty <mliberty@precisioninno.com>
This commit is contained in:
commit
ce98bde188
|
|
@ -15,8 +15,6 @@ cmake-build-debug
|
|||
build
|
||||
pvt
|
||||
|
||||
include/sta/StaConfig.hh
|
||||
|
||||
app/sta
|
||||
app/libOpenSTA.*
|
||||
app/sta.exe
|
||||
|
|
|
|||
|
|
@ -389,7 +389,7 @@ message(STATUS "SSTA: ${SSTA}")
|
|||
|
||||
# configure a header file to pass some of the CMake settings
|
||||
configure_file(${STA_HOME}/util/StaConfig.hh.cmake
|
||||
${STA_HOME}/include/sta/StaConfig.hh
|
||||
${CMAKE_CURRENT_BINARY_DIR}/include/sta/StaConfig.hh
|
||||
)
|
||||
|
||||
###########################################################
|
||||
|
|
@ -464,6 +464,7 @@ target_include_directories(sta_swig
|
|||
PRIVATE
|
||||
${STA_HOME}
|
||||
${TCL_INCLUDE_PATH}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/include/sta
|
||||
)
|
||||
|
||||
###########################################################
|
||||
|
|
@ -515,6 +516,7 @@ target_include_directories(OpenSTA
|
|||
include
|
||||
${TCL_INCLUDE_PATH}
|
||||
${FLEX_INCLUDE_DIRS}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/include/sta
|
||||
|
||||
PRIVATE
|
||||
include/sta
|
||||
|
|
@ -582,6 +584,7 @@ install(TARGETS OpenSTA DESTINATION lib)
|
|||
|
||||
# include
|
||||
install(DIRECTORY include/sta DESTINATION include)
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include/sta DESTINATION include)
|
||||
|
||||
################################################################
|
||||
|
||||
|
|
@ -589,6 +592,7 @@ add_custom_target(sta_tags etags -o TAGS
|
|||
${STA_SOURCE}
|
||||
*/*.hh
|
||||
include/sta/*.hh
|
||||
${CMAKE_CURRENT_BINARY_DIR}/include/sta/*.hh
|
||||
${SWIG_FILES}
|
||||
${STA_TCL_FILES}
|
||||
${SWIG_TCL_FILES}
|
||||
|
|
|
|||
BIN
doc/OpenSTA.odt
BIN
doc/OpenSTA.odt
Binary file not shown.
BIN
doc/OpenSTA.pdf
BIN
doc/OpenSTA.pdf
Binary file not shown.
|
|
@ -969,8 +969,9 @@ Vertex::init(Pin *pin,
|
|||
is_check_clk_ = false;
|
||||
is_constrained_ = false;
|
||||
has_downstream_clk_pin_ = false;
|
||||
color_ = unsigned(LevelColor::white);
|
||||
level_ = 0;
|
||||
visited1_ = false;
|
||||
visited2_ = false;
|
||||
bfs_in_queue_ = 0;
|
||||
crpr_path_pruning_disabled_ = false;
|
||||
}
|
||||
|
|
@ -1040,9 +1041,15 @@ Vertex::setLevel(Level level)
|
|||
}
|
||||
|
||||
void
|
||||
Vertex::setColor(LevelColor color)
|
||||
Vertex::setVisited(bool visited)
|
||||
{
|
||||
color_ = unsigned(color);
|
||||
visited1_ = visited;
|
||||
}
|
||||
|
||||
void
|
||||
Vertex::setVisited2(bool visited)
|
||||
{
|
||||
visited2_ = visited;
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
|||
|
|
@ -44,8 +44,6 @@ namespace sta {
|
|||
class MinMax;
|
||||
class Sdc;
|
||||
|
||||
enum class LevelColor { white, gray, black };
|
||||
|
||||
typedef ObjectTable<Vertex> VertexTable;
|
||||
typedef ObjectTable<Edge> EdgeTable;
|
||||
typedef Map<const Pin*, Vertex*> PinVertexMap;
|
||||
|
|
@ -254,11 +252,13 @@ public:
|
|||
bool isDriver(const Network *network) const;
|
||||
Level level() const { return level_; }
|
||||
void setLevel(Level level);
|
||||
bool visited() const { return visited1_; }
|
||||
void setVisited(bool visited);
|
||||
bool visited2() const { return visited2_; }
|
||||
void setVisited2(bool visited);
|
||||
bool isRoot() const{ return level_ == 0; }
|
||||
bool hasFanin() const;
|
||||
bool hasFanout() const;
|
||||
LevelColor color() const { return static_cast<LevelColor>(color_); }
|
||||
void setColor(LevelColor color);
|
||||
Slew *slews() { return slews_; }
|
||||
const Slew *slews() const { return slews_; }
|
||||
Path *paths() const { return paths_; }
|
||||
|
|
@ -330,16 +330,15 @@ protected:
|
|||
// Each bit corresponds to a different BFS queue.
|
||||
std::atomic<uint8_t> bfs_in_queue_; // 8
|
||||
|
||||
unsigned int level_:Graph::vertex_level_bits; // 24
|
||||
int level_:Graph::vertex_level_bits; // 24
|
||||
unsigned int slew_annotated_:slew_annotated_bits; // 4
|
||||
// Levelization search state.
|
||||
// LevelColor gcc barfs if this is dcl'd.
|
||||
unsigned color_:2;
|
||||
// LogicValue gcc barfs if this is dcl'd.
|
||||
unsigned sim_value_:3;
|
||||
// Bidirect pins have two vertices.
|
||||
// This flag distinguishes the driver and load vertices.
|
||||
bool is_bidirect_drvr_:1;
|
||||
|
||||
unsigned object_idx_:VertexTable::idx_bits; // 7
|
||||
bool is_reg_clk_:1;
|
||||
bool is_disabled_constraint_:1;
|
||||
bool is_gated_clk_enable_:1;
|
||||
|
|
@ -350,7 +349,8 @@ protected:
|
|||
bool is_constrained_:1;
|
||||
bool has_downstream_clk_pin_:1;
|
||||
bool crpr_path_pruning_disabled_:1;
|
||||
unsigned object_idx_:VertexTable::idx_bits; // 7
|
||||
bool visited1_:1;
|
||||
bool visited2_:1;
|
||||
|
||||
private:
|
||||
friend class Graph;
|
||||
|
|
|
|||
|
|
@ -542,7 +542,7 @@ public:
|
|||
ClockSet *to_clks,
|
||||
InstanceSet *to_insts,
|
||||
const RiseFallBoth *rf,
|
||||
const RiseFallBoth *end_rf);
|
||||
const RiseFallBoth *end_rf);
|
||||
void checkExceptionToPins(ExceptionTo *to,
|
||||
const char *file, int) const;
|
||||
void deleteExceptionTo(ExceptionTo *to);
|
||||
|
|
@ -1048,7 +1048,7 @@ public:
|
|||
bool annotated);
|
||||
// Make sure levels are up to date and return vertex level.
|
||||
Level vertexLevel(Vertex *vertex);
|
||||
GraphLoopSeq *graphLoops();
|
||||
GraphLoopSeq &graphLoops();
|
||||
PathAnalysisPt *pathAnalysisPt(Path *path);
|
||||
DcalcAnalysisPt *pathDcalcAnalysisPt(Path *path);
|
||||
TagIndex tagCount() const;
|
||||
|
|
|
|||
|
|
@ -4086,7 +4086,7 @@ Sdc::makeFilterPath(ExceptionFrom *from,
|
|||
void
|
||||
Sdc::makeLoopExceptions()
|
||||
{
|
||||
for (GraphLoop *loop : *levelize_->loops())
|
||||
for (GraphLoop *loop : levelize_->loops())
|
||||
makeLoopExceptions(loop);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -184,12 +184,10 @@ CheckTiming::checkLoops()
|
|||
{
|
||||
// These may not need to be sorted because the graph roots are
|
||||
// sorted during levelization so the discovery should be consistent.
|
||||
GraphLoopSeq *loops = levelize_->loops();
|
||||
GraphLoopSeq &loops = levelize_->loops();
|
||||
// Count the combinational loops.
|
||||
int loop_count = 0;
|
||||
GraphLoopSeq::Iterator loop_iter1(loops);
|
||||
while (loop_iter1.hasNext()) {
|
||||
GraphLoop *loop = loop_iter1.next();
|
||||
for (GraphLoop *loop : loops) {
|
||||
if (loop->isCombinational())
|
||||
loop_count++;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
#include "Levelize.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <deque>
|
||||
|
||||
#include "Report.hh"
|
||||
#include "Debug.hh"
|
||||
|
|
@ -45,14 +46,13 @@ using std::max;
|
|||
|
||||
Levelize::Levelize(StaState *sta) :
|
||||
StaState(sta),
|
||||
search_pred_(new SearchPredNonLatch2(sta)),
|
||||
search_pred_(sta),
|
||||
levelized_(false),
|
||||
levels_valid_(false),
|
||||
max_level_(0),
|
||||
level_space_(10),
|
||||
roots_(new VertexSet(graph_)),
|
||||
relevelize_from_(new VertexSet(graph_)),
|
||||
loops_(nullptr),
|
||||
observer_(nullptr)
|
||||
{
|
||||
}
|
||||
|
|
@ -61,9 +61,8 @@ Levelize::~Levelize()
|
|||
{
|
||||
delete roots_;
|
||||
delete relevelize_from_;
|
||||
delete search_pred_;
|
||||
delete observer_;
|
||||
deleteLoops();
|
||||
loops_.deleteContents();
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -87,7 +86,9 @@ Levelize::clear()
|
|||
roots_->clear();
|
||||
relevelize_from_->clear();
|
||||
clearLoopEdges();
|
||||
deleteLoops();
|
||||
loops_.deleteContentsClear();
|
||||
loop_edges_.clear();
|
||||
max_level_ = 0;
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -101,17 +102,6 @@ Levelize::clearLoopEdges()
|
|||
disabled_loop_edges_.clear();
|
||||
}
|
||||
|
||||
void
|
||||
Levelize::deleteLoops()
|
||||
{
|
||||
if (loops_) {
|
||||
loops_->deleteContents();
|
||||
delete loops_;
|
||||
loops_ = nullptr;
|
||||
loop_edges_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Levelize::ensureLevelized()
|
||||
{
|
||||
|
|
@ -123,29 +113,42 @@ Levelize::ensureLevelized()
|
|||
}
|
||||
}
|
||||
|
||||
// Depth first search.
|
||||
// "Introduction to Algorithms", section 23.3 pg 478.
|
||||
#define onPath() visied2()
|
||||
#define setOnPath(on_path) setVisited2(on_path)
|
||||
|
||||
void
|
||||
Levelize::levelize()
|
||||
{
|
||||
Stats stats(debug_, report_);
|
||||
debugPrint(debug_, "levelize", 1, "levelize");
|
||||
max_level_ = 0;
|
||||
clearLoopEdges();
|
||||
deleteLoops();
|
||||
loops_ = new GraphLoopSeq;
|
||||
clear();
|
||||
|
||||
VertexIterator vertex_iter(graph_);
|
||||
while (vertex_iter.hasNext()) {
|
||||
Vertex *vertex = vertex_iter.next();
|
||||
// findBackEdges() init
|
||||
vertex->setVisited(false);
|
||||
vertex->setOnPath(false);
|
||||
// assignLevels init
|
||||
vertex->setLevel(-1);
|
||||
}
|
||||
|
||||
findRoots();
|
||||
VertexSeq roots;
|
||||
// Sort the roots so that loop breaking is stable in regressions.
|
||||
// In situations where port directions are broken pins may
|
||||
// be treated as bidirects, leading to a plethora of degenerate
|
||||
// roots that take forever to sort.
|
||||
if (roots.size() < 100)
|
||||
sortRoots(roots);
|
||||
levelizeFrom(roots);
|
||||
findBackEdges();
|
||||
VertexSeq topo_sorted = findToplologicalOrder();
|
||||
assignLevels(topo_sorted);
|
||||
ensureLatchLevels();
|
||||
// Find vertices in cycles that are were not accessible from roots.
|
||||
levelizeCycles();
|
||||
|
||||
// Set level of stranded vertices (constants) to zero.
|
||||
VertexIterator vertex_iter2(graph_);
|
||||
while (vertex_iter2.hasNext()) {
|
||||
Vertex *vertex = vertex_iter2.next();
|
||||
if (vertex->level() == -1)
|
||||
setLevel(vertex, 0);
|
||||
// cleanup
|
||||
vertex->setVisited(false);
|
||||
vertex->setOnPath(false);
|
||||
}
|
||||
levelized_ = true;
|
||||
levels_valid_ = true;
|
||||
stats.report("Levelize");
|
||||
|
|
@ -154,128 +157,279 @@ Levelize::levelize()
|
|||
void
|
||||
Levelize::findRoots()
|
||||
{
|
||||
roots_->clear();
|
||||
VertexIterator vertex_iter(graph_);
|
||||
while (vertex_iter.hasNext()) {
|
||||
Vertex *vertex = vertex_iter.next();
|
||||
setLevel(vertex, 0);
|
||||
if (isRoot(vertex)) {
|
||||
debugPrint(debug_, "levelize", 2, "root %s",
|
||||
vertex->to_string(this).c_str());
|
||||
debugPrint(debug_, "levelize", 2, "root %s%s",
|
||||
vertex->to_string(this).c_str(),
|
||||
hasFanout(vertex) ? " fanout" : "");
|
||||
roots_->insert(vertex);
|
||||
if (hasFanout(vertex, search_pred_, graph_))
|
||||
// Color roots with no fanout black so that they are
|
||||
// not treated as degenerate loops by levelizeCycles().
|
||||
vertex->setColor(LevelColor::black);
|
||||
}
|
||||
else
|
||||
vertex->setColor(LevelColor::white);
|
||||
}
|
||||
if (debug_->check("levelize", 1)) {
|
||||
size_t fanout_roots = 0;
|
||||
for (Vertex *root : *roots_) {
|
||||
if (hasFanout(root))
|
||||
fanout_roots++;
|
||||
}
|
||||
debugPrint(debug_, "levelize", 1, "Found %zu roots %zu with fanout",
|
||||
roots_->size(),
|
||||
fanout_roots);
|
||||
}
|
||||
}
|
||||
|
||||
// Root vertices have at no enabled (non-disabled) edges entering them
|
||||
// and are not disabled.
|
||||
// Root vertices have at no non-disabled edges entering them
|
||||
// and are not disabled and have non-disabled fanout edges.
|
||||
bool
|
||||
Levelize::isRoot(Vertex *vertex)
|
||||
{
|
||||
if (search_pred_->searchTo(vertex)) {
|
||||
VertexInEdgeIterator edge_iter(vertex, graph_);
|
||||
while (edge_iter.hasNext()) {
|
||||
Edge *edge = edge_iter.next();
|
||||
if (search_pred_.searchTo(vertex)) {
|
||||
VertexInEdgeIterator edge_iter1(vertex, graph_);
|
||||
while (edge_iter1.hasNext()) {
|
||||
Edge *edge = edge_iter1.next();
|
||||
Vertex *from_vertex = edge->from(graph_);
|
||||
if (search_pred_->searchFrom(from_vertex)
|
||||
&& search_pred_->searchThru(edge))
|
||||
return false;
|
||||
if (search_pred_.searchFrom(from_vertex)
|
||||
&& search_pred_.searchThru(edge))
|
||||
return false;
|
||||
}
|
||||
// Bidirect pins are not treated as roots in this case.
|
||||
return !graph_delay_calc_->bidirectDrvrSlewFromLoad(vertex->pin());
|
||||
// Levelize bidirect driver as if it was a fanout of the bidirect load.
|
||||
return !(graph_delay_calc_->bidirectDrvrSlewFromLoad(vertex->pin())
|
||||
&& vertex->isBidirectDriver());
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
Levelize::sortRoots(VertexSeq &roots)
|
||||
bool
|
||||
Levelize::hasFanout(Vertex *vertex)
|
||||
{
|
||||
// roots_ is a set so insert/delete are fast for incremental changes.
|
||||
// Copy the set into a vector for sorting.
|
||||
for (Vertex *root : *roots_)
|
||||
roots.push_back(root);
|
||||
sort(roots, VertexNameLess(network_));
|
||||
}
|
||||
|
||||
void
|
||||
Levelize::levelizeFrom(VertexSeq &roots)
|
||||
{
|
||||
VertexSeq::Iterator root_iter(roots);
|
||||
while (root_iter.hasNext()) {
|
||||
Vertex *root = root_iter.next();
|
||||
EdgeSeq path;
|
||||
visit(root, 0, level_space_, path);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Levelize::visit(Vertex *vertex,
|
||||
Level level,
|
||||
Level level_space,
|
||||
EdgeSeq &path)
|
||||
{
|
||||
Pin *from_pin = vertex->pin();
|
||||
debugPrint(debug_, "levelize", 3, "level %d %s",
|
||||
level, vertex->to_string(this).c_str());
|
||||
vertex->setColor(LevelColor::gray);
|
||||
setLevel(vertex, level);
|
||||
max_level_ = max(level, max_level_);
|
||||
level += level_space;
|
||||
if (level >= Graph::vertex_level_max)
|
||||
criticalError(616, "maximum logic level exceeded");
|
||||
|
||||
if (search_pred_->searchFrom(vertex)) {
|
||||
VertexOutEdgeIterator edge_iter(vertex, graph_);
|
||||
while (edge_iter.hasNext()) {
|
||||
Edge *edge = edge_iter.next();
|
||||
Vertex *to_vertex = edge->to(graph_);
|
||||
if (search_pred_->searchThru(edge)
|
||||
&& search_pred_->searchTo(to_vertex)) {
|
||||
LevelColor to_color = to_vertex->color();
|
||||
if (to_color == LevelColor::gray)
|
||||
// Back edges form feedback loops.
|
||||
recordLoop(edge, path);
|
||||
else if (to_color == LevelColor::white
|
||||
|| to_vertex->level() < level) {
|
||||
path.push_back(edge);
|
||||
visit(to_vertex, level, level_space, path);
|
||||
path.pop_back();
|
||||
}
|
||||
bool has_fanout = false;
|
||||
if (search_pred_.searchFrom(vertex)) {
|
||||
VertexOutEdgeIterator edge_iter2(vertex, graph_);
|
||||
while (edge_iter2.hasNext()) {
|
||||
Edge *edge = edge_iter2.next();
|
||||
Vertex *to_vertex = edge->from(graph_);
|
||||
if (search_pred_.searchTo(to_vertex)
|
||||
&& search_pred_.searchThru(edge)) {
|
||||
has_fanout = true;
|
||||
break;
|
||||
}
|
||||
if (edge->role() == TimingRole::latchDtoQ())
|
||||
latch_d_to_q_edges_.insert(edge);
|
||||
}
|
||||
// Levelize bidirect driver as if it was a fanout of the bidirect load.
|
||||
if (graph_delay_calc_->bidirectDrvrSlewFromLoad(from_pin)
|
||||
&& !vertex->isBidirectDriver()) {
|
||||
Vertex *to_vertex = graph_->pinDrvrVertex(from_pin);
|
||||
if (search_pred_->searchTo(to_vertex)
|
||||
&& (to_vertex->color() == LevelColor::white
|
||||
|| to_vertex->level() < level))
|
||||
visit(to_vertex, level, level_space, path);
|
||||
}
|
||||
if (graph_delay_calc_->bidirectDrvrSlewFromLoad(vertex->pin())
|
||||
&& !vertex->isBidirectDriver())
|
||||
has_fanout = true;
|
||||
}
|
||||
vertex->setColor(LevelColor::black);
|
||||
return has_fanout;
|
||||
}
|
||||
|
||||
// Non-recursive DFS to find back edges so the graph is acyclic.
|
||||
void
|
||||
Levelize::reportPath(EdgeSeq &path) const
|
||||
Levelize::findBackEdges()
|
||||
{
|
||||
bool first_edge = true;
|
||||
EdgeSeq::Iterator edge_iter(path);
|
||||
while (edge_iter.hasNext()) {
|
||||
Edge *edge = edge_iter.next();
|
||||
if (first_edge)
|
||||
report_->reportLine(" %s", edge->from(graph_)->to_string(this).c_str());
|
||||
report_->reportLine(" %s", edge->to(graph_)->to_string(this).c_str());
|
||||
first_edge = false;
|
||||
Stats stats(debug_, report_);
|
||||
EdgeSeq path;
|
||||
FindBackEdgesStack stack;
|
||||
|
||||
VertexSeq sorted_roots = sortedRootsWithFanout();
|
||||
for (Vertex *vertex : sorted_roots) {
|
||||
vertex->setVisited(true);
|
||||
vertex->setOnPath(true);
|
||||
stack.emplace(vertex, new VertexOutEdgeIterator(vertex, graph_));
|
||||
}
|
||||
|
||||
findBackEdges(path, stack);
|
||||
findCycleBackEdges();
|
||||
stats.report("Levelize find back edges");
|
||||
}
|
||||
|
||||
VertexSeq
|
||||
Levelize::sortedRootsWithFanout()
|
||||
{
|
||||
VertexSeq roots;
|
||||
for (Vertex *root : *roots_) {
|
||||
if (hasFanout(root))
|
||||
roots.push_back(root);
|
||||
}
|
||||
// Sort the roots so that loop breaking is stable in regressions.
|
||||
// Skip sorting if it will take a long time.
|
||||
if (roots.size() < 100)
|
||||
sort(roots, VertexNameLess(network_));
|
||||
return roots;
|
||||
}
|
||||
|
||||
EdgeSet
|
||||
Levelize::findBackEdges(EdgeSeq &path,
|
||||
FindBackEdgesStack &stack)
|
||||
{
|
||||
EdgeSet back_edges;
|
||||
while (!stack.empty()) {
|
||||
VertexEdgeIterPair vertex_iter = stack.top();
|
||||
Vertex *vertex = vertex_iter.first;
|
||||
VertexOutEdgeIterator *edge_iter = vertex_iter.second;
|
||||
if (edge_iter->hasNext()) {
|
||||
Edge *edge = edge_iter->next();
|
||||
if (search_pred_.searchThru(edge)) {
|
||||
Vertex *to_vertex = edge->to(graph_);
|
||||
if (!to_vertex->visited()) {
|
||||
to_vertex->setVisited(true);
|
||||
to_vertex->setOnPath(true);
|
||||
path.push_back(edge);
|
||||
stack.emplace(to_vertex, new VertexOutEdgeIterator(to_vertex, graph_));
|
||||
}
|
||||
else if (to_vertex->visited2()) { // on path
|
||||
// Found a back edge (loop).
|
||||
recordLoop(edge, path);
|
||||
back_edges.insert(edge);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
delete edge_iter;
|
||||
stack.pop();
|
||||
vertex->setOnPath(false);
|
||||
if (!path.empty())
|
||||
path.pop_back();
|
||||
}
|
||||
}
|
||||
return back_edges;
|
||||
}
|
||||
|
||||
// Find back edges in cycles that are were not accessible from roots.
|
||||
// Add roots for the disabled back edges so they are become accessible.
|
||||
void
|
||||
Levelize::findCycleBackEdges()
|
||||
{
|
||||
// Search root-less cycles for back edges.
|
||||
VertexSeq unvisited = findUnvisitedVertices();
|
||||
// Sort cycle vertices so results are stable.
|
||||
// Skip sorting if it will take a long time.
|
||||
if (unvisited.size() < 100)
|
||||
sort(unvisited, VertexNameLess(network_));
|
||||
size_t back_edge_count = 0;
|
||||
VertexSet visited(graph_);
|
||||
for (Vertex *vertex : unvisited) {
|
||||
if (visited.find(vertex) == visited.end()) {
|
||||
VertexSet path_vertices(graph_);
|
||||
EdgeSeq path;
|
||||
FindBackEdgesStack stack;
|
||||
visited.insert(vertex);
|
||||
path_vertices.insert(vertex);
|
||||
stack.emplace(vertex, new VertexOutEdgeIterator(vertex, graph_));
|
||||
EdgeSet back_edges = findBackEdges(path, stack);
|
||||
for (Edge *back_edge : back_edges)
|
||||
roots_->insert(back_edge->from(graph_));
|
||||
back_edge_count += back_edges.size();
|
||||
}
|
||||
}
|
||||
debugPrint(debug_, "levelize", 1, "Found %zu cycle back edges", back_edge_count);
|
||||
}
|
||||
|
||||
// Find vertices in cycles that are were not accessible from roots.
|
||||
VertexSeq
|
||||
Levelize::findUnvisitedVertices()
|
||||
{
|
||||
VertexSeq unvisited;
|
||||
VertexIterator vertex_iter(graph_);
|
||||
while (vertex_iter.hasNext()) {
|
||||
Vertex *vertex = vertex_iter.next();
|
||||
if (!vertex->visited()
|
||||
&& search_pred_.searchFrom(vertex))
|
||||
unvisited.push_back(vertex);
|
||||
}
|
||||
return unvisited;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
VertexSeq
|
||||
Levelize::findToplologicalOrder()
|
||||
{
|
||||
Stats stats(debug_, report_);
|
||||
std::map<Vertex*, int> in_degree;
|
||||
|
||||
VertexIterator vertex_iter(graph_);
|
||||
while (vertex_iter.hasNext()) {
|
||||
Vertex *vertex = vertex_iter.next();
|
||||
if (search_pred_.searchFrom(vertex)) {
|
||||
VertexOutEdgeIterator edge_iter(vertex, graph_);
|
||||
while (edge_iter.hasNext()) {
|
||||
Edge *edge = edge_iter.next();
|
||||
Vertex *to_vertex = edge->to(graph_);
|
||||
if (search_pred_.searchThru(edge)
|
||||
&& search_pred_.searchTo(to_vertex))
|
||||
in_degree[to_vertex] += 1;
|
||||
if (edge->role() == TimingRole::latchDtoQ())
|
||||
latch_d_to_q_edges_.insert(edge);
|
||||
}
|
||||
// Levelize bidirect driver as if it was a fanout of the bidirect load.
|
||||
const Pin *pin = vertex->pin();
|
||||
if (graph_delay_calc_->bidirectDrvrSlewFromLoad(pin)
|
||||
&& !vertex->isBidirectDriver()) {
|
||||
Vertex *to_vertex = graph_->pinDrvrVertex(pin);;
|
||||
if (search_pred_.searchTo(to_vertex))
|
||||
in_degree[to_vertex] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::deque<Vertex*> queue;
|
||||
for (Vertex *root : *roots_)
|
||||
queue.push_back(root);
|
||||
|
||||
VertexSeq topo_order;
|
||||
while (!queue.empty()) {
|
||||
Vertex *vertex = queue.front();
|
||||
queue.pop_front();
|
||||
topo_order.push_back(vertex);
|
||||
if (search_pred_.searchFrom(vertex)) {
|
||||
VertexOutEdgeIterator edge_iter(vertex, graph_);
|
||||
while (edge_iter.hasNext()) {
|
||||
Edge *edge = edge_iter.next();
|
||||
Vertex *to_vertex = edge->to(graph_);
|
||||
if (search_pred_.searchThru(edge)
|
||||
&& search_pred_.searchTo(to_vertex)) {
|
||||
const auto &to_degree_itr = in_degree.find(to_vertex);
|
||||
int &to_in_degree = to_degree_itr->second;
|
||||
to_in_degree -= 1;
|
||||
if (to_in_degree == 0)
|
||||
queue.push_back(to_vertex);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Levelize bidirect driver as if it was a fanout of the bidirect load.
|
||||
const Pin *pin = vertex->pin();
|
||||
if (graph_delay_calc_->bidirectDrvrSlewFromLoad(pin)
|
||||
&& !vertex->isBidirectDriver()) {
|
||||
Vertex *to_vertex = graph_->pinDrvrVertex(pin);
|
||||
if (search_pred_.searchTo(to_vertex)) {
|
||||
const auto °ree_itr = in_degree.find(to_vertex);
|
||||
int &in_degree = degree_itr->second;
|
||||
in_degree -= 1;
|
||||
if (in_degree == 0)
|
||||
queue.push_back(to_vertex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (debug_->check("levelize", 1)) {
|
||||
VertexIterator vertex_iter(graph_);
|
||||
while (vertex_iter.hasNext()) {
|
||||
Vertex *vertex = vertex_iter.next();
|
||||
if (in_degree[vertex] != 0)
|
||||
debugPrint(debug_, "levelize", 2, "topological sort missing %s",
|
||||
vertex->to_string(this).c_str());
|
||||
}
|
||||
}
|
||||
if (debug_->check("levelize", 3)) {
|
||||
report_->reportLine("Topological sort");
|
||||
for (Vertex *vertex : topo_order)
|
||||
report_->reportLine("%s", vertex->to_string(this).c_str());
|
||||
}
|
||||
stats.report("Levelize topological sort");
|
||||
return topo_order;
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -285,14 +439,12 @@ Levelize::recordLoop(Edge *edge,
|
|||
debugPrint(debug_, "levelize", 2, "Loop edge %s (%s)",
|
||||
edge->to_string(this).c_str(),
|
||||
edge->role()->to_string().c_str());
|
||||
// Do not record loops if they have been invalidated.
|
||||
if (loops_) {
|
||||
EdgeSeq *loop_edges = loopEdges(path, edge);
|
||||
GraphLoop *loop = new GraphLoop(loop_edges);
|
||||
loops_->push_back(loop);
|
||||
if (variables_->dynamicLoopBreaking())
|
||||
sdc_->makeLoopExceptions(loop);
|
||||
}
|
||||
EdgeSeq *loop_edges = loopEdges(path, edge);
|
||||
GraphLoop *loop = new GraphLoop(loop_edges);
|
||||
loops_.push_back(loop);
|
||||
if (variables_->dynamicLoopBreaking())
|
||||
sdc_->makeLoopExceptions(loop);
|
||||
|
||||
// Record disabled loop edges so they can be cleared without
|
||||
// traversing the entire graph to find them.
|
||||
disabled_loop_edges_.insert(edge);
|
||||
|
|
@ -328,6 +480,53 @@ Levelize::loopEdges(EdgeSeq &path,
|
|||
return loop_edges;
|
||||
}
|
||||
|
||||
void
|
||||
Levelize::reportPath(EdgeSeq &path) const
|
||||
{
|
||||
bool first_edge = true;
|
||||
EdgeSeq::Iterator edge_iter(path);
|
||||
while (edge_iter.hasNext()) {
|
||||
Edge *edge = edge_iter.next();
|
||||
if (first_edge)
|
||||
report_->reportLine(" %s", edge->from(graph_)->to_string(this).c_str());
|
||||
report_->reportLine(" %s", edge->to(graph_)->to_string(this).c_str());
|
||||
first_edge = false;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
void
|
||||
Levelize::assignLevels(VertexSeq &topo_sorted)
|
||||
{
|
||||
for (Vertex *root : *roots_)
|
||||
setLevel(root, 0);
|
||||
for (Vertex *vertex : topo_sorted) {
|
||||
if (vertex->level() != -1) {
|
||||
VertexOutEdgeIterator edge_iter(vertex, graph_);
|
||||
while (edge_iter.hasNext()) {
|
||||
Edge *edge = edge_iter.next();
|
||||
Vertex *to_vertex = edge->to(graph_);
|
||||
if (search_pred_.searchThru(edge)
|
||||
&& search_pred_.searchTo(to_vertex))
|
||||
setLevel(to_vertex, max(to_vertex->level(),
|
||||
vertex->level() + level_space_));
|
||||
}
|
||||
// Levelize bidirect driver as if it was a fanout of the bidirect load.
|
||||
const Pin *pin = vertex->pin();
|
||||
if (graph_delay_calc_->bidirectDrvrSlewFromLoad(pin)
|
||||
&& !vertex->isBidirectDriver()) {
|
||||
Vertex *to_vertex = graph_->pinDrvrVertex(pin);
|
||||
if (search_pred_.searchTo(to_vertex))
|
||||
setLevel(to_vertex, max(to_vertex->level(),
|
||||
vertex->level() + level_space_));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// Make sure latch D input level is not the same as the Q level.
|
||||
// This is because the Q arrival depends on the D arrival and
|
||||
// to find them in parallel they have to be scheduled separately
|
||||
|
|
@ -346,84 +545,63 @@ Levelize::ensureLatchLevels()
|
|||
latch_d_to_q_edges_.clear();
|
||||
}
|
||||
|
||||
void
|
||||
Levelize::levelizeCycles()
|
||||
{
|
||||
// Find vertices that were not discovered by searching from all
|
||||
// graph roots.
|
||||
VertexSeq uncolored;
|
||||
VertexIterator vertex_iter(graph_);
|
||||
while (vertex_iter.hasNext()) {
|
||||
Vertex *vertex = vertex_iter.next();
|
||||
if (vertex->color() == LevelColor::white
|
||||
&& search_pred_->searchFrom(vertex))
|
||||
uncolored.push_back(vertex);
|
||||
}
|
||||
|
||||
// Sort cycle vertices so results are stable.
|
||||
sort(uncolored, VertexNameLess(network_));
|
||||
|
||||
VertexSeq::Iterator uncolored_iter(uncolored);
|
||||
while (uncolored_iter.hasNext()) {
|
||||
Vertex *vertex = uncolored_iter.next();
|
||||
// Only search from and assign root status to vertices that
|
||||
// previous searches did not visit. Otherwise "everybody is a
|
||||
// root".
|
||||
if (vertex->color() == LevelColor::white) {
|
||||
EdgeSeq path;
|
||||
roots_->insert(vertex);
|
||||
visit(vertex, 0, level_space_, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Levelize::invalid()
|
||||
{
|
||||
debugPrint(debug_, "levelize", 1, "levels invalid");
|
||||
clear();
|
||||
if (levelized_) {
|
||||
debugPrint(debug_, "levelize", 1, "levels invalid");
|
||||
levelized_ = false;
|
||||
levels_valid_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Levelize::invalidFrom(Vertex *vertex)
|
||||
{
|
||||
debugPrint(debug_, "levelize", 1, "level invalid from %s",
|
||||
vertex->to_string(this).c_str());
|
||||
VertexInEdgeIterator edge_iter(vertex, graph_);
|
||||
while (edge_iter.hasNext()) {
|
||||
Edge *edge = edge_iter.next();
|
||||
Vertex *from_vertex = edge->from(graph_);
|
||||
relevelize_from_->insert(from_vertex);
|
||||
if (levelized_) {
|
||||
debugPrint(debug_, "levelize", 1, "level invalid from %s",
|
||||
vertex->to_string(this).c_str());
|
||||
VertexInEdgeIterator edge_iter(vertex, graph_);
|
||||
while (edge_iter.hasNext()) {
|
||||
Edge *edge = edge_iter.next();
|
||||
Vertex *from_vertex = edge->from(graph_);
|
||||
relevelize_from_->insert(from_vertex);
|
||||
}
|
||||
relevelize_from_->insert(vertex);
|
||||
levels_valid_ = false;
|
||||
}
|
||||
relevelize_from_->insert(vertex);
|
||||
levels_valid_ = false;
|
||||
}
|
||||
|
||||
void
|
||||
Levelize::deleteVertexBefore(Vertex *vertex)
|
||||
{
|
||||
roots_->erase(vertex);
|
||||
relevelize_from_->erase(vertex);
|
||||
if (levelized_) {
|
||||
roots_->erase(vertex);
|
||||
relevelize_from_->erase(vertex);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Levelize::relevelizeFrom(Vertex *vertex)
|
||||
{
|
||||
debugPrint(debug_, "levelize", 1, "invalid relevelize from %s",
|
||||
vertex->to_string(this).c_str());
|
||||
relevelize_from_->insert(vertex);
|
||||
levels_valid_ = false;
|
||||
if (levelized_) {
|
||||
debugPrint(debug_, "levelize", 1, "invalid relevelize from %s",
|
||||
vertex->to_string(this).c_str());
|
||||
relevelize_from_->insert(vertex);
|
||||
levels_valid_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Levelize::deleteEdgeBefore(Edge *edge)
|
||||
{
|
||||
if (loop_edges_.hasKey(edge)) {
|
||||
// Relevelize if a loop edge is removed. Incremental levelization
|
||||
// fails because the DFS path will be missing.
|
||||
invalid();
|
||||
// Prevent refererence to deleted edge by clearLoopEdges().
|
||||
if (levelized_
|
||||
&& loop_edges_.hasKey(edge)) {
|
||||
disabled_loop_edges_.erase(edge);
|
||||
// Relevelize if a loop edge is removed. Incremental levelization
|
||||
// fails because the DFS path will be missing.
|
||||
levelized_ = false;
|
||||
levels_valid_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -441,13 +619,15 @@ Levelize::relevelize()
|
|||
for (Vertex *vertex : *relevelize_from_) {
|
||||
debugPrint(debug_, "levelize", 1, "relevelize from %s",
|
||||
vertex->to_string(this).c_str());
|
||||
if (search_pred_->searchFrom(vertex)) {
|
||||
if (search_pred_.searchFrom(vertex)) {
|
||||
if (isRoot(vertex)) {
|
||||
setLevel(vertex, 0);
|
||||
roots_->insert(vertex);
|
||||
}
|
||||
VertexSet visited(graph_);
|
||||
VertexSet path_vertices(graph_);
|
||||
EdgeSeq path;
|
||||
visit(vertex, vertex->level(), 1, path);
|
||||
visit(vertex, nullptr, vertex->level(), 1, visited, path_vertices, path);
|
||||
}
|
||||
}
|
||||
ensureLatchLevels();
|
||||
|
|
@ -455,6 +635,55 @@ Levelize::relevelize()
|
|||
relevelize_from_->clear();
|
||||
}
|
||||
|
||||
void
|
||||
Levelize::visit(Vertex *vertex,
|
||||
Edge *from,
|
||||
Level level,
|
||||
Level level_space,
|
||||
VertexSet &visited,
|
||||
VertexSet &path_vertices,
|
||||
EdgeSeq &path)
|
||||
{
|
||||
Pin *from_pin = vertex->pin();
|
||||
setLevel(vertex, level);
|
||||
level += level_space;
|
||||
visited.insert(vertex);
|
||||
path_vertices.insert(vertex);
|
||||
if (from)
|
||||
path.push_back(from);
|
||||
|
||||
if (search_pred_.searchFrom(vertex)) {
|
||||
VertexOutEdgeIterator edge_iter(vertex, graph_);
|
||||
while (edge_iter.hasNext()) {
|
||||
Edge *edge = edge_iter.next();
|
||||
Vertex *to_vertex = edge->to(graph_);
|
||||
if (search_pred_.searchThru(edge)
|
||||
&& search_pred_.searchTo(to_vertex)) {
|
||||
if (path_vertices.find(to_vertex) != path_vertices.end())
|
||||
// Back edges form feedback loops.
|
||||
recordLoop(edge, path);
|
||||
else if (visited.find(to_vertex) == visited.end()
|
||||
&& to_vertex->level() < level)
|
||||
visit(to_vertex, edge, level, level_space, visited, path_vertices, path);
|
||||
}
|
||||
if (edge->role() == TimingRole::latchDtoQ())
|
||||
latch_d_to_q_edges_.insert(edge);
|
||||
}
|
||||
// Levelize bidirect driver as if it was a fanout of the bidirect load.
|
||||
if (graph_delay_calc_->bidirectDrvrSlewFromLoad(from_pin)
|
||||
&& !vertex->isBidirectDriver()) {
|
||||
Vertex *to_vertex = graph_->pinDrvrVertex(from_pin);
|
||||
if (search_pred_.searchTo(to_vertex)
|
||||
&& (visited.find(to_vertex) == visited.end()
|
||||
|| to_vertex->level() < level))
|
||||
visit(to_vertex, nullptr, level, level_space, visited, path_vertices, path);
|
||||
}
|
||||
}
|
||||
path_vertices.erase(vertex);
|
||||
if (from)
|
||||
path.pop_back();
|
||||
}
|
||||
|
||||
bool
|
||||
Levelize::isDisabledLoop(Edge *edge) const
|
||||
{
|
||||
|
|
@ -465,11 +694,17 @@ void
|
|||
Levelize::setLevel(Vertex *vertex,
|
||||
Level level)
|
||||
{
|
||||
debugPrint(debug_, "levelize", 2, "set level %s %d",
|
||||
vertex->to_string(this).c_str(),
|
||||
level);
|
||||
if (vertex->level() != level) {
|
||||
if (observer_)
|
||||
observer_->levelChangedBefore(vertex);
|
||||
vertex->setLevel(level);
|
||||
}
|
||||
max_level_ = max(level, max_level_);
|
||||
if (level >= Graph::vertex_level_max)
|
||||
criticalError(616, "maximum logic level exceeded");
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -24,9 +24,12 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <stack>
|
||||
|
||||
#include "NetworkClass.hh"
|
||||
#include "SdcClass.hh"
|
||||
#include "GraphClass.hh"
|
||||
#include "SearchPred.hh"
|
||||
#include "StaState.hh"
|
||||
|
||||
namespace sta {
|
||||
|
|
@ -34,10 +37,13 @@ namespace sta {
|
|||
class SearchPred;
|
||||
class LevelizeObserver;
|
||||
|
||||
typedef std::pair<Vertex*,VertexOutEdgeIterator*> VertexEdgeIterPair;
|
||||
typedef std::stack<VertexEdgeIterPair> FindBackEdgesStack;
|
||||
|
||||
class Levelize : public StaState
|
||||
{
|
||||
public:
|
||||
explicit Levelize(StaState *sta);
|
||||
Levelize(StaState *sta);
|
||||
virtual ~Levelize();
|
||||
// Space between initially assigned levels that is filled in by
|
||||
// incremental levelization. Set level space before levelization.
|
||||
|
|
@ -54,40 +60,54 @@ public:
|
|||
// Vertices with no fanin edges.
|
||||
VertexSet *roots() { return roots_; }
|
||||
bool isRoot(Vertex *vertex);
|
||||
bool hasFanout(Vertex *vertex);
|
||||
// Reset to virgin state.
|
||||
void clear();
|
||||
// Edge is disabled to break combinational loops.
|
||||
bool isDisabledLoop(Edge *edge) const;
|
||||
// Only valid when levels are valid.
|
||||
GraphLoopSeq *loops() { return loops_; }
|
||||
GraphLoopSeq &loops() { return loops_; }
|
||||
// Set the observer for level changes.
|
||||
void setObserver(LevelizeObserver *observer);
|
||||
|
||||
protected:
|
||||
void levelize();
|
||||
void findRoots();
|
||||
void sortRoots(VertexSeq &roots);
|
||||
void levelizeFrom(VertexSeq &roots);
|
||||
void visit(Vertex *vertex, Level level, Level level_space, EdgeSeq &path);
|
||||
void levelizeCycles();
|
||||
void relevelize();
|
||||
void clearLoopEdges();
|
||||
void deleteLoops();
|
||||
void recordLoop(Edge *edge, EdgeSeq &path);
|
||||
EdgeSeq *loopEdges(EdgeSeq &path, Edge *closing_edge);
|
||||
VertexSeq sortedRootsWithFanout();
|
||||
VertexSeq findToplologicalOrder();
|
||||
void assignLevels(VertexSeq &topo_sorted);
|
||||
void recordLoop(Edge *edge,
|
||||
EdgeSeq &path);
|
||||
EdgeSeq *loopEdges(EdgeSeq &path,
|
||||
Edge *closing_edge);
|
||||
void ensureLatchLevels();
|
||||
void findBackEdges();
|
||||
EdgeSet findBackEdges(EdgeSeq &path,
|
||||
FindBackEdgesStack &stack);
|
||||
void findCycleBackEdges();
|
||||
VertexSeq findUnvisitedVertices();
|
||||
void relevelize();
|
||||
void visit(Vertex *vertex,
|
||||
Edge *from,
|
||||
Level level,
|
||||
Level level_space,
|
||||
VertexSet &visited,
|
||||
VertexSet &path_vertices,
|
||||
EdgeSeq &path);
|
||||
void setLevel(Vertex *vertex,
|
||||
Level level);
|
||||
void clearLoopEdges();
|
||||
void deleteLoops();
|
||||
void reportPath(EdgeSeq &path) const;
|
||||
|
||||
SearchPred *search_pred_;
|
||||
SearchPredNonLatch2 search_pred_;
|
||||
bool levelized_;
|
||||
bool levels_valid_;
|
||||
Level max_level_;
|
||||
Level level_space_;
|
||||
VertexSet *roots_;
|
||||
VertexSet *relevelize_from_;
|
||||
GraphLoopSeq *loops_;
|
||||
GraphLoopSeq loops_;
|
||||
EdgeSet loop_edges_;
|
||||
EdgeSet disabled_loop_edges_;
|
||||
EdgeSet latch_d_to_q_edges_;
|
||||
|
|
|
|||
|
|
@ -3565,8 +3565,10 @@ Search::ensureDownstreamClkPins()
|
|||
// as having downstream clk pins.
|
||||
ClkTreeSearchPred pred(this);
|
||||
BfsBkwdIterator iter(BfsIndex::other, &pred, this);
|
||||
for (Vertex *vertex : *graph_->regClkVertices())
|
||||
iter.enqueue(vertex);
|
||||
for (Vertex *vertex : *graph_->regClkVertices()) {
|
||||
if (!vertex->isConstant())
|
||||
iter.enqueue(vertex);
|
||||
}
|
||||
|
||||
while (iter.hasNext()) {
|
||||
Vertex *vertex = iter.next();
|
||||
|
|
|
|||
|
|
@ -314,7 +314,7 @@ report_loops()
|
|||
{
|
||||
Sta *sta = Sta::sta();
|
||||
Report *report = sta->report();
|
||||
for (GraphLoop *loop : *sta->graphLoops()) {
|
||||
for (GraphLoop *loop : sta->graphLoops()) {
|
||||
loop->report(sta);
|
||||
report->reportLineString("");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2073,7 +2073,7 @@ Sta::makeExceptionTo(PinSet *to_pins,
|
|||
ClockSet *to_clks,
|
||||
InstanceSet *to_insts,
|
||||
const RiseFallBoth *rf,
|
||||
const RiseFallBoth *end_rf)
|
||||
const RiseFallBoth *end_rf)
|
||||
{
|
||||
return sdc_->makeExceptionTo(to_pins, to_clks, to_insts, rf, end_rf);
|
||||
}
|
||||
|
|
@ -3529,7 +3529,7 @@ Sta::vertexLevel(Vertex *vertex)
|
|||
return vertex->level();
|
||||
}
|
||||
|
||||
GraphLoopSeq *
|
||||
GraphLoopSeq &
|
||||
Sta::graphLoops()
|
||||
{
|
||||
ensureLevelized();
|
||||
|
|
|
|||
|
|
@ -366,16 +366,16 @@ proc check_percent { cmd_arg arg } {
|
|||
set ::sta_continue_on_error 0
|
||||
|
||||
define_cmd_args "include" \
|
||||
{[-echo] filename [> filename] [>> filename]}
|
||||
{[-e|-echo] [-v|-verbose] filename [> filename] [>> filename]}
|
||||
|
||||
# Tcl "source" command analog to support -echo and -verbose return values.
|
||||
proc_redirect include {
|
||||
parse_key_args "include" args keys {-encoding} flags {-echo -verbose}
|
||||
parse_key_args "include" args keys {-encoding} flags {-e -echo -v -verbose}
|
||||
if { [llength $args] != 1 } {
|
||||
cmd_usage_error "include"
|
||||
}
|
||||
set echo [info exists flags(-echo)]
|
||||
set verbose [info exists flags(-verbose)]
|
||||
set echo [expr [info exists flags(-echo)] || [info exists flags(-e)]]
|
||||
set verbose [expr [info exists flags(-verbose)] || [info exists flags(-v)]]
|
||||
set filename [file nativename [lindex $args 0]]
|
||||
include_file $filename $echo $verbose
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue