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

// encrypted data
int c[8] = {0x1, 0x2, 0x3, 0x4, 0xffffffff, 0xfffffffe, 0xfffffffd, 0xfffffffc};   // 0-7

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

// result
int a[4] = {0xffffffd8, 0x28, 0xb4, 0xffffff88};    // 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 = 8; // Default, anything <= 16 works
    length = 4; // Default anything <= 16 works
  }

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

  // Enable dumping a VCD file

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

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

  int start_stamp = 100;
  int read_stamp = 100;
  
  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->on = 1;   // Put "start" on
    }

    // take inputs
    if (time >= start_stamp && time <= start_stamp + 20 && dut->clk == 1) {
        dut->c_1 = c[0];
        dut->c_2 = c[1];
        dut->c_3 = c[2];
        dut->c_4 = c[3];
        dut->c_5 = c[4];
        dut->c_6 = c[5];
        dut->c_7 = c[6];
        dut->c_8 = c[7];
        dut->c_9 = 0;       // unused
        dut->c_10 = 0;
        dut->c_11 = 0;
        dut->c_12 = 0;
        dut->c_13 = 0;
        dut->c_14 = 0;
        dut->c_15 = 0;
        dut->c_16 = 0;

        std::cout << "vector input received: " << c[0];
        std::cout << std::endl;
        std::cout << "vector input received: " << c[1];
        std::cout << std::endl;
        std::cout << "vector input received: " << c[2];
        std::cout << std::endl;
        std::cout << "vector input received: " << c[3];
        std::cout << std::endl;
        std::cout << "vector input received: " << c[4];
        std::cout << std::endl;
        std::cout << "vector input received: " << c[5];
        std::cout << std::endl;
        std::cout << "vector input received: " << c[6];
        std::cout << std::endl;
        std::cout << "vector input received: " << c[7];
        std::cout << std::endl;
        std::cout << "vector input received: " << 0;
        std::cout << std::endl;
        std::cout << "vector input received: " << 0;
        std::cout << std::endl;
        std::cout << "vector input received: " << 0;
        std::cout << std::endl;
        std::cout << "vector input received: " << 0;
        std::cout << std::endl;
        std::cout << "vector input received: " << 0;
        std::cout << std::endl;
        std::cout << "vector input received: " << 0;
        std::cout << std::endl;
        std::cout << "vector input received: " << 0;
        std::cout << std::endl;
        std::cout << "vector input received: " << 0;
        std::cout << std::endl;
    }

    // take matrix input
    if (time >= start_stamp && time <= start_stamp + 20 * length && dut->clk == 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 = M[r][4];
        dut->M_6 = M[r][5];
        dut->M_7 = M[r][6];
        dut->M_8 = M[r][7];
        dut->M_9 = 0;       // unused
        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: " << M[r][4];
        std::cout << std::endl;
        std::cout << "matrix input received: " << M[r][5];
        std::cout << std::endl;
        std::cout << "matrix input received: " << M[r][6];
        std::cout << std::endl;
        std::cout << "matrix input received: " << M[r][7];
        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;
    }

    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 >= read_stamp && dut->write && 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 == length) {
        dut->on = 0;
        break;
    }

  }

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

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

  return 0;
}