diff --git a/include/verilated.cpp b/include/verilated.cpp index cc9c26e24..58a2fdd55 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -1141,7 +1141,13 @@ done: //=========================================================================== // File I/O -FILE* VL_CVT_I_FP(IData lhs) VL_MT_SAFE { return VerilatedImp::fdToFp(lhs); } +FILE* VL_CVT_I_FP(IData lhs) VL_MT_SAFE { + // Expected non-MCD case; returns ONLY the first file descriptor seen in lhs (which + // in the MCD case can result in descriptors being ignored). + FILE* fp[1] = {NULL}; + VerilatedImp::fdToFp(lhs, fp, 1); + return fp[0]; +} void _VL_VINT_TO_STRING(int obits, char* destoutp, WDataInP sourcep) VL_MT_SAFE { // See also VL_DATA_TO_STRING_NW @@ -1209,38 +1215,25 @@ IData VL_FERROR_IN(IData, std::string& outputr) VL_MT_SAFE { return ret; } -IData VL_FOPEN_NI(const std::string& filename, IData mode) VL_MT_SAFE { - // While threadsafe, each thread can only access different file handles - char modez[5]; - EData modee = mode; - _VL_VINT_TO_STRING(VL_IDATASIZE, modez, &modee); - return VL_FOPEN_S(filename.c_str(), modez); +IData VL_FOPEN_NN(const std::string& filename, const std::string& mode) { + return VerilatedImp::fdNew(filename.c_str(), mode.c_str()); } -IData VL_FOPEN_QI(QData filename, IData mode) VL_MT_SAFE { - // While threadsafe, each thread can only access different file handles - WData fnw[VL_WQ_WORDS_E]; - VL_SET_WQ(fnw, filename); - return VL_FOPEN_WI(VL_WQ_WORDS_E, fnw, mode); -} -IData VL_FOPEN_WI(int fnwords, WDataInP filenamep, IData mode) VL_MT_SAFE { - // While threadsafe, each thread can only access different file handles - char filenamez[VL_TO_STRING_MAX_WORDS * VL_EDATASIZE + 1]; - _VL_VINT_TO_STRING(fnwords * VL_EDATASIZE, filenamez, filenamep); - EData modee = mode; - char modez[5]; - _VL_VINT_TO_STRING(4 * sizeof(char), modez, &modee); - return VL_FOPEN_S(filenamez, modez); -} -IData VL_FOPEN_S(const char* filenamep, const char* modep) VL_MT_SAFE { - return VerilatedImp::fdNew(fopen(filenamep, modep)); +IData VL_FOPEN_MCD_N(const std::string& filename) VL_MT_SAFE { + return VerilatedImp::fdNewMcd(filename.c_str()); } +void VL_FFLUSH_I(IData fdi) VL_MT_SAFE { + VerilatedImp::fdFlush(fdi); +} +IData VL_FSEEK_I(IData fdi, IData offset, IData origin) VL_MT_SAFE { + return VerilatedImp::fdSeek(fdi, offset, origin); +} +IData VL_FTELL_I(IData fdi) VL_MT_SAFE { + return VerilatedImp::fdTell(fdi); +} void VL_FCLOSE_I(IData fdi) VL_MT_SAFE { // While threadsafe, each thread can only access different file handles - FILE* fp = VL_CVT_I_FP(fdi); - if (VL_UNLIKELY(!fp)) return; - fclose(fp); - VerilatedImp::fdDelete(fdi); + VerilatedImp::fdClose(fdi); } void VL_FFLUSH_ALL() VL_MT_SAFE { fflush(stdout); } @@ -1335,15 +1328,18 @@ void VL_FWRITEF(IData fpi, const char* formatp, ...) VL_MT_SAFE { // While threadsafe, each thread can only access different file handles static VL_THREAD_LOCAL std::string output; // static only for speed output = ""; - FILE* fp = VL_CVT_I_FP(fpi); - if (VL_UNLIKELY(!fp)) return; va_list ap; va_start(ap, formatp); _vl_vsformat(output, formatp, ap); va_end(ap); - fputs(output.c_str(), fp); + FILE* fp[30]; + const int n = VerilatedImp::fdToFp(fpi, fp, 30); + for (std::size_t i = 0; i < n; i++) { + if (VL_UNLIKELY(!fp[i])) continue; + fputs(output.c_str(), fp[i]); + } } IData VL_FSCANF_IX(IData fpi, const char* formatp, ...) VL_MT_SAFE { diff --git a/include/verilated.h b/include/verilated.h index 9eb804125..bddb1c9fd 100644 --- a/include/verilated.h +++ b/include/verilated.h @@ -673,13 +673,9 @@ extern WDataOutP _vl_moddiv_w(int lbits, WDataOutP owp, WDataInP lwp, WDataInP r /// File I/O extern IData VL_FGETS_IXI(int obits, void* destp, IData fpi); -extern IData VL_FOPEN_S(const char* filenamep, const char* modep); -extern IData VL_FOPEN_WI(int fnwords, WDataInP filenamep, IData mode); -extern IData VL_FOPEN_QI(QData filename, IData mode); -inline IData VL_FOPEN_II(IData filename, IData mode) VL_MT_SAFE { - return VL_FOPEN_QI(filename, mode); -} - +extern void VL_FFLUSH_I(IData fdi); +extern IData VL_FSEEK_I(IData fdi, IData offset, IData origin); +extern IData VL_FTELL_I(IData fdi); extern void VL_FCLOSE_I(IData fdi); extern IData VL_FREAD_I(int width, int array_lsb, int array_size, void* memp, IData fpi, @@ -737,7 +733,7 @@ extern const char* vl_mc_scan_plusargs(const char* prefixp); // PLIish #define _VL_SET_QII(ld, rd) ((static_cast(ld) << VL_ULL(32)) | static_cast(rd)) /// Return FILE* from IData -extern FILE* VL_CVT_I_FP(IData lhs); +extern FILE* VL_CVT_I_FP(IData lhs) VL_MT_SAFE; // clang-format off // Use a union to avoid cast-to-different-size warnings diff --git a/include/verilated_heavy.h b/include/verilated_heavy.h index ee71d867d..03df61629 100644 --- a/include/verilated_heavy.h +++ b/include/verilated_heavy.h @@ -427,7 +427,8 @@ extern std::string VL_TOLOWER_NN(const std::string& ld); extern std::string VL_TOUPPER_NN(const std::string& ld); extern IData VL_FERROR_IN(IData fpi, std::string& outputr) VL_MT_SAFE; -extern IData VL_FOPEN_NI(const std::string& filename, IData mode) VL_MT_SAFE; +extern IData VL_FOPEN_NN(const std::string& filename, const std::string& mode) VL_MT_SAFE; +extern IData VL_FOPEN_MCD_N(const std::string& filename) VL_MT_SAFE; extern void VL_READMEM_N(bool hex, int bits, QData depth, int array_lsb, const std::string& filename, void* memp, QData start, QData end) VL_MT_SAFE; diff --git a/include/verilated_imp.h b/include/verilated_imp.h index d8cb73785..e31effa34 100644 --- a/include/verilated_imp.h +++ b/include/verilated_imp.h @@ -32,6 +32,7 @@ #include #include #include +#include #ifdef VL_THREADED # include # include @@ -229,17 +230,19 @@ protected: VerilatedMutex m_fdMutex; ///< Protect m_fdps, m_fdFree std::vector m_fdps VL_GUARDED_BY(m_fdMutex); ///< File descriptors /// List of free descriptors (SLOW - FOPEN/CLOSE only) - std::deque m_fdFree VL_GUARDED_BY(m_fdMutex); + std::vector m_fdFree VL_GUARDED_BY(m_fdMutex); + // List of free descriptors in the MCT region [4, 32) + std::vector m_fdFreeMct VL_GUARDED_BY(m_fdMutex); public: // But only for verilated*.cpp // CONSTRUCTORS VerilatedImp() : m_argVecLoaded(false) , m_exportNext(0) { - m_fdps.resize(3); - m_fdps[0] = stdin; - m_fdps[1] = stdout; - m_fdps[2] = stderr; + s_s.m_fdps.resize(31); + std::fill(s_s.m_fdps.begin(), s_s.m_fdps.end(), (FILE*)0); + s_s.m_fdFreeMct.resize(30); + std::iota(s_s.m_fdFreeMct.begin(), s_s.m_fdFreeMct.end(), 1); } ~VerilatedImp() {} @@ -448,36 +451,96 @@ public: // But only for verilated*.cpp public: // But only for verilated*.cpp // METHODS - file IO - static IData fdNew(FILE* fp) VL_MT_SAFE { + static IData fdNewMcd(const char* filenamep) VL_MT_SAFE { + VerilatedLockGuard lock(s_s.m_fdMutex); + if (s_s.m_fdFreeMct.empty()) return 0; + IData idx = s_s.m_fdFreeMct.back(); + s_s.m_fdFreeMct.pop_back(); + s_s.m_fdps[idx] = fopen(filenamep, "w"); + if(VL_UNLIKELY(!s_s.m_fdps[idx])) return 0; + return (1 << idx); + } + static IData fdNew(const char* filenamep, const char* modep) VL_MT_SAFE { + FILE* fp = fopen(filenamep, modep); if (VL_UNLIKELY(!fp)) return 0; // Bit 31 indicates it's a descriptor not a MCD VerilatedLockGuard lock(s_s.m_fdMutex); if (s_s.m_fdFree.empty()) { // Need to create more space in m_fdps and m_fdFree - size_t start = s_s.m_fdps.size(); - s_s.m_fdps.resize(start * 2); - for (size_t i = start; i < start * 2; ++i) { - s_s.m_fdFree.push_back(static_cast(i)); - } + const size_t start = std::max(31ul + 1ul + 3ul, s_s.m_fdps.size()); + const size_t excess = 10; + s_s.m_fdps.resize(start + excess); + std::fill(s_s.m_fdps.begin() + start, s_s.m_fdps.end(), (FILE*)0); + s_s.m_fdFree.resize(excess); + std::iota(s_s.m_fdFree.begin(), s_s.m_fdFree.end(), start); } IData idx = s_s.m_fdFree.back(); s_s.m_fdFree.pop_back(); s_s.m_fdps[idx] = fp; return (idx | (1UL << 31)); // bit 31 indicates not MCD } - static void fdDelete(IData fdi) VL_MT_SAFE { - IData idx = VL_MASK_I(31) & fdi; + static void fdFlush(IData fdi) VL_MT_SAFE { + FILE* fp[30]; VerilatedLockGuard lock(s_s.m_fdMutex); - if (VL_UNLIKELY(!(fdi & (VL_ULL(1) << 31)) || idx >= s_s.m_fdps.size())) return; - if (VL_UNLIKELY(!s_s.m_fdps[idx])) return; // Already free - s_s.m_fdps[idx] = NULL; - s_s.m_fdFree.push_back(idx); + const int n = fdToFp(fdi, fp, 30); + for (int i = 0; i < n; i++) fflush(fp[i]); } - static inline FILE* fdToFp(IData fdi) VL_MT_SAFE { - IData idx = VL_MASK_I(31) & fdi; - VerilatedLockGuard lock(s_s.m_fdMutex); // This might get slow, if it does we can cache it - if (VL_UNLIKELY(!(fdi & (VL_ULL(1) << 31)) || idx >= s_s.m_fdps.size())) return NULL; - return s_s.m_fdps[idx]; + static IData fdSeek(IData fdi, IData offset, IData origin) VL_MT_SAFE { + FILE* fp; + VerilatedLockGuard lock(s_s.m_fdMutex); + const int n = fdToFp(fdi, &fp); + if(VL_UNLIKELY(!fp || (n != 1))) return 0; + return static_cast(fseek(fp, static_cast(offset), static_cast(origin))); + } + static IData fdTell(IData fdi) VL_MT_SAFE { + FILE* fp; + VerilatedLockGuard lock(s_s.m_fdMutex); + const int n = fdToFp(fdi, &fp); + if(VL_UNLIKELY(!fp || (n != 1))) return 0; + return static_cast(ftell(fp)); + } + static void fdClose(IData fdi) VL_MT_SAFE { + VerilatedLockGuard lock(s_s.m_fdMutex); + if ((fdi & (1 << 31)) != 0) { + // Non-MCD case + IData idx = VL_MASK_I(31) & fdi; + if (VL_UNLIKELY(idx >= s_s.m_fdps.size())) return; + if (VL_UNLIKELY(!s_s.m_fdps[idx])) return; // Already free + fclose(s_s.m_fdps[idx]); + s_s.m_fdps[idx] = (FILE*)0; + s_s.m_fdFree.push_back(idx); + } else { + // MCD case + for (int i = 0; (fdi != 0) && (i < 31); i++, fdi >>= 1) { + if (fdi & VL_MASK_I(1)) { + fclose(s_s.m_fdps[i]); + s_s.m_fdps[i] = NULL; + s_s.m_fdFreeMct.push_back(i); + } + } + } + } + static inline int fdToFp(IData fdi, FILE** fp, std::size_t max = 1) VL_MT_SAFE { + if (VL_UNLIKELY(!fp || (max == 0))) return 0; + int out = 0; + if ((fdi & (1 << 31)) != 0) { + // Non-MCD case + IData idx = fdi & VL_MASK_I(31); + switch (idx) { + case 0: fp[out++] = stdin; break; + case 1: fp[out++] = stdout; break; + case 2: fp[out++] = stderr; break; + default: + if (VL_LIKELY(idx < s_s.m_fdps.size())) + fp[out++] = s_s.m_fdps[idx]; + break; + } + } else { + // MCD Case + for (int i = 0; (fdi != 0) && (out < max) && (i < 31); i++, fdi >>= 1) + if (fdi & VL_MASK_I(1)) fp[out++] = s_s.m_fdps[i]; + } + return out; } }; diff --git a/include/verilated_vpi.cpp b/include/verilated_vpi.cpp index 6d8b099a8..1950d9173 100644 --- a/include/verilated_vpi.cpp +++ b/include/verilated_vpi.cpp @@ -1937,7 +1937,7 @@ void vpi_get_time(vpiHandle object, p_vpi_time time_p) { PLI_UINT32 vpi_mcd_open(PLI_BYTE8* filenamep) { VerilatedVpiImp::assertOneCheck(); _VL_VPI_ERROR_RESET(); - return VL_FOPEN_S(filenamep, "wb"); + return VL_FOPEN_NN(filenamep, "wb"); } PLI_UINT32 vpi_mcd_close(PLI_UINT32 mcd) { diff --git a/src/V3AstNodes.h b/src/V3AstNodes.h index 95ece193f..e4bfdf48a 100644 --- a/src/V3AstNodes.h +++ b/src/V3AstNodes.h @@ -3801,6 +3801,27 @@ public: AstNode* modep() const { return op3p(); } }; +class AstFOpenMcd : public AstNodeStmt { + // Although a system function in IEEE, here a statement which sets the file pointer (MCD) +public: + AstFOpenMcd(FileLine* fl, AstNode* filep, AstNode* filenamep) + : ASTGEN_SUPER(fl) { + setOp1p(filep); + setOp2p(filenamep); + } + ASTNODE_NODE_FUNCS(FOpenMcd) + virtual string verilogKwd() const { return "$fopen"; } + virtual bool isGateOptimizable() const { return false; } + virtual bool isPredictOptimizable() const { return false; } + virtual bool isPure() const { return false; } + virtual bool isOutputter() const { return true; } + virtual bool isUnlikely() const { return true; } + virtual V3Hash sameHash() const { return V3Hash(); } + virtual bool same(const AstNode* samep) const { return true; } + AstNode* filep() const { return op1p(); } + AstNode* filenamep() const { return op2p(); } +}; + class AstFFlush : public AstNodeStmt { // Parents: stmtlist // Children: file which must be a varref diff --git a/src/V3EmitC.cpp b/src/V3EmitC.cpp index 86391fe97..1ef39d769 100644 --- a/src/V3EmitC.cpp +++ b/src/V3EmitC.cpp @@ -526,20 +526,18 @@ public: } virtual void visit(AstFOpen* nodep) VL_OVERRIDE { iterateAndNextNull(nodep->filep()); - puts(" = VL_FOPEN_"); - emitIQW(nodep->filenamep()); - emitIQW(nodep->modep()); + puts(" = VL_FOPEN_NN("); + emitCvtPackStr(nodep->filenamep()); + putbs(", "); if (nodep->modep()->width() > 4 * 8) nodep->modep()->v3error("$fopen mode should be <= 4 characters"); - puts("("); - if (nodep->filenamep()->isWide()) { - puts(cvtToStr(nodep->filenamep()->widthWords())); - putbs(", "); - } - checkMaxWords(nodep->filenamep()); - iterateAndNextNull(nodep->filenamep()); - putbs(", "); - iterateAndNextNull(nodep->modep()); + emitCvtPackStr(nodep->modep()); + puts(");\n"); + } + virtual void visit(AstFOpenMcd* nodep) VL_OVERRIDE { + iterateAndNextNull(nodep->filep()); + puts(" = VL_FOPEN_MCD_N("); + emitCvtPackStr(nodep->filenamep()); puts(");\n"); } virtual void visit(AstNodeReadWriteMem* nodep) VL_OVERRIDE { @@ -599,29 +597,29 @@ public: } else { puts("if ("); iterateAndNextNull(nodep->filep()); - puts(") { fflush(VL_CVT_I_FP("); + puts(") { VL_FFLUSH_I("); iterateAndNextNull(nodep->filep()); - puts(")); }\n"); + puts("); }\n"); } } virtual void visit(AstFSeek* nodep) VL_OVERRIDE { - puts("(fseek(VL_CVT_I_FP("); + puts("(VL_FSEEK_I("); iterateAndNextNull(nodep->filep()); - puts("),"); + puts(","); iterateAndNextNull(nodep->offset()); puts(","); iterateAndNextNull(nodep->operation()); puts(")==-1?-1:0)"); } virtual void visit(AstFTell* nodep) VL_OVERRIDE { - puts("ftell(VL_CVT_I_FP("); + puts("VL_FTELL_I("); iterateAndNextNull(nodep->filep()); - puts("))"); + puts(")"); } virtual void visit(AstFRewind* nodep) VL_OVERRIDE { - puts("(fseek(VL_CVT_I_FP("); + puts("(VL_FSEEK_I("); iterateAndNextNull(nodep->filep()); - puts("), 0, 0)==-1?-1:0)"); + puts(", 0, 0)==-1?-1:0)"); } virtual void visit(AstFRead* nodep) VL_OVERRIDE { puts("VL_FREAD_I("); diff --git a/src/V3EmitCInlines.cpp b/src/V3EmitCInlines.cpp index 23a4aecf8..6da034853 100644 --- a/src/V3EmitCInlines.cpp +++ b/src/V3EmitCInlines.cpp @@ -100,6 +100,14 @@ class EmitCInlines : EmitCBaseVisitor { v3Global.needHeavy(true); iterateChildren(nodep); } + virtual void visit(AstFOpen* nodep) VL_OVERRIDE { + v3Global.needHeavy(true); + iterateChildren(nodep); + } + virtual void visit(AstFOpenMcd* nodep) VL_OVERRIDE { + v3Global.needHeavy(true); + iterateChildren(nodep); + } //--------------------------------------- virtual void visit(AstNode* nodep) VL_OVERRIDE { iterateChildren(nodep); } diff --git a/src/V3EmitV.cpp b/src/V3EmitV.cpp index bea707289..34ba8ae1c 100644 --- a/src/V3EmitV.cpp +++ b/src/V3EmitV.cpp @@ -252,12 +252,16 @@ class EmitVBaseVisitor : public EmitCBaseVisitor { } virtual void visit(AstFOpen* nodep) VL_OVERRIDE { putfs(nodep, nodep->verilogKwd()); - putbs(" ("); - if (nodep->filep()) iterateAndNextNull(nodep->filep()); + putbs("("); + iterateAndNextNull(nodep->filenamep()); putbs(","); - if (nodep->filenamep()) iterateAndNextNull(nodep->filenamep()); - putbs(","); - if (nodep->modep()) iterateAndNextNull(nodep->modep()); + iterateAndNextNull(nodep->modep()); + puts(");\n"); + } + virtual void visit(AstFOpenMcd* nodep) VL_OVERRIDE { + putfs(nodep, nodep->verilogKwd()); + putbs("("); + iterateAndNextNull(nodep->filenamep()); puts(");\n"); } virtual void visit(AstFClose* nodep) VL_OVERRIDE { diff --git a/src/V3LinkLValue.cpp b/src/V3LinkLValue.cpp index 251146a94..585c78eae 100644 --- a/src/V3LinkLValue.cpp +++ b/src/V3LinkLValue.cpp @@ -91,6 +91,16 @@ private: } m_setRefLvalue = last_setRefLvalue; } + virtual void visit(AstFOpenMcd* nodep) VL_OVERRIDE { + bool last_setRefLvalue = m_setRefLvalue; + { + m_setRefLvalue = true; + iterateAndNextNull(nodep->filep()); + m_setRefLvalue = false; + iterateAndNextNull(nodep->filenamep()); + } + m_setRefLvalue = last_setRefLvalue; + } virtual void visit(AstFClose* nodep) VL_OVERRIDE { bool last_setRefLvalue = m_setRefLvalue; { diff --git a/src/V3LinkResolve.cpp b/src/V3LinkResolve.cpp index 1a576e857..ad8832c6b 100644 --- a/src/V3LinkResolve.cpp +++ b/src/V3LinkResolve.cpp @@ -397,6 +397,10 @@ private: iterateChildren(nodep); expectDescriptor(nodep, VN_CAST(nodep->filep(), NodeVarRef)); } + virtual void visit(AstFOpenMcd* nodep) VL_OVERRIDE { + iterateChildren(nodep); + expectDescriptor(nodep, VN_CAST(nodep->filep(), NodeVarRef)); + } virtual void visit(AstFClose* nodep) VL_OVERRIDE { iterateChildren(nodep); expectDescriptor(nodep, VN_CAST(nodep->filep(), NodeVarRef)); diff --git a/src/V3Width.cpp b/src/V3Width.cpp index b57fe02c7..2b45da211 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -3317,6 +3317,11 @@ private: userIterateAndNext(nodep->filenamep(), WidthVP(SELF, BOTH).p()); userIterateAndNext(nodep->modep(), WidthVP(SELF, BOTH).p()); } + virtual void visit(AstFOpenMcd* nodep) VL_OVERRIDE { + assertAtStatement(nodep); + iterateCheckFileDesc(nodep, nodep->filep(), BOTH); + userIterateAndNext(nodep->filenamep(), WidthVP(SELF, BOTH).p()); + } virtual void visit(AstFClose* nodep) VL_OVERRIDE { assertAtStatement(nodep); iterateCheckFileDesc(nodep, nodep->filep(), BOTH); diff --git a/src/verilog.y b/src/verilog.y index 55631624e..5c3693047 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -2936,7 +2936,7 @@ statementVerilatorPragmas: foperator_assignment: // IEEE: operator_assignment (for first part of expression) fexprLvalue '=' delayE expr { $$ = new AstAssign($2,$1,$4); } - | fexprLvalue '=' yD_FOPEN '(' expr ')' { $$ = NULL; BBUNSUP($3, "Unsupported: $fopen with multichannel descriptor. Add ,\"w\" as second argument to open a file descriptor."); } + | fexprLvalue '=' yD_FOPEN '(' expr ')' { $$ = new AstFOpenMcd($3,$1,$5); } | fexprLvalue '=' yD_FOPEN '(' expr ',' expr ')' { $$ = new AstFOpen($3,$1,$5,$7); } // //UNSUP ~f~exprLvalue '=' delay_or_event_controlE expr { UNSUP } diff --git a/test_regress/t/t_sys_file_basic_mcd.out b/test_regress/t/t_sys_file_basic_mcd.out new file mode 100644 index 000000000..bac06f7e9 --- /dev/null +++ b/test_regress/t/t_sys_file_basic_mcd.out @@ -0,0 +1,2 @@ +Sean Connery was the best Bond. +*-* All Finished *-* diff --git a/test_regress/t/t_sys_file_basic_mcd.pl b/test_regress/t/t_sys_file_basic_mcd.pl new file mode 100755 index 000000000..106810fa0 --- /dev/null +++ b/test_regress/t/t_sys_file_basic_mcd.pl @@ -0,0 +1,29 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 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(simulator => 1); + +unlink("$Self->{obj_dir}/t_sys_file_basic_mcd.log"); + +compile(); +execute( + check_finished => 1, + expect_filename => $Self->{golden_filename}, + ); + +files_identical("$Self->{obj_dir}/t_sys_file_basic_mcd_test2_0.dat", + "$Self->{t_dir}/t_sys_file_basic_mcd_test2_0.dat"); +files_identical("$Self->{obj_dir}/t_sys_file_basic_mcd_test2_1.dat", + "$Self->{t_dir}/t_sys_file_basic_mcd_test2_1.dat"); +files_identical("$Self->{obj_dir}/t_sys_file_basic_mcd_test2_2.dat", + "$Self->{t_dir}/t_sys_file_basic_mcd_test2_2.dat"); + +ok(1); +1; diff --git a/test_regress/t/t_sys_file_basic_mcd.v b/test_regress/t/t_sys_file_basic_mcd.v new file mode 100644 index 000000000..fd59a8211 --- /dev/null +++ b/test_regress/t/t_sys_file_basic_mcd.v @@ -0,0 +1,109 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2020 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + + +module t; +`define STR(__s) `"__s`" + + task automatic fail(string s); begin + $display({"FAIL! Reason: ", s}); + $stop; + end endtask + + task automatic test1; begin + int fd[30], fd_fail, fd_success, fd_close, tmp; + for (int i = 0; i < 30; i++) begin + // Attempt to allocate 30 MCD descriptors; returned descriptors + // should fall within correct range: [1, 30]. + tmp = $fopen($sformatf("%s/some_file%0d.dat", `STR(`TEST_OBJ_DIR), i)); + fd[i] = tmp; + if ((fd[i] == 0) || !$onehot(fd[i])) + fail($sformatf("MCD descriptor out of range %d", fd[i])); + end + // Attempt to allocate another MCD descriptor when all should + // be used. We expect this operation to fail and return the + // invalid descriptor (0). + fd_fail = $fopen($sformatf("%s/another_file.dat", `STR(`TEST_OBJ_DIR))); + if (fd_fail != 0) + fail("Able to allocate MCD descriptor when fully utilized."); + // Return descriptor back to pool + fd_close = fd[0]; + $fclose(fd_close); + // Re-attempt MCD allocation; should pass at this point. + fd_success = $fopen($sformatf("%s/yet_another_file.dat", `STR(`TEST_OBJ_DIR))); + if (fd_success == 0) + fail("Expect to have free descriptors at this point."); + // Returned descriptor should have a value matching that which + // had previously just been returned back to the pool. + if (fd_success != fd[0]) + fail("Descriptor has incorrect value."); + // Return all descriptors back to the pool. + for (int i = 1; i < 30; i++) begin + fd_close = fd[i]; + $fclose(fd_close); + end + end endtask + + task automatic test2; begin + // Validate basic MCD functionality. + + integer fd[3], fd_all, tmp; + for (int i = 0; i < 3; i++) begin + + tmp = $fopen($sformatf("%s/t_sys_file_basic_mcd_test2_%0d.dat", `STR(`TEST_OBJ_DIR), i)); + fd[i] = tmp; + end + + fd_all = 0; + for (int i = 0; i < 3; i++) + fd_all |= fd[i]; + + $fwrite(fd_all, "Scotland is the greatest country.\n"); + $fwrite(fd_all, "All other countries are inferior.\n"); + $fwrite(fd_all, "Woe betide those to stand against the mighty Scottish nation.\n"); + $fclose(fd_all); + end endtask + + task automatic test3; begin + // Write some things to standard output. + $fwrite(32'h8000_0001, "Sean Connery was the best Bond.\n"); + end endtask + + task automatic test4; begin + int fd; + // Small + fd = $fopen("a"); + if (fd == 0) fail("Small filename could not be opened."); + $fclose(fd); + // Large + fd = $fopen({"some_very_large_filename_that_no_one_would_ever_use_", + "except_to_purposefully_break_my_beautiful_code_", + "in a regression setting.dat"}); + if (fd == 0) fail("Long filename could not be opened."); + $fclose(fd); + end endtask + + initial begin + + // Test1: Validate file descriptor region. + test1; + + // Test2: Validate basic MCD functionality. + test2; + + // Test3: Validate explicit descriptor ID + test3; + + // Test4: Validate filename lengths + test4; + + $write("*-* All Finished *-*\n"); + $finish(0); // Test arguments to finish + + end // initial begin + +`undef STR +endmodule // t diff --git a/test_regress/t/t_sys_file_basic_mcd_test2_0.dat b/test_regress/t/t_sys_file_basic_mcd_test2_0.dat new file mode 100644 index 000000000..d31f40f60 --- /dev/null +++ b/test_regress/t/t_sys_file_basic_mcd_test2_0.dat @@ -0,0 +1,3 @@ +Scotland is the greatest country. +All other countries are inferior. +Woe betide those to stand against the mighty Scottish nation. diff --git a/test_regress/t/t_sys_file_basic_mcd_test2_1.dat b/test_regress/t/t_sys_file_basic_mcd_test2_1.dat new file mode 100644 index 000000000..d31f40f60 --- /dev/null +++ b/test_regress/t/t_sys_file_basic_mcd_test2_1.dat @@ -0,0 +1,3 @@ +Scotland is the greatest country. +All other countries are inferior. +Woe betide those to stand against the mighty Scottish nation. diff --git a/test_regress/t/t_sys_file_basic_mcd_test2_2.dat b/test_regress/t/t_sys_file_basic_mcd_test2_2.dat new file mode 100644 index 000000000..d31f40f60 --- /dev/null +++ b/test_regress/t/t_sys_file_basic_mcd_test2_2.dat @@ -0,0 +1,3 @@ +Scotland is the greatest country. +All other countries are inferior. +Woe betide those to stand against the mighty Scottish nation.