// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2022 by Antmicro Ltd.
// SPDX-License-Identifier: CC0-1.0

`ifdef TEST_VERBOSE
 `define WRITE_VERBOSE(args) $write args
`else
 `define WRITE_VERBOSE(args)
`endif

class BaseClass;
    virtual task sleep;
    endtask

    virtual task await;
    endtask
endclass

module t;
    // =============================================
    // EVENTS
    class EventClass extends BaseClass;
        event e;
        int trig_count;

        function new;
            trig_count = 0;
        endfunction

        task inc_trig_count;
            trig_count++;
        endtask;

        task sleep;
            @e inc_trig_count;
            `WRITE_VERBOSE(("Event in class triggered at time %0t!\n", $time));
        endtask

        task wake;
            ->e;
        endtask
    endclass

    class WaitClass extends BaseClass;
        int a;
        int b;
        logic ok;

        function new;
            a = 0;
            b = 0;
            ok = 0;
        endfunction

        task await;
            wait(a == 4 && b > 16) if (a != 4 || b <= 16) $stop;
            ok = 1;
            `WRITE_VERBOSE(("Condition in object met at time %0t!\n", $time));
        endtask
    endclass

    class LocalWaitClass extends BaseClass;
        logic ok;

        function new;
            ok = 0;
        endfunction

        task await;
            int a = 0;
            int b = 100;
            fork
                wait(a == 42 || b != 100) if (a != 42 && b == 100) $stop;
                #10 a = 42;
            join
            ok = 1;
            `WRITE_VERBOSE(("Condition with local variables met at time %0t!\n", $time));
        endtask
    endclass

    class ClkClass;
        logic clk;
        int count;

        function new;
            clk = 0;
            count = 0;
        endfunction

        task flip;
            clk = ~clk;
        endtask;

        task count_5;
            @(posedge clk) count++;
            @(posedge clk) count++;
            @(posedge clk) count++;
            @(posedge clk) count++;
            @(posedge clk) count++;
        endtask
    endclass

    EventClass ec = new;
    WaitClass wc = new;
    LocalWaitClass lc = new;
    ClkClass cc = new;

    initial begin
        @ec.e;
        ec.sleep;
        if (wc.ok) $stop;
        wc.await;
        if (lc.ok) $stop;
        lc.await;
    end

    initial #20 ec.wake;
    initial #40 ->ec.e;
    initial begin
        wc.a = #50 4;
        wc.b = #10 32;
    end

    always @ec.e begin
        ec.inc_trig_count;
        `WRITE_VERBOSE(("Event in class triggered at time %0t!\n", $time));
    end

    always #5 cc.flip;

    initial cc.count_5;

    initial begin
        #80
        if (cc.count != 5) $stop;
        if (ec.trig_count != 3) $stop;
        if (!wc.ok) $stop;
        if (!lc.ok) $stop;
    end

    // =============================================
    // DELAYS
    virtual class DelayClass;
        pure virtual task do_delay;
        pure virtual task do_sth_else;
    endclass

    `ifdef TEST_VERBOSE
     `define DELAY_CLASS(dt) \
     class Delay``dt extends DelayClass; \
         virtual task do_delay; \
             $write("Starting a #%0d delay\n", dt); \
             #dt \
             $write("Ended a #%0d delay\n", dt); \
         endtask \
         virtual task do_sth_else; \
             $write("Task with no delay (in Delay%0d)\n", dt); \
         endtask \
     endclass
    `else
     `define DELAY_CLASS(dt) \
     class Delay``dt extends DelayClass; \
         virtual task do_delay; \
             #dt; \
         endtask \
         virtual task do_sth_else; \
         endtask \
     endclass
    `endif

    `DELAY_CLASS(10);
    `DELAY_CLASS(20);
    `DELAY_CLASS(40);

    class NoDelay extends DelayClass;
        virtual task do_delay;
            `WRITE_VERBOSE(("Task with no delay\n"));
        endtask
        virtual task do_sth_else;
            `WRITE_VERBOSE(("Task with no delay (in NoDelay)\n"));
        endtask
    endclass

    class AssignDelayClass;
        logic x;
        logic y;
        task do_assign;
            y = #10 x;
            `WRITE_VERBOSE(("Did assignment with delay\n"));
        endtask
    endclass

    initial begin
        DelayClass dc;
        Delay10 d10 = new;
        Delay20 d20 = new;
        Delay40 d40 = new;
        NoDelay dNo = new;
        AssignDelayClass dAsgn = new;
        `WRITE_VERBOSE(("I'm at time %0t\n", $time));
        dc = d10;
        dc.do_delay;
        dc.do_sth_else;
        `WRITE_VERBOSE(("I'm at time %0t\n", $time));
        if ($time != 10) $stop;
        dc = d20;
        dc.do_delay;
        dc.do_sth_else;
        `WRITE_VERBOSE(("I'm at time %0t\n", $time));
        if ($time != 30) $stop;
        dc = d40;
        dc.do_delay;
        dc.do_sth_else;
        `WRITE_VERBOSE(("I'm at time %0t\n", $time));
        if ($time != 70) $stop;
        dc = dNo;
        dc.do_delay;
        dc.do_sth_else;
        `WRITE_VERBOSE(("I'm at time %0t\n", $time));
        dAsgn.x = 1;
        dAsgn.y = 0;
        fork #5 dAsgn.x = 0; join_none
        dAsgn.do_assign;
        if ($time != 80) $stop;
        if (dAsgn.y != 1) $stop;
        // Test if the object is deleted before do_assign finishes:
        fork dAsgn.do_assign; join_none
        #5 dAsgn = null;
        #15 $write("*-* All Finished *-*\n");
        $finish;
    end

    // =============================================
    // FORKS
    class ForkDelayClass;
        task do_delay; #40; endtask
    endclass

    class ForkClass;
        int done = 0;
        task do_fork();
            ForkDelayClass d;
            fork
                begin
                    #10 done++;
                    `WRITE_VERBOSE(("Forked process %0d ending at time %0t\n", done, $time));
                end
                fork
                    begin
                        #20 done++;
                        `WRITE_VERBOSE(("Forked process %0d ending at time %0t\n", done, $time));
                        d = new;
                    end
                    begin
                        #30 d.do_delay;
                        done++;
                        `WRITE_VERBOSE(("Forked process %0d ending at time %0t\n", done, $time));
                    end
                join
            join
            done++;
            `WRITE_VERBOSE(("All forked processes ended at time %0t\n", $time));
        endtask
    endclass

    initial begin
        ForkClass fc = new;
        fc.do_fork;
        if (fc.done != 4 || $time != 70) $stop;
    end

    initial #101 $stop; // timeout
endmodule
