Skip to content
VLSI Mentor

Makefile · Part II · Module 7 · EDA Tool Integration

Synthesis and STA Automation

Synthesis and timing analysis are driven by Tcl, not by command-line options, and they answer in a report file rather than an exit status. That changes what the Makefile is responsible for: not just composing a command, but reading the answer. This chapter builds the flow — and includes the moment my own slack gate printed a timing violation and let the build pass.

Advanced28 min readMakefileGNU MakePart IISynthesisSTA

Up to now every tool in this track has been a simulator, invoked with options and asked a yes-or-no question. Synthesis and static timing analysis work differently in two ways that both matter to a Makefile.

They are driven by Tcl. dc_shell -f synth.tcl hands the tool a program, not a list of flags, so most of the configuration lives in a script the tool interprets — which is why Chapter 7.3 came first.

And they answer in a report file. A simulation's testbench at least prints UVM_ERROR : 0; a synthesis run prints pages of statistics and leaves the number you care about — the worst slack — in a timing report. Nothing in the exit status tells you whether the design made timing.

1. A Tcl Script the Tool Runs

Here is a synthesis script. It is a real Tcl program: the top third is ordinary Tcl, and only the lower part calls tool commands.

Azvya Education Pvt. Ltd.VLSI Mentor
syn/synth.tcl
# Parameters come from the environment, so the Makefile owns them.
set design   [expr {[info exists ::env(DESIGN)]  ? $::env(DESIGN)  : "axi_lite_slave"}]
set period   [expr {[info exists ::env(PERIOD)]  ? $::env(PERIOD)  : 10.0}]
set rundir   [expr {[info exists ::env(RUNDIR)]  ? $::env(RUNDIR)  : "build/syn"}]
set filelist [expr {[info exists ::env(FILELIST)]? $::env(FILELIST): "filelists/all.f"}]

file mkdir $rundir
puts "  \[synth\] design=$design period=${period}ns rundir=$rundir"

# Read the SAME filelist the simulation uses - one list, many readers (5.2).
set srcs {}
set fh [open $filelist r]
foreach line [split [read $fh] \n] {
    set line [string trim $line]
    if {$line eq "" || [string match "//*" $line] || [string match "+*" $line]} { continue }
    lappend srcs $line
}
close $fh
puts "  \[synth\] [llength $srcs] source file(s) from $filelist"

read_verilog $srcs
current_design $design
link
create_clock clk -period $period
set_input_delay  1.0 -clock clk [list rst_n wr_en rd_en]
set_output_delay 1.0 -clock clk [list rd_data err]
compile_ultra -no_autoungroup
report_timing -file [file join $rundir timing.rpt]
report_area   -file [file join $rundir area.rpt]
write -format verilog -hierarchy -output [file join $rundir ${design}_netlist.v]
exit 0

Two design decisions in there are worth defending.

Parameters arrive through the environment, not as hardcoded values. The Makefile sets DESIGN, PERIOD, RUNDIR and FILELIST, and the script falls back to sensible defaults so it can also be run by hand. That is Chapter 7.3 §2's environment channel, chosen because paths contain characters you do not want re-split.

The script reads the same filelist as the simulation. A synthesis flow with its own source list is a flow that will eventually synthesise a different design from the one you verified. One list, many readers — Chapter 5.2's argument, now paying off across tools.

Azvya Education Pvt. Ltd.VLSI Mentor
Actual output — the mock shell running the real script
  [synth] design=axi_lite_slave period=10.0ns rundir=build/syn
  [synth] 2 source file(s) from filelists/all.f
  dc> read_verilog (2 file(s))
  dc> current_design axi_lite_slave
  dc> link
  dc> create_clock clk -period 10.0
  dc> set_input_delay 1.0 -clock clk {rst_n wr_en rd_en}
  dc> set_output_delay 1.0 -clock clk {rd_data err}
  dc> compile_ultra -no_autoungroup
  dc> report_timing -> build/syn/timing.rpt (slack 2.600)
  dc> report_area -> build/syn/area.rpt
  dc> write -> build/syn/axi_lite_slave_netlist.v
  [synth] done

