Add `+verilator+log+file` (#4505) (#7645)

Fixes #4505.
This commit is contained in:
Tracy Narine 2026-05-27 14:33:19 -04:00 committed by GitHub
parent 62f475709f
commit a2fae5eb4b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 277 additions and 0 deletions

View File

@ -597,6 +597,7 @@ description of these arguments.
+verilator+debugi+<value> Enable debugging at a level
+verilator+error+limit+<value> Set error limit
+verilator+help Show help
+verilator+log+file+<filename> Log stdout and stderr output to filename
+verilator+noassert Disable assert checking
+verilator+prof+exec+file+<filename> Set execution profile filename
+verilator+prof+exec+start+<value> Set execution profile starting point

View File

@ -48,6 +48,11 @@ Options:
Display help and exit.
.. option:: +verilator+log+file+<filename>
Log all stdout and stderr to the specified output filename. If not specified
the normal stdout/stderr streams are used.
.. option:: +verilator+noassert
Disable assert checking per runtime argument. This is the same as

View File

@ -61,6 +61,7 @@
#include <chrono>
#include <cstdlib>
#include <ctime>
#include <fcntl.h>
#include <iostream>
#include <limits>
#include <list>
@ -73,6 +74,9 @@
// clang-format off
#if defined(_WIN32) || defined(__MINGW32__)
# include <direct.h> // mkdir
# include <io.h> // open, read, write, close
# define STDOUT_FILENO _fileno(stdout)
# define STDERR_FILENO _fileno(stderr)
#endif
#ifdef __GLIBC__
# include <cxxabi.h>
@ -2158,7 +2162,13 @@ IData VL_SYSTEM_IW(int lhswords, const WDataInP lhsp) VL_MT_SAFE {
return VL_SYSTEM_IN(lhs);
}
IData VL_SYSTEM_IN(const std::string& lhs) VL_MT_SAFE {
VerilatedContext* ctxp = Verilated::threadContextp();
const bool sending = ctxp->logOutputToFile();
// system() command may echo and read so restore default stdout and stderr if sending
if (sending) ctxp->logRestoreOutput();
const int code = std::system(lhs.c_str()); // Yes, std::system() is threadsafe
// Log to file again if we were sending
if (sending) ctxp->logOutputToFile(true /* append */);
return code >> 8; // Want exit status
}
@ -2868,6 +2878,9 @@ VerilatedContext::VerilatedContext()
m_ns.m_profExecFilename = "profile_exec.dat";
m_ns.m_profVltFilename = "profile.vlt";
m_ns.m_solverProgram = VlOs::getenvStr("VERILATOR_SOLVER", VL_SOLVER_DEFAULT);
m_ns.m_logFD = -1;
m_ns.m_stdoutFD = -1;
m_ns.m_stderrFD = -1;
m_fdps.resize(31);
std::fill(m_fdps.begin(), m_fdps.end(), static_cast<FILE*>(nullptr));
m_fdFreeMct.resize(30);
@ -2879,6 +2892,7 @@ VerilatedContext::VerilatedContext()
VerilatedContext::~VerilatedContext() {
checkMagic(this);
m_magic = 0x1; // Arbitrary but 0x1 is what Verilator src uses for a deleted pointer
logRestoreOutput();
}
void VerilatedContext::checkMagic(const VerilatedContext* contextp) {
@ -2948,6 +2962,52 @@ std::string VerilatedContext::coverageFilename() const VL_MT_SAFE {
const VerilatedLockGuard lock{m_mutex};
return m_ns.m_coverageFilename;
}
void VerilatedContext::logFilename(const std::string& flag) VL_MT_SAFE {
const VerilatedLockGuard lock{m_mutex};
assert(m_ns.m_logFD == -1);
m_ns.m_logFilename = flag;
}
std::string VerilatedContext::logFilename() const VL_MT_SAFE {
const VerilatedLockGuard lock{m_mutex};
return m_ns.m_logFilename;
}
bool VerilatedContext::logOutputToFile() const VL_MT_SAFE {
const VerilatedLockGuard lock{m_mutex};
return m_ns.m_logFD >= 0;
}
void VerilatedContext::logOutputToFile(bool append) VL_MT_SAFE {
int log_fd = ::open(m_ns.m_logFilename.c_str(),
O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC), 0666);
std::string error_msg;
if (log_fd >= 0) {
const VerilatedLockGuard lock{m_mutex};
m_ns.m_logFD = log_fd;
// See https://man7.org/linux/man-pages/man2/dup.2.html
m_ns.m_stdoutFD = ::dup(STDOUT_FILENO); // Save original fd
::dup2(m_ns.m_logFD, STDOUT_FILENO); // Replace STDOUT_FILENO with m_logFD
m_ns.m_stderrFD = ::dup(STDERR_FILENO); // Save original fd
::dup2(m_ns.m_logFD, STDERR_FILENO); // Replace STDERR_FILENO with m_logFD
} else {
const VerilatedLockGuard lock{m_mutex};
m_ns.m_logFD = -1;
const std::string op_str = append ? "appended" : "created";
error_msg = "Logfile " + m_ns.m_logFilename + " cannot be " + op_str;
}
if (!error_msg.empty()) { VL_FATAL_MT("", 0, "", error_msg.c_str()); }
}
void VerilatedContext::logRestoreOutput() VL_MT_SAFE {
const VerilatedLockGuard lock{m_mutex};
if (m_ns.m_logFD >= 0) {
std::fflush(stdout); // Flush logfile
std::fflush(stderr);
::dup2(m_ns.m_stdoutFD, STDOUT_FILENO); // Restore STDOUT_FILENO with saved m_stdoutFD
::dup2(m_ns.m_stderrFD, STDERR_FILENO); // Restore STDERR_FILENO with saved m_stderrFD
::close(m_ns.m_logFD); // Close logfile
m_ns.m_logFD = -1;
m_ns.m_stdoutFD = -1;
m_ns.m_stderrFD = -1;
}
}
void VerilatedContext::dumpfile(const std::string& flag) VL_MT_SAFE_EXCLUDES(m_timeDumpMutex) {
const VerilatedLockGuard lock{m_timeDumpMutex};
m_dumpfile = flag;
@ -3277,6 +3337,9 @@ void VerilatedContextImp::commandArgVl(const std::string& arg) {
VL_PRINTF_MT("For help, please see 'verilator --help'\n");
VL_FATAL_MT("COMMAND_LINE", 0, "",
"Exiting due to command line argument (not an error)");
} else if (commandArgVlString(arg, "+verilator+log+file+", str)) {
logFilename(str);
logOutputToFile(false /* append */);
} else if (arg == "+verilator+noassert") {
assertOn(false);
} else if (commandArgVlUint64(arg, "+verilator+prof+exec+start+", u64)) {

View File

@ -409,6 +409,7 @@ protected:
uint32_t m_profExecWindow = 2; // +prof+exec+window size
// Slow path
std::string m_coverageFilename; // +coverage+file filename
std::string m_logFilename; // +log+file filename
std::string m_profExecFilename; // +prof+exec+file filename
std::string m_profVltFilename; // +prof+vlt filename
std::string m_solverLogFilename; // SMT solver log filename
@ -417,6 +418,9 @@ protected:
VlOs::DeltaCpuTime m_cpuTimeStart{false}; // CPU time, starts when create first model
VlOs::DeltaWallTime m_wallTimeStart{false}; // Wall time, starts when create first model
std::vector<traceBaseModelCb_t> m_traceBaseModelCbs; // Callbacks to traceRegisterModel
int m_stdoutFD; // Duplicated stdout file descriptor
int m_stderrFD; // Duplicated stderr file descriptor
int m_logFD; // Log file descriptor
} m_ns;
mutable VerilatedMutex m_argMutex; // Protect m_argVec, m_argVecLoaded
@ -650,6 +654,13 @@ public:
std::string coverageFilename() const VL_MT_SAFE;
void coverageFilename(const std::string& flag) VL_MT_SAFE;
// Internal: logfile
std::string logFilename() const VL_MT_SAFE;
void logFilename(const std::string& flag) VL_MT_SAFE;
bool logOutputToFile() const VL_MT_SAFE;
void logOutputToFile(bool append) VL_MT_SAFE;
void logRestoreOutput() VL_MT_SAFE;
// Internal: $dumpfile
std::string dumpfile() const VL_MT_SAFE_EXCLUDES(m_timeDumpMutex);
void dumpfile(const std::string& flag) VL_MT_SAFE_EXCLUDES(m_timeDumpMutex);

View File

@ -0,0 +1,5 @@
Hello World!
Hello 3rd rock!
Hello Mars!
Hello Pluto!
- t/t_runflag_logfile.v:20: Verilog $finish

View File

@ -0,0 +1,22 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.compile()
logfile = test.obj_dir + "/logfile.log"
test.execute(all_run_flags=['+verilator+log+file+' + logfile])
test.files_identical(logfile, test.golden_filename)
test.passes()

View File

@ -0,0 +1,22 @@
// DESCRIPTION: Verilator: Verilog example module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
int stdout_fd = 32'h8000_0001;
int stderr_fd = 32'h8000_0002;
module top;
initial begin
// display
$display("Hello World!");
$system("echo In a shell now");
$display("Hello 3rd rock!");
// fdisplay with stdout and stderr
$fdisplay(stdout_fd, "Hello Mars!");
$system("echo In another shell now");
$fdisplay(stderr_fd, "Hello Pluto!");
$finish;
end
endmodule

View File

@ -0,0 +1,35 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
have_gdb = 'VERILATOR_GDB' in os.environ
if not have_gdb:
if 'VERILATOR_TEST_NO_GDB' in os.environ:
test.skip("Skipping due to VERILATOR_TEST_NO_GDB")
if not test.have_gdb:
test.skip("No gdb installed")
test.compile()
logfile = test.obj_dir + "/logfile.log"
test.execute(all_run_flags=['+verilator+log+file+' + logfile])
# $display checks
test.file_grep(logfile, r'Hello World!')
test.file_grep(logfile, r'Hello 3rd rock!')
# $fdisplay checks
test.file_grep(logfile, r'Hello Mars!')
test.file_grep(logfile, r'Hello Pluto!')
test.passes()

View File

@ -0,0 +1,25 @@
// DESCRIPTION: Verilator: Verilog example module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
// See also https://verilator.org/guide/latest/examples.html"
// Found on Stackoverflow
int stdout_fd = 32'h8000_0001;
int stderr_fd = 32'h8000_0002;
module top;
initial begin
// display
$display("Hello World!");
$system("echo In a shell now");
$display("Hello 3rd rock!");
// fdisplay with stdout and stderr
$fdisplay(stdout_fd, "Hello Mars!");
$system("echo In another shell now");
$fdisplay(stderr_fd, "Hello Pluto!");
$finish;
end
endmodule

View File

@ -0,0 +1,3 @@
[0] %Error: t_runflag_logfile_error.v:11: Assertion failed in top.top: This is a generated error!
%Error: t/t_runflag_logfile_error.v:11: Verilog $stop
Aborting...

View File

@ -0,0 +1,22 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.compile()
logfile = test.obj_dir + "/logfile.log"
test.execute(all_run_flags=['+verilator+log+file+' + logfile], fails=True)
test.files_identical(logfile, test.golden_filename)
test.passes()

View File

@ -0,0 +1,14 @@
// DESCRIPTION: Verilator: Verilog example module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
// See also https://verilator.org/guide/latest/examples.html"
module top;
initial begin
$error("This is a generated error!");
$finish;
end
endmodule

View File

@ -0,0 +1,2 @@
%Error: Logfile obj_vlt/t_runflag_logfile_open_bad/logfile.log cannot be created
Aborting...

View File

@ -0,0 +1,35 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# 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-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import os
import stat
import vltest_bootstrap
test.scenarios('vlt')
test.compile()
logfile = test.obj_dir + "/logfile.log"
capturefile = test.obj_dir + "/capture.log"
# Create the logfile
with open(logfile, 'w', encoding='utf8') as f:
f.write("Read-only logfile\n")
# Change permission to read only so execute() fail when creating the logfile
os.chmod(logfile, stat.S_IREAD)
log_flags = '+verilator+log+file+' + logfile
test.execute(all_run_flags=[log_flags, ">", capturefile, "2>&1"], fails=True)
# Set read + write so test does not fail next time it is run
os.chmod(logfile, stat.S_IREAD | stat.S_IWRITE)
test.files_identical(capturefile, test.golden_filename)
test.passes()

View File

@ -0,0 +1,12 @@
// DESCRIPTION: Verilator: Verilog example module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module top;
initial begin
$display("We should not see this message");
$finish;
end
endmodule