#!/usr/bin/env perl
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2003 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0
use IO::File;
#use Data::Dumper;
use strict;
use vars qw($Self);

scenarios(simulator => 1);

my $width = 64*($ENV{VERILATOR_TEST_WIDTH}||4);
my $vars = 64;

$Self->{cycles} = ($Self->{benchmark} ? 1_000_000 : 100);
$Self->{sim_time} = $Self->{cycles} * 10 + 1000;

sub gen {
    my $filename = shift;

    my $fh = IO::File->new(">$filename");
    $fh->print("// Generated by t_gate_tree.pl\n");
    $fh->print("module t (clk);\n");
    $fh->print("  input clk;\n");
    $fh->print("\n");
    $fh->print("  integer cyc=0;\n");
    $fh->print("  reg reset;\n");
    $fh->print("\n");

    my %tree;
    my $fanin = 8;
    my $stages = int(log($vars)/log($fanin)+0.99999)+1;
    my $result = 0;
    for (my $n=0; $n<$vars; $n++) {
        $result += ($n||1);
        $tree{0}{$n}{$n} = 1;
        my $nl = $n;
        for (my $stage=1; $stage<$stages; $stage++) {
            my $lastn = $nl;
            $nl = int($nl/$fanin);
            $tree{$stage}{$nl}{$lastn} = 1;
        }
    }
    #print Dumper(\%tree);

    $fh->print("\n");
    my $workingset = 0;
    foreach my $stage (sort {$a<=>$b} keys %tree) {
        foreach my $n (sort {$a<=>$b} keys %{$tree{$stage}}) {
            $fh->print(    "   reg [".($width-1).":0] v${stage}_${n};\n");
            $workingset += int($width/8 + 7);
        }
    }

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

    $fh->print("\n");
    for (my $n=0; $n<$vars; $n++) {
        $fh->print("   always @ (posedge clk)"
                   ." v0_${n} <= reset ? ${width}'d".(${n}||1)." : v0_"
                   .((int($n/$fanin)*$fanin) + (($n+1) % $fanin)).";\n");
    }

    foreach my $stage (sort {$a<=>$b} keys %tree) {
        next if $stage == 0;
        $fh->print("\n");
        foreach my $n (sort {$a<=>$b} keys %{$tree{$stage}}) {
            $fh->print("   always @ (posedge clk)"
                       ." v${stage}_${n} <=");
            my $op = "";
            foreach my $ni (sort {$a<=>$b} keys %{$tree{$stage}{$n}}) {
                $fh->print($op." v".(${stage}-1)."_${ni}");
                $op = " +";
            }
            $fh->print(";\n");
        }
    }

    $fh->print("endmodule\n");
}

top_filename("$Self->{obj_dir}/t_gate_tree.v");

gen($Self->{top_filename});

compile(
    v_flags2 => ["+define+SIM_CYCLES=$Self->{cycles}",],
    verilator_flags2=>["--stats --x-assign fast --x-initial fast",
                       "-Wno-UNOPTTHREADS"],
    );

execute(
    all_run_flags => ["+verilator+prof+threads+start+100",
                      " +verilator+prof+threads+window+2",
                      " +verilator+prof+threads+file+$Self->{obj_dir}/profile_threads.dat",
                      ],
    check_finished => 1,
    );

ok(1);
1;