The [synth] lines are the script talking; the dc> lines are the stubs. Note that the filelist parsing found 2 source files — that is real Tcl reading the real file.

2. Driving It From Make

The Makefile's job is to set the parameters, put the outputs somewhere findable, and react to the answer.

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile — the synthesis stage
DESIGN   ?= axi_lite_slave
PERIOD   ?= 10.0
DC_SHELL ?= dc_shell
SYNDIR    = $(BUILD)/syn/$(DESIGN)/$(PERIOD)

export DESIGN PERIOD FILELIST

.PHONY: synth
synth: $(SYNDIR)/timing.rpt

$(SYNDIR)/timing.rpt: $(FILELIST) syn/synth.tcl
	@mkdir -p $(@D)
	@RUNDIR=$(SYNDIR) $(DC_SHELL) -f syn/synth.tcl > $(SYNDIR)/synth.log 2>&1 \
	   || { echo "  [synth] TOOL FAILED - see $(SYNDIR)/synth.log"; exit 1; }
	@echo "  [synth] $(DESIGN) @ $(PERIOD)ns -> $(SYNDIR)"

Three things carried over from earlier chapters. The tool is named by a variable, never found on PATH (Chapter 7.1 §6). The run directory carries everything that distinguishes one run from another — here the design and the clock period, because synthesising the same design at 8 ns and 10 ns gives two different results that must not overwrite each other (Chapter 5.3 §6). And the whole tool log is captured to a file rather than left on the terminal, because a synthesis log is thousands of lines.

The prerequisites are the filelist and the script. Edit either and synthesis re-runs; edit neither and it does not. For a real flow you would add the constraints file and, as Chapter 5.2 §7 showed, the sources the filelist names.

3. The Tool Exits 0 on a Timing Violation

Now the behaviour that decides the shape of everything else. Ask for a clock the design cannot make — 7 ns instead of 10:

Azvya Education Pvt. Ltd.VLSI Mentor
Actual output — timing.rpt at PERIOD=7.0
  Startpoint: clk
  Endpoint: count_reg
  data arrival time    7.400
  data required time  7.000
  slack (VIOLATED)         -0.400
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
  dc_shell exit status = 0

Negative slack, the word VIOLATED in the report, and an exit status of zero.

This is the same asymmetry Chapter 5.1 §4 and Chapter 5.4 §5 measured for the simulator, and for the same reason: from the tool's point of view nothing went wrong. It was asked to synthesise a design against a constraint, and it did. "The design did not make timing" is a result, not a tool error — and a tool that exited non-zero every time a design missed timing would be unusable, because missing timing is the normal state of affairs for most of a project.

So the Makefile has to read the report. There is no alternative.

4. A Slack Gate

The gate is a small Tcl script, which keeps it usable both inside the tool and outside it:

Azvya Education Pvt. Ltd.VLSI Mentor
scripts/slack.tcl
# Usage: slack.tcl <timing.rpt> [threshold]
# Exits 0 if the worst slack is >= threshold, 1 if not, 2 if unreadable.
if {[llength $argv] < 1} { puts stderr "usage: slack.tcl <timing.rpt> \[threshold\]"; exit 2 }
set rpt [lindex $argv 0]
set thr [expr {[llength $argv] > 1 ? [lindex $argv 1] : 0.0}]
if {![file exists $rpt]} { puts stderr "slack.tcl: no such report: $rpt"; exit 2 }
set fh [open $rpt r]; set txt [read $fh]; close $fh
set worst ""
foreach line [split $txt \n] {
    if {[regexp {slack\s+\((MET|VIOLATED)\)\s+(-?[0-9.]+)} $line -> verdict value]} {
        if {$worst eq "" || $value < $worst} { set worst $value; set wverdict $verdict }
    }
}
if {$worst eq ""} { puts stderr "slack.tcl: no slack line found in $rpt"; exit 2 }
puts [format "worst slack %s ns (%s), threshold %s" $worst $wverdict $thr]
if {$worst < $thr} { exit 1 } else { exit 0 }

