#include <systemc>

using namespace sc_core;
using namespace sc_dt;
using namespace std;

template <typename T>
SC_MODULE(gcd) {
    sc_in<T> x_i, y_i;
    sc_out<T> r_o;
    SC_CTOR(gcd) {
        SC_THREAD(run);
        sensitive << x_i << y_i;
    }
private:
    void run() {
        while(1) {
            wait();
            T x = x_i.read();
            T y = y_i.read();
            wait(10, SC_NS);
            while (x != y) {
                if (x > y) {
                    x -= y;
                } else {
                    y -= x;
                }
                wait(10, SC_NS);
            }
            r_o.write(x);
        }
    }
};

template <typename T>
SC_MODULE(tb_gcd) {
    sc_in<T> r_i;
    sc_out<T> x_o, y_o;
    SC_CTOR(tb_gcd) {
        SC_THREAD(run);
        sensitive << r_i;
    }
private:
    void check(const T& x, const T& y, const T& r) {
        auto start_time_stamp = sc_time_stamp();
        x_o.write(x);
        y_o.write(y);
        wait();
        wait(10, SC_NS);
        assert(r_i.read() == r);
        auto end_time_stamp = sc_time_stamp();
        cout << "@: " << sc_time_stamp() 
             << ": gcd(" << x << "," << y << ") = " << r 
             << " duration: " << end_time_stamp - start_time_stamp << endl;
    }
    void run() {
        check(0, 0, 0);
        check(234, 96, 6);
        check(12345, 67891, 1);
        check(12345, 67890, 15);
        check(12345, 12345, 12345);

        wait(10, SC_NS);
        x_o.write(0);
        y_o.write(0);
    }
};

int sc_main(int argc, char *argv[]) {
    gcd<unsigned int> gcd("gcd");
    tb_gcd<unsigned int> tb_gcd("tb_gcd");
    sc_buffer<unsigned int> x, y, r;
    
    gcd.x_i(x);
    gcd.y_i(y);
    gcd.r_o(r);

    tb_gcd.x_o(x);
    tb_gcd.y_o(y);
    tb_gcd.r_i(r);

    auto tf = sc_create_vcd_trace_file("trace");
    tf->set_time_unit(1, SC_NS);
    sc_trace(tf, x, "x");
    sc_trace(tf, y, "y");
    sc_trace(tf, r, "r");

    sc_start(100, SC_MS);

    sc_close_vcd_trace_file(tf);

    cin.get();
    return 0;
}
