#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2024 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0

import vltest_bootstrap
import collections
import math

test.scenarios('simulator')
test.top_filename = test.obj_dir + "/t_gate_tree.v"
test.cycles = (1000000 if test.benchmark else 100)
test.sim_time = test.cycles * 10 + 1000

width = 64 * int(test.getenv_def("VERILATOR_TEST_WIDTH", "4"))
nvars = 64


def gen(filename):
    with open(filename, 'w', encoding="utf8") as fh:
        fh.write("// Generated by t_gate_tree.py\n")
        fh.write("module t (clk);\n")
        fh.write("  input clk;\n")
        fh.write("\n")
        fh.write("  integer cyc=0;\n")
        fh.write("  reg reset;\n")
        fh.write("\n")

        tree = collections.defaultdict(dict)
        fanin = 8
        stages = int(math.log(nvars) / math.log(fanin) + 0.99999) + 1
        result = 0
        for n in range(0, nvars):
            result += max(n, 1)
            if 0 not in tree:
                tree[0] = {}
            if n not in tree[0]:
                tree[0][n] = {}
            tree[0][n][n] = True
            nl = n
            for stage in range(1, stages):
                lastn = nl
                nl = int(nl / fanin)
                if stage not in tree:
                    tree[stage] = {}
                if nl not in tree[stage]:
                    tree[stage][nl] = {}
                tree[stage][nl][lastn] = True

        # pprint(tree)

        fh.write("\n")
        workingset = 0
        for stage in sorted(tree.keys()):
            for n in sorted(tree[stage].keys()):
                fh.write("   reg [" + str(width - 1) + ":0] v" + str(stage) + "_" + str(n) + ";\n")
                workingset += int(width / 8 + 7)

        fh.write("\n")
        fh.write("   always @ (posedge clk) begin\n")
        fh.write("      cyc <= cyc + 1;\n")
        fh.write("`ifdef TEST_VERBOSE\n")
        fh.write("         $write(\"[%0t] rst=%0x  v0_0=%0x  v1_0=%0x  result=%0x\\n\""
                 ", $time, reset, v0_0, v1_0, v" + str(stages - 1) + "_0);\n")
        fh.write("`endif\n")
        fh.write("      if (cyc==0) begin\n")
        fh.write("         reset <= 1;\n")
        fh.write("      end\n")
        fh.write("      else if (cyc==10) begin\n")
        fh.write("         reset <= 0;\n")
        fh.write("      end\n")
        fh.write("`ifndef SIM_CYCLES\n")
        fh.write(" `define SIM_CYCLES 99\n")
        fh.write("`endif\n")
        fh.write("      else if (cyc==`SIM_CYCLES) begin\n")
        fh.write("         if (v" + str(stages - 1) + "_0 != " + str(width) + "'d" + str(result) +
                 ") $stop;\n")
        fh.write("         $write(\"VARS=" + str(nvars) + " WIDTH=" + str(width) + " WORKINGSET=" +
                 str(int(workingset / 1024)) + "KB\\n\");\n")
        fh.write('         $write("*-* All Finished *-*\\n");' + "\n")
        fh.write('         $finish;' + "\n")
        fh.write("      end\n")
        fh.write("   end\n")

        fh.write("\n")
        for n in range(0, nvars):
            fh.write("   always @ (posedge clk)" + " v0_" + str(n) + " <= reset ? " + str(width) +
                     "'d" + str(max(n, 1)) + " : v0_" + str((int(n / fanin) * fanin) +
                                                            ((n + 1) % fanin)) + ";\n")

        for stage in sorted(tree.keys()):
            if stage == 0:
                continue
            fh.write("\n")
            for n in sorted(tree[stage].keys()):
                fh.write("   always @ (posedge clk) v" + str(stage) + "_" + str(n) + " <=")
                op = ""
                for ni in sorted(tree[stage][n].keys()):
                    fh.write(op + " v" + str(stage - 1) + "_" + str(ni))
                    op = " +"
                fh.write(";\n")

        fh.write("endmodule\n")


gen(test.top_filename)

test.compile(v_flags2=["+define+SIM_CYCLES=" + str(test.cycles)],
             verilator_flags2=["--stats --x-assign fast --x-initial fast", "-Wno-UNOPTTHREADS"])

test.execute(all_run_flags=[
    "+verilator+prof+exec+start+100",
    " +verilator+prof+exec+window+2",
    " +verilator+prof+exec+file+" + test.obj_dir + "/profile_exec.dat",
    " +verilator+prof+vlt+file+" + test.obj_dir + "/profile.vlt"])   # yapf:disable

test.passes()