It walks the file line by line and matches each line, rather than reading the whole file and anchoring with ^ — which is Chapter 7.3 §4's lesson applied rather than re-learned. And it tracks the worst slack rather than the first, because a real report lists many paths.

Four outcomes, all measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
--- the 10 ns run (slack +2.600) ---
  rc=0 :: worst slack 2.600 ns (MET), threshold 0.0
--- the 7 ns run (slack -0.400) ---
  rc=1 :: worst slack -0.400 ns (VIOLATED), threshold 0.0
--- a report with no slack line ---
  rc=2 :: slack.tcl: no slack line found in /tmp/empty.rpt
--- a missing report ---
  rc=2 :: slack.tcl: no such report: /tmp/nosuch.rpt

The two rc=2 cases are the ones people leave out, and they are the ones that matter most on a bad day. A truncated report because the disk filled, or a missing report because the tool died in elaboration, must not be reported as "timing met". An unreadable answer is not a passing answer — the same principle as Chapter 6.4 §3's INCOMPLETE state.

A SLACK_MIN threshold above zero is worth having, too: make synth SLACK_MIN=0.2 demands 200 ps of margin, which is how you keep a design off the edge of its constraint while the back end still has work to do.

5. My Own Gate Printed the Violation and Passed

Here is the gate wired into the recipe, as I first wrote it:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile — the version with the bug
	@tclsh scripts/slack.tcl $@ $(SLACK_MIN) | sed 's/^/      /' \
	   || { echo "  [synth] TIMING NOT MET at $(PERIOD)ns"; exit 1; }
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output — make synth PERIOD=7.0
  [synth] axi_lite_slave @ 7.0ns -> build/syn/axi_lite_slave/7.0
      worst slack -0.400 ns (VIOLATED), threshold 0.0
  make rc=0

Read that carefully. The gate ran. The gate printed VIOLATED. The gate's message is right there on the screen. And make exited 0.

The || was testing the exit status of the pipeline, and the last command in that pipeline is sed, which succeeded at indenting the text. This is exactly Chapter 4.3 §4's hazard and exactly what Chapter 7.3 §5 measured — and I wrote it anyway, one chapter later, because the | sed was there to make the output pretty and pretty-printing does not feel like a logic change.

Three variants, measured:

Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
--- piped (the bug) ---
   worst slack -0.400 ns (VIOLATED), threshold 0.0
   (recipe continued - gate did NOT fire)
  make rc=0
--- with pipefail ---
   worst slack -0.400 ns (VIOLATED), threshold 0.0
   GATE FIRED
make: *** [pipefail] Error 1
  make rc=2
--- no pipe at all ---
   worst slack -0.400 ns (VIOLATED), threshold 0.0
   GATE FIRED
make: *** [nopipe] Error 1
  make rc=2

The fix used here keeps the indentation and loses the pipe, by capturing the status before printing anything:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile — the gate, fixed
	@tclsh scripts/slack.tcl $@ $(SLACK_MIN) > $(@D)/slack.txt; st=$$?; \
	 sed 's/^/      /' $(@D)/slack.txt; \
	 [ $$st -eq 0 ] || { echo "  [synth] TIMING NOT MET at $(PERIOD)ns (gate rc=$$st)"; exit 1; }
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output — 10 ns then 7 ns
$ make synth
  [synth] axi_lite_slave @ 10.0ns -> build/syn/axi_lite_slave/10.0
      worst slack 2.600 ns (MET), threshold 0.0
  make rc=0

$ make synth PERIOD=7.0
  [synth] axi_lite_slave @ 7.0ns -> build/syn/axi_lite_slave/7.0
      worst slack -0.400 ns (VIOLATED), threshold 0.0
  [synth] TIMING NOT MET at 7.0ns (gate rc=1)
make: *** [build/syn/axi_lite_slave/7.0/timing.rpt] Error 1
  make rc=2

It also writes the verdict to slack.txt, which turns out to be useful on its own: a one-line file per run that a report can collect without re-parsing the full timing report.

6. Never Make the Report the Target

