diff --git a/Makefile.in b/Makefile.in index fb2bf08a8..fcee49bca 100644 --- a/Makefile.in +++ b/Makefile.in @@ -131,6 +131,7 @@ DISTFILES1 = $(INFOS) .gitignore \ verilator-config.cmake.in \ verilator-config-version.cmake.in \ bin/verilator \ + bin/verilator_ccache_report \ bin/verilator_coverage \ bin/verilator_difftree \ bin/verilator_gantt \ @@ -194,6 +195,7 @@ DISTFILES2 = \ INST_PROJ_FILES = \ bin/verilator \ + bin/verilator_ccache_report \ bin/verilator_coverage \ bin/verilator_gantt \ bin/verilator_includer \ @@ -282,7 +284,7 @@ verilator.pdf: Makefile # See uninstall also - don't put wildcards in this variable, it might uninstall other stuff VL_INST_BIN_FILES = verilator verilator_bin$(EXEEXT) verilator_bin_dbg$(EXEEXT) verilator_coverage_bin_dbg$(EXEEXT) \ - verilator_coverage verilator_gantt verilator_includer verilator_profcfunc + verilator_ccache_report verilator_coverage verilator_gantt verilator_includer verilator_profcfunc # Some scripts go into both the search path and pkgdatadir, # so they can be found by the user, and under $VERILATOR_ROOT. @@ -311,6 +313,7 @@ installbin: ( cd bin ; $(INSTALL_PROGRAM) verilator_coverage_bin_dbg$(EXEEXT) $(DESTDIR)$(bindir)/verilator_coverage_bin_dbg$(EXEEXT) ) $(MKINSTALLDIRS) $(DESTDIR)$(pkgdatadir)/bin ( cd ${srcdir}/bin ; $(INSTALL_PROGRAM) verilator_includer $(DESTDIR)$(pkgdatadir)/bin/verilator_includer ) + ( cd ${srcdir}/bin ; $(INSTALL_PROGRAM) verilator_ccache_report $(DESTDIR)$(pkgdatadir)/bin/verilator_ccache_report ) # Man files can either be part of the original kit, or built in current directory # So important we use $^ so VPATH is searched @@ -477,6 +480,7 @@ clang-format: $(CLANGFORMAT) $(CLANGFORMAT_FLAGS) $(CLANGFORMAT_FILES) PY_PROGRAMS = \ + bin/verilator_ccache_report \ examples/xml_py/vl_file_copy \ examples/xml_py/vl_hier_graph \ docs/guide/conf.py \ diff --git a/bin/verilator_ccache_report b/bin/verilator_ccache_report new file mode 100755 index 000000000..d5dc1e5d7 --- /dev/null +++ b/bin/verilator_ccache_report @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# pylint: disable=C0103,C0114,C0115,C0116,C0123,C0301,R0902,R0913,R0914,R0912,R0915,W0621 +###################################################################### + +import argparse +import collections +import re + +parser = argparse.ArgumentParser( + allow_abbrev=False, + formatter_class=argparse.RawDescriptionHelpFormatter, + description="""Report ccache behavior of a Verilated model build.""", + epilog= + """Copyright 2002-2021 by Wilson Snyder. This program is free software; you +can redistribute it and/or modify it under the terms of either the GNU +Lesser General Public License Version 3 or the Perl Artistic License +Version 2.0. + +SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0""") + +parser.add_argument('-o', type=argparse.FileType('w'), metavar="OUTFILE", + required=True, + help='output file') +parser.add_argument('logfile', type=argparse.FileType('r'), + help='ccache log file') + +args = parser.parse_args() + +results = {} + +for line in args.logfile: + line = line.strip() + match = re.match(r'.*Object file: (.*)$', line) + if match: + obj = match.group(1) + match = re.match(r'.*Result: (.*)$', line) + if match: + results[obj] = match.group(1) + +args.o.write("#" * 80 + "\n") +args.o.write("ccache report (from verilator_ccache_report) :\n") + +if not results: + args.o.write("\nAll object files up to date\n") +else: + args.o.write("\nCompiled object files:\n") + width = max(len(_) for _ in results) + 1 + for k in sorted(results.keys()): + args.o.write("{:{width}} : {}\n".format(k, results[k], width=width)) + + args.o.write("\nSummary:\n") + counts = collections.Counter(_ for _ in results.values()) + total = sum(counts.values()) + width = max(len(_) for _ in results.values()) + 1 + for k in sorted(counts.keys()): + c = counts[k] + args.o.write("{:{width}}| {} ({:.2%})\n".format(k, c, c / total, width=width)) + +args.o.write("#" * 80 + "\n") diff --git a/docs/guide/faq.rst b/docs/guide/faq.rst index 5628828ec..c3ffd5b76 100644 --- a/docs/guide/faq.rst +++ b/docs/guide/faq.rst @@ -387,7 +387,7 @@ How do I get faster build times? identical source builds, even across different users. If ccache was installed when Verilator was built it is used, or see OBJCACHE environment variable to override this. Also see the - :vlopt:`--output-split` option. + :vlopt:`--output-split` option and :ref: `Profiling ccache efficiency` * To reduce the compile time of classes that use a Verilated module (e.g. a top CPP file) you may wish to add a diff --git a/docs/guide/simulating.rst b/docs/guide/simulating.rst index 39ebf1464..3033a5c22 100644 --- a/docs/guide/simulating.rst +++ b/docs/guide/simulating.rst @@ -316,6 +316,35 @@ statistics. For more information see :command:`verilator_gantt`. +.. _Profiling ccache efficiency: + +Profiling ccache efficiency +=========================== + +The Verilator generated Makefile provides support for basic profiling of +ccache behavior during the build. This can be used to track down files that +might be unnecessarily rebuilt, though as of today even small code changes +will usually require rebuilding a large number of files. Improving ccache +efficiency during the edit/compile/test loop is an active area of +development. + +To get a basic report of how well ccache is doing, add the `ccache-report` +target when invoking the generated Makefile: + +.. code-block:: bash + + make -C obj_dir -f Vout.mk Vout ccache-report + +This will print a report based on all executions of ccache during this +invocation of Make. The report is also written to a file, in this example +`obj_dir/Vout__cache_report.txt`. + +To use the `ccache-report` target, at least one other explicit build target +must be specified, and OBJCACHE must be set to 'ccache'. + +This feature is currently experimental and might change in subsequent +releases. + .. _Save/Restore: Save/Restore diff --git a/include/verilated.mk.in b/include/verilated.mk.in index 2edde9c11..c183d1d7c 100644 --- a/include/verilated.mk.in +++ b/include/verilated.mk.in @@ -10,6 +10,7 @@ ###################################################################### PERL = @PERL@ +PYTHON3 = @PYTHON3@ CXX = @CXX@ LINK = @CXX@ AR = ar @@ -35,6 +36,7 @@ CFG_LDLIBS_THREADS = @CFG_LDLIBS_THREADS@ VERILATOR_COVERAGE = $(PERL) $(VERILATOR_ROOT)/bin/verilator_coverage VERILATOR_INCLUDER = $(PERL) $(VERILATOR_ROOT)/bin/verilator_includer +VERILATOR_CCACHE_REPORT = $(PYTHON3) $(VERILATOR_ROOT)/bin/verilator_ccache_report ###################################################################### # Make checks @@ -254,6 +256,37 @@ endif #.cpp.o: # $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $< +###################################################################### +### ccache report + +ifneq ($(findstring ccache-report,$(MAKECMDGOALS)),) + ifneq ($(OBJCACHE),ccache) + $(error ccache-report requires OBJCACHE to equal 'ccache') + endif + VK_OTHER_GOALS := $(strip $(subst ccache-report,,$(MAKECMDGOALS))) + ifeq ($(VK_OTHER_GOALS),) + $(error ccache-report must be used with at least one other explicit target) + endif + + # Report ccache behaviour for this invocation of make + export CCACHE_LOGFILE := $(VM_PREFIX).ccache-log + VK_CCACHE_REPORT := $(VM_PREFIX)__ccache_report.txt + # Truncate logfile + $(shell echo "" > $(CCACHE_LOGFILE)) + # Remove previous report + $(shell rm -f $(VK_CCACHE_REPORT)) + +$(VK_CCACHE_REPORT): $(VK_OBJS) + $(VERILATOR_CCACHE_REPORT) -o $@ $(CCACHE_LOGFILE) + +.PHONY: ccache-report +ccache-report: $(VK_CCACHE_REPORT) + @cat $< + +# ccache-report runs last +ccache-report: $(VK_OTHER_GOALS) +endif + ###################################################################### ### Debugging diff --git a/test_regress/driver.pl b/test_regress/driver.pl index 37d0f3a3c..54dd82683 100755 --- a/test_regress/driver.pl +++ b/test_regress/driver.pl @@ -2304,6 +2304,10 @@ sub cfg_with_threaded { return 1; # C++11 now always required } +sub cfg_with_ccache { + return `grep "OBJCACHE \?= ccache" "$ENV{VERILATOR_ROOT}/include/verilated.mk"` ne ""; +} + sub tries { # Number of retries when reading logfiles, generally only need many # retries when system is busy running a lot of tests diff --git a/test_regress/t/t_ccache_report.pl b/test_regress/t/t_ccache_report.pl new file mode 100755 index 000000000..1e36b8b1a --- /dev/null +++ b/test_regress/t/t_ccache_report.pl @@ -0,0 +1,49 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2021 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(vlt => 1); + +if (!$Self->cfg_with_ccache) { + skip("Requires configuring with ccache"); +} + +top_filename("t_a1_first_cc.v"); + +# This test requires rebuilding the object files to check the ccache log +foreach my $filename (glob ("$Self->{obj_dir}/*.o")) { + print "rm $filename\n" if $Self->{verbose}; + unlink $filename; +} + +compile( + verilator_flags2 => ['--trace'], + make_flags => "ccache-report" + ); + +my $report = "$Self->{obj_dir}/$Self->{VM_PREFIX}__ccache_report.txt"; + +# We do not actually want to make this test depend on whether the file was +# cached or not, so trim the report to ignore actual caching behaviour +run(cmd => ["sed", "-i", "-e", "'s/ : .*/ : IGNORED/; /^Summary/,\$d;'", $report]); + +files_identical($report, "t/$Self->{name}__ccache_report_initial.out"); + +# Now rebuild again (should be all up to date) +run( + logfile => "$Self->{obj_dir}/rebuild.log", + cmd => ["make", "-C", $Self->{obj_dir}, + "-f", "$Self->{VM_PREFIX}.mk", + $Self->{VM_PREFIX}, "ccache-report"] + ); + +files_identical($report, "t/$Self->{name}__ccache_report_rebuild.out"); + +ok(1); +1; diff --git a/test_regress/t/t_ccache_report__ccache_report_initial.out b/test_regress/t/t_ccache_report__ccache_report_initial.out new file mode 100644 index 000000000..88f2fe542 --- /dev/null +++ b/test_regress/t/t_ccache_report__ccache_report_initial.out @@ -0,0 +1,9 @@ +################################################################################ +ccache report (from verilator_ccache_report) : + +Compiled object files: +Vt_ccache_report__ALL.o : IGNORED +Vt_ccache_report__main.o : IGNORED +verilated.o : IGNORED +verilated_vcd_c.o : IGNORED + diff --git a/test_regress/t/t_ccache_report__ccache_report_rebuild.out b/test_regress/t/t_ccache_report__ccache_report_rebuild.out new file mode 100644 index 000000000..767cd181a --- /dev/null +++ b/test_regress/t/t_ccache_report__ccache_report_rebuild.out @@ -0,0 +1,5 @@ +################################################################################ +ccache report (from verilator_ccache_report) : + +All object files up to date +################################################################################ diff --git a/test_regress/t/t_flag_help.pl b/test_regress/t/t_flag_help.pl index f3dc0cca4..cb38bfb87 100755 --- a/test_regress/t/t_flag_help.pl +++ b/test_regress/t/t_flag_help.pl @@ -10,23 +10,33 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(dist => 1); +# See also t_flag_version.pl + +sub check { + my $interpreter = shift; + my $prog = shift; + + run(fails => 0, + cmd => [$interpreter, $prog, "--help"], + logfile => "$Self->{obj_dir}/t_help.log", + tee => 0, + verilator_run => 1, + ); + + file_grep("$Self->{obj_dir}/t_help.log", qr/DISTRIBUTION/i); +} + foreach my $prog ( - # See also t_flag_version.pl "../bin/verilator", "../bin/verilator_coverage", "../bin/verilator_difftree", "../bin/verilator_gantt", "../bin/verilator_profcfunc", ) { - run(fails => 0, - cmd => ["perl", $prog, - "--help"], - logfile => "$Self->{obj_dir}/t_help.log", - tee => 0, - verilator_run => 1, - ); - file_grep("$Self->{obj_dir}/t_help.log", qr/DISTRIBUTION/i); + check("perl", $prog); } +check("python3", "../bin/verilator_ccache_report"); + ok(1); 1;