#pragma once #include #include /** * @brief TESTBENCH Class; Instantiates topmodule, keep track of cycles elapsed, handles VCD trace generation. * * @tparam VTop module to be instantiated */ template class Testbench { public: /** * @brief Construct a new TESTBENCH object */ Testbench(void); /** * @brief Destroy the TESTBENCH object */ virtual ~Testbench(void); /** * @brief Open/create a trace file * * @param vcdname name of vcd file */ virtual void openTrace(const char *vcdname); /** * @brief Close a trace file */ virtual void closeTrace(void); /** * @brief Check if trace is open or not */ virtual bool isTraceOpen(); /** * @brief Reset topmodule */ virtual void reset(void); /** * @brief Run for one cycle */ virtual void tick(void); /** * @brief Check if simulation ended * * @return true if verilator has encountered $finish * @return false if verilator hasn't encountered $finish yet */ virtual bool done(void); /** * @brief Get the tickcount (since last reset) * * @return uint64_t ticks */ virtual uint64_t get_tickcount() { return m_tickcount; } /** * @brief Get the total tickcounts * * @return uint64_t ticks */ virtual uint64_t get_total_tickcount() { return m_tickcount_total; } private: /** * @brief topmodule ptr */ VTop * m_core = NULL; /** * @brief trace obj ptr */ VerilatedVcdC * m_trace = NULL; /** * @brief TickCounter to count clock cycles fom last reset */ uint64_t m_tickcount = 0; /** * @brief TickCounter to count clock cycles */ uint64_t m_tickcount_total = 0; friend class Backend_atomsim; }; template Testbench::Testbench(void) { m_core = new VTop; Verilated::traceEverOn(true); m_tickcount = 0l; } template Testbench::~Testbench(void) { closeTrace(); delete m_core; m_core = NULL; } template void Testbench::openTrace(const char *vcdname) { if (m_trace==NULL) { m_trace = new VerilatedVcdC; m_core->trace(m_trace, 99); m_trace->open(vcdname); } } template void Testbench::closeTrace(void) { if (m_trace!=NULL) { m_trace->close(); delete m_trace; } } template bool Testbench::isTraceOpen() { return (m_trace!=NULL); } template void Testbench::reset(void) { m_core-> rst_i = 1; this -> tick(); // Make sure any inheritance gets applied m_tickcount = 0; m_core-> rst_i = 0; } template void Testbench::tick(void) { // Increment our own internal time reference m_tickcount++; m_tickcount_total++; // Make sure any combinatorial logic depending upon // inputs that may have changed before we called tick() // has settled before the rising edge of the clock. m_core -> clk_i = 0; m_core -> eval(); // Dump values to our trace file before clock edge if(m_trace) { m_trace->dump(10*m_tickcount-2); } // ---------- Toggle the clock ------------ // Rising edge m_core -> clk_i = 1; m_core -> eval(); // Dump values to our trace file after clock edge if(m_trace) m_trace->dump(10*m_tickcount); // Falling edge m_core -> clk_i = 0; m_core -> eval(); if (m_trace) { // This portion, though, is a touch different. // After dumping our values as they exist on the // negative clock edge ... m_trace->dump(10*m_tickcount+5); // // We'll also need to make sure we flush any I/O to // the trace file, so that we can use the assert() // function between now and the next tick if we want to. m_trace->flush(); } } template bool Testbench::done(void) { return (Verilated::gotFinish()); }