The fixed gate exits non-zero — and the flow still has a hole. Ask for the same failing run again:

Azvya Education Pvt. Ltd.VLSI Mentor
Actual output — make synth PERIOD=7.0, immediately after the failure above
make: Nothing to be done for `synth'.
  make rc=0

Exit 0, nothing run, and the timing violation has been forgotten.

This is Chapter 5.4 §6's bug arriving in a new place, for the same reason: timing.rpt is the target, the gate's failure does not remove it, and a target that exists and is newer than its prerequisites is up to date by definition. The second invocation never ran the gate at all.

The fix is 5.4's fix. Make the verdict the target and let the report be a product of getting there:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile — the stamp is the target
synth: $(SYNDIR)/timing.ok

$(SYNDIR)/timing.ok: $(FILELIST) syn/synth.tcl
	@mkdir -p $(@D)
	@RUNDIR=$(SYNDIR) $(DC_SHELL) -f syn/synth.tcl > $(SYNDIR)/synth.log 2>&1 \
	   || { echo "  [synth] TOOL FAILED - see $(SYNDIR)/synth.log"; exit 1; }
	@tclsh scripts/slack.tcl $(SYNDIR)/timing.rpt $(SLACK_MIN) > $(@D)/slack.txt; st=$$?; \
	 sed 's/^/      /' $(@D)/slack.txt; \
	 [ $$st -eq 0 ] || { echo "  [synth] TIMING NOT MET at $(PERIOD)ns"; exit 1; }
	@touch $@
	@echo "  [synth] $(DESIGN) @ $(PERIOD)ns MET -> $(SYNDIR)"

Measured, in sequence — and the third invocation is the one that matters:

Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
$ make synth
      worst slack 2.600 ns (MET), threshold 0.0
  [synth] axi_lite_slave @ 10.0ns MET -> build/syn/axi_lite_slave/10.0
  rc=0

$ make synth PERIOD=7.0
      worst slack -0.400 ns (VIOLATED), threshold 0.0
  [synth] TIMING NOT MET at 7.0ns
make: *** [build/syn/axi_lite_slave/7.0/timing.ok] Error 1
  rc=2

$ make synth PERIOD=7.0
      worst slack -0.400 ns (VIOLATED), threshold 0.0
  [synth] TIMING NOT MET at 7.0ns
make: *** [build/syn/axi_lite_slave/7.0/timing.ok] Error 1
  rc=2
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output — find build -name timing.ok
build/syn/axi_lite_slave/10.0/timing.ok

timing.ok exists only if the gate passed, touch $@ is the last line so a failure cannot record itself as a success, and the timing report and the full tool log both survive for whoever has to look at them. The failing run asked for a second time re-runs and fails again, which is the whole point.

7. From Synthesis to STA

Timing analysis runs on the netlist synthesis produced, which makes it a genuine dependency rather than a convention:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile — the STA stage
PT_SHELL ?= pt_shell
STADIR    = $(BUILD)/sta/$(DESIGN)/$(PERIOD)
NETLIST   = $(SYNDIR)/$(DESIGN)_netlist.v

.PHONY: sta
sta: $(STADIR)/timing.rpt

$(STADIR)/timing.rpt: $(SYNDIR)/timing.rpt sta/sta.tcl
	@mkdir -p $(@D)
	@RUNDIR=$(STADIR) NETLIST=$(NETLIST) \
	   $(PT_SHELL) -f sta/sta.tcl > $(STADIR)/sta.log 2>&1 \
	   || { echo "  [sta] TOOL FAILED - see $(STADIR)/sta.log"; exit 1; }
	@tclsh scripts/slack.tcl $@ $(SLACK_MIN) > $(@D)/slack.txt; st=$$?; \
	 sed 's/^/      /' $(@D)/slack.txt; \
	 [ $$st -eq 0 ] || { echo "  [sta] TIMING NOT MET at $(PERIOD)ns"; exit 1; }

The STA script checks that the netlist it was told to read actually exists, and fails loudly if not — because "I analysed nothing and found no violations" is the worst possible outcome:

Azvya Education Pvt. Ltd.VLSI Mentor
sta/sta.tcl — the guard that matters
if {$netlist ne "" && ![file exists $netlist]} {
    puts stderr "  \[sta\] ERROR: netlist not found: $netlist"
    exit 1
}

One command, both stages:

Azvya Education Pvt. Ltd.VLSI Mentor
Actual output — make sta on a clean tree
  [synth] axi_lite_slave @ 10.0ns -> build/syn/axi_lite_slave/10.0
      worst slack 2.600 ns (MET), threshold 0.0
  [sta] axi_lite_slave @ 10.0ns -> build/sta/axi_lite_slave/10.0
      worst slack 2.250 ns (MET), threshold 0.0
  make rc=0
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output — find build -type f | sort
build/sta/axi_lite_slave/10.0/pt_shell.argv
build/sta/axi_lite_slave/10.0/slack.txt
build/sta/axi_lite_slave/10.0/sta.log
build/sta/axi_lite_slave/10.0/timing.rpt
build/syn/axi_lite_slave/10.0/area.rpt
build/syn/axi_lite_slave/10.0/axi_lite_slave_netlist.v
build/syn/axi_lite_slave/10.0/dc_shell.argv
build/syn/axi_lite_slave/10.0/slack.txt
build/syn/axi_lite_slave/10.0/synth.log
build/syn/axi_lite_slave/10.0/timing.rpt

(The two .argv files are the mocks recording what they were handed, as in Chapter 7.1 §7; a real tool leaves no such file.)

Asking for sta ran synth first, because the netlist is a real prerequisite. Nobody had to remember the order — which has been the point since Chapter 1.2.

Five rows in sequence. Row one, sources and the filelist, are shared with the simulation flow. Row two, the synthesis Tcl script, is run by the tool as a program. Row three, the synthesis timing report, carries the answer because the tool exits zero either way. Row four, the netlist, is a real prerequisite that makes timing analysis depend on synthesis. Row five, the STA timing report, is gated separately because it can disagree with synthesis.Synthesis and timing, stage by stage01SOURCES + FILELISTshared with simulation02SYNTH.TCLthe tool runs a program03SYNTHESIS REPORTgate reads the slack04NETLISTa real prerequisite05STA REPORTgated separately
Figure 1 — the synthesis and timing flow. Each stage is driven by a Tcl script and answers in a report, so each needs its own gate; the arrows are the dependencies Make enforces. The two gates are separate because the two stages can disagree, which section 8 measures.

8. Sweeping the Clock Period

With both stages gated, "what is the fastest clock this design makes?" becomes a loop — the same idea as Chapter 6.3's seed sweep, over a different variable:

Azvya Education Pvt. Ltd.VLSI Mentor
Makefile — the period sweep
PERIODS ?= 7.0 7.5 7.6 8.0 9.0 10.0

.PHONY: sweep
sweep:
	@printf '  %-8s %-26s %s\n' period synthesis sta
	@for p in $(PERIODS); do \
	   s=$$($(MAKE) -s -f $(firstword $(MAKEFILE_LIST)) synth PERIOD=$$p > /dev/null 2>&1 \
	        && echo MET || echo "NOT MET"); \
	   t=$$($(MAKE) -s -f $(firstword $(MAKEFILE_LIST)) sta   PERIOD=$$p > /dev/null 2>&1 \
	        && echo MET || echo "NOT MET"); \
	   printf '  %-8s %-26s %s\n' "$$p" "$$s" "$$t"; \
	 done

Note -f $(firstword $(MAKEFILE_LIST)) on the recursive calls. A sub-make does not inherit -f, which Chapter 6.4 §7 measured the hard way, and $(MAKEFILE_LIST) carries a leading space so the $(firstword ...) is not optional.

Azvya Education Pvt. Ltd.VLSI Mentor
Actual output — make sweep
  period   synthesis                  sta
  7.0      NOT MET                    NOT MET
  7.5      MET                        NOT MET
  7.6      MET                        NOT MET
  8.0      MET                        MET
  9.0      MET                        MET
  10.0     MET                        MET
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output — the slack numbers behind the table
  7.0    synth: slack (VIOLATED)  -0.400   sta: slack (VIOLATED)  -0.750
  7.5    synth: slack (MET)        0.100   sta: slack (VIOLATED)  -0.250
  7.6    synth: slack (MET)        0.200   sta: slack (VIOLATED)  -0.150
  8.0    synth: slack (MET)        0.600   sta: slack (MET)        0.250
  9.0    synth: slack (MET)        1.600   sta: slack (MET)        1.250
  10.0   synth: slack (MET)        2.600   sta: slack (MET)        2.250

Look at 7.5 and 7.6: synthesis says MET and STA says VIOLATED. A flow that gated only on the synthesis report would have passed both of those and handed the design on.

That two-stage disagreement is the structural point, and it is why the two gates in §6 and §7 are separate rather than one check at the end. The size of the gap here is a constant I chose in the mock — 0.35 ns — and says nothing about any real technology. The direction is the part worth carrying away: each stage sees more of reality than the one before it, so each needs its own gate, and the later one is the one that gets to be right.

9. Common Mistakes

Trusting the tool's exit status. Measured: slack -0.400, the word VIOLATED in the report, and dc_shell exiting 0 (§3).

Putting a pipe between the gate and the status. Measured in my own Makefile: the gate printed VIOLATED and make exited 0, because || tested sed (§5).

Testing a gate by reading its message. Test it on a known failure and check $? — the message was correct in the broken version (§5).

Making the report the target. A failed gate leaves the report on disk, so the next invocation reports nothing to do and exits 0 — measured (§6).

touching the stamp before the gate. Then a violation records itself as a pass; the touch goes last (§6).

Treating an unreadable report as a pass. A missing or truncated report must be its own outcome, not "timing met" (§4).

Leaving the clock period out of the run path. Two periods are two different results and will overwrite each other (§2).

Giving synthesis its own source list. It will eventually synthesise a design you never verified; read the simulation's filelist (§1).

Gating only on synthesis. Measured: 7.5 ns and 7.6 ns passed synthesis and failed STA (§8).

A bare $(MAKE) in the sweep. -f is not inherited, so the sub-make reads the wrong file (Chapter 6.4 §7, §8 here).

Parallelising a synthesis sweep like a simulation sweep. Licenses are counted; simulations mostly are not (§8).

10. Check Your Understanding

Question 1. A synthesis flow runs nightly and has never failed. The timing reports contain negative slack. How is that possible, and what single change fixes it?

Question 2. A slack gate prints worst slack -0.400 ns (VIOLATED) and the build passes. The gate script is correct and exits 1 when run by hand. Where is the bug?

Question 3. After the gate is fixed, the first make synth at an impossible period fails correctly, and the second one exits 0 without running anything. Why, and what is the fix?

Question 4. Why should a missing timing report be a different outcome from a report showing negative slack, when both mean "not shippable"?

Question 5. A design meets timing in synthesis at 7.5 ns and fails STA at the same period. Which gate should the flow believe, and what does that imply about where the gates go?

11. Exercise

The reports below come from the mock shells described in the opening callout; the Tcl, the Makefile and every exit status are real. With real tools, the same steps apply unchanged.

Step 1. Synthesise at the default period and read the gate's verdict:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
make synth
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
  [synth] axi_lite_slave @ 10.0ns -> build/syn/axi_lite_slave/10.0
      worst slack 2.600 ns (MET), threshold 0.0

Step 2. Confirm the tool itself does not report a violation. Run it directly and check the status:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
PERIOD=7.0 RUNDIR=build/syn7 ./tools/mock/dc_shell -f syn/synth.tcl > /dev/null
echo "dc_shell rc=$?"
grep slack build/syn7/timing.rpt
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
dc_shell rc=0
  slack (VIOLATED)         -0.400

Step 3. Now let the gate do its job, and check $? rather than the message:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
make synth PERIOD=7.0; echo "make rc=$?"
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
  [synth] axi_lite_slave @ 7.0ns -> build/syn/axi_lite_slave/7.0
      worst slack -0.400 ns (VIOLATED), threshold 0.0
  [synth] TIMING NOT MET at 7.0ns (gate rc=1)
make: *** [build/syn/axi_lite_slave/7.0/timing.rpt] Error 1
make rc=2

Step 4. Ask for the same failing run again:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
make synth PERIOD=7.0; echo "make rc=$?"
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
make: Nothing to be done for `synth'.
make rc=0

