//  *********************************************************************************************
//  Description      : Verilator tb
//  Project Version  : v1.0
//  Project          : [BCDC] Microtec Academy Course: Building a RISC-V CPU with SystemVerilog
//  -----
//  Copyright (c)    : 2025 Fraunhofer IIS, Department IDS
//  Created          : 12.Aug.2025  by Marcus Bednara
//  Last Modified    : 15.Oct.2025  by Hussein Elzomor [commit d0452cd]
//  -----
//  HISTORY          : Date         By         Comments
//                     -----------  ---------  -------------------------------------------------
//                     15.Oct.2025  H.Elzomor  Renamed modules from *soc* to *cpu*
//                     15.Oct.2025  H.Elzomor  Added evalText function
//  *********************************************************************************************



using namespace std;

#include  <stdlib.h>
#include  <iostream>
#include  <verilated.h>
#include  <verilated_fst_c.h>
#include  "Vcpu_harness.h"
#include  "Vcpu_harness__Dpi.h"

// only required for accessing model internal memory ressources via the rootp pointer.
// Bad style, better use systemverilog harness with tasks and functions to do that.
// #include  "Vcpu_harness___024root.h"
// #include "svdpi.h"


#define SOC_CLK_PERIOD 40

//--------------------------------------------------------------
/// @brief Simulation environment class for the cpu model.
/// Contains a set of high level methods for controlling the model from test environment.
class cpu
{
  private:

        Vcpu_harness *dut;
        VerilatedFstC *mTrace;
        vluint64_t    T;

        uint32_t      cpuClkState;

        bool          running;

        string        program;

  public:

    //--------------------------------------------------------------
    /// @brief Constructor
    cpu (int argc, char** argv)
    {
        // setup verilator stuff
        T = 0;
        dut = new Vcpu_harness();
        Verilated::commandArgs(argc, argv);
        Verilated::traceEverOn(true);
        mTrace = new VerilatedFstC;
        dut->trace(mTrace, 5);
        mTrace->open("wavedump.fst");

        cout << "Registered DPI-C functions:\n";
        Verilated::scopesDump();
        const svScope scope = svGetScopeFromName("TOP.cpu_harness");
        assert(scope);  // Check for nullptr if scope not found
        svSetScope(scope);

        running = false;

        // initialize all input signals
        initSignals();

    } // swirl()


    //--------------------------------------------------------------
    /// @brief simulation tick, advances the simulation time and calls the eval() function of the model on every active clock edge
    void tick ()
    {
        int do_eval;

        if (T%SOC_CLK_PERIOD==0) {
            dut->setClk(cpuClkState);
            cpuClkState = 1-cpuClkState;
            do_eval = 1;
        }


        // Other clock domains must be generated the same way

        // if (T%OTHER_CLK_PERIOD==0) {
        //     dut->setOtherClk(otherClkState);
        //     otherClkState = 1-otherClkState;
        //     do_eval = 1;
        // }

        if (do_eval) {
            dut->eval();
            // evaluate the LED status in each clock cycle if the CPU is running (i.e., after reset)
            if (running && program == "unknown") evalLed();
            // evaluate the Fibonacci Series location in stack (starting at 2027) and print value if it has changed
            if (running && program == "fibonacci") evalFibonacci();
            // evaluate the 'hello world!' location in the DataMem in each clock cycle if the CPU is running (i.e., after reset)
            if (running && program == "helloWorld") evalHelloWorld();
            // evaluate the Prime Factors location in RegFile (x17) and print value if it has changed
            if (running && program == "primeFactors") evalPrimeFactors();
            mTrace->dump(T);
            do_eval = 0;
        }

        T++;

    } // tick()


    //--------------------------------------------------------------
    /// @brief Initialize all input signals of the hardware model to a defined value
    void initSignals()
    {
        dut->setInitial();
    } // initSignals()

    //--------------------------------------------------------------
    /// @brief Wait for a number of active edges of clk signal
    /// @param numEdges number of rising edges
    /// @param active active 1=rising 0=falling edge
    void waitClocks (int numEdges=1, int activeEdge=1)
    {
        int clk_d;

        for (int j=0; j<numEdges; ++j) {
          while (1) {
            clk_d = cpuClkState;
            tick();
            if (clk_d==1-activeEdge && cpuClkState==activeEdge) break;
          }
        } // for
    } // waitClocks()

    //--------------------------------------------------------------
    /// @brief Toggle the reset signal active (low)
    /// @param numEdges number of clock cyles keeping the reset active
    void reset (int numEdges=1)
    {
        dut->setReset(0);    // reset is active low
        waitClocks (numEdges);
        dut->setReset(1);
    } // reset();

    //--------------------------------------------------------------
    /// @brief load the cpu memory from vmem formatted file
    void loadRAM (char* vmemFile)
    {
      dut->loadRAM(vmemFile);
    } // loadRAM()

    /// @brief check the LED status and print value if it has change
    void evalLed ()
    {
        static uint8_t oldLedStatus=0xff;
        uint8_t currentLedStatus;
        currentLedStatus=dut->getLed();
        if (currentLedStatus!=oldLedStatus) {
          cout << "T=" << T << ": LED=" << std::bitset<8>(currentLedStatus) << endl;
          oldLedStatus = currentLedStatus;
        }
    } // evalLed()

    /// @brief check Fibonacci Series location in stack (starting at 2027) and print value if it has changed
    void evalFibonacci()
    {
      int memStartLoc = 2027;
      const int memLengthInBytes = 40;
      const int memLengthInWords = ceil(memLengthInBytes / 4.0);
      static uint32_t oldValue[memLengthInWords];
      uint32_t vlaue[memLengthInWords];
      bool update = 0;
      for (int i = 0; i < memLengthInWords; i++)
      {
        vlaue[i] = dut->getMem(memStartLoc + i);
        if (oldValue[i] != vlaue[i])
          update = 1;
      }
      if (update)
      {
        cout << endl
             << "T=" << T << ": \t       Hex   \t Dec" << endl;
        for (int i = 0; i < memLengthInWords; i++)
        {
          dut->printMem(memStartLoc + i, "fibonacci");
          oldValue[i] = vlaue[i];
        }
        cout << endl;
      }
    } // evalFibonacci()

    /// @brief check 'hello world' location in DataMem (starting at 1024) and print value if it has changed
    void evalHelloWorld()
    {
      int memStartLoc = 1024;
      const int memLengthInBytes = 13;
      const int memLengthInWords = ceil(memLengthInBytes / 4.0);
      static uint32_t oldValue[memLengthInWords];
      uint32_t vlaue[memLengthInWords];
      bool update = 0;
      for (int i = 0; i < memLengthInWords; i++)
      {
        vlaue[i] = dut->getMem(memStartLoc + i);
        if (oldValue[i] != vlaue[i])
          update = 1;
      }
      if (update)
      {
        cout << endl
             << "T=" << T << ": \t       Hex   \t    ASCII" << endl;
        for (int i = 0; i < memLengthInWords; i++)
        {
          dut->printMem(memStartLoc + i, "helloWorld");
          oldValue[i] = vlaue[i];
        }
        cout << endl;
      }
    } // evalHelloWorld()

    /// @brief check Prime Factors location in RegFile (x17) and print value if it has changed
    void evalPrimeFactors()
    {
      // Number to be factorised
      int regLoc_Number = 16;
      static uint32_t oldValue_Number;
      uint32_t value_Number;
      value_Number = dut->getReg(regLoc_Number);
      if (oldValue_Number != value_Number & value_Number > 1) dut->printReg(regLoc_Number, "primeNumber");
      oldValue_Number = value_Number;

      // Prime Factors
      int regLoc_PrimeFactor = 17;
      static uint32_t oldValue_PrimeFactor;
      uint32_t value_PrimeFactor;
      value_PrimeFactor = dut->getReg(regLoc_PrimeFactor);
      if (oldValue_PrimeFactor != value_PrimeFactor & value_PrimeFactor > 1) dut->printReg(regLoc_PrimeFactor, "primeFactors");
      oldValue_PrimeFactor = value_PrimeFactor;

    } // evalPrimeFactors()

    //--------------------------------------------------------------
    /// @brief start the DUT
    void start()
    {
      running = true;
      dut->enable(1);
    } // start()


    //--------------------------------------------------------------
    /// @brief close the trace file and delete the Verilator object
    void stop()
    {
      dut->enable(0);
      mTrace->close();
      delete dut;
    } // stop()

    //--------------------------------------------------------------
    /// @brief Set the program name
    void setProgram(string prog)
    {
      if (prog.find("fibonacci") != string::npos) program = "fibonacci";
      else if (prog.find("helloWorld") != string::npos) program = "helloWorld";
      else if (prog.find("primeFactors") != string::npos) program = "primeFactors";
      else program = "unknown";

      cout << "program set to " << program << endl;
    } // setProgram()

    //--------------------------------------------------------------
    /// @brief Get the program name
    string getProgram()
    {
      return program;
    } // getProgram()

}; // class cpu





int main (int argc, char** argv)
{
    uint32_t nCycles;
    char* program_path = new char[strlen(argv[2])]();

    // Get number of simulation cycles
    nCycles = atoi(argv[1]);

    // Get the program
    program_path = argv[2];
    string program_path_str = program_path;

    cout << "\nCreating model...\n";
    cpu *m = new cpu(argc, argv);
    m->loadRAM(program_path);
    m->setProgram(program_path_str);

    cout << "\nStarting model...\n";

    cout << "\nResetting...\n";
    m->reset(10);

    cout << "Starting CPU...\n" << std::flush;
		m->start();

    cout << "Running for " << nCycles << " clock cycles...\n\n" << std::flush;
    cout << "Printing evaluation for " << m->getProgram() << " program!\n" << std::flush;
    m->waitClocks(nCycles);

    cout << "\nDone, closing simulation.\n\n\n" << std::flush;
    m->stop();
    delete m;

    exit(EXIT_SUCCESS);
}
