mirror of
https://github.com/verilator/verilator.git
synced 2025-01-21 05:44:03 +00:00
343 lines
10 KiB
C++
343 lines
10 KiB
C++
// -*- mode: C++; c-file-style: "cc-mode" -*-
|
|
//*************************************************************************
|
|
//
|
|
// Copyright 2021 by Wilson Snyder and Marlon James. 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
|
|
//
|
|
//*************************************************************************
|
|
|
|
// Setup multiple one-time callbacks with different time delays.
|
|
// Ensure they are not called before the delay has elapsed.
|
|
|
|
#ifdef IS_VPI
|
|
|
|
#include "vpi_user.h"
|
|
|
|
#include <cstdlib>
|
|
|
|
#else
|
|
|
|
#include "verilated.h"
|
|
#include "verilated_vpi.h"
|
|
|
|
#include VM_PREFIX_INCLUDE
|
|
|
|
#endif
|
|
|
|
#include "TestSimulator.h"
|
|
#include "TestVpi.h"
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <iostream>
|
|
#include <vector>
|
|
|
|
typedef struct {
|
|
PLI_UINT32 count;
|
|
PLI_UINT32* exp_times;
|
|
PLI_UINT32 number_of_exp_times;
|
|
} cb_stats;
|
|
|
|
static cb_stats CallbackStats[cbAtEndOfSimTime + 1];
|
|
|
|
bool got_error = false;
|
|
|
|
static vpiHandle ValueHandle, ToggleHandle, ClockHandle;
|
|
|
|
#ifdef TEST_VERBOSE
|
|
bool verbose = true;
|
|
#else
|
|
bool verbose = false;
|
|
#endif
|
|
|
|
#define CHECK_RESULT_NZ(got) \
|
|
if (!(got)) { \
|
|
printf("%%Error: %s:%d: GOT = NULL EXP = !NULL\n", __FILE__, __LINE__); \
|
|
got_error = true; \
|
|
return __LINE__; \
|
|
}
|
|
|
|
// Use cout to avoid issues with %d/%lx etc
|
|
#define CHECK_RESULT(got, exp) \
|
|
if ((got) != (exp)) { \
|
|
std::cout << std::dec << "%Error: " << __FILE__ << ":" << __LINE__ << ": GOT = " << (got) \
|
|
<< " EXP = " << (exp) << std::endl; \
|
|
got_error = true; \
|
|
return __LINE__; \
|
|
}
|
|
|
|
#define STRINGIFY_CB_CASE(_cb) \
|
|
case _cb: return #_cb
|
|
|
|
static const char* cb_reason_to_string(int cb_name) {
|
|
switch (cb_name) {
|
|
STRINGIFY_CB_CASE(cbAtStartOfSimTime);
|
|
STRINGIFY_CB_CASE(cbReadWriteSynch);
|
|
STRINGIFY_CB_CASE(cbReadOnlySynch);
|
|
STRINGIFY_CB_CASE(cbNextSimTime);
|
|
STRINGIFY_CB_CASE(cbStartOfSimulation);
|
|
STRINGIFY_CB_CASE(cbEndOfSimulation);
|
|
STRINGIFY_CB_CASE(cbAtEndOfSimTime);
|
|
default: return "Unsupported callback";
|
|
}
|
|
}
|
|
|
|
#undef STRINGIFY_CB_CASE
|
|
|
|
bool cb_time_is_delay(int cb_name) {
|
|
// For some callbacks, time is interpreted as a delay from current time
|
|
// instead of an absolute time
|
|
if (cb_name == cbReadOnlySynch || cb_name == cbReadWriteSynch) { return true; }
|
|
return false;
|
|
}
|
|
|
|
// forward declaration
|
|
static PLI_INT32 TheCallback(s_cb_data* data);
|
|
|
|
static PLI_INT32 AtEndOfSimTimeCallback(s_cb_data* data) {
|
|
s_vpi_time t;
|
|
|
|
cb_stats* stats = &CallbackStats[data->reason];
|
|
|
|
t.type = vpiSimTime;
|
|
vpi_get_time(0, &t);
|
|
|
|
if (verbose) vpi_printf(const_cast<char*>("- [@%d] AtEndOfSimTime Callback\n"), t.low);
|
|
|
|
CHECK_RESULT(t.low, stats->exp_times[stats->count]);
|
|
stats->count += 1;
|
|
|
|
s_cb_data cb_data;
|
|
s_vpi_time time = {vpiSimTime, 0, 417, 0}; // non-zero time to check that it's ignored
|
|
cb_data.time = &time;
|
|
cb_data.reason = cbNextSimTime;
|
|
cb_data.cb_rtn = TheCallback;
|
|
vpiHandle Handle = vpi_register_cb(&cb_data);
|
|
CHECK_RESULT_NZ(Handle);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static PLI_INT32 TheCallback(s_cb_data* data) {
|
|
s_vpi_time t;
|
|
|
|
cb_stats* stats = &CallbackStats[data->reason];
|
|
|
|
t.type = vpiSimTime;
|
|
vpi_get_time(0, &t);
|
|
|
|
if (verbose) {
|
|
vpi_printf(const_cast<char*>("- [@%d] %s Callback\n"), t.low,
|
|
cb_reason_to_string(data->reason));
|
|
}
|
|
|
|
CHECK_RESULT(t.low, stats->exp_times[stats->count]);
|
|
stats->count += 1;
|
|
|
|
if (stats->count >= stats->number_of_exp_times) return 0;
|
|
|
|
s_cb_data cb_data;
|
|
PLI_UINT32 next_time;
|
|
|
|
if (data->reason == cbNextSimTime) {
|
|
// if a cbNextSimTime calback is scheduled from
|
|
// another cbNextSimTime callback, it will
|
|
// be called in the same timestep, so we need
|
|
// to delay registering
|
|
next_time = t.low;
|
|
cb_data.reason = cbAtEndOfSimTime;
|
|
cb_data.cb_rtn = AtEndOfSimTimeCallback;
|
|
} else {
|
|
next_time = stats->exp_times[stats->count];
|
|
if (cb_time_is_delay(data->reason)) { next_time -= t.low; }
|
|
cb_data.reason = data->reason;
|
|
cb_data.cb_rtn = TheCallback;
|
|
}
|
|
|
|
if (verbose) {
|
|
vpi_printf(const_cast<char*>("- [@%d] Registering %s Callback with time = %d\n"), t.low,
|
|
cb_reason_to_string(cb_data.reason), next_time);
|
|
}
|
|
|
|
s_vpi_time time = {vpiSimTime, 0, next_time, 0};
|
|
cb_data.time = &time;
|
|
vpiHandle Handle = vpi_register_cb(&cb_data);
|
|
CHECK_RESULT_NZ(Handle);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static PLI_INT32 StartOfSimulationCallback(s_cb_data* data) {
|
|
s_cb_data cb_data;
|
|
s_vpi_time timerec = {vpiSimTime, 0, 0, 0};
|
|
|
|
s_vpi_time t;
|
|
t.type = vpiSimTime;
|
|
vpi_get_time(0, &t);
|
|
|
|
if (verbose) vpi_printf(const_cast<char*>("- [@%d] cbStartOfSimulation Callback\n"), t.low);
|
|
|
|
CHECK_RESULT(t.low, CallbackStats[data->reason].exp_times[0]);
|
|
CallbackStats[data->reason].count += 1;
|
|
|
|
cb_data.time = &timerec;
|
|
cb_data.value = 0;
|
|
cb_data.user_data = 0;
|
|
cb_data.obj = 0;
|
|
cb_data.cb_rtn = TheCallback;
|
|
|
|
CallbackStats[cbAtStartOfSimTime].exp_times = new PLI_UINT32[3]{5, 15, 20};
|
|
CallbackStats[cbAtStartOfSimTime].number_of_exp_times = 3;
|
|
timerec.low = 5;
|
|
cb_data.reason = cbAtStartOfSimTime;
|
|
vpiHandle ASOSHandle = vpi_register_cb(&cb_data);
|
|
CHECK_RESULT_NZ(ASOSHandle);
|
|
|
|
CallbackStats[cbReadWriteSynch].exp_times = new PLI_UINT32[3]{6, 16, 21};
|
|
CallbackStats[cbReadWriteSynch].number_of_exp_times = 3;
|
|
timerec.low = 6;
|
|
cb_data.reason = cbReadWriteSynch;
|
|
vpiHandle RWHandle = vpi_register_cb(&cb_data);
|
|
CHECK_RESULT_NZ(RWHandle);
|
|
|
|
CallbackStats[cbReadOnlySynch].exp_times = new PLI_UINT32[3]{7, 17, 22};
|
|
CallbackStats[cbReadOnlySynch].number_of_exp_times = 3;
|
|
timerec.low = 7;
|
|
cb_data.reason = cbReadOnlySynch;
|
|
vpiHandle ROHandle = vpi_register_cb(&cb_data);
|
|
CHECK_RESULT_NZ(ROHandle);
|
|
|
|
CallbackStats[cbNextSimTime].exp_times = new PLI_UINT32[9]{5, 6, 7, 15, 16, 17, 20, 21, 22};
|
|
CallbackStats[cbNextSimTime].number_of_exp_times = 9;
|
|
timerec.low = 8;
|
|
cb_data.reason = cbNextSimTime;
|
|
vpiHandle NSTHandle = vpi_register_cb(&cb_data);
|
|
CHECK_RESULT_NZ(NSTHandle);
|
|
|
|
CallbackStats[cbAtEndOfSimTime].exp_times = new PLI_UINT32[8]{5, 6, 7, 15, 16, 17, 20, 21};
|
|
CallbackStats[cbAtEndOfSimTime].number_of_exp_times = 8;
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int EndOfSimulationCallback(p_cb_data cb_data) {
|
|
s_vpi_time t;
|
|
t.type = vpiSimTime;
|
|
vpi_get_time(0, &t);
|
|
|
|
(void)cb_data; // Unused
|
|
|
|
if (verbose) vpi_printf(const_cast<char*>("- [@%d] cbEndOfSimulation Callback\n"), t.low);
|
|
|
|
CHECK_RESULT(t.low, CallbackStats[cbEndOfSimulation].exp_times[0]);
|
|
CallbackStats[cbEndOfSimulation].count += 1;
|
|
|
|
CHECK_RESULT(CallbackStats[cbStartOfSimulation].count, 1);
|
|
CHECK_RESULT(CallbackStats[cbAtStartOfSimTime].count, 3);
|
|
CHECK_RESULT(CallbackStats[cbNextSimTime].count, 9);
|
|
CHECK_RESULT(CallbackStats[cbReadWriteSynch].count, 3);
|
|
CHECK_RESULT(CallbackStats[cbReadOnlySynch].count, 3);
|
|
CHECK_RESULT(CallbackStats[cbAtEndOfSimTime].count, 8);
|
|
CHECK_RESULT(CallbackStats[cbEndOfSimulation].count, 1);
|
|
|
|
if (!got_error) { printf("*-* All Finished *-*\n"); }
|
|
return 0;
|
|
}
|
|
|
|
// cver entry
|
|
static void VPIRegister(void) {
|
|
// Clear stats
|
|
for (int cb = 1; cb <= cbAtEndOfSimTime; cb++) { CallbackStats[cb].count = 0; }
|
|
CallbackStats[cbStartOfSimulation].exp_times = new PLI_UINT32(0);
|
|
CallbackStats[cbEndOfSimulation].exp_times = new PLI_UINT32(22);
|
|
s_cb_data cb_data;
|
|
s_vpi_time timerec = {vpiSuppressTime, 0, 0, 0};
|
|
|
|
cb_data.time = &timerec;
|
|
cb_data.value = 0;
|
|
cb_data.user_data = 0;
|
|
cb_data.obj = 0;
|
|
cb_data.reason = cbStartOfSimulation;
|
|
cb_data.cb_rtn = StartOfSimulationCallback;
|
|
|
|
vpi_register_cb(&cb_data);
|
|
|
|
cb_data.reason = cbEndOfSimulation;
|
|
cb_data.cb_rtn = EndOfSimulationCallback;
|
|
vpi_register_cb(&cb_data);
|
|
}
|
|
|
|
#ifdef IS_VPI
|
|
|
|
// icarus entry
|
|
void (*vlog_startup_routines[])(void) = {VPIRegister, 0};
|
|
|
|
#else
|
|
|
|
int main(int argc, char** argv, char** env) {
|
|
double sim_time = 100;
|
|
const std::unique_ptr<VerilatedContext> contextp{new VerilatedContext};
|
|
|
|
bool cbs_called;
|
|
contextp->commandArgs(argc, argv);
|
|
// contextp->debug(9);
|
|
|
|
const std::unique_ptr<VM_PREFIX> topp{new VM_PREFIX{contextp.get(),
|
|
// Note null name - we're flattening it out
|
|
""}};
|
|
|
|
topp->clk = 1;
|
|
|
|
// StartOfSimulationCallback(nullptr);
|
|
VPIRegister();
|
|
|
|
VerilatedVpi::callCbs(cbStartOfSimulation);
|
|
|
|
topp->clk = 0;
|
|
topp->eval();
|
|
|
|
while (contextp->time() < sim_time && !contextp->gotFinish()) {
|
|
VerilatedVpi::callTimedCbs();
|
|
VerilatedVpi::callCbs(cbNextSimTime);
|
|
VerilatedVpi::callCbs(cbAtStartOfSimTime);
|
|
|
|
topp->eval();
|
|
|
|
VerilatedVpi::callValueCbs();
|
|
VerilatedVpi::callCbs(cbReadWriteSynch);
|
|
VerilatedVpi::callCbs(cbReadOnlySynch);
|
|
VerilatedVpi::callCbs(cbAtEndOfSimTime);
|
|
|
|
const uint64_t next_time = VerilatedVpi::cbNextDeadline();
|
|
if (next_time != -1) contextp->time(next_time);
|
|
if (verbose)
|
|
vpi_printf(const_cast<char*>("- [@%" PRId64 "] time change\n"), contextp->time());
|
|
if (next_time == -1 && !contextp->gotFinish()) {
|
|
if (got_error) {
|
|
vl_stop(__FILE__, __LINE__, "TOP-cpp");
|
|
} else {
|
|
VerilatedVpi::callCbs(cbEndOfSimulation);
|
|
contextp->gotFinish(true);
|
|
}
|
|
}
|
|
|
|
// Count updates on rising edge, so cycle through falling edge as well
|
|
topp->clk = !topp->clk;
|
|
topp->eval();
|
|
topp->clk = !topp->clk;
|
|
}
|
|
|
|
if (!contextp->gotFinish()) {
|
|
vl_fatal(__FILE__, __LINE__, "main", "%Error: Timeout; never got a $finish");
|
|
}
|
|
topp->final();
|
|
|
|
exit(0L);
|
|
}
|
|
|
|
#endif
|