That is §6's bug. Switch the target to timing.ok as shown there and repeat — the second invocation must fail again.

Step 5. Run the full chain and the sweep:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
make clean && make sta
make sweep
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
  period   synthesis                  sta
  7.0      NOT MET                    NOT MET
  7.5      MET                        NOT MET
  7.6      MET                        NOT MET
  8.0      MET                        MET
  9.0      MET                        MET
  10.0     MET                        MET

Step 6. Prove the gate's two unreadable outcomes, which are the ones most flows omit:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
echo "nothing here" > /tmp/empty.rpt
tclsh scripts/slack.tcl /tmp/empty.rpt 0.0 ; echo "rc=$?"
tclsh scripts/slack.tcl /tmp/nosuch.rpt 0.0 ; echo "rc=$?"
Azvya Education Pvt. Ltd.VLSI Mentor
Actual output
slack.tcl: no slack line found in /tmp/empty.rpt
rc=2
slack.tcl: no such report: /tmp/nosuch.rpt
rc=2

Then break the gate the way I did, and check the status rather than the screen. Change the gate line back to tclsh scripts/slack.tcl $@ $(SLACK_MIN) | sed 's/^/ /' || { ...; exit 1; } and run make synth PERIOD=7.0; echo $?. The violation prints, and the status is 0. Add set -o pipefail; to the front of that line and run it again — status 2. That one-character-wide difference between a gate that works and a gate that only looks like it works is the most useful thing in this chapter.

