Tcl · Module 23
ECO Scripting
Late in the flow you change a handful of cells rather than rerun the flow. The change is easy; the record of why it was made is what keeps the design signable.
Advanced19 min readtcledapart-iiphysical-designecosignoff
Part II · Module 23 · Chapter 23.6 · ECO Scripting
An ECO — Engineering Change Order — is a targeted change made late in the flow, after placement and routing, instead of rerunning synthesis.
The reason is economics. Re-synthesising to fix one path throws away a placed, routed, timing-closed database and produces a different one, with different problems, days later. Resizing three buffers keeps everything else exactly as it was.
The cost is bookkeeping, and that is what this chapter is about.
1. The problem an ECO creates
So the engineering content of an ECO script is not the change. It is the log.
2. A mock to make it concrete
There is no EDA tool here, so the examples below use a small teaching mock: instances with a reference cell name, and a size_cell that swaps the reference. Real tool command names differ; the shape of the wrapper around them does not.
proc size_cell {name newref} {
if {![info exists ::pd::inst($name)]} { error "no such instance: $name" {} {ECO NOINST} }
set old $::pd::inst($name)
set ::pd::inst($name) $newref
return $old
}Two things it does that a real ECO command may not: it validates the target and it returns the old value. Both matter in §4 and §5.
The starting state:
=== before ===
u_core/u_buf1 BUFX1
u_core/u_buf2 BUFX1
u_mem/u_and AND2X13. The wrapper that cannot make an unlogged change
namespace eval ::eco { variable log {} }
proc eco_size {name newref why} {
if {[string trim $why] eq ""} { error "an ECO change needs a reason" {} {ECO NOREASON} }
set old [size_cell $name $newref]
lappend ::eco::log [list size $name $old $newref $why]
return $old
}Four lines, and every one of them is doing something deliberate.
The reason is a required positional argument. Not an option with a default, not a comment in the script. You cannot call eco_size without supplying one, which means the script cannot contain an unexplained change.
The reason is checked, not just accepted. [string trim $why] eq "" rejects "" and " ", because a required argument you can satisfy with whitespace is not required.
The reason is checked before the change. If validation came after size_cell, a rejected change would already have happened — and the log would be missing an entry for a cell that was modified. Order is the difference between a refusal and a corruption.
The log captures the old value as well as the new. size_cell returns it, so the log entry is {size name old new why} — enough to reverse the change, which is the question asked when the ECO turns out to be the wrong idea.
Note also where the log lives: a variable inside ::eco, not a global. Chapter 11.7's namespace discipline, applied to the one piece of state that must not be accidentally clobbered by another sourced script.
4. Making the changes
eco_size u_core/u_buf1 BUFX4 {fixing setup on the u_io/u_sync path; 0.07 short at the slow corner}
eco_size u_mem/u_and AND2X2 {load increased after the buffer change above}
puts " [eco_report] change(s)"=== the ECO ===
size u_core/u_buf1 BUFX1 -> BUFX4 fixing setup on the u_io/u_sync path; 0.07 short at the slow corner
size u_mem/u_and AND2X1 -> AND2X2 load increased after the buffer change above
2 change(s)=== after ===
u_core/u_buf1 BUFX4
u_core/u_buf2 BUFX1
u_mem/u_and AND2X2Read the two reasons, because they are the point.
The first names the path, the amount, and the corner — u_io/u_sync, 0.07 ns, the slow corner. Six months later, that is enough for someone to find out whether the change is still needed, which is exactly what they will want to know. "fix timing" would not be.
The second says why it follows from the first. Sizing up u_core/u_buf1 increased the load on whatever drives it, and u_mem/u_and is the knock-on. An ECO is rarely one change; recording the dependency is what stops someone reverting the first and leaving the second stranded.
5. Both refusals
puts " [catch {eco_size u_core/u_buf2 BUFX4 {}} e] / $e"
puts " [catch {eco_size u_core/u_bufX BUFX4 {some reason}} e] / $e"=== an unjustified change is refused ===
1 / an ECO change needs a reason
=== and a typo'd instance ===
1 / no such instance: u_core/u_bufXThe first is the discipline from §3: no reason, no change. Note that u_core/u_buf2 is still BUFX1 in the "after" report above — the refusal happened before the modification, as designed.
The second is the other half, and it is the one people forget.
u_core/u_bufX does not exist. In this mock, size_cell raises. In a real tool, a command given a name that matches nothing frequently succeeds and does nothing — it iterates over an empty collection, and an empty collection is not an error. That is Chapter 18.8's point about empty collections and Chapter 23.1 §5's about commands succeeding on inputs that cannot work, arriving together in the worst possible place: an ECO you believe you applied, which is not in the database, on a path you have stopped worrying about.
So an ECO wrapper validates the target. The cost is one info exists; the alternative is a silent no-op.
6. The script, the log, and the netlist
An ECO script that stops at making the changes has done half the job. The full sequence:
source eco/eco_lib.tcl ;# eco_size and the log live here
source eco/changes_rev3.tcl ;# the changes themselves, nothing else
if {[eco_report] == 0} {
puts stderr " the ECO script made no changes — check the instance names"
exit 1
}
place_eco_cells
route_eco_nets
update_timing
report_constraint -all_violators > reports/post_eco_violations.rpt
write_netlist netlist/top_eco_rev3.v
eco_log_to_file eco/applied_rev3.logplace_eco_cells, route_eco_nets, write_netlist and eco_log_to_file are placeholders for whatever your tool and your project call those steps — only eco_size and eco_report are defined in this chapter. Five things in the shape are deliberate.
The changes are in their own file, separate from the library. The file is the record of this revision's intent, and it can be read, reviewed and diffed on its own.
Zero changes is an error. A script that resolved no instance names runs perfectly and produces nothing, which is the failure mode §5 just described, at script scale. Chapter 21.2 made the same argument about an empty file list.
Placement and routing are repaired. A resized cell is physically bigger; it may overlap its neighbour, and its new pins need connecting. An ECO is not finished when the netlist changes.
Timing is re-reported, not assumed. The reason for the ECO was a timing number; whether it actually moved is a measurement. And update_timing first, for Chapter 22.5's reason — the whole argument of Module 22 was that a stale graph returns a plausible report rather than an error.
The netlist and the log are written together, named for the same revision. top_eco_rev3.v and applied_rev3.log. That pairing is the reconciliation §1 said was the only thing that keeps the design signable.
7. Where this idea came from
Worth naming explicitly, because it is the thread running through Part II.
Chapter 20.5 argued that a set_false_path is a claim — that no real operation uses the path — and that the claim needs a reason recorded next to it, because nobody can re-derive it later and the cost of a wrong one is a path that is never checked again.
This chapter is that argument made executable. eco_size does not ask you to record a reason as a matter of good practice. It refuses to work without one.
That is the difference between a convention and a mechanism, and it is the most useful thing to take from Module 23: the discipline you can rely on is the discipline the script enforces.
8. Exercises
Exercise 1. Why make an ECO rather than rerun synthesis?
Exercise 2. What problem does an ECO create, and what solves it?
Exercise 3. Why is the reason a required positional argument rather than an option?
Exercise 4. Why is the reason validated before the change is made?
Exercise 5. Why does the log record the old reference as well as the new?
Exercise 6. What is wrong with "fix timing" as an ECO reason?
Exercise 7. Why does eco_size check that the instance exists?
Exercise 8. Why is "the ECO script made zero changes" an error rather than a no-op?
Exercise 9. Why are the netlist and the ECO log written with matching revision names?
Where this fits
Part of the Tcl curriculum.
