//
// Copyright 2015 Ettus Research LLC
//

// Initializes state for a test bench.
// This macro *must be* called within the testbench module but 
// outside the primary initial block
// Its sets up boilerplate code for:
// - Logging to console
// - Test execution tracking
// - Gathering test results
// - Bounding execution time based on the SIM_TIMEOUT_US vdef

`ifndef SIM_TIMEOUT_US
`define SIM_TIMEOUT_US 100000 // Default: 100 ms
`endif

// Usage: `TEST_BENCH_INIT(test_name,min_tc_run_count,ns_per_tick)
// where
//  - tb_name:          Name of the testbench. (Only used during reporting)
//  - min_tc_run_count: Number of test cases in testbench. (Used to detect stalls and inf-loops)
//  - ns_per_tick:      The time_unit_base from the timescale declaration
//
`define TEST_BENCH_INIT(tb_name, min_tc_run_count, ns_per_tick) \
  localparam sim_time_increment = 100; \
  reg tc_running = 0; \
  reg tc_failed = 0; \
  reg tc_all_done = 0; \
  real sim_time = 0.0; \
  integer tc_run_count = 0; \
  integer tc_pass_count = 0; \
  \
  initial begin : tb_timekeeper \
    #0; \
    $timeformat(-9, 0, " ns", 10); \
    $display("========================================================"); \
    $display("TESTBENCH STARTED: %s", tb_name); \
    $display("========================================================"); \
    if (1000.0*`SIM_TIMEOUT_US < sim_time_increment) begin \
      $error("Total simulation time less than simulation step size!"); \
    end \
    tc_running = 0; \
    tc_failed = 0; \
    tc_run_count = 0; \
    tc_pass_count = 0; \
    while (~tc_all_done & sim_time < 1000.0*`SIM_TIMEOUT_US) begin \
       #(sim_time_increment); \
       sim_time += sim_time_increment; \
    end \
    $display("========================================================"); \
    $display("TESTBENCH FINISHED: %s", tb_name); \
    $display(" - Time elapsed:   %0t%s", $realtime(), (sim_time >= 1000.0*`SIM_TIMEOUT_US) ? " (Timed out!)" : ""); \
    $display(" - Tests Expected: %0d", min_tc_run_count); \
    $display(" - Tests Run:      %0d", tc_run_count); \
    $display(" - Tests Passed:   %0d", tc_pass_count); \
    $display("Result: %s", ((tc_run_count>=min_tc_run_count)&&(tc_run_count==tc_pass_count)?"PASSED   ":"FAILED!!!")); \
    $display("========================================================"); \
    $finish; \
  end

// Ends test bench. Place after final test.
//
// Usage: `TEST_BENCH_DONE
`define TEST_BENCH_DONE tc_all_done = 1;

// Indicates the start of a test case
// This macro *must be* called inside the primary initial block
//
// Usage: `TEST_CASE_START(test_name)
// where
//  - test_name:        The name of the test.
//
`define TEST_CASE_START(test_name) \
  #0; \
  tc_running = 1; \
  tc_failed = 0; \
  tc_run_count = tc_run_count + 1; \
  $display("[TEST CASE %3d] (t=%09d) BEGIN: %s...", tc_run_count, $time, test_name);

// Indicates the end of a test case
// This macro *must be* called inside the primary initial block
// The pass/fail status of test case is determined based on the
// the user specified outcome and the number of fatal or error
// ASSERTs triggered in the test case.
//
// Usage: `TEST_CASE_DONE(test_result)
// where
//  - test_result:  User specified outcome
//
`define TEST_CASE_DONE(result) \
  #0; \
  tc_running = 0; \
  $display("[TEST CASE %3d] (t=%09d) DONE... %s", tc_run_count, $time, ((((result)===1'b1)&~tc_failed)?"Passed":"FAILED")); \
  if (((result)===1'b1)&~tc_failed) tc_pass_count = tc_pass_count + 1;

// Wrapper around a an assert.
// ASSERT_FATAL throws an error assertion and halts the simulator
// if cond is not satisfied
//
// Usage: `ASSERT_FATAL(cond,msg)
// where
//  - cond: Condition for the assert
//  - msg:  Message for the assert
//
`define ASSERT_FATAL(cond, msg) \
  assert(cond) else begin \
    tc_failed = 1; \
    $error(msg); \
    tc_all_done = 1; \
    #(sim_time_increment); \
  end

// Wrapper around a an assert.
// ASSERT_ERROR throws an error assertion and fails the test case
// if cond is not satisfied. The simulator will *not* halt
//
// Usage: `ASSERT_ERROR(cond,msg)
// where
//  - cond: Condition for the assert
//  - msg:  Message for the assert
//
`define ASSERT_ERROR(cond, msg) \
  assert(cond) else begin \
    tc_failed = 1; \
    $error(msg); \
  end

// Wrapper around a an assert.
// ASSERT_WARNING throws an warning assertion but does not fail the
// test case if cond is not satisfied. The simulator will *not* halt
//
// Usage: `ASSERT_WARNING(cond,msg)
// where
//  - cond: Condition for the assert
//  - msg:  Message for the assert
//
`define ASSERT_WARN(cond, msg) \
  assert(cond) else $warning(msg);