12. Remember

  • Synthesis and STA are driven by Tcl, not options. The tool interprets a program, so most configuration lives in a script — and the script should take its parameters from the environment.
  • Read the same filelist as the simulation. A separate source list eventually synthesises a design nobody verified.
  • The tool exits 0 on a timing violation. Measured: slack -0.400, VIOLATED in the report, exit status 0 — the same asymmetry as the simulator, for the same reason.
  • So the Makefile must parse the report. There is no other channel.
  • A gate needs four outcomes: met, violated, no slack line, and no report. An unreadable answer is not a passing answer.
  • Never pipe a gate's output before testing its status. Measured in my own Makefile: VIOLATED printed and make exited 0 because || tested sed.
  • Test a gate on a known failure and check $?, not the message. The broken gate's message was perfectly correct.
  • Never make the report the target. Measured: after a failed gate, the next invocation said nothing to be done and exited 0, forgetting the violation.
  • Make the verdict the target, with touch $@ last, and keep the report and the full tool log as products.
  • Put the clock period in the run path, because two periods are two different results.
  • The netlist makes STA depend on synthesis, so one command runs both in the right order.
  • Gate each stage separately. Measured: 7.5 ns and 7.6 ns met timing in synthesis and violated it in STA, so a synthesis-only gate would have passed them.
  • Use -f $(firstword $(MAKEFILE_LIST)) for recursive calls in a sweep, since -f is not inherited.
  • Do not parallelise a synthesis sweep like a simulation sweep. Licenses are counted and expensive.

Module 7 is complete. The flow now drives any of three simulators by a single variable, hands structured work to Bash, Tcl and Python without losing values or failures, and runs Tcl-driven synthesis and timing analysis with a gate on each stage's report.

What it is not yet is a flow somebody else can own. Everything so far has lived in one Makefile per subject, written by the person who runs it, on a tree small enough to hold in your head. A real project has several blocks, several flows, shared rules nobody wants to copy, a compute farm with limits, and new engineers who need it to work on the first try. Module 8 is about that: organising a large Makefile without it becoming unreadable, making parallel builds reliable rather than merely fast, and the practices that keep a flow trustworthy when it is no longer yours alone.

Where this fits

Part of the Makefile curriculum.