#include <iostream>
#include <iomanip>
#include "Vweighted_inner_product.h"
#include <verilated.h>
#include <verilated_vcd_c.h>

// encrypted data
int c_1[2] = {0x1, 0xfffffffe};   // 0-1

int c_2[2] = {0x5, 0xfffffffd};   // 0-1

// encrypted linear operator
int M[4][4] = { {0x1, 0x2, 0x3, 0x4},   // 0-7
			    {0xfffffffe, 0xfffffffd, 0xfffffffc, 0xfffffffb},   // 8-15
                {0x3, 0x6, 0x9, 0xc},   // 16-23
                {0xfffffffc, 0xfffffffb, 0x9, 0xc}   // 24-31
               };

// reminder: vectorized result
int x[4] = {0x5, 0xfffffffd, 0xfffffff6, 0x6};    // 0-4

int w = 1;

// expected result
int a[4] = {0xfffffff9, 0x9, 0xffffffeb, 0xffffffe9};    // 0-4

int main(int argc, const char ** argv, const char ** env) {
  Verilated::commandArgs(argc, argv);

  // Treat the argument on the command-line as the place to start
  int width, length, ell;
  if (argc > 1 && argv[1][0] != '+') {
    width = atoi(argv[1]);
  } else {
    width = 2; // Default, anything <= 4 works
  }

  Vweighted_inner_product * dut = new Vweighted_inner_product;  // Instantiate a new module

  // Enable dumping a VCD file

  Verilated::traceEverOn(true);
  VerilatedVcdC * tfp = new VerilatedVcdC;
  dut->trace(tfp, 99);
  tfp->open("weighted_inner_product.vcd");

  dut->clk = 0;
  dut->start = 0;
  dut->width = width;
  
  int time;
  std::cout << "width: " << width;
  std::cout << std::endl;
  
  int r = 0;
  int i = 0;
  int err = 0;

  int start_stamp = 100;
  // after outer product and vectorization completes
  int comp_stamp = start_stamp + width * width * 20 + width * width * 20 + 10* 20;
  std::cout << "comp_stamp: " << comp_stamp;
  std::cout << std::endl;
  
  for (time = 0 ; time < 10000 ; time += 10) {
    dut->clk = ((time % 20) >= 10) ? 1 : 0; // Simulate a 50 MHz clock
    if (time == 20) dut->reset = 1; // Handle "reset" on for four cycles
    if (time == 100) {
      dut->reset = 0;
      dut->start = 1;   // Put "start" on
    }

    // take inputs
    if (time >= start_stamp && time <= start_stamp + 20 && dut->clk == 1) {
        dut->w = w;

        dut->c_1_1 = c_1[0];
        dut->c_1_2 = c_1[1];
        dut->c_1_3 = 0;
        dut->c_1_4 = 0;

        std::cout << "vector 1 input received: " << (int) c_1[0];
        std::cout << std::endl;
        std::cout << "vector 1 input received: " << (int) c_1[1];
        std::cout << std::endl;
        std::cout << "vector 1 input received: " << 0;
        std::cout << std::endl;
        std::cout << "vector 1 input received: " << 0;
        std::cout << std::endl;

        dut->c_2_1 = c_2[0];
        dut->c_2_2 = c_2[1];
        dut->c_2_3 = 0;
        dut->c_2_4 = 0;

        std::cout << "vector 2 input received: " << (int) c_2[0];
        std::cout << std::endl;
        std::cout << "vector 2 input received: " << (int) c_2[1];
        std::cout << std::endl;
        std::cout << "vector 2 input received: " << 0;
        std::cout << std::endl;
        std::cout << "vector 2 input received: " << 0;
        std::cout << std::endl;
    }

    if (time == start_stamp + 20) {
        dut->start = 0;
    }

    // take matrix input
    if (time >= comp_stamp && time <= comp_stamp + 20 * width * width && dut->clk == 1) {
        dut->M_on = 1;
        dut->M_1 = M[r][0];
        dut->M_2 = M[r][1];
        dut->M_3 = M[r][2];
        dut->M_4 = M[r][3];
        dut->M_5 = 0;           // unused
        dut->M_6 = 0;
        dut->M_7 = 0;
        dut->M_8 = 0;
        dut->M_9 = 0;  
        dut->M_10 = 0;
        dut->M_11 = 0;
        dut->M_12 = 0;
        dut->M_13 = 0;
        dut->M_14 = 0;
        dut->M_15 = 0;
        dut->M_16 = 0;

        std::cout << "matrix input received: " << M[r][0];
        std::cout << std::endl;
        std::cout << "matrix input received: " << M[r][1];
        std::cout << std::endl;
        std::cout << "matrix input received: " << M[r][2];
        std::cout << std::endl;
        std::cout << "matrix input received: " << M[r][3];
        std::cout << std::endl;
        std::cout << "matrix input received: " << 0;
        std::cout << std::endl;
        std::cout << "matrix input received: " << 0;
        std::cout << std::endl;
        std::cout << "matrix input received: " << 0;
        std::cout << std::endl;
        std::cout << "matrix input received: " << 0;
        std::cout << std::endl;
        std::cout << "matrix input received: " << 0;
        std::cout << std::endl;
        std::cout << "matrix input received: " << 0;
        std::cout << std::endl;
        std::cout << "matrix input received: " << 0;
        std::cout << std::endl;
        std::cout << "matrix input received: " << 0;
        std::cout << std::endl;
        std::cout << "matrix input received: " << 0;
        std::cout << std::endl;
        std::cout << "matrix input received: " << 0;
        std::cout << std::endl;
        std::cout << "matrix input received: " << 0;
        std::cout << std::endl;
        std::cout << "matrix input received: " << 0;
        std::cout << std::endl;

        r+= 1;
    }

    if (time == comp_stamp + 20 * width * width + 20) {
            dut->M_on = 0;
    }


    dut->eval();     // Run the simulation for a cycle
    tfp->dump(time); // Write the VCD file for this cycle

    // compare outputs for (length) rows
    // for each row, compare outputs for (width * ell) cycles
    if (time >= comp_stamp && dut->done && dut->clk == 1) {
        std::cout << "vector output received: " << (int) dut->y;
        std::cout << std::endl;
        if (dut->y == a[i])
            std::cout << " OK";
        else {
            std::cout << " INCORRECT expected " << std::setfill('0') << (int) a[i];
            err += 1;
        }
        std::cout << std::endl;

        i += 1;
    }

    if (i == width * width) {
        break;
    }

  }

  tfp->close(); // Stop dumping the VCD file
  delete tfp;

  dut->final(); // Stop the simulation
  delete dut;

  return 0;
}