Skip to content
VLSI Mentor

Tcl · Module 21

Deriving the Top Module From the RTL

The top module name appears in the script, the constraints and the output filenames. Deriving it from the sources turns three places that can disagree into one check that cannot.

Intermediate16 min readtcledapart-iisynthesisregexprtlvalidation

Part II · Module 21 · Chapter 21.3 · Deriving the Top Module From the RTL

The top module name shows up in several places: the elaborate command, the output netlist filename, the report paths, often the constraints. Written as a literal in each, they can disagree — and the symptom is a run that elaborates one design and writes a netlist named after another.

The sources already know the answer. Extracting it is one regexp.

1. Finding the modules

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc modules_in {path} {
    set fp [open $path r]
    set text [read $fp]
    close $fp
    set found {}
    foreach line [split $text "\n"] {
        if {[regexp {^\s*module\s+(\w+)} $line -> name]} { lappend found $name }
    }
    return $found
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    alu.v        defines: alu
    cpu_top.v    defines: cpu_top
    fifo.v       defines: fifo, fifo_ctrl

    all modules : alu, cpu_top, fifo, fifo_ctrl

One pattern, ^\s*module\s+(\w+), read as: start of line, optional whitespace, the word module, whitespace, then capture the identifier. The -> is the discard argument for the whole match, so name gets the first group — the idiom from Chapter 13.4.

Note fifo.v defines two modules. One file, one module is a convention, not a rule, and a script that assumes it will be wrong eventually.

2. The obvious derivation

The top module is the one nothing else instantiates. So: collect every module defined, collect every module instantiated, and subtract.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc instantiated_modules {text defined} {
    set out {}
    foreach line [split $text "\n"] {
        set l [string trim $line]
        if {[regexp {^\s*(module|input|output|wire|reg|always|assign|endmodule|//|`)} $l]} { continue }
        # strip a parameter block first: it can contain nested parentheses
        regsub {#\s*\([^()]*(\([^()]*\)[^()]*)*\)} $l {} l
        if {[regexp {^(\w+)\s+(\w+)\s*\(} $l -> type inst]} {
            if {[lsearch -exact $defined $type] >= 0 && [lsearch -exact $out $type] < 0} {
                lappend out $type
            }
        }
    }
    return $out
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    defined      : alu, cpu_top, fifo, fifo_ctrl
    instantiated : alu, fifo
    candidates for top (defined but never instantiated): cpu_top, fifo_ctrl

3. Why that does not pick the top

Two candidates. cpu_top is the real top; fifo_ctrl is an orphan — a module defined in fifo.v that nothing in this design uses.

4. A regexp over RTL is a heuristic

Worth stating plainly, because the limits matter.

Building the instantiation matcher took three attempts. The first missed this line entirely:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
fifo #(.DEPTH(8)) u_fifo (.clk(clk), .rst_n(reset_n));

because the parameter block #(.DEPTH(8)) contains nested parentheses, which a simple [^)]* cannot span. The obvious fix — a greedy #\s*\(.*\) — then consumed the whole line to the final ). The working version allows one level of nesting.

That is a small example of a general truth: Verilog is not a regular language, and a regexp is not a parser. This approach will also be confused by instantiations inside generate blocks, by names split across lines, by module names appearing in comments or strings, and by preprocessor conditionals.

5. The useful form: declare, then check

Invert it. Declare the top — in one place, where it is reviewable — and use the derivation to check it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc check_top {declared candidates defined} {
    if {[lsearch -exact $defined $declared] < 0} {
        error "top module '$declared' is not defined in any source file" \
              {} [list TOP UNDEFINED $declared]
    }
    if {[lsearch -exact $candidates $declared] < 0} {
        error "top module '$declared' is instantiated by something else — is it really the top?" \
              {} [list TOP INSTANTIATED $declared]
    }
    set others {}
    foreach c $candidates { if {$c ne $declared} { lappend others $c } }
    if {[llength $others]} {
        puts "    warning: also uncalled, and not the declared top: [join $others {, }]"
    }
    return $declared
}

Three outcomes, all captured:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    declared top 'cpu_top':
    warning: also uncalled, and not the declared top: fifo_ctrl
      -> cpu_top

    declared top 'alu' (it IS instantiated):
      -> 1 / top module 'alu' is instantiated by something else — is it really the top?

    declared top 'cpu_tpo' (a typo):
      -> 1 / top module 'cpu_tpo' is not defined in any source file

Each one is a real mistake caught in the first second:

  • The typo — cpu_tpo is not defined anywhere. Without the check, the tool would be asked to elaborate a module that does not exist.
  • The wrong top — alu is instantiated by cpu_top, so elaborating it synthesises a sub-block in isolation. Easy to do when copying a script between blocks, and the result looks plausible.
  • The warning — fifo_ctrl is uncalled. Not an error, but worth knowing: it is either dead code or a missing instantiation.

That last one is a genuine design finding that fell out of a consistency check.

6. Using it in the script

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set TOP   cpu_top                       ;# declared once, here
set files [check_files $sources]

set text  [read_all $sources]
set def   [lsort -unique [defined_modules $text]]
set inst  [lsort -unique [instantiated_modules $text $def]]
set tops  {}
foreach m $def { if {[lsearch -exact $inst $m] < 0} { lappend tops $m } }

check_top $TOP $tops $def

elaborate_top $TOP                      ;# a tool command
write_netlist [file join $rundir netlist ${TOP}_mapped.v]

$TOP now appears once as a literal and everywhere else as a variable — including in the output filename, so the netlist cannot be named after a different design than the one elaborated.

7. Exercises

Exercise 1. Write the regexp that finds a module definition, and explain the ->.

Exercise 2. fifo.v defined two modules. Why does that matter?

Exercise 3. How is the "obvious" top derivation defined, and why did it return two candidates?

Exercise 4. Why is an orphan module normal rather than a bug?

Exercise 5. What broke the first instantiation pattern, and what does that illustrate?

Exercise 6. Why declare the top and check it rather than derive it?

Exercise 7. Name the three outcomes check_top distinguishes.

Where this fits

Part of the Tcl curriculum.