//-------------------------------------------
//  Verilog Testbench for Verifying a digital I/O cell
//  Description: This test is applicable to the embedded I/O cell
//  used in FPGA fabric. This is a self-testing testbench that 
//  checks the functionality of 
//  - The I/O isolation signal, which force the I/O in input mode
//  - The input mode, 
//    - where input from SOC can be propagated to FPGA, when enabled
//    - where FPGA input holds high-impedence state ('Z'), when disabled
//  - The output mode, 
//    - where output from FPGA can be propagated to SOC, when enabled
//    - where SOC output holds high-impedence state ('Z'), when disabled
//
//  Author: Xifan TANG
//  Organization: University of Utah
//-------------------------------------------
//----- Time scale -----
`timescale 1ns / 1ps

`define CLOCK_PERIOD 5
`define NUM_TEST_CLOCK_CYCLES 10

module digital_io_hd_test;
// Local clock to
// - synchronize stimulus generation
// - trigger checkout point
reg [0:0] clock;

// Local wires for Design Under Test (DUT)
reg [0:0] IO_ISOL_N;
reg [0:0] FPGA_DIR;
reg [0:0] SOC_IN;
reg [0:0] FPGA_OUT;
wire [0:0] SOC_DIR;
wire [0:0] FPGA_IN;
wire [0:0] SOC_OUT;

// ----- Counters for error checking -----
integer num_clock_cycles = 0; 
integer num_errors = 0; 
integer num_checked_points = 0; 

// Clock pulse generation
initial
  begin
    clock[0] = 1'b0;
  end
always
  begin
    #`CLOCK_PERIOD clock[0] = ~clock[0];
  end

// IO_ISOL_N stimuli:
// - enabled for two clock cycles
// - disabled then
initial
  begin
    IO_ISOL_N[0] = 1'b0;
    #(`CLOCK_PERIOD*4) IO_ISOL_N[0] = 1'b1;
  end

// FPGA_DIR stimuli: swing between 0 and 1
// - Test if DIR works when IO_ISOL_N is enabled
// - Test if DIR works when IO_ISOL_N is disabled
initial
  begin
    FPGA_DIR[0] = 1'b0;
    #(`CLOCK_PERIOD) FPGA_DIR[0] = 1'b1;
    forever #(`CLOCK_PERIOD*4) FPGA_DIR[0] = ~FPGA_DIR[0];
  end

// SOC_IN stimuli: swing in the frequency of double clock period
initial
  begin
    SOC_IN[0] = 1'b0;
  end
always
  begin
    #(`CLOCK_PERIOD*2) SOC_IN[0] = ~SOC_IN[0];
  end


// FPGA_OUTPUT stimuli: swing in the frequency of double clock period
initial
  begin
    FPGA_OUT[0] = 1'b0;
  end
always
  begin
    #(`CLOCK_PERIOD*2) FPGA_OUT[0] = ~FPGA_OUT[0];
  end

// Instanciate the digital I/O cell
  EMBEDDED_IO_HD IO_DUT (
                        .IO_ISOL_N(IO_ISOL_N),
                        .FPGA_DIR(FPGA_DIR),
                        .FPGA_IN(FPGA_IN),
                        .FPGA_OUT(FPGA_OUT),
                        .SOC_DIR(SOC_DIR),
                        .SOC_IN(SOC_IN),
                        .SOC_OUT(SOC_OUT)
                       );

// Count number of clock cycles
  always @(posedge clock[0]) begin
    num_clock_cycles = num_clock_cycles + 1; 
  end

// Check expected values at SOC_DIR port
  always @(posedge clock[0]) begin
    // SOC DIR should stay at logic '1' when IO_ISOL_N is enabled
    if (1'b0 == IO_ISOL_N) begin
      if (1'b1 !== SOC_DIR) begin
        $display("Error: SOC_DIR = %b (expect =%b)", SOC_DIR, 1'b1);
        num_errors = num_errors + 1;
      end
    end else if (1'b1 == IO_ISOL_N) begin
      if (FPGA_DIR !== SOC_DIR) begin
        $display("Error: SOC_DIR = %b (expect =%b)", SOC_DIR, FPGA_DIR);
        num_errors = num_errors + 1;
      end
    end
    num_checked_points = num_checked_points + 1;
  end

// Check expected values at FPGA_IN port
  always @(posedge clock[0]) begin
    // FPGA_IN should be same as SOC_IN when IO_ISOL_N is enabled
    if (1'b0 == IO_ISOL_N) begin
      if (SOC_IN !== FPGA_IN) begin
        $display("Error: FPGA_IN = %b (expect =%b)", FPGA_IN, SOC_IN);
        num_errors = num_errors + 1;
      end
    end else if (1'b1 == IO_ISOL_N) begin
      if (1'b1 == FPGA_DIR) begin
        if (SOC_IN !== FPGA_IN) begin
          $display("Error: FPGA_IN = %b (expect =%b)", FPGA_IN, SOC_IN);
          num_errors = num_errors + 1;
        end
      end else if (1'b0 == FPGA_DIR) begin
        if (1'bz !== FPGA_IN) begin
          $display("Error: FPGA_IN = %b (expect =%b)", FPGA_IN, 1'bz);
          num_errors = num_errors + 1;
        end
      end
    end
    num_checked_points = num_checked_points + 1;
  end

// Check expected values at SOC_OUT port
  always @(posedge clock[0]) begin
    // SOC_OUT should be 'z' when IO_ISOL_N is enabled
    if (1'b0 == IO_ISOL_N) begin
      if (1'bz !== SOC_OUT) begin
        $display("Error: SOC_OUT = %b (expect =%b)", SOC_OUT, 1'bz);
        num_errors = num_errors + 1;
      end
    end else if (1'b1 == IO_ISOL_N) begin
      if (1'b1 == FPGA_DIR) begin
        if (1'bz !== SOC_OUT) begin
          $display("Error: SOC_OUT = %b (expect =%b)", SOC_OUT, 1'bz);
          num_errors = num_errors + 1;
        end
      end else if (1'b0 == FPGA_DIR) begin
        if (FPGA_OUT !== SOC_OUT) begin
          $display("Error: SOC_OUT = %b (expect =%b)", SOC_OUT, FPGA_OUT);
          num_errors = num_errors + 1;
        end
      end
    end
    num_checked_points = num_checked_points + 1;
  end

  // Finish after a number of clock cycles
  always @(posedge clock[0]) begin
    if (`NUM_TEST_CLOCK_CYCLES < num_clock_cycles) begin
      $display("Simulation finish with %d errors / %d checkpoints", num_errors, num_checked_points);
      $finish;
    end
  end

endmodule