// DESCRIPTION: Verilator: Verilog Test module
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2019 Todd Strader
// SPDX-License-Identifier: CC0-1.0

`define DRIVE(sig) \
/* Just throw a bunch of bits at the input */ \
/* verilator lint_off WIDTH */ \
sig``_in <= {8{crc}}; \
/* verilator lint_on WIDTH */
`define CHECK(sig) \
if (cyc > 0 && sig``_in != sig``_out) begin \
  $display(`"%%Error (%m) sig``_in (%p) != sig``_out (%p)`", \
        sig``_in, sig``_out); \
    $stop; \
     end

module t #(parameter GATED_CLK = 0) (/*AUTOARG*/
  // Inputs
  clk
  );
  input clk;

  localparam last_cyc =
`ifdef TEST_BENCHMARK
          `TEST_BENCHMARK;
`else
  10;
`endif

  genvar     x;
  generate
    for (x = 0; x < 2; x = x + 1) begin: gen_loop
      integer cyc = 0;
      reg [63:0] crc = 64'h5aef0c8d_d70a4497;
      logic [31:0] accum_in;
      logic [31:0] accum_out;
      logic        accum_bypass;
      logic [31:0] accum_bypass_out;
      logic [31:0] accum_out_expect;
      logic [31:0] accum_bypass_out_expect;
      logic        s1_in;
      logic        s1_out;
      logic        s1up_in[2];
      logic        s1up_out[2];
      logic [1:0]  s2_in;
      logic [1:0]  s2_out;
      logic [7:0]  s8_in;
      logic [7:0]  s8_out;
      logic [32:0] s33_in;
      logic [32:0] s33_out;
      logic [63:0] s64_in;
      logic [63:0] s64_out;
      logic [64:0] s65_in;
      logic [64:0] s65_out;
      logic [128:0] s129_in;
      logic [128:0] s129_out;
      logic [3:0] [31:0] s4x32_in;
      logic [3:0] [31:0] s4x32_out;
      /*verilator lint_off ASCRANGE*/
      logic [0:15]       s6x16up_in[0:1][2:0];
      logic [0:15]       s6x16up_out[0:1][2:0];
      /*verilator lint_on ASCRANGE*/
      logic [15:0]       s8x16up_in[1:0][0:3];
      logic [15:0]       s8x16up_out[1:0][0:3];
      logic [15:0]       s8x16up_3d_in[1:0][0:1][0:1];
      logic [15:0]       s8x16up_3d_out[1:0][0:1][0:1];

      wire               clk_en = crc[0];

      secret
        secret (
             .accum_in,
             .accum_out,
             .accum_bypass,
             .accum_bypass_out,
             .s1_in,
             .s1_out,
             .s1up_in,
             .s1up_out,
             .s2_in,
             .s2_out,
             .s8_in,
             .s8_out,
             .s33_in,
             .s33_out,
             .s64_in,
             .s64_out,
             .s65_in,
             .s65_out,
             .s129_in,
             .s129_out,
             .s4x32_in,
             .s4x32_out,
             .s6x16up_in,
             .s6x16up_out,
             .s8x16up_in,
             .s8x16up_out,
             .s8x16up_3d_in,
             .s8x16up_3d_out,
             .clk_en,
             .clk);

      always @(posedge clk) begin
`ifdef TEST_VERBOSE
        $display("[%0t] x=%0d, cyc=%0d accum_in=%0d accum_out=%0d accum_bypass_out=%0d",
               $time, x, cyc, accum_in, accum_out, accum_bypass_out);
`endif
        cyc <= cyc + 1;
        crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[0]};
        accum_in <= accum_in + 5;
        `DRIVE(s1)
        `DRIVE(s2)
        `DRIVE(s8)
        `DRIVE(s33)
        `DRIVE(s64)
        `DRIVE(s65)
        `DRIVE(s129)
        `DRIVE(s4x32)
        {s1up_in[1], s1up_in[0]} <= {^crc, ~(^crc)};
        {s6x16up_in[0][0], s6x16up_in[0][1], s6x16up_in[0][2]} <= crc[47:0];
        {s6x16up_in[1][0], s6x16up_in[1][1], s6x16up_in[1][2]} <= ~crc[63:16];
        {s8x16up_in[0][0], s8x16up_in[0][1], s8x16up_in[0][2], s8x16up_in[0][3]} <= crc;
        {s8x16up_in[1][0], s8x16up_in[1][1], s8x16up_in[1][2], s8x16up_in[1][3]} <= ~crc;
        {s8x16up_3d_in[0][0][0], s8x16up_3d_in[0][0][1]} <= ~crc[31:0];
        {s8x16up_3d_in[0][1][0], s8x16up_3d_in[0][1][1]} <= ~crc[63:32];
        {s8x16up_3d_in[1][0][0], s8x16up_3d_in[1][0][1]} <= crc[31:0];
        {s8x16up_3d_in[1][1][0], s8x16up_3d_in[1][1][1]} <= crc[63:32];
        if (cyc == 0) begin
          accum_in <= x*100;
          accum_bypass <= '0;
        end else if (cyc > 0) begin
          if (accum_out_expect != accum_out) begin
            $display("%%Error: (%m) accum_out expected %0d got %0d",
                     accum_out_expect, accum_out);
            $stop;
          end
          if (accum_bypass_out_expect != accum_bypass_out) begin
            $display("%%Error: (%m) accum_bypass_out expected %0d got %0d",
                     accum_bypass_out_expect, accum_bypass_out);
            $stop;
          end
        end

        if (cyc == 5) accum_bypass <= '1;

        if (x == 0 && cyc == last_cyc) begin
          $display("final cycle = %0d", cyc);
          $write("*-* All Finished *-*\n");
          $finish;
        end
      end

      logic possibly_gated_clk;
      if (GATED_CLK != 0) begin: yes_gated_clock
        logic clk_en_latch;
        // verilator lint_off COMBDLY,LATCH
        always_comb if (clk == '0) clk_en_latch <= clk_en;
        // verilator lint_on COMBDLY,LATCH
        assign possibly_gated_clk = clk & clk_en_latch;
      end else begin: no_gated_clock
        assign possibly_gated_clk = clk;
      end

      always @(posedge possibly_gated_clk) begin
        // 7 is the secret_value inside the secret module
        accum_out_expect <= accum_in + accum_out_expect + 7;
      end

      always @(*) begin
        // XSim (and maybe all event simulators?) sees the moment where
        // s1_in has not yet propagated to s1_out, however, they do always
        // both change at the same time
        /* verilator lint_off STMTDLY */
        #1;
        /* verilator lint_on STMTDLY */
        `CHECK(s1)
        `CHECK(s1up)
        `CHECK(s2)
        `CHECK(s8)
        `CHECK(s33)
        `CHECK(s64)
        `CHECK(s65)
        `CHECK(s129)
        `CHECK(s4x32)
        `CHECK(s6x16up)
        `CHECK(s8x16up)
        `CHECK(s8x16up_3d)
      end

      assign accum_bypass_out_expect = accum_bypass ? accum_in :
                                    accum_out_expect;
    end
  endgenerate

endmodule
