Support `$sscanf %t`

This commit is contained in:
Wilson Snyder 2025-09-01 14:40:22 -04:00
parent 33b838f139
commit 67f26508ba
6 changed files with 101 additions and 12 deletions

View File

@ -1329,7 +1329,7 @@ IData _vl_vsscanf(FILE* fp, // If a fscanf
default: {
// Deal with all read-and-scan somethings
// Note LSBs are preserved if there's an overflow
const int obits = inIgnore ? 0 : va_arg(ap, int);
int obits = inIgnore ? 0 : va_arg(ap, int);
VlWide<VL_WQ_WORDS_E> qowp;
VL_SET_WQ(qowp, 0ULL);
WDataOutP owp = qowp;
@ -1390,7 +1390,26 @@ IData _vl_vsscanf(FILE* fp, // If a fscanf
VL_SET_WQ(owp, u.ld);
break;
}
case 't': // FALLTHRU // Time
case 't': { // Time
_vl_vsss_skipspace(fp, floc, fromp, fstr);
_vl_vsss_read_str(fp, floc, fromp, fstr, t_tmp, "+-.0123456789eE");
if (!t_tmp[0]) goto done;
union {
double r;
int64_t ld;
} u;
// Get pointer argument first, as proceeds the timeunit value
if (obits != 64) goto done;
QData* const realp = va_arg(ap, QData*);
const int timeunit = va_arg(ap, int);
const int userUnits
= Verilated::threadContextp()->impp()->timeFormatUnits(); // 0..-15
const int shift = -userUnits + timeunit; // 0..-15
u.r = std::strtod(t_tmp, nullptr) * vl_time_multiplier(-shift);
*realp = VL_CLEAN_QQ(obits, obits, u.ld);
obits = 0; // Already loaded the value, don't read arg
break;
}
case '#': { // Unsigned decimal
_vl_vsss_skipspace(fp, floc, fromp, fstr);
_vl_vsss_read_str(fp, floc, fromp, fstr, t_tmp, "0123456789+-xXzZ?_");

View File

@ -2016,6 +2016,7 @@ class AstSScanF final : public AstNodeExpr {
// @astgen op1 := exprsp : List[AstNode] // VarRefs for results
// @astgen op2 := fromp : AstNode
string m_text;
VTimescale m_timeunit; // Parent module time unit
public:
AstSScanF(FileLine* fl, const string& text, AstNode* fromp, AstNode* exprsp)
@ -2035,10 +2036,13 @@ public:
bool isOutputter() override { return true; } // SPECIAL: makes output
bool cleanOut() const override { return false; }
bool sameNode(const AstNode* samep) const override {
return text() == VN_DBG_AS(samep, SScanF)->text();
const AstSScanF* const asamep = VN_DBG_AS(samep, SScanF);
return text() == asamep->text() && timeunit() == asamep->timeunit();
}
string text() const { return m_text; } // * = Text to display
void text(const string& text) { m_text = text; }
VTimescale timeunit() const { return m_timeunit; }
void timeunit(const VTimescale& flag) { m_timeunit = flag; }
};
class AstSampled final : public AstNodeExpr {
// Verilog $sampled

View File

@ -290,18 +290,20 @@ void EmitCFunc::displayArg(AstNode* dispp, AstNode** elistp, bool isScan, const
}
m_emitDispState.pushArg(fmtLetter, argp, "");
if (fmtLetter == 't' || fmtLetter == '^') {
const AstSFormatF* fmtp = nullptr;
VTimescale timeunit = VTimescale::NONE;
if (const AstDisplay* const nodep = VN_CAST(dispp, Display)) {
fmtp = nodep->fmtp();
timeunit = nodep->fmtp()->timeunit();
} else if (const AstSFormat* const nodep = VN_CAST(dispp, SFormat)) {
fmtp = nodep->fmtp();
} else {
fmtp = VN_CAST(dispp, SFormatF);
timeunit = nodep->fmtp()->timeunit();
} else if (const AstSScanF* const nodep = VN_CAST(dispp, SScanF)) {
timeunit = nodep->timeunit();
} else if (const AstSFormatF* const nodep = VN_CAST(dispp, SFormatF)) {
timeunit = nodep->timeunit();
}
UASSERT_OBJ(fmtp, dispp,
"Use of %t must be under AstDisplay, AstSFormat, or AstSFormatF");
UASSERT_OBJ(!fmtp->timeunit().isNone(), fmtp, "timenunit must be set");
m_emitDispState.pushArg(' ', nullptr, cvtToStr((int)fmtp->timeunit().powerOfTen()));
UASSERT_OBJ(!timeunit.isNone(), dispp,
"Use of %t must be under AstDisplay, AstSFormat, or AstSFormatF, or "
"SScanF, and timeunit set");
m_emitDispState.pushArg(' ', nullptr, cvtToStr((int)timeunit.powerOfTen()));
}
} else {
m_emitDispState.pushArg(fmtLetter, nullptr, "");

View File

@ -815,6 +815,11 @@ class LinkParseVisitor final : public VNVisitor {
iterateChildren(nodep);
nodep->timeunit(m_modp->timeunit());
}
void visit(AstSScanF* nodep) override {
cleanFileline(nodep);
iterateChildren(nodep);
nodep->timeunit(m_modp->timeunit());
}
void visit(AstTime* nodep) override {
cleanFileline(nodep);
iterateChildren(nodep);

18
test_regress/t/t_time_sscanf.py Executable file
View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2025 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
import vltest_bootstrap
test.scenarios('simulator')
test.compile()
test.execute()
test.passes()

View File

@ -0,0 +1,41 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by Wilson Snyder.
// SPDX-License-Identifier: CC0-1.0
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
`define checkr(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got=%f exp=%f\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
// verilog_format: on
`timescale 1ns / 100ps
module main;
real r;
integer rc;
time t;
// verilator lint_off REALCVT
initial begin
rc = $sscanf("8.125", "%f", r); // as real
`checkd(rc, 1);
`checkr(r, 8.125);
rc = $sscanf("8125", "%t", r); // in ns but round to 100 ps
`checkd(rc, 1);
t = r;
`checkr(t, 813);
$timeformat(-3, 2, "ms", 10);
rc = $sscanf("8.125", "%t", r); // in ms
`checkd(rc, 1);
t = r;
`checkr(t, 8125000);
$finish;
end
endmodule