#include <systemc>

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

SC_MODULE(Counter) {
    sc_in_clk clk;
    sc_in<bool> reset, enable;
    sc_out<sc_uint<8>> out;
    Counter (sc_module_name Counter, int init_count_down = false): 
            sc_module(Counter), count_down(init_count_down) {
        SC_THREAD(do_count);
        sensitive << clk.pos();
    }
private:
    sc_uint<8> count;
    bool count_down; 
    void do_count() {
        while (true) {
            if (count_down){
                if (reset.read()) count = 255;
                if (enable.read()) count--;
            }
            else {
                if (reset.read()) count = 0;
                if (enable.read()) count++;
            }
            out.write(count);
            wait();
        }
    }
    SC_HAS_PROCESS(Counter);
};

SC_MODULE(Testbench) {
    sc_in_clk clk;
    sc_out<bool> reset, enable;
    SC_CTOR(Testbench){
        SC_THREAD(testprocess);
        sensitive << clk.pos();
    }
private:
    void testprocess() {
        enable.write(true);
        reset.write(true);
        wait(25, SC_NS);
        reset.write(false);
        wait(200, SC_NS);
        enable.write(false);
        wait(50, SC_NS);
        reset.write(true);
        wait(50, SC_NS);
        reset.write(false);
    }
};

int sc_main(int argc, char *argv[]) {
    Counter counter1("CounterUp");
    Counter counter2("CounterDown", true);
    Testbench test1("Test1");
    sc_clock sysclock("clock", 20, SC_NS);
    sc_signal<bool> reset, enable;
    sc_signal<sc_uint<8>> countUp, countDown;
    counter1.clk(sysclock);
    counter1.reset(reset);
    counter1.enable(enable);
    counter1.out(countUp);
    counter2.clk(sysclock);
    counter2.reset(reset);
    counter2.enable(enable);
    counter2.out(countDown);
    test1.reset(reset);
    test1.enable(enable);
    test1.clk(sysclock);

    // Record (trace) signals for verification
    auto tf = sc_create_vcd_trace_file("trace");
    tf->set_time_unit(1, SC_NS);
    sc_trace(tf, sysclock, "sysclock");
    sc_trace(tf, reset, "reset");
    sc_trace(tf, enable, "enable");
    sc_trace(tf, countUp, "countUp");
    sc_trace(tf, countDown, "countDown");

    // Start the simulation for 200ns 
    sc_start(500, SC_NS);

    sc_close_vcd_trace_file(tf);
    cin.get();
    return 0;
}
