Add std::process class (#4212)

This commit is contained in:
Aleksander Kiryk 2023-06-01 07:02:08 -07:00 committed by GitHub
parent 9cc218db3e
commit db7935faf3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 714 additions and 121 deletions

View File

@ -26,10 +26,6 @@
// verilator lint_off TIMESCALEMOD
// verilator lint_off UNUSEDSIGNAL
package std;
// The process class is not implemented, but it's predeclared here,
// so the linter accepts references to it.
typedef class process;
class mailbox #(type T);
protected int m_bound;
protected T m_queue[$];
@ -45,7 +41,7 @@ package std;
task put(T message);
`ifdef VERILATOR_TIMING
if (m_bound != 0)
wait (m_queue.size() < m_bound);
wait (m_queue.size() < m_bound);
m_queue.push_back(message);
`endif
endtask
@ -117,43 +113,80 @@ package std;
endclass
class process;
typedef enum { FINISHED, RUNNING, WAITING, SUSPENDED, KILLED } state;
static process _s_global_process;
typedef enum {
FINISHED = 0,
RUNNING = 1,
WAITING = 2,
SUSPENDED = 3,
KILLED = 4
} state;
`ifdef VERILATOR_TIMING
// Width visitor changes it to VlProcessRef
protected chandle m_process;
`endif
static function process self();
// Unsupported, emulating with single process' state
if (!_s_global_process) _s_global_process = new;
return _s_global_process;
process p = new;
`ifdef VERILATOR_TIMING
$c(p.m_process, " = vlProcess;");
`endif
return p;
endfunction
protected function void set_status(state s);
`ifdef VERILATOR_TIMING
$c(m_process, "->state(", s, ");");
`endif
endfunction
function state status();
// Unsupported, emulating with single process' state
`ifdef VERILATOR_TIMING
return state'($c(m_process, "->state()"));
`else
return RUNNING;
`endif
endfunction
function void kill();
$error("std::process::kill() not supported");
set_status(KILLED);
endfunction
task await();
$error("std::process::await() not supported");
endtask
function void suspend();
$error("std::process::suspend() not supported");
endfunction
function void resume();
$error("std::process::resume() not supported");
set_status(RUNNING);
endfunction
// When really implemented, srandom must operates on the process, but for
task await();
`ifdef VERILATOR_TIMING
wait (status() == FINISHED || status() == KILLED);
`endif
endtask
// When really implemented, srandom must operate on the process, but for
// now rely on the srandom() that is automatically generated for all
// classes.
//
// function void srandom(int seed);
// endfunction
// The methods below work only if set_randstate is never applied to
// a state string created before another such string. Full support
// could use VlRNG class to store the state per process in VlProcess
// objects.
function string get_randstate();
// Could operate on all proceses for now
// No error, as harmless until set_randstate is called
return "NOT_SUPPORTED";
string s;
s.itoa($random); // Get a random number
set_randstate(s); // Pretend it's the state of RNG
return s;
endfunction
function void set_randstate(string randstate);
$error("std::process::set_randstate() not supported");
// Could operate on all proceses for now
function void set_randstate(string s);
$urandom(s.atoi()); // Set the seed using a string
endfunction
endclass
endpackage

View File

@ -30,7 +30,16 @@ void VlCoroutineHandle::resume() {
// main process
if (VL_LIKELY(m_coro)) {
VL_DEBUG_IF(VL_DBG_MSGF(" Resuming: "); dump(););
m_coro();
if (m_process) { // If process state is managed with std::process
if (m_process->state() == VlProcess::KILLED) {
m_coro.destroy();
} else {
m_process->state(VlProcess::RUNNING);
m_coro();
}
} else {
m_coro();
}
m_coro = nullptr;
}
}

View File

@ -86,6 +86,36 @@ public:
#endif
};
//===================================================================
// VlProcess stores metadata of running processes
class VlProcess final {
// MEMBERS
int m_state; // Current state of the process
public:
// TYPES
enum : int { // Type int for compatibility with $c
FINISHED = 0,
RUNNING = 1,
WAITING = 2,
SUSPENDED = 3,
KILLED = 4,
};
// CONSTRUCTORS
VlProcess()
: m_state{RUNNING} {}
// METHODS
int state() { return m_state; }
void state(int s) { m_state = s; }
};
using VlProcessRef = std::shared_ptr<VlProcess>;
inline std::string VL_TO_STRING(const VlProcessRef& p) { return std::string("process"); }
//=============================================================================
// VlCoroutineHandle is a non-copyable (but movable) coroutine handle. On resume, the handle is
// cleared, as we assume that either the coroutine has finished and deleted itself, or, if it got
@ -96,25 +126,38 @@ class VlCoroutineHandle final {
// MEMBERS
std::coroutine_handle<> m_coro; // The wrapped coroutine handle
VlProcessRef m_process; // Data of the suspended process, null if not needed
VlFileLineDebug m_fileline;
public:
// CONSTRUCTORS
// Construct
VlCoroutineHandle()
: m_coro{nullptr} {}
VlCoroutineHandle(std::coroutine_handle<> coro, VlFileLineDebug fileline)
VlCoroutineHandle(VlProcessRef process)
: m_coro{nullptr}
, m_process{process} {
if (m_process) m_process->state(VlProcess::WAITING);
}
VlCoroutineHandle(std::coroutine_handle<> coro, VlProcessRef process, VlFileLineDebug fileline)
: m_coro{coro}
, m_fileline{fileline} {}
, m_process{process}
, m_fileline{fileline} {
if (m_process) m_process->state(VlProcess::WAITING);
}
// Move the handle, leaving a nullptr
VlCoroutineHandle(VlCoroutineHandle&& moved)
: m_coro{std::exchange(moved.m_coro, nullptr)}
, m_process{moved.m_process}
, m_fileline{moved.m_fileline} {}
// Destroy if the handle isn't null
~VlCoroutineHandle() {
// Usually these coroutines should get resumed; we only need to clean up if we destroy a
// model with some coroutines suspended
if (VL_UNLIKELY(m_coro)) m_coro.destroy();
if (VL_UNLIKELY(m_coro)) {
m_coro.destroy();
if (m_process && m_process->state() != VlProcess::KILLED) {
m_process->state(VlProcess::FINISHED);
}
}
}
// METHODS
// Move the handle, leaving a null handle
@ -122,7 +165,7 @@ public:
m_coro = std::exchange(moved.m_coro, nullptr);
return *this;
}
// Resume the coroutine if the handle isn't null
// Resume the coroutine if the handle isn't null and the process isn't killed
void resume();
#ifdef VL_DEBUG
void dump() const;
@ -173,21 +216,24 @@ public:
void dump() const;
#endif
// Used by coroutines for co_awaiting a certain simulation time
auto delay(uint64_t delay, const char* filename = VL_UNKNOWN, int lineno = 0) {
auto delay(uint64_t delay, VlProcessRef process, const char* filename = VL_UNKNOWN,
int lineno = 0) {
struct Awaitable {
VlProcessRef process; // Data of the suspended process, null if not needed
VlDelayedCoroutineQueue& queue;
uint64_t delay;
VlFileLineDebug fileline;
bool await_ready() const { return false; } // Always suspend
void await_suspend(std::coroutine_handle<> coro) {
queue.push_back({delay, VlCoroutineHandle{coro, fileline}});
queue.push_back({delay, VlCoroutineHandle{coro, process, fileline}});
// Move last element to the proper place in the max-heap
std::push_heap(queue.begin(), queue.end());
}
void await_resume() const {}
};
return Awaitable{m_queue, m_context.time() + delay, VlFileLineDebug{filename, lineno}};
return Awaitable{process, m_queue, m_context.time() + delay,
VlFileLineDebug{filename, lineno}};
}
};
@ -224,21 +270,23 @@ public:
void dump(const char* eventDescription) const;
#endif
// Used by coroutines for co_awaiting a certain trigger
auto trigger(bool commit, const char* eventDescription = VL_UNKNOWN,
auto trigger(bool commit, VlProcessRef process, const char* eventDescription = VL_UNKNOWN,
const char* filename = VL_UNKNOWN, int lineno = 0) {
VL_DEBUG_IF(VL_DBG_MSGF(" Suspending process waiting for %s at %s:%d\n",
eventDescription, filename, lineno););
struct Awaitable {
VlCoroutineVec& suspended; // Coros waiting on trigger
VlProcessRef process; // Data of the suspended process, null if not needed
VlFileLineDebug fileline;
bool await_ready() const { return false; } // Always suspend
void await_suspend(std::coroutine_handle<> coro) {
suspended.emplace_back(coro, fileline);
suspended.emplace_back(coro, process, fileline);
}
void await_resume() const {}
};
return Awaitable{commit ? m_ready : m_uncommitted, VlFileLineDebug{filename, lineno}};
return Awaitable{commit ? m_ready : m_uncommitted, process,
VlFileLineDebug{filename, lineno}};
}
};
@ -271,18 +319,19 @@ class VlDynamicTriggerScheduler final {
// with destructive post updates, e.g. named events)
// METHODS
auto awaitable(VlCoroutineVec& queue, const char* filename, int lineno) {
auto awaitable(VlProcessRef process, VlCoroutineVec& queue, const char* filename, int lineno) {
struct Awaitable {
VlProcessRef process; // Data of the suspended process, null if not needed
VlCoroutineVec& suspended; // Coros waiting on trigger
VlFileLineDebug fileline;
bool await_ready() const { return false; } // Always suspend
void await_suspend(std::coroutine_handle<> coro) {
suspended.emplace_back(coro, fileline);
suspended.emplace_back(coro, process, fileline);
}
void await_resume() const {}
};
return Awaitable{queue, VlFileLineDebug{filename, lineno}};
return Awaitable{process, queue, VlFileLineDebug{filename, lineno}};
}
public:
@ -296,23 +345,26 @@ public:
void dump() const;
#endif
// Used by coroutines for co_awaiting trigger evaluation
auto evaluation(const char* eventDescription, const char* filename, int lineno) {
auto evaluation(VlProcessRef process, const char* eventDescription, const char* filename,
int lineno) {
VL_DEBUG_IF(VL_DBG_MSGF(" Suspending process waiting for %s at %s:%d\n",
eventDescription, filename, lineno););
return awaitable(m_suspended, filename, lineno);
return awaitable(process, m_suspended, filename, lineno);
}
// Used by coroutines for co_awaiting the trigger post update step
auto postUpdate(const char* eventDescription, const char* filename, int lineno) {
auto postUpdate(VlProcessRef process, const char* eventDescription, const char* filename,
int lineno) {
VL_DEBUG_IF(
VL_DBG_MSGF(" Process waiting for %s at %s:%d awaiting the post update step\n",
eventDescription, filename, lineno););
return awaitable(m_post, filename, lineno);
return awaitable(process, m_post, filename, lineno);
}
// Used by coroutines for co_awaiting the resumption step (in 'act' eval)
auto resumption(const char* eventDescription, const char* filename, int lineno) {
auto resumption(VlProcessRef process, const char* eventDescription, const char* filename,
int lineno) {
VL_DEBUG_IF(VL_DBG_MSGF(" Process waiting for %s at %s:%d awaiting resumption\n",
eventDescription, filename, lineno););
return awaitable(m_triggered, filename, lineno);
return awaitable(process, m_triggered, filename, lineno);
}
};
@ -342,24 +394,27 @@ class VlForkSync final {
public:
// Create the join object and set the counter to the specified number
void init(size_t count) { m_join.reset(new VlJoin{count, {}}); }
void init(size_t count, VlProcessRef process) { m_join.reset(new VlJoin{count, {process}}); }
// Called whenever any of the forked processes finishes. If the join counter reaches 0, the
// main process gets resumed
void done(const char* filename = VL_UNKNOWN, int lineno = 0);
// Used by coroutines for co_awaiting a join
auto join(const char* filename = VL_UNKNOWN, int lineno = 0) {
auto join(VlProcessRef process, const char* filename = VL_UNKNOWN, int lineno = 0) {
assert(m_join);
VL_DEBUG_IF(
VL_DBG_MSGF(" Awaiting join of fork at: %s:%d\n", filename, lineno););
struct Awaitable {
VlProcessRef process; // Data of the suspended process, null if not needed
const std::shared_ptr<VlJoin> join; // Join to await on
VlFileLineDebug fileline;
bool await_ready() { return join->m_counter == 0; } // Suspend if join still exists
void await_suspend(std::coroutine_handle<> coro) { join->m_susp = {coro, fileline}; }
void await_suspend(std::coroutine_handle<> coro) {
join->m_susp = {coro, process, fileline};
}
void await_resume() const {}
};
return Awaitable{m_join, VlFileLineDebug{filename, lineno}};
return Awaitable{process, m_join, VlFileLineDebug{filename, lineno}};
}
};

View File

@ -461,6 +461,7 @@ public:
TRIGGER_SCHEDULER,
DYNAMIC_TRIGGER_SCHEDULER,
FORK_SYNC,
PROCESS_REFERENCE,
// Unsigned and two state; fundamental types
UINT32,
UINT64,
@ -493,6 +494,7 @@ public:
"VlTriggerScheduler",
"VlDynamicTriggerScheduler",
"VlFork",
"VlProcessRef",
"IData",
"QData",
"LOGIC_IMPLICIT",
@ -500,13 +502,20 @@ public:
return names[m_e];
}
const char* dpiType() const {
static const char* const names[]
= {"%E-unk", "svBit", "char", "void*", "char",
"int", "%E-integer", "svLogic", "long long", "double",
"short", "%E-time", "const char*", "%E-untyped", "dpiScope",
"const char*", "%E-mtaskstate", "%E-triggervec", "%E-dly-sched", "%E-trig-sched",
"%E-dyn-sched", "%E-fork", "IData", "QData", "%E-logic-implct",
" MAX"};
static const char* const names[] = {"%E-unk", "svBit",
"char", "void*",
"char", "int",
"%E-integer", "svLogic",
"long long", "double",
"short", "%E-time",
"const char*", "%E-untyped",
"dpiScope", "const char*",
"%E-mtaskstate", "%E-triggervec",
"%E-dly-sched", "%E-trig-sched",
"%E-dyn-sched", "%E-fork",
"%E-proc-ref", "IData",
"QData", "%E-logic-implct",
" MAX"};
return names[m_e];
}
static void selfTest() {
@ -545,6 +554,7 @@ public:
case TRIGGER_SCHEDULER: return 0; // opaque
case DYNAMIC_TRIGGER_SCHEDULER: return 0; // opaque
case FORK_SYNC: return 0; // opaque
case PROCESS_REFERENCE: return 0; // opaque
case UINT32: return 32;
case UINT64: return 64;
default: return 0;
@ -584,7 +594,7 @@ public:
return (m_e == EVENT || m_e == STRING || m_e == SCOPEPTR || m_e == CHARPTR
|| m_e == MTASKSTATE || m_e == TRIGGERVEC || m_e == DELAY_SCHEDULER
|| m_e == TRIGGER_SCHEDULER || m_e == DYNAMIC_TRIGGER_SCHEDULER || m_e == FORK_SYNC
|| m_e == DOUBLE || m_e == UNTYPED);
|| m_e == PROCESS_REFERENCE || m_e == DOUBLE || m_e == UNTYPED);
}
bool isDouble() const VL_MT_SAFE { return m_e == DOUBLE; }
bool isEvent() const { return m_e == EVENT; }

View File

@ -438,6 +438,7 @@ public:
bool isEvent() const VL_MT_SAFE { return keyword() == VBasicDTypeKwd::EVENT; }
bool isTriggerVec() const VL_MT_SAFE { return keyword() == VBasicDTypeKwd::TRIGGERVEC; }
bool isForkSync() const VL_MT_SAFE { return keyword() == VBasicDTypeKwd::FORK_SYNC; }
bool isProcessRef() const VL_MT_SAFE { return keyword() == VBasicDTypeKwd::PROCESS_REFERENCE; }
bool isDelayScheduler() const VL_MT_SAFE {
return keyword() == VBasicDTypeKwd::DELAY_SCHEDULER;
}

View File

@ -86,6 +86,7 @@ private:
bool m_recursive : 1; // Recursive or part of recursion
bool m_underGenerate : 1; // Under generate (for warning)
bool m_virtual : 1; // Virtual method in class
bool m_fromStd : 1; // Part of std
VLifetime m_lifetime; // Lifetime
protected:
AstNodeFTask(VNType t, FileLine* fl, const string& name, AstNode* stmtsp)
@ -110,7 +111,8 @@ protected:
, m_pureVirtual{false}
, m_recursive{false}
, m_underGenerate{false}
, m_virtual{false} {
, m_virtual{false}
, m_fromStd{false} {
addStmtsp(stmtsp);
cname(name); // Might be overridden by dpi import/export
}
@ -170,6 +172,8 @@ public:
bool underGenerate() const { return m_underGenerate; }
void isVirtual(bool flag) { m_virtual = flag; }
bool isVirtual() const { return m_virtual; }
void isFromStd(bool flag) { m_fromStd = flag; }
bool isFromStd() const { return m_fromStd; }
void lifetime(const VLifetime& flag) { m_lifetime = flag; }
VLifetime lifetime() const { return m_lifetime; }
bool isFirstInMyListOfStatements(AstNode* n) const override { return n == stmtsp(); }
@ -272,10 +276,13 @@ public:
class AstNodeProcedure VL_NOT_FINAL : public AstNode {
// IEEE procedure: initial, final, always
// @astgen op2 := stmtsp : List[AstNode] // Note: op1 is used in some sub-types only
bool m_suspendable = false; // Is suspendable by a Delay, EventControl, etc.
bool m_suspendable : 1; // Is suspendable by a Delay, EventControl, etc.
bool m_needProcess : 1; // Implements part of a process that allocates std::process
protected:
AstNodeProcedure(VNType t, FileLine* fl, AstNode* stmtsp)
: AstNode{t, fl} {
m_needProcess = false;
m_suspendable = false;
addStmtsp(stmtsp);
}
@ -286,6 +293,8 @@ public:
bool isJustOneBodyStmt() const { return stmtsp() && !stmtsp()->nextp(); }
bool isSuspendable() const { return m_suspendable; }
void setSuspendable() { m_suspendable = true; }
bool needProcess() const { return m_needProcess; }
void setNeedProcess() { m_needProcess = true; }
};
class AstNodeRange VL_NOT_FINAL : public AstNode {
// A range, sized or unsized
@ -576,6 +585,7 @@ private:
bool m_dpiImportPrototype : 1; // This is the DPI import prototype (i.e.: provided by user)
bool m_dpiImportWrapper : 1; // Wrapper for invoking DPI import prototype from generated code
bool m_dpiTraceInit : 1; // DPI trace_init
bool m_needProcess : 1; // Implements part of a process that allocates std::process
public:
AstCFunc(FileLine* fl, const string& name, AstScope* scopep, const string& rtnType = "")
: ASTGEN_SUPER_CFunc(fl) {
@ -595,6 +605,7 @@ public:
m_isLoose = false;
m_isInline = false;
m_isVirtual = false;
m_needProcess = false;
m_entryPoint = false;
m_pure = false;
m_dpiContext = false;
@ -663,6 +674,8 @@ public:
void isInline(bool flag) { m_isInline = flag; }
bool isVirtual() const { return m_isVirtual; }
void isVirtual(bool flag) { m_isVirtual = flag; }
bool needProcess() const { return m_needProcess; }
void setNeedProcess() { m_needProcess = true; }
bool entryPoint() const { return m_entryPoint; }
void entryPoint(bool flag) { m_entryPoint = flag; }
bool pure() const { return m_pure; }

View File

@ -784,6 +784,8 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound) const VL_M
info.m_type = "VlDynamicTriggerScheduler";
} else if (bdtypep->isForkSync()) {
info.m_type = "VlForkSync";
} else if (bdtypep->isProcessRef()) {
info.m_type = "VlProcessRef";
} else if (bdtypep->isEvent()) {
info.m_type = "VlEvent";
} else if (dtypep->widthMin() <= 8) { // Handle unpacked arrays; not bdtypep->width
@ -1352,6 +1354,7 @@ void AstNode::dump(std::ostream& str) const {
void AstNodeProcedure::dump(std::ostream& str) const {
this->AstNode::dump(str);
if (isSuspendable()) str << " [SUSP]";
if (needProcess()) str << " [NPRC]";
}
void AstAlways::dump(std::ostream& str) const {
@ -2294,6 +2297,7 @@ void AstCFunc::dump(std::ostream& str) const {
if (isDestructor()) str << " [DTOR]";
if (isVirtual()) str << " [VIRT]";
if (isCoroutine()) str << " [CORO]";
if (needProcess()) str << " [NPRC]";
}
const char* AstCAwait::broken() const {
BROKEN_RTN(m_sensesp && !m_sensesp->brokeExists());

View File

@ -186,7 +186,8 @@ private:
&& !VN_IS(backp, NodeCCall) && !VN_IS(backp, CMethodHard) && !VN_IS(backp, SFormatF)
&& !VN_IS(backp, ArraySel) && !VN_IS(backp, StructSel) && !VN_IS(backp, RedXor)
&& (nodep->varp()->basicp() && !nodep->varp()->basicp()->isTriggerVec()
&& !nodep->varp()->basicp()->isForkSync())
&& !nodep->varp()->basicp()->isForkSync()
&& !nodep->varp()->basicp()->isProcessRef())
&& backp->width() && castSize(nodep) != castSize(nodep->varp())) {
// Cast vars to IData first, else below has upper bits wrongly set
// CData x=3; out = (QData)(x<<30);

View File

@ -79,6 +79,10 @@ string EmitCBaseVisitorConst::cFuncArgs(const AstCFunc* nodep) {
args += prefixNameProtect(EmitCParentModule::get(nodep));
args += "* vlSelf";
}
if (nodep->needProcess()) {
if (!args.empty()) args += ", ";
args += "VlProcessRef vlProcess";
}
if (!nodep->argTypes().empty()) {
if (!args.empty()) args += ", ";
args += nodep->argTypes();

View File

@ -417,6 +417,15 @@ void EmitCFunc::emitCCallArgs(const AstNodeCCall* nodep, const string& selfPoint
puts(selfPointer);
comma = true;
}
if (nodep->funcp()->needProcess()) {
if (comma) puts(", ");
if (VN_IS(nodep->backp(), CAwait)) {
puts("vlProcess");
} else {
puts("std::make_shared<VlProcess>()");
}
comma = true;
}
if (!nodep->argTypes().empty()) {
if (comma) puts(", ");
puts(nodep->argTypes());
@ -695,6 +704,8 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, const string& varNameP
return "";
} else if (basicp && basicp->isForkSync()) {
return "";
} else if (basicp && basicp->isProcessRef()) {
return "";
} else if (basicp && basicp->isDelayScheduler()) {
return "";
} else if (basicp && basicp->isTriggerScheduler()) {

View File

@ -1214,9 +1214,11 @@ AstActive* OrderProcess::processMoveOneLogic(const OrderLogicVertex* lvertexp,
// Process procedures per statement (unless profCFuncs), so we can split CFuncs within
// procedures. Everything else is handled in one go
bool suspendable = false;
bool needProcess = false;
bool slow = m_slow;
if (AstNodeProcedure* const procp = VN_CAST(nodep, NodeProcedure)) {
suspendable = procp->isSuspendable();
needProcess = procp->needProcess();
if (suspendable) slow = slow && !VN_IS(procp, Always);
nodep = procp->stmtsp();
pushDeletep(procp);
@ -1241,6 +1243,7 @@ AstActive* OrderProcess::processMoveOneLogic(const OrderLogicVertex* lvertexp,
const string name = cfuncName(modp, domainp, scopep, nodep);
newFuncpr
= new AstCFunc{nodep->fileline(), name, scopep, suspendable ? "VlCoroutine" : ""};
if (needProcess) newFuncpr->setNeedProcess();
newFuncpr->isStatic(false);
newFuncpr->isLoose(true);
newFuncpr->slow(slow);

View File

@ -247,6 +247,7 @@ void orderSequentially(AstCFunc* funcp, const LogicByScope& lbs) {
subFuncp = createNewSubFuncp(scopep);
subFuncp->name(subFuncp->name() + "__" + cvtToStr(scopep->user2Inc()));
subFuncp->rtnType("VlCoroutine");
if (procp->needProcess()) subFuncp->setNeedProcess();
if (VN_IS(procp, Always)) {
subFuncp->slow(false);
FileLine* const flp = procp->fileline();

View File

@ -155,6 +155,19 @@ TimingKit prepareTiming(AstNetlist* const netlistp) {
std::vector<AstVarScope*> m_writtenBySuspendable;
// METHODS
// Add arguments to a resume() call based on arguments in the suspending call
void addResumePins(AstCMethodHard* const resumep, AstNodeExpr* pinsp) {
AstCExpr* const exprp = VN_CAST(pinsp, CExpr);
AstText* const textp = VN_CAST(exprp->exprsp(), Text);
if (textp) {
// The first argument, vlProcess, isn't used by any of resume() methods, skip it
if ((pinsp = VN_CAST(pinsp->nextp(), NodeExpr))) {
resumep->addPinsp(pinsp->cloneTree(false));
}
} else {
resumep->addPinsp(pinsp->cloneTree(false));
}
}
// Create an active with a timing scheduler resume() call
void createResumeActive(AstCAwait* const awaitp) {
auto* const methodp = VN_AS(awaitp->exprp(), CMethodHard);
@ -171,7 +184,8 @@ TimingKit prepareTiming(AstNetlist* const netlistp) {
// The first pin is the commit boolean, the rest (if any) should be debug info
// See V3Timing for details
if (AstNode* const dbginfop = methodp->pinsp()->nextp()) {
resumep->addPinsp(static_cast<AstNodeExpr*>(dbginfop)->cloneTree(false));
if (methodp->pinsp())
addResumePins(resumep, static_cast<AstNodeExpr*>(dbginfop));
}
} else if (schedulerp->dtypep()->basicp()->isDynamicTriggerScheduler()) {
auto* const postp = resumep->cloneTree(false);
@ -262,6 +276,7 @@ void transformForks(AstNetlist* const netlistp) {
// STATE
bool m_inClass = false; // Are we in a class?
bool m_beginHasAwaits = false; // Does the current begin have awaits?
bool m_beginNeedProcess = false; // Does the current begin have process::self dependency?
AstFork* m_forkp = nullptr; // Current fork
AstCFunc* m_funcp = nullptr; // Current function
@ -348,8 +363,9 @@ void transformForks(AstNetlist* const netlistp) {
UASSERT_OBJ(m_forkp, nodep, "Begin outside of a fork");
// Start with children, so later we only find awaits that are actually in this begin
m_beginHasAwaits = false;
m_beginNeedProcess = false;
iterateChildrenConst(nodep);
if (m_beginHasAwaits) {
if (m_beginHasAwaits || m_beginNeedProcess) {
UASSERT_OBJ(!nodep->name().empty(), nodep, "Begin needs a name");
// Create a function to put this begin's statements in
FileLine* const flp = nodep->fileline();
@ -372,9 +388,19 @@ void transformForks(AstNetlist* const netlistp) {
}
// Put the begin's statements in the function, delete the begin
newfuncp->addStmtsp(nodep->stmtsp()->unlinkFrBackWithNext());
if (m_beginNeedProcess) {
newfuncp->setNeedProcess();
newfuncp->addStmtsp(new AstCStmt{nodep->fileline(),
"vlProcess->state(VlProcess::FINISHED);\n"});
}
if (!m_beginHasAwaits) {
// co_return at the end (either that or a co_await is required in a coroutine
newfuncp->addStmtsp(new AstCStmt{nodep->fileline(), "co_return;\n"});
}
remapLocals(newfuncp, callp);
} else {
// No awaits, just inline the forked process
// The begin has neither awaits nor a process::self call, just inline the
// statements
nodep->replaceWith(nodep->stmtsp()->unlinkFrBackWithNext());
}
VL_DO_DANGLING(nodep->deleteTree(), nodep);
@ -383,6 +409,10 @@ void transformForks(AstNetlist* const netlistp) {
m_beginHasAwaits = true;
iterateChildrenConst(nodep);
}
void visit(AstCCall* nodep) override {
if (nodep->funcp()->needProcess()) m_beginNeedProcess = true;
iterateChildrenConst(nodep);
}
//--------------------
void visit(AstNodeExpr*) override {} // Accelerate

View File

@ -1316,6 +1316,9 @@ private:
}
}
// Mark the fact that this function allocates std::process
if (nodep->isFromStd() && nodep->name() == "self") cfuncp->setNeedProcess();
// Delete rest of cloned task and return new func
VL_DO_DANGLING(pushDeletep(nodep), nodep);
if (debug() >= 9) cfuncp->dumpTree("- userFunc: ");

View File

@ -61,6 +61,16 @@
VL_DEFINE_DEBUG_FUNCTIONS;
// ######################################################################
enum TimingFlag : uint8_t {
// Properties of flags with higher numbers include properties of flags with
// lower numbers
T_NORM = 0, // Normal non-suspendable process
T_SUSP = 1, // Suspendable
T_PROC = 2 // Suspendable with process metadata
};
// ######################################################################
// Detect nodes affected by timing
@ -94,8 +104,10 @@ private:
// AstClass::user1() -> bool. Set true if the class
// member cache has been
// refreshed.
// Ast{NodeProcedure,CFunc,Begin}::user2() -> bool. Set true if process/task is
// suspendable
// Ast{NodeProcedure,CFunc,Begin}::user2() -> int. Set to >= T_SUSP if
// process/task suspendable
// and to T_PROC if it
// needs process metadata.
// Ast{NodeProcedure,CFunc,Begin}::user3() -> DependencyVertex*. Vertex in m_depGraph
const VNUser1InUse m_user1InUse;
const VNUser2InUse m_user2InUse;
@ -113,15 +125,23 @@ private:
if (!nodep->user3p()) nodep->user3p(new TimingDependencyVertex{&m_depGraph, nodep});
return nodep->user3u().to<TimingDependencyVertex*>();
}
// Propagate suspendable flag to all nodes that depend on the given one
void propagateSuspendable(TimingDependencyVertex* const vxp) {
// Set timing flag of a node
bool setTimingFlag(AstNode* nodep, int flag) {
// Properties of flags with higher numbers include properties of flags with lower
// numbers, so modify nodep->user2() only if it will increase.
if (nodep->user2() < flag) {
nodep->user2(flag);
return true;
}
return false;
}
// Propagate suspendable/needProcess flag to all nodes that depend on the given one
void propagateTimingFlags(TimingDependencyVertex* const vxp) {
auto* const parentp = vxp->nodep();
for (V3GraphEdge* edgep = vxp->inBeginp(); edgep; edgep = edgep->inNextp()) {
auto* const depVxp = static_cast<TimingDependencyVertex*>(edgep->fromp());
AstNode* const depp = depVxp->nodep();
if (!depp->user2()) {
depp->user2(true);
propagateSuspendable(depVxp);
}
if (setTimingFlag(depp, parentp->user2())) propagateTimingFlags(depVxp);
}
}
@ -142,6 +162,7 @@ private:
m_procp = nodep;
iterateChildren(nodep);
TimingDependencyVertex* const vxp = getDependencyVertex(nodep);
if (nodep->needProcess()) nodep->user2(T_PROC);
if (!m_classp) return;
// If class method (possibly overrides another method)
if (!m_classp->user1SetOnce()) m_classp->repairCache();
@ -162,15 +183,13 @@ private:
// the root of the inheritance hierarchy and check if the original method is
// virtual or not.
if (!cextp->classp()->user1SetOnce()) cextp->classp()->repairCache();
if (AstCFunc* const overriddenp
if (auto* const overriddenp
= VN_CAST(cextp->classp()->findMember(nodep->name()), CFunc)) {
if (overriddenp->user2()) { // If it's suspendable
nodep->user2(true); // Then we are also suspendable
// As both are suspendable already, there is no need to add it as our
// dependency or self to its dependencies
} else {
// Make a dependency cycle, as being suspendable should propagate both
// up and down the inheritance tree
setTimingFlag(nodep, overriddenp->user2());
if (nodep->user2()
< T_PROC) { // Add a vertex only if the flag can still change
// Make a dependency cycle, as being suspendable should propagate both up
// and down the inheritance tree
TimingDependencyVertex* const overriddenVxp
= getDependencyVertex(overriddenp);
new V3GraphEdge{&m_depGraph, vxp, overriddenVxp, 1};
@ -184,11 +203,8 @@ private:
}
}
void visit(AstNodeCCall* nodep) override {
if (nodep->funcp()->user2()) {
m_procp->user2(true);
// Both the caller and the callee are suspendable, no need to make dependency edges
// between them
} else {
setTimingFlag(m_procp, nodep->funcp()->user2());
if (m_procp->user2() < T_PROC) { // Add a vertex only if the flag can still change
TimingDependencyVertex* const procVxp = getDependencyVertex(m_procp);
TimingDependencyVertex* const funcVxp = getDependencyVertex(nodep->funcp());
new V3GraphEdge{&m_depGraph, procVxp, funcVxp, 1};
@ -203,7 +219,7 @@ private:
void visit(AstNode* nodep) override {
if (nodep->isTimingControl()) {
v3Global.setUsesTiming();
if (m_procp) m_procp->user2(true);
if (m_procp) m_procp->user2(T_SUSP);
}
iterateChildren(nodep);
}
@ -218,7 +234,7 @@ public:
m_depGraph.removeTransitiveEdges();
for (V3GraphVertex* vxp = m_depGraph.verticesBeginp(); vxp; vxp = vxp->verticesNextp()) {
TimingDependencyVertex* const depVxp = static_cast<TimingDependencyVertex*>(vxp);
if (depVxp->nodep()->user2()) propagateSuspendable(depVxp);
if (depVxp->nodep()->user2()) propagateTimingFlags(depVxp);
}
if (dumpGraphLevel() >= 6) m_depGraph.dumpDotFilePrefixed("timing_deps");
}
@ -252,6 +268,7 @@ private:
AstClass* m_classp = nullptr; // Current class
AstScope* m_scopep = nullptr; // Current scope
AstActive* m_activep = nullptr; // Current active
AstNode* m_procp = nullptr; // NodeProcedure/CFunc/Begin we're under
double m_timescaleFactor = 1.0; // Factor to scale delays by
// Unique names
@ -460,6 +477,14 @@ private:
methodp->addPinsp(createEventDescription(sensesp));
addDebugInfo(methodp);
}
// Adds process pointer to a hardcoded method call
void addProcessInfo(AstCMethodHard* const methodp) const {
FileLine* const flp = methodp->fileline();
AstCExpr* const ap = new AstCExpr{
flp, m_procp && m_procp->user2() == T_PROC ? "vlProcess" : "nullptr", 0};
ap->dtypeSetVoid();
methodp->addPinsp(ap);
}
// Creates the fork handle type and returns it
AstBasicDType* getCreateForkSyncDTypep() {
if (m_forkDtp) return m_forkDtp;
@ -512,11 +537,13 @@ private:
auto* const initp = new AstCMethodHard{flp, new AstVarRef{flp, forkVscp, VAccess::WRITE},
"init", new AstConst{flp, joinCount}};
initp->dtypeSetVoid();
addProcessInfo(initp);
forkp->addHereThisAsNext(initp->makeStmt());
// Await the join at the end
auto* const joinp
= new AstCMethodHard{flp, new AstVarRef{flp, forkVscp, VAccess::WRITE}, "join"};
joinp->dtypeSetVoid();
addProcessInfo(joinp);
addDebugInfo(joinp);
AstCAwait* const awaitp = new AstCAwait{flp, joinp};
awaitp->dtypeSetVoid();
@ -543,13 +570,24 @@ private:
m_activep = nullptr;
}
void visit(AstNodeProcedure* nodep) override {
VL_RESTORER(m_procp);
m_procp = nodep;
iterateChildren(nodep);
if (nodep->user2()) nodep->setSuspendable();
if (nodep->user2() >= T_SUSP) nodep->setSuspendable();
if (nodep->user2() >= T_PROC) nodep->setNeedProcess();
}
void visit(AstInitial* nodep) override {
visit(static_cast<AstNodeProcedure*>(nodep));
if (nodep->needProcess() && !nodep->user1SetOnce()) {
nodep->addStmtsp(
new AstCStmt{nodep->fileline(), "vlProcess->state(VlProcess::FINISHED);\n"});
}
}
void visit(AstAlways* nodep) override {
if (nodep->user1SetOnce()) return;
iterateChildren(nodep);
if (!nodep->user2()) return;
if (nodep->user2() == T_PROC) nodep->setNeedProcess();
nodep->setSuspendable();
FileLine* const flp = nodep->fileline();
AstSenTree* const sensesp = m_activep->sensesp();
@ -567,6 +605,8 @@ private:
m_activep->addNextHere(activep);
}
void visit(AstCFunc* nodep) override {
VL_RESTORER(m_procp);
m_procp = nodep;
iterateChildren(nodep);
if (!nodep->user2()) return;
nodep->rtnType("VlCoroutine");
@ -588,6 +628,7 @@ private:
firstCoStmtp->v3warn(E_UNSUPPORTED,
"Unsupported: Timing controls inside DPI-exported tasks");
}
if (nodep->user2() == T_PROC) nodep->setNeedProcess();
}
void visit(AstNodeCCall* nodep) override {
if (nodep->funcp()->user2() && !nodep->user1SetOnce()) { // If suspendable
@ -627,6 +668,7 @@ private:
auto* const delayMethodp = new AstCMethodHard{
flp, new AstVarRef{flp, getCreateDelayScheduler(), VAccess::WRITE}, "delay", valuep};
delayMethodp->dtypeSetVoid();
addProcessInfo(delayMethodp);
addDebugInfo(delayMethodp);
// Create the co_await
AstCAwait* const awaitp = new AstCAwait{flp, delayMethodp, getCreateDelaySenTree()};
@ -666,6 +708,7 @@ private:
flp, new AstVarRef{flp, getCreateDynamicTriggerScheduler(), VAccess::WRITE},
"evaluation"};
evalMethodp->dtypeSetVoid();
addProcessInfo(evalMethodp);
auto* const sensesp = nodep->sensesp();
addEventDebugInfo(evalMethodp, sensesp);
// Create the co_await
@ -723,6 +766,7 @@ private:
// If it should be committed immediately, pass true, otherwise false
triggerMethodp->addPinsp(nodep->user2() ? new AstConst{flp, AstConst::BitTrue{}}
: new AstConst{flp, AstConst::BitFalse{}});
addProcessInfo(triggerMethodp);
addEventDebugInfo(triggerMethodp, sensesp);
// Create the co_await
AstCAwait* const awaitp = new AstCAwait{flp, triggerMethodp, sensesp};
@ -858,6 +902,11 @@ private:
}
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
void visit(AstBegin* nodep) override {
VL_RESTORER(m_procp);
m_procp = nodep;
iterateChildren(nodep);
}
void visit(AstFork* nodep) override {
if (nodep->user1SetOnce()) return;
v3Global.setUsesTiming();

View File

@ -232,7 +232,7 @@ private:
const AstWith* m_withp = nullptr; // Current 'with' statement
const AstFunc* m_funcp = nullptr; // Current function
const AstAttrOf* m_attrp = nullptr; // Current attribute
AstNodeModule* m_modp = nullptr; // Current module
AstPackage* m_pkgp = nullptr; // Current package
const bool m_paramsOnly; // Computing parameter value; limit operation
const bool m_doGenerate; // Do errors later inside generate statement
int m_dtTables = 0; // Number of created data type tables
@ -2628,6 +2628,16 @@ private:
}
void visit(AstClass* nodep) override {
if (nodep->didWidthAndSet()) return;
// If the package is std::process, set m_process type to VlProcessRef
if (m_pkgp && m_pkgp->name() == "std" && nodep->name() == "process") {
if (AstVar* const varp = VN_CAST(nodep->findMember("m_process"), Var)) {
AstBasicDType* const dtypep = new AstBasicDType{
nodep->fileline(), VBasicDTypeKwd::PROCESS_REFERENCE, VSigning::UNSIGNED};
v3Global.rootp()->typeTablep()->addTypesp(dtypep);
varp->getChildDTypep()->unlinkFrBack();
varp->dtypep(dtypep);
}
}
// Must do extends first, as we may in functions under this class
// start following a tree of extends that takes us to other classes
VL_RESTORER(m_classp);
@ -2636,6 +2646,11 @@ private:
userIterateChildren(nodep, nullptr); // First size all members
nodep->repairCache();
}
void visit(AstPackage* nodep) override {
VL_RESTORER(m_pkgp);
m_pkgp = nodep;
userIterateChildren(nodep, nullptr);
}
void visit(AstThisRef* nodep) override {
if (nodep->didWidthAndSet()) return;
nodep->dtypep(iterateEditMoveDTypep(nodep, nodep->childDTypep()));
@ -2646,12 +2661,6 @@ private:
// though causes problems with t_class_forward.v, so for now avoided
// userIterateChildren(nodep->classp(), nullptr);
}
void visit(AstNodeModule* nodep) override {
// Visitor does not include AstClass - specialized visitor above
VL_RESTORER(m_modp);
m_modp = nodep;
userIterateChildren(nodep, nullptr);
}
void visit(AstClassOrPackageRef* nodep) override {
if (nodep->didWidthAndSet()) return;
userIterateChildren(nodep, nullptr);
@ -3491,7 +3500,7 @@ private:
VL_DANGLING(index_exprp); // May have been edited
return VN_AS(nodep->pinsp(), Arg)->exprp();
}
void methodCallWarnTiming(AstMethodCall* const nodep, const std::string& className) {
void methodCallWarnTiming(AstNodeFTaskRef* const nodep, const std::string& className) {
if (v3Global.opt.timing().isSetFalse()) {
nodep->v3warn(E_NOTIMING,
className << "::" << nodep->name() << "() requires --timing");
@ -3515,14 +3524,12 @@ private:
if (classp->name() == "semaphore" || classp->name() == "process"
|| VString::startsWith(classp->name(), "mailbox")) {
// Find the package the class is in
AstNode* pkgItemp = classp;
while (pkgItemp->backp() && pkgItemp->backp()->nextp() == pkgItemp) {
pkgItemp = pkgItemp->backp();
}
AstPackage* const packagep = VN_CAST(pkgItemp->backp(), Package);
AstPackage* const packagep = getItemPackage(classp);
// Check if it's std
if (packagep && packagep->name() == "std") {
if (classp->name() == "semaphore" && nodep->name() == "get") {
if (classp->name() == "process") {
methodCallWarnTiming(nodep, "process");
} else if (classp->name() == "semaphore" && nodep->name() == "get") {
methodCallWarnTiming(nodep, "semaphore");
} else if (nodep->name() == "put" || nodep->name() == "get"
|| nodep->name() == "peek") {
@ -5323,6 +5330,7 @@ private:
nodep->didWidth(true);
return;
}
if (m_pkgp && m_pkgp->name() == "std") nodep->isFromStd(true);
if (nodep->classMethod() && nodep->name() == "rand_mode") {
nodep->v3error("The 'rand_mode' method is built-in and cannot be overridden"
" (IEEE 1800-2017 18.8)");
@ -5405,9 +5413,25 @@ private:
}
}
AstPackage* getItemPackage(AstNode* pkgItemp) {
while (pkgItemp->backp() && pkgItemp->backp()->nextp() == pkgItemp) {
pkgItemp = pkgItemp->backp();
}
return VN_CAST(pkgItemp->backp(), Package);
}
void visit(AstFuncRef* nodep) override {
visit(static_cast<AstNodeFTaskRef*>(nodep));
nodep->dtypeFrom(nodep->taskp());
if (nodep->fileline()->timingOn()) {
AstNodeModule* const classp = nodep->classOrPackagep();
if (nodep->name() == "self" && classp->name() == "process") {
// Find if package the class is in is std::
AstPackage* const packagep = getItemPackage(classp);
if (packagep && packagep->name() == "std") {
methodCallWarnTiming(nodep, "process");
}
}
}
// if (debug()) nodep->dumpTree("- FuncOut: ");
}
// Returns true if dtypep0 and dtypep1 have same dimensions

View File

@ -1,3 +1,2 @@
[0] %Error: verilated_std.sv:154: Assertion failed in top.std.process.set_randstate: std::process::set_randstate() not supported
%Error: verilated_std.sv:154: Verilog $stop
Aborting...
'{m_process:process}
*-* All Finished *-*

View File

@ -10,14 +10,19 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
scenarios(simulator => 1);
compile(
);
if (!$Self->have_coroutines) {
skip("No coroutine support");
}
else {
compile(
v_flags2 => ["--timing"],
);
execute(
fails => $Self->{vlt_all},
expect_filename => $Self->{golden_filename},
check_finished => !$Self->{vlt_all},
);
execute(
check_finished => 1,
expect_filename => $Self->{golden_filename},
);
}
ok(1);
1;

View File

@ -38,6 +38,8 @@ module t(/*AUTOARG*/);
p.srandom(0);
p.set_randstate(p.get_randstate());
$display("%p", p);
$write("*-* All Finished *-*\n");
$finish;
end

View File

@ -11,7 +11,7 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di
scenarios(vlt => 1);
lint(
verilator_flags2 => ["--xml-only"],
verilator_flags2 => ["--xml-only", "--timing"],
fails => 1,
expect_filename => $Self->{golden_filename},
);

View File

@ -0,0 +1,27 @@
#!/usr/bin/env perl
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2023 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);
if (!$Self->have_coroutines) {
skip("No coroutine support");
}
else {
compile(
verilator_flags2 => ["--timing"],
);
execute(
check_finished => 1,
);
}
ok(1);
1;

View File

@ -0,0 +1,24 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2023 by Antmicro Ltd.
// SPDX-License-Identifier: CC0-1.0
module t (/*AUTOARG*/
// Inputs
clk
);
input clk;
process p;
initial begin
p = process::self();
end
always @(posedge clk) begin
if (p.status() != process::FINISHED)
$stop;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,11 @@
job started
job started
job started
job started
job started
job started
job started
job started
all jobs started
all jobs finished
*-* All Finished *-*

View File

@ -0,0 +1,28 @@
#!/usr/bin/env perl
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2023 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);
if (!$Self->have_coroutines) {
skip("No coroutine support");
}
else {
compile(
verilator_flags2 => ["--timing"],
);
execute(
check_finished => 1,
expect_filename => $Self->{golden_filename},
);
}
ok(1);
1;

View File

@ -0,0 +1,30 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2023 by Antmicro Ltd.
// SPDX-License-Identifier: CC0-1.0
module t;
process job[] = new [8];
bit is_alloc = 0;
initial begin
foreach (job[j]) fork
begin
$write("job started\n");
job[j] = process::self();
end
join_none
foreach (job[j]) begin
is_alloc = !!job[j];
wait (is_alloc);
end
$write("all jobs started\n");
foreach (job[j]) begin
job[j].await();
end
$write("all jobs finished\n");
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,27 @@
#!/usr/bin/env perl
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2023 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);
if (!$Self->have_coroutines) {
skip("No coroutine support");
}
else {
compile(
verilator_flags2 => ["--timing"],
);
execute(
check_finished => 1,
);
}
ok(1);
1;

View File

@ -0,0 +1,31 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2023 by Antmicro Ltd.
// SPDX-License-Identifier: CC0-1.0
module t (/*AUTOARG*/
// Inputs
clk
);
input clk;
process p;
bit s = 0;
initial begin
wait (s);
p.kill();
p.await();
$write("*-* All Finished *-*\n");
$finish;
end
always @(posedge clk) begin
if (!p) begin
p = process::self();
s = 1;
end else begin
$stop;
end
end
endmodule

View File

@ -0,0 +1,54 @@
%Error-NOTIMING: t/t_process.v:26:20: process::self() requires --timing
: ... In instance t
26 | p = process::self();
| ^~~~
... For error description see https://verilator.org/warn/NOTIMING?v=latest
%Error-NOTIMING: t/t_process.v:27:13: process::status() requires --timing
: ... In instance t
27 | if (p.status() != process::RUNNING) $stop;
| ^~~~~~
%Error-NOTIMING: t/t_process.v:28:13: process::status() requires --timing
: ... In instance t
28 | if (p.status() == process::WAITING) $stop;
| ^~~~~~
%Error-NOTIMING: t/t_process.v:29:13: process::status() requires --timing
: ... In instance t
29 | if (p.status() == process::SUSPENDED) $stop;
| ^~~~~~
%Error-NOTIMING: t/t_process.v:30:13: process::status() requires --timing
: ... In instance t
30 | if (p.status() == process::KILLED) $stop;
| ^~~~~~
%Error-NOTIMING: t/t_process.v:31:13: process::status() requires --timing
: ... In instance t
31 | if (p.status() == process::FINISHED) $stop;
| ^~~~~~
%Error-NOTIMING: t/t_process.v:33:16: process::kill() requires --timing
: ... In instance t
33 | if (0) p.kill();
| ^~~~
%Error-NOTIMING: t/t_process.v:34:16: process::await() requires --timing
: ... In instance t
34 | if (0) p.await();
| ^~~~~
%Error-NOTIMING: t/t_process.v:35:16: process::suspend() requires --timing
: ... In instance t
35 | if (0) p.suspend();
| ^~~~~~~
%Error-NOTIMING: t/t_process.v:36:16: process::resume() requires --timing
: ... In instance t
36 | if (0) p.resume();
| ^~~~~~
%Error-NOTIMING: t/t_process.v:38:9: process::srandom() requires --timing
: ... In instance t
38 | p.srandom(0);
| ^~~~~~~
%Error-NOTIMING: t/t_process.v:39:25: process::get_randstate() requires --timing
: ... In instance t
39 | p.set_randstate(p.get_randstate());
| ^~~~~~~~~~~~~
%Error-NOTIMING: t/t_process.v:39:9: process::set_randstate() requires --timing
: ... In instance t
39 | p.set_randstate(p.get_randstate());
| ^~~~~~~~~~~~~
%Error: Exiting due to

View File

@ -0,0 +1,22 @@
#!/usr/bin/env perl
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2020 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);
top_filename("t/t_process.v");
compile(
expect_filename => $Self->{golden_filename},
v_flags2 => ["+define+T_PROCESS+std::process", "--no-timing"],
fails => 1,
);
ok(1);
1;

View File

@ -0,0 +1,27 @@
#!/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);
if (!$Self->have_coroutines) {
skip("No coroutine support");
}
else {
compile(
v_flags2 => ["--timing"],
);
execute(
check_finished => 1,
);
}
ok(1);
1;

View File

@ -0,0 +1,52 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2023 by Antmicro Ltd.
// SPDX-License-Identifier: CC0-1.0
module t;
process p;
integer seed;
string state;
int a;
int b;
initial begin
p = process::self();
// Test setting RNG state with state string
state = p.get_randstate();
p.set_randstate(state);
a = $random;
p.set_randstate(state);
b = $random;
$display("a=%d, b=%d", a, b);
if (a != b) $stop;
// Test the same with $urandom
state = p.get_randstate();
p.set_randstate(state);
a = $urandom;
p.set_randstate(state);
b = $urandom;
$display("a=%d, b=%d", a, b);
if (a != b) $stop;
// Test if the results repeat after the state is reset
state = p.get_randstate();
for (int i = 0; i < 10; i++)
$random;
a = $random;
// Now reset the state and take 11th result again
p.set_randstate(state);
for (int i = 0; i < 10; i++)
$random;
b = $random;
$display("a=%d, b=%d", a, b);
if (a != b) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -12,15 +12,18 @@ scenarios(simulator => 1);
top_filename("t/t_process.v");
compile(
v_flags2 => ["+define+T_PROCESS+std::process"],
);
if (!$Self->have_coroutines) {
skip("No coroutine support");
}
else {
compile(
v_flags2 => ["+define+T_PROCESS+std::process", "--timing"],
);
execute(
check_finished => !$Self->{vlt_all},
fails => $Self->{vlt_all},
expect_filename => $Self->{golden_filename},
);
execute(
check_finished => 1,
) if !$Self->{vlt_all};
}
ok(1);
1;