Tcl · Module 19
Finding Unconnected Ports
Some unconnected ports are deliberate and some are mistakes, and a list that does not tell them apart gets ignored. The useful query is the one that reports only the surprising ones.
Advanced16 min readtcledapart-iiconnectivitychecksqueriesquality
Part II · Module 19 · Chapter 19.3 · Finding Unconnected Ports
A top-level port that connects to nothing is a defect with an unusual cost curve. At RTL it is a minute's work. After synthesis it is an hour of tracing. After tapeout it is a respin.
The query that finds it is five lines. The entire value is in running it early, and automatically, so nobody has to remember to look.
1. Every port, and what it connects to
Start by making connectivity visible:
foreach_in_collection p [get_ports *] {
set n [get_attribute $p net]
puts [format " %-12s %-4s %s" $p [get_attribute $p direction] \
[expr {$n eq "" ? "<UNCONNECTED>" : $n}]]
}=== every port, with the net it connects to ===
clk in clk_net
reset_n in reset_net
d_in[0] in d0_net
d_in[1] in d1_net
d_out[0] out q0_net
d_out[1] out <UNCONNECTED>
test_mode in <UNCONNECTED>
scan_en in <UNCONNECTED>Three of eight connect to nothing. On a real design this table has hundreds of rows, which is exactly why it has to become a query rather than something you read.
2. The query
proc unconnected_ports {} {
set bad {}
foreach_in_collection p [get_ports *] {
if {[get_attribute $p net] eq ""} { lappend bad $p }
}
return $bad
}
set bad [unconnected_ports]
puts " [llength $bad] unconnected: [join $bad {, }]"=== the query that finds them ===
3 unconnected: d_out[1], test_mode, scan_enFive lines, and now the problem: three findings, and only one of them is a bug.
3. Why the raw list gets ignored
This is the failure mode of most automated checks. A list that reports three things when one matters trains people to skim it; a list that reports two hundred trains them to switch it off.
test_mode and scan_en are almost certainly deliberate — test ports that are unused in this configuration. Reporting them every run, forever, is how a useful check becomes noise.
So the query is not the hard part. Separating the expected from the surprising is. Two ways to do it, and they are complementary.
4. Cut one: direction
The better first cut, because it needs no configuration:
foreach_in_collection p [get_ports *] {
if {[get_attribute $p net] ne ""} { continue }
set d [get_attribute $p direction]
set verdict [expr {$d eq "out" ? "SUSPICIOUS - an output driving nothing" \
: "often intentional - an unused input"}]
puts [format " %-12s %-4s %s" $p $d $verdict]
}=== classify by direction, which is the better first cut ===
d_out[1] out SUSPICIOUS - an output driving nothing
test_mode in often intentional - an unused input
scan_en in often intentional - an unused input5. Cut two: an allowlist
Direction is a good heuristic and not a rule — an unused input can be a bug too, when it is the enable nobody wired up. The complement is to declare what you expect:
set expected {test_mode scan_en}
set real {}
foreach p $bad { if {[lsearch -exact $expected $p] < 0} { lappend real $p } }
puts " known-unused (declared) : [join $expected {, }]"
puts " genuinely unexpected : [expr {[llength $real] ? [join $real {, }] : "<none>"}]"=== but not every one is a bug ===
known-unused (declared) : test_mode, scan_en
genuinely unexpected : d_out[1]
-> this is the one to investigateOne finding. The same one the direction rule flagged — and the two cuts agreeing is itself reassuring.
6. The check worth automating
Putting it together, as something a flow runs every time:
proc check_unconnected_ports {{expected {}}} {
set problems {}
foreach_in_collection p [get_ports *] {
if {[get_attribute $p net] ne ""} { continue }
if {[lsearch -exact $expected $p] >= 0} { continue }
lappend problems [list $p [get_attribute $p direction]]
}
foreach entry $problems {
lassign $entry name dir
set sev [expr {$dir eq "out" ? "ERROR" : "WARNING"}]
puts stderr [format " %-7s unconnected %-3s port: %s" $sev $dir $name]
}
set errs 0
foreach entry $problems { if {[lindex $entry 1] eq "out"} { incr errs } }
return $errs
}
exit [expr {[check_unconnected_ports {test_mode scan_en}] > 0 ? 1 : 0}]Four design decisions, each from Part I:
- Diagnostics go to
stderr, so the check can run in a pipeline whose stdout is the real report — Chapter 14.9 §2. - Outputs are errors, inputs are warnings. Different severities mean the check can fail a build on the serious case without blocking on the routine one.
- It returns a count and the caller exits on it, rather than exiting itself — Chapter 15.4 §3.
- The allowlist is a parameter, so the same procedure serves every block with its own exceptions.
7. When to run it
As early as possible. The query works on any loaded design, so it belongs immediately after the netlist is read — before synthesis, before anything expensive.
On every run, not on request. A check somebody has to remember is a check that gets run after the problem is expensive.
With a non-zero exit status when it finds something serious, so the surrounding automation stops rather than continuing to the next stage — the whole argument of Chapter 15.4 §6.
8. Exercises
Exercise 1. Why is a dangling port much cheaper to fix early?
Exercise 2. Write the five-line query that lists unconnected ports.
Exercise 3. Why does the raw list get ignored in practice?
Exercise 4. Why is an unconnected output more suspicious than an unconnected input?
Exercise 5. Give an example of an unconnected input that is a real bug.
Exercise 6. Why keep a reason next to each allowlist entry?
Exercise 7. The check reports no problems on a design that was never loaded. What has gone wrong, and what is the fix?
Where this fits
Part of the Tcl curriculum.
