// SPDX-License-Identifier: Apache-2.0
// Author: Florian Zaruba <zarubaf@iis.ee.ethz.ch>

#include "Vecc_decode.h"
#include "verilated.h"
#include "verilated_vcd_c.h"
#include "ecc.h"

#include <stdio.h>
#include <stdint.h>
#include <assert.h>
#include <math.h>

// This is a 64-bit integer to reduce wrap over issues and
// allow modulus.  You can also use a double, if you wish.
vluint64_t main_time = 0;

// Called by $time in Verilog
// converts to double, to match
// what SystemC does
double sc_time_stamp () {
    return main_time;
}


int main(int argc, char** argv, char** env) {

    Verilated::commandArgs(argc, argv);
    Vecc_decode* dut = new Vecc_decode;

    int d = 64; // code word length
    int p = calcParityLen(d);  // parity length

    // number of bytes to store the codeword
    int cw_byte = (int) ceil((d + p + 1)/32.0);

    // input word
    uint64_t word = 0x55555555;
    uint8_t codeword [d + p + 1];
    // allocate 8 bit per byt
    uint8_t encoded_word [cw_byte];
    uint8_t parity [d];

    uint8_t single_error, parity_error, double_error;

    Verilated::traceEverOn(true);
    VerilatedVcdC* tfp = new VerilatedVcdC;
    dut->trace(tfp, 99);  // Trace 99 levels of hierarchy
    tfp->open("ecc_decode.vcd");

    while (!Verilated::gotFinish()) {

        ecc_encode(word, d, codeword);

        for (int i = 0; i < cw_byte; i++) encoded_word[i] = 0;

        toByte(encoded_word, codeword, d + p + 1);

        printf("[DUT] input  = %llx\n", word);
        // Apply Input Data
        for (int i = 0; i < cw_byte; i++) {
            dut->data_i[i] = *((unsigned int *) encoded_word + i);
        }

        // randomly inject some errors
        single_error = 0;
        double_error = 0;
        parity_error = 0;

        switch (random() % 10) {
            // inject single error
            case 0:
                printf("Injecting single error\n");
                single_error = 1;
                dut->data_i[random() % (cw_byte - 1)] ^= 0x1 << (random() % 8);
                break;
            // parity error
            case 1:
                printf("Injecting parity error\n");
                parity_error = 1;
                dut->data_i[cw_byte-1] ^= 0x80;
                break;
            // inject double error
            case 2:
                printf("Injecting double error\n");
                double_error = 1;
                dut->data_i[random() % (cw_byte - 1)] ^= 0x3 << (random() % 8);
                break;
        }

        // Eval DUT
        dut->eval();

        printf("[DUT] output = %lx\n", dut->data_o);
        assert (double_error || dut->data_o == word);
        assert (dut->single_error_o == single_error);
        assert (dut->parity_error_o == parity_error);
        assert (dut->double_error_o == double_error);
        // Advance Time
        main_time++;

        word = random() << 32 | random();

        tfp->dump(main_time);

        if (main_time > 100000) break;
    }
    dut->final();
    tfp->close();
    delete dut;
    exit(0);
}