`default_nettype none
`timescale 1ns/1ns

module accumulator (
  input wire clk,
  input wire reset,
  input wire valid,
  input wire [7:0] acc_in,

  output reg [7:0] acc_mem_0, // Output for the first memory location
  output reg [7:0] acc_mem_1, // Output for the second memory location
  output reg full // Flag to indicate when the accumulator is full
);

  // Define a register array to store multiple accumulated values
  reg [7:0] acc_mem [0:1]; // Changed to 2 entries
  reg [1:0] index; // Index to manage storage locations
  integer i; // Declare integer outside of the always block

  // Implement a state machine for this maybe??
  always @(posedge clk or posedge reset) begin
    if (reset) begin

          acc_mem[i] <= 0;
      end

      index <= 0;
      full <= 0;
      acc_mem_0 <= 0;
      acc_mem_1 <= 0;

    end else begin
      // Nothing starts until valid (compute) flag is set!
      // Valid flag must be held high for compute to finish!
      if (valid && acc_in != 0) begin

        if (index < 2) begin
          acc_mem[index] <= acc_in;
          index <= index + 1;
        end else begin
          acc_mem_0 <= acc_mem[0];
          acc_mem_1 <= acc_mem[1];
          full <= 1;
        end

      end
    end
  end

endmodule


`default_nettype none
`timescale 1ns/1ns

module control_unit (
  input wire fetch_ins,
  input wire [7:0] ui_in,
  input wire [3:0] dma_address,

  input wire clk,
  input wire reset,
  input wire start,
  output reg [4:0] base_address,
  output reg load_input,
  output reg load_weight,
  output reg valid,
  output reg store,
  output reg ext
);

  // Instruction definitions
  localparam [7:0] NO_OP = 16'b000_00000;
  localparam [7:0] LOAD_ADDR = 16'b001_00000;
  localparam [7:0] LOAD_WEIGHT = 16'b010_00000;
  localparam [7:0] LOAD_INPUTS = 16'b011_00000;
  localparam [7:0] COMPUTE = 16'b100_00000;
  localparam [7:0] STORE = 16'b101_00000;
  localparam [7:0] EXT = 16'b111_000000;

  // FSM states for interal instruction dispatch
  typedef enum reg [1:0] {IDLE, FETCH, EXECUTE, FINISH} state_t;
  state_t state = IDLE;

  // Instruction memory
  reg [7:0] instruction_mem [0:9]; // Adjust the size as needed.
  reg [7:0] instruction;           // Instruction register

  integer instruction_pointer;
  integer compute_cycle_counter;    // Counter for compute cycles

  // READ instruction data from host computer
  always @(posedge clk or posedge reset) begin
    if (fetch_ins) begin
        instruction_mem[dma_address] <= ui_in;
    end
  end

  // Instruction decoding and control signal generation block
  always @(*) begin
      if (reset) begin // i think this creates a latch error i think. need to do the (!reset) method (look at the tt priv repo)
        base_address = 0;
        load_weight = 0;
        load_input = 0;
        valid = 0;
        store = 0;
        ext = 0;
        // instruction = 0;
      end else begin
          load_weight = 0; // clears register for this flag so its only on for two cycles
          load_input = 0;
          ext = 0;
          store = 0;
        // Dispatch
        case (instruction[7:5])
          3'b001: base_address = instruction[4:0]; // LOAD_ADDR
          3'b010: load_weight = 1;                   // LOAD_WEIGHT
          3'b011: load_input = 1;                    // LOAD_INPUTS
          3'b100: valid = 1;                         // VALID (COMPUTE)
          3'b101: store = 1;                         // STORE
          3'b111: ext = 1;
          default: ; // NO_OP or unrecognized instruction
        endcase
      end
    end

  // State transition and instruction execution block
  always @(posedge clk or posedge reset) begin
    if (reset) begin
      state <= IDLE;
      instruction_pointer <= 0;
      compute_cycle_counter <= 0;
      instruction <= 0;
    end else begin
      case (state)
        IDLE: begin
          if (start) state <= FETCH;
        end
        FETCH: begin
          instruction <= instruction_mem[instruction_pointer];
          state <= EXECUTE;
        end
        EXECUTE: begin // Program is running here!
          case (instruction)
            COMPUTE: begin
              if (compute_cycle_counter < 4) begin
                compute_cycle_counter <= compute_cycle_counter + 1;
              end else begin
                compute_cycle_counter <= 0;
                instruction_pointer <= instruction_pointer + 1;
                state <= FETCH;
              end
            end
            NO_OP: state <= FINISH;
            default: begin
              instruction_pointer <= instruction_pointer + 1;

            end
          endcase
        end
        FINISH: begin // triggered on NOP instruction
          // valid <= 0; // i want to add this but gotta keep blocking and non blocking mutually excl.
          state <= FINISH;
        end
        default: state <= IDLE;
      endcase
    end
  end

endmodule


`default_nettype none
`timescale 1ns/1ns

module dma (
    // INPUTS
    input clk,
    input reset,
    input wire [7:0] uio_in,
    // OUTPUTS
    output wire fetch_w,
    output wire fetch_inp,
    output wire fetch_ins,
    output wire start,

    assign start = (uio_in[7:5] == 3'b100) ? 1 : 0;
    assign dma_address = uio_in[3:0];

endmodule

    output wire [3:0] dma_address
);

    // Combinational logic for outputs


`default_nettype none
`timescale 1ns/1ns

module input_setup(
    input wire clk,
    input wire reset,
    input wire valid,

    input wire [7:0] a11,
    input wire [7:0] a12,
    input wire [7:0] a21,
    input wire [7:0] a22,

    output reg [7:0] a_in1,
    output reg [7:0] a_in2
);
    // n + 1 cells for each row (to accommodate zero-padding)
    reg [7:0] augmented_activation_row1 [0:2];
    reg [7:0] augmented_activation_row2 [0:2];
    reg [2:0] counter;
    integer i;

    typedef enum reg [1:0] {IDLE, READ, WRITE} state_t;
    state_t state = IDLE;

    always @(posedge clk or posedge reset) begin
        if (reset) begin
            for (i = 0; i < 3; i = i + 1) begin
                augmented_activation_row1[i] <= 8'b0;
                augmented_activation_row2[i] <= 8'b0;
            end

            IDLE: begin
                if (valid) begin
                    state <= READ;
                end
                a_in1 <= 8'b0;
                a_in2 <= 8'b0;
            end
            READ: begin
                if (counter == 0) begin
                    augmented_activation_row1[0] <= a11;
                    augmented_activation_row1[1] <= a12;
                    augmented_activation_row1[2] <= 8'b0;

                    augmented_activation_row2[0] <= 8'b0;
                    augmented_activation_row2[1] <= a21;
                    augmented_activation_row2[2] <= a22;
                    state <= WRITE;
                end
            end
            WRITE: begin
                if (counter < 3) begin
                    a_in1 <= augmented_activation_row1[counter];
                    a_in2 <= augmented_activation_row2[counter];
                    counter <= counter + 1;
                end else begin
                    state <= IDLE;
                    counter <= 3'b0;
                end
            end

        endcase

        end
        end
endmodule

            counter <= 3'b0;
            a_in1 <= 8'b0;
            a_in2 <= 8'b0;
            state <= IDLE;


`default_nettype none
`timescale 1ns/1ns

module mmu (
  input wire clk,
  input wire reset,
  input wire load_weight,    // Signal to load weight
  input wire valid,          // Valid signal indicating new data is available

  input wire [7:0] a_in1,   // Input A for PE(0,0)
  input wire [7:0] a_in2,   // Input A for PE(1,0)

  input wire [7:0] weight1, // Weight for PE(0,0)
  input wire [7:0] weight2, // Weight for PE(0,1)
  input wire [7:0] weight3, // Weight for PE(1,0)
  input wire [7:0] weight4, // Weight for PE(1,1)

  // these values need to be stored in a register in matrix format
  output wire [7:0] acc_out1, // Accumulated value from PE(1,0)
  output wire [7:0] acc_out2  // Accumulated value from PE(1,1)
);
  // Internal signals for connections between PEs
  wire [7:0] a_inter_01, a_inter_11;
  wire [7:0] acc_inter_00, acc_inter_01;

  // Instantiate PE(0,0)
  processing_element PE00 (
    .clk(clk),
    .reset(reset),

    .a_out(a_inter_01),
    .acc_out(acc_inter_00)
  );

  // Instantiate PE(0,1)
  processing_element PE01 (
    .clk(clk),
    .reset(reset),
    .load_weight(load_weight),
    .valid(valid),
    .a_in(a_inter_01),
    .weight(weight3),
    .acc_in(8'b0),   // Top-right corner has no accumulated input
    .a_out(), // no output
    .acc_out(acc_inter_01)
  );

  // Instantiate PE(1,0)
  processing_element PE10 (
    .clk(clk),
    .reset(reset),
    .load_weight(load_weight),
    .valid(valid),
    .a_in(a_in2),
    .weight(weight2),
    .acc_in(acc_inter_00), // Bottom-left corner gets accumulated input from PE(0,0)
    .a_out(a_inter_11),
    .acc_out(acc_out1)
  );

  // Instantiate PE(1,1)
  processing_element PE11 (
    .clk(clk),
    .reset(reset),
    .load_weight(load_weight),
    .valid(valid),
    .a_in(a_inter_11),
    .weight(weight4),
    .acc_in(acc_inter_01), // Bottom-right corner gets accumulated input from PE(0,1)
    .a_out(), // no output
    .acc_out(acc_out2)
  );

endmodule

    .load_weight(load_weight),
    .valid(valid),
    .a_in(a_in1),


`default_nettype none
`timescale 1ns/1ns

module processing_element (
  input wire clk,
  input wire reset,
  input wire load_weight,       // Signal to load weight
  input wire valid,             // Valid signal indicating new data is available

  input wire [7:0] a_in,       // Input A from left neighbor
  input wire [7:0] weight,     // Weight input
  input wire [7:0] acc_in,     // Accumulated value from the PE above

  always @(posedge clk or posedge reset) begin
    if (reset) begin

      a_out <= 8'b0;
      acc_out <= 8'b0;
      weight_reg <= 8'b0;

    end else begin

      if (load_weight) begin
        weight_reg <= weight;
      end

      if (valid) begin // means compute!
        acc_out <= acc_in + (a_in * weight_reg);
        a_out <= a_in;
      end

    end
  end
endmodule

  output reg [7:0] a_out,    // Output A to right neighbor
  output reg [7:0] acc_out   // Accumulated value to the PE below
);


`default_nettype none
`timescale 1ns/1ns

module tpu (
  // INPUTS
  input wire clk,
  input wire reset,
  input wire [7:0] ui_in,   // TODO: perhaps create an assign statement to COMBINE both ui_in and uio_in so i can have a 16 bit bus???
  input wire [7:0] uio_in,
   // OUTPUTS
  output wire [7:0] uo_out // this is uo_out (should rename to uo_out)
);

  // Internal signals which connect dma controller flags to memory devices (data select flags)
  wire start;
  wire fetch_w;
  wire fetch_inp;
  wire fetch_ins;
  wire [3:0] dma_address;

  // "Zero-buffered" staggered "x" value matrix data transfer from input setup into mmu
  wire [7:0] a_in1;
  wire [7:0] a_in2;

  // Internal signals for control unit
  wire [4:0] base_address; // this is the address decoded from the ISA (ran from program mem)
  wire load_weight;
  wire load_input;
  wire valid;
  wire store;
  wire ext;

  // Internal signals for accumulated values from the systolic array
  wire [7:0] systolic_acc_out1;
  wire [7:0] systolic_acc_out2;

  // Flags from each accumulator which go high when they're full
  wire acc1_full;
  wire acc2_full;

  // Internal signals for weights from memory
  wire [7:0] weight1;
  wire [7:0] weight2;
  wire [7:0] weight3;
  wire [7:0] weight4;

  // Internal signals for output matrix individual row vectors to unified buffer
  wire [7:0] acc1_mem_0_to_ub;
  wire [7:0] acc1_mem_1_to_ub;
  wire [7:0] acc2_mem_0_to_ub;
  wire [7:0] acc2_mem_1_to_ub;

  // Internal signals for activation inputs from unified buffer
  wire [7:0] out_ub_to_input_setup_00;
  wire [7:0] out_ub_to_input_setup_01;
  wire [7:0] out_ub_to_input_setup_10;
  wire [7:0] out_ub_to_input_setup_11;

  // Instantiate direct memory controller
  dma dma (
    // .ui_in(ui_in)  delete this after -> keep it here for testing
    // INPUTS
    .clk(clk),
    .reset(reset),
    .uio_in(uio_in),
    // OUTPUTS
    .fetch_w(fetch_w),
    .fetch_inp(fetch_inp),
    .fetch_ins(fetch_ins),
    .start(start),
    .dma_address(dma_address)
  );

  // Instantiate the control unit
  control_unit cu (
    .fetch_ins(fetch_ins),
    .ui_in(ui_in),
    .dma_address(dma_address),

    .start(start),
    .clk(clk),
    .reset(reset),
    .load_weight(load_weight),
    .base_address(base_address),
    .load_input(load_input),
    .valid(valid),
    .store(store),
    .ext(ext)
  );

  // Instantiate the weight memory
  weight_memory wm (
    .fetch_w(fetch_w),
    .ui_in(ui_in),
    .dma_address(dma_address),

    .clk(clk),
    .reset(reset),
    .load_weight(load_weight),
    .addr(base_address),
    .weight1(weight1),
    .weight2(weight2),
    .weight3(weight3),
    .weight4(weight4)
  );

  input_setup is (
    .clk(clk),
    .reset(reset),
    .valid(valid),

    .a11(out_ub_to_input_setup_00),
    .a12(out_ub_to_input_setup_01),
    .a21(out_ub_to_input_setup_10),
    .a22(out_ub_to_input_setup_11),

    .a_in1(a_in1),
    .a_in2(a_in2)
  );

  // Instantiate the systolic array
  mmu systolic_array_inst (
    .clk(clk),
    .reset(reset),
    .load_weight(load_weight),
    .valid(valid),
    .a_in1(a_in1),
    .a_in2(a_in2),
    .weight1(weight1),
    .weight2(weight2),
    .weight3(weight3),
    .weight4(weight4),
    .acc_out1(systolic_acc_out1),
    .acc_out2(systolic_acc_out2)
  );

  // Instantiate the first accumulator
  accumulator acc1 (
    .clk(clk),
    .reset(reset),
    .valid(valid),
    .acc_in(systolic_acc_out1),
    .acc_mem_0(acc1_mem_0_to_ub),
    .acc_mem_1(acc1_mem_1_to_ub),
    .full(acc1_full)
  );

  // Instantiate the second accumulator
  accumulator acc2 (
    .clk(clk),

    .valid(valid),
    .acc_in(systolic_acc_out2),
    .acc_mem_0(acc2_mem_0_to_ub),
    .acc_mem_1(acc2_mem_1_to_ub),
    .full(acc2_full)
  );

  // Instantiate the unified buffer
  unified_buffer ub (
    // INPUTS
    .ext(ext), // flag for dispatching data out of chip
    .store(store),
    .fetch_inp(fetch_inp),
    .ui_in(ui_in),
    .clk(clk),
    .reset(reset),
    .load_input(load_input),
    .full_acc1(acc1_full), // Only store when accumulator is full
    .full_acc2(acc2_full), // Only store when accumulator is full
    .acc1_mem_0(acc1_mem_0_to_ub),
    .acc1_mem_1(acc1_mem_1_to_ub),
    .acc2_mem_0(acc2_mem_0_to_ub),
    .acc2_mem_1(acc2_mem_1_to_ub),
    .addr(base_address),
    .dma_address(dma_address),
    // OUTPUTS
    .out_ub_00(out_ub_to_input_setup_00),
    .out_ub_01(out_ub_to_input_setup_01),
    .out_ub_10(out_ub_to_input_setup_10),
    .out_ub_11(out_ub_to_input_setup_11),
    .final_out(uo_out) // bus of output data wires
  );

endmodule


`default_nettype none
`timescale 1ns/1ns

// This module is literally just a wrapper to ensure i meet tinytapeout specifications.

module tt_um_tpu (
    input  wire [7:0] ui_in,    // Dedicated inputs (make this data input)
    output wire [7:0] uo_out,   // Dedicated outputs

    // 8 physical pins
    input  wire [7:0] uio_in,   // IOs: Input path (make this flag input)
    output wire [7:0] uio_out,  // IOs: Output path

    output wire [7:0] uio_oe,   // IOs: Enable path (active high: 0=input, 1=output)
    input  wire       ena,      // always 1 when the design is powered, so you can ignore it
    input  wire       clk,      // clock

);
    // wire reset = ~rst_n;
    assign uio_oe  = 8'b0000_0000;

    tpu tpu (
    // INPUTS
    .clk(clk),
    .reset(reset),
    .ui_in(ui_in),
    .uio_in(uio_in),
     // OUTPUTS
    .uo_out(uo_out)
    );

endmodule

input  wire       reset     // rst_n - low to reset --> // TODO: rename back to rst_n!


`default_nettype none
`timescale 1ns/1ns

module unified_buffer (
  input wire fetch_inp,
  input wire [7:0] ui_in,
  input wire [3:0] dma_address,

  input wire clk,
  input wire reset,

  input wire full_acc1, // full flag from accumulator 1
  input wire full_acc2, // full flag from accumulator 2

  input wire [4:0] addr, // address to input to
  input wire load_input, // flag for loading input from own memory to input_setup buffer

  // triggered on write operation
  output reg [7:0] out_ub_00,
  output reg [7:0] out_ub_01,
  output reg [7:0] out_ub_10,
  output reg [7:0] out_ub_11,

  output reg [7:0] final_out
);

  typedef enum reg [1:0] {IDLE, WRITE_TO_HOST} state_t; // this is for taking product matrix out of chip
  state_t state = IDLE;

  parameter MEM_SIZE = 16;
  reg [7:0] unified_mem [0:MEM_SIZE-1];
  reg [4:0] memory_pointer; // Addressable up to 32 bits
  integer i;

  reg [4:0] addr_pointer;
  reg [4:0] end_addr;

  always @(posedge clk or posedge reset) begin
    if (reset) begin
      for (i = 0; i < MEM_SIZE; i = i + 1) begin
        unified_mem[i] <= 8'b0;
      end
      out_ub_00 <= 8'b0;
      out_ub_01 <= 8'b0;
      out_ub_10 <= 8'b0;
      out_ub_11 <= 8'b0;
      final_out <= 8'b0;
      end_addr <= 5'b0;
      addr_pointer <= 5'b0;
    end else begin
      // READ FROM MEMORY (perhaps put this into the FSM?)
      if (load_input) begin
        out_ub_00 <= unified_mem[addr];
        out_ub_01 <= unified_mem[addr + 1];
        out_ub_10 <= unified_mem[addr + 2];
        out_ub_11 <= unified_mem[addr + 3];
      end

      // STORE HOST COMPUTER VALUES TO MEMORY (perhaps put this into the FSM?)
      if (fetch_inp) begin
            unified_mem[dma_address] <= ui_in;
      end

      // STORE PRODUCT MATRIX TO MEMORY (perhaps put this into the FSM?)
      if (store && full_acc1 && full_acc2) begin
        unified_mem[addr] <= acc1_mem_0;
        unified_mem[addr + 1] <= acc1_mem_1;
        unified_mem[addr + 2] <= acc2_mem_0;
        unified_mem[addr + 3] <= acc2_mem_1;
      end

        // State machine for handling product matrix output
        case (state)
          IDLE: begin
            final_out <= 8'b0;
            end_addr <= 5'b0;
            addr_pointer <= 5'b0;
            if (ext) begin // flag for writing product matrix to output
              state <= WRITE_TO_HOST;
              addr_pointer <= addr;
              end_addr <= addr + 4;
            end
          end
          WRITE_TO_HOST: begin
            if (addr_pointer < end_addr) begin
              final_out <= unified_mem[addr_pointer];
              addr_pointer <= addr_pointer + 1;
            end else begin
                state <= IDLE;
            end
          end
        endcase

    end
  end
endmodule

  input wire store, // flag for storing data from accumulators to unified buffer
  input wire ext, // flag for output to host computer

  input wire [7:0] acc1_mem_0,


`default_nettype none
`timescale 1ns/1ns

module weight_memory (
  input wire fetch_w,
  input wire [7:0] ui_in,
  input wire [3:0] dma_address,

  input wire clk,
  input wire reset,
  input wire load_weight,
  input wire [4:0] addr, // 5 bit address but only need 3 of those bits to address 8 cells.
  output reg [7:0] weight1,
  output reg [7:0] weight2,
  output reg [7:0] weight3,
  output reg [7:0] weight4
);
  reg [7:0] memory [0:7]; // Simple memory to store weights (only 8 addresses)
  integer i;

  always @(posedge clk or posedge reset) begin
    if (reset) begin
      for (i = 0; i < 8; i++) begin
        memory[i] <= 8'b0;
      end
      weight1 <= 8'b0;
      weight2 <= 8'b0;
      weight3 <= 8'b0;

      weight2 <= memory[addr + 1];
      weight3 <= memory[addr + 2];
      weight4 <= memory[addr + 3];

    end
  end
endmodule

      weight4 <= 8'b0;

    end else if (fetch_w) begin // READ data into weight memory
      memory[dma_address] <= ui_in;


module dump();
    initial begin
        $dumpfile ("acc.vcd");
        $dumpvars (0, accumulator);


module dump();
    initial begin
        $dumpfile ("dma.vcd");
        $dumpvars (0, dma);

        $dumpvars(0, dma.test_storage[0]);
        $dumpvars(0, dma.test_storage[1]);

        $dumpvars(0, dma.test_storage[2]);
        $dumpvars(0, dma.test_storage[3]);

        $dumpvars(0, dma.test_storage[4]);


module dump();
    initial begin
        $dumpfile ("is.vcd");
        $dumpvars (0, input_setup);


module dump();
    initial begin
        $dumpfile ("mmu.vcd");


module dump();
    initial begin
        $dumpfile ("tpu.vcd");

        $dumpvars (0, tpu);
        #1;
    end
endmodule


    initial begin
        $dumpfile ("tt_um_tpu.vcd");
        $dumpvars (0, tt_um_tpu);
        #1;
    end


module dump();
    initial begin
        $dumpfile ("wm.vcd");
        $dumpvars (0, weight_memory);

        $dumpvars (0, weight_memory.memory[0]);
        $dumpvars (0, weight_memory.memory[1]);
        $dumpvars (0, weight_memory.memory[2]);

        // these are just to ensure that the memory doesnt go here
        $dumpvars (0, weight_memory.memory[4]);
        $dumpvars (0, weight_memory.memory[5]);
        #1;
    end
endmodule


//==============================================================================
// USB 2.0 FS Device controller core
//
// References:
//   - Universal Serial Bus Specification Revision 2.0
//   - UTMI specification, version 1.05.
//   - USB 101: An Introduction to Universal Serial Bus 2.0 (AN57294)
//   - USB in a Nutshell
//   - USB Made Simple
//
//------------------------------------------------------------------------------
// [usb20dev] 2018 Eden Synrez <esynr3z@gmail.com>
//==============================================================================

import usb_pkg::*;

module usb (
    input  logic clk_48m,   // Clock 48 MHz
    input  logic rst,       // Asynchronous reset

    usb_fe_if.ctrl fe_ctrl  // USB frontend control
);

usb_sie_if sie_bus();

usb_sie sie(
    .clk     (clk_48m),     //  i: Clock
    .rst     (rst),         //  i: Asynchronous reset
    .fe_ctrl (fe_ctrl),     // if: USB frontend control
    .sie_bus (sie_bus.sie)  // if: SIE Data Bus
);


    else if (busy)
        crc <= crc_next;
    else if (clear)
        crc <= '1;
end

endmodule : usb_crc16

always_ff @(posedge clk or posedge rst)
begin


//==============================================================================
// USB CRC5 calculation and checking. Parallel load with serializer inside.
//
// Polynomial:
//   G(X) = X^5 + X^2 + 1
// Bit representation:
//   00101
//
//------------------------------------------------------------------------------
// [usb20dev] 2018 Eden Synrez <esynr3z@gmail.com>
//==============================================================================

import usb_pkg::*;

module usb_crc5(
    input  logic       clk,    // Clock
    input  logic       rst,    // Asynchronous reset

    input  bus8_t      data,   // Input data
    input  logic       wr,     // Data write strobe
    input  logic       clear,  // Init CRC with default value
    output logic       busy,   // CRC is being calculated
    output logic [4:0] crc     // CRC data
);

    if (rst)
        busy  <= 1'b0;
    else if (wr)
        busy  <= 1'b1;
    else if (dbit_cnt == '1)
        busy  <= 1'b0;
end

always_ff @(posedge clk or posedge rst)
begin
    if (rst)
        dbyte <= 'h0;
    else if (wr)
        dbyte <= data;
    else if (busy)
        dbyte <= {1'b0, dbyte[7:1]};
end

always_ff @(posedge clk or posedge rst)
begin
    if (rst)
        dbit_cnt <= '0;
    else if (busy)
        dbit_cnt <= dbit_cnt + 1;
    else
        dbit_cnt <= '0;
end

assign dbit = dbyte[0];

//-----------------------------------------------------------------------------
// CRC5 calculation and checking
//-----------------------------------------------------------------------------
logic       crc_in;
logic [4:0] crc_next;
logic       crc_en;

assign crc_en   = busy;
assign crc_in   = dbit^crc[4];
assign crc_next = {crc[3], crc[2], crc[1]^crc_in, crc[0], crc_in};

always_ff @(posedge clk or posedge rst)
begin
    if (rst)
        crc <= '1;
    else if (crc_en)
        crc <= crc_next;
    else if (clear)
        crc <= '1;
end

endmodule : usb_crc5


//==============================================================================
// USB Serial Interface Engine with 8 bit interface
//
// Main functions:
//   - Bit stuffing / unstuffing
//   - NRZI encoding / decoding
//   - SYNC and EOP handling
//   - Serial-Parallel / Parallel-Serial Conversion
//
// Based on:
//   - UTMI specification, version 1.05.
//   - Universal Serial Bus Specification Revision 2.0, Ch. 7
//
//------------------------------------------------------------------------------
// [usb20dev] 2018 Eden Synrez <esynr3z@gmail.com>
//==============================================================================

import usb_pkg::*;

module usb_sie (
    input  logic    clk,        // Clock
    input  logic    rst,        // Asynchronous reset

    usb_fe_if.ctrl  fe_ctrl,    // USB frontend control

    usb_sie_if.sie  sie_bus     // SIE Data Bus
);

//-----------------------------------------------------------------------------
// Receive side
//-----------------------------------------------------------------------------
usb_sie_rx rx (
    .clk        (clk),                  //  i: Clock
    .rst        (rst),                  //  i: Asynchronous reset

    .dn_rx      (fe_ctrl.dn_rx),        //  i: USB Data- input
    .dp_rx      (fe_ctrl.dp_rx),        //  i: USB Data+ input

    .tx_active  (sie_bus.tx_active),    //  i: Transmit state machine is active
    .rx_data    (sie_bus.rx_data),      //  o: Received USB data
    .rx_valid   (sie_bus.rx_valid),     //  o: rx_data bus has valid data
    .rx_active  (sie_bus.rx_active),    //  o: Receive state machine is active (from detecting SYNC to detecting EOP)
    .rx_error   (sie_bus.rx_error),     //  o: Receive error detection (bitstuff error)

//-----------------------------------------------------------------------------
// Transmit side
//-----------------------------------------------------------------------------
usb_sie_tx tx (
    .clk        (clk),                  //  i: Clock
    .rst        (rst),                  //  i: Asynchronous reset

    .dp_tx      (fe_ctrl.dp_tx),        //  o: USB Data+ output
    .dn_tx      (fe_ctrl.dn_tx),        //  o: USB Data- output
    .tx_oen     (fe_ctrl.tx_oen),       //  o: USB Data output enable

    .tx_data    (sie_bus.tx_data),      //  i: USB data to transmit
    .tx_valid   (sie_bus.tx_valid),     //  i: Data on tx_data bus is valid
    .tx_ready   (sie_bus.tx_ready),     //  o: SIE ready to load transmit data into holding registers
    .tx_active  (sie_bus.tx_active)     //  o: Transmit state machine is active (from SYNC sending start to EOP sending end)
);

// FIXME: fix pullup control
assign fe_ctrl.pu = 1'b1;

endmodule : usb_sie

    .bus_reset  (sie_bus.reset)         //  o: Bus reset active
);


);

// Protocol Engine side
modport pe (
    input  reset,

    output tx_data,
    output tx_valid,
    input  tx_ready,
    input  tx_active,

    input  rx_data,
    input  rx_valid,
    input  rx_active,
    input  rx_error
);

endinterface : usb_sie_if


//==============================================================================
// SIE receive side:
//   - data recovery from line states
//   - NRZI decoder
//   - bit unstuffer
//   - receive FSM
//
//------------------------------------------------------------------------------
// [usb20dev] 2018 Eden Synrez <esynr3z@gmail.com>
//==============================================================================

import usb_pkg::*;

module usb_sie_rx (
    input  logic    clk,       // Clock
    input  logic    rst,       // Asynchronous reset

    // Frontend rx
    input  logic    dn_rx,     // USB Data- input
    input  logic    dp_rx,     // USB Data+ input

    // SIE rx
    input  logic    tx_active, // Transmit state machine is active
    output bus8_t   rx_data,   // USB data output bus
    output logic    rx_valid,  // rx_data bus has valid data
    output logic    rx_active, // Receive state machine is active
    output logic    rx_error,  // Receive error detection
    output logic    bus_reset  // Bus reset active
);

localparam LINE_STATE_HIST_LEN = 3;

//-----------------------------------------------------------------------------
// Line state recovery
//-----------------------------------------------------------------------------
// Double-flop synchronization for input data lines
logic [3:0]       line_pair_ff;
usb_line_state_t line_pair;
usb_line_state_t line_state_curr;
logic             line_trans;
logic             line_idle;
logic             detect_eop;

always_ff @(posedge clk)
begin
    if (rst)
        line_pair_ff <= {USB_LS_J, USB_LS_J};
    else
        line_pair_ff <= {line_pair_ff[1:0], dn_rx, dp_rx};
end

assign line_pair = usb_line_state_t'(line_pair_ff[3:2]);

// We dont use true diff pair, so we have to handle data transition moments
// and change line state one clock after transition.
// It is supposed, that transition period is shorter than sample clock period.

always_ff @(posedge clk or posedge rst)
begin
    if (rst)
        line_trans <= 1'b0;
    else if ((line_state_curr != line_pair) && !line_trans)
        line_trans <= 1'b1;
    else
        line_trans <= 1'b0;
end

always_ff @(posedge clk or posedge rst)
begin
    if (rst)
        line_state_curr <= USB_LS_J;
    else if (line_trans)
        line_state_curr <= line_pair;
end

// Generate valid signal in the middle of each bit
logic [1:0] line_phase_cnt;
logic       line_state_valid;

always_ff @(posedge clk or posedge rst)
begin
    if (rst)
        line_phase_cnt <= '0;
    else if (line_trans)
        line_phase_cnt <= '0;
    else
        line_phase_cnt <= line_phase_cnt + 'b1;
end

assign line_state_valid = (line_phase_cnt == 'b1) ? 1'b1 : 1'b0;

// Push line states to the history buffer for NRZI decoding and EOP detection
usb_line_state_t [LINE_STATE_HIST_LEN-1:0] line_state_hist;

always_ff @(posedge clk or posedge rst)
begin
    for (int i = 0; i < LINE_STATE_HIST_LEN; i++) begin
        if (rst)
            line_state_hist[i] <= USB_LS_J;
        else if (line_state_valid && (i == 0)) begin
            line_state_hist[i] <= line_state_curr;
        end else if (line_state_valid) begin
            line_state_hist[i] <= line_state_hist[i-1];
        end
    end
end

assign detect_eop  = (line_state_hist == USB_EOP_PATTERN);

//-----------------------------------------------------------------------------
// NRZI decoder
//-----------------------------------------------------------------------------
logic dec_nrzi_bit;
logic dec_nrzi_valid;

always_comb
begin
    if ((line_state_hist[1] == USB_LS_J) &&
        (line_state_hist[0] == USB_LS_J)) begin
        dec_nrzi_bit   = 1'b1;
        dec_nrzi_valid = line_state_valid;
    end else if ((line_state_hist[1] == USB_LS_J) &&
                 (line_state_hist[0] == USB_LS_K)) begin
        dec_nrzi_bit   = 1'b0;
        dec_nrzi_valid = line_state_valid;
    end else if ((line_state_hist[1] == USB_LS_K) &&
                 (line_state_hist[0] == USB_LS_J)) begin
        dec_nrzi_bit   = 1'b0;
        dec_nrzi_valid = line_state_valid;
    end else if ((line_state_hist[1] == USB_LS_K) &&
                 (line_state_hist[0] == USB_LS_K)) begin
        dec_nrzi_bit   = 1'b1;
        dec_nrzi_valid = line_state_valid;
    end else begin
        dec_nrzi_bit   = 1'b0;
        dec_nrzi_valid = 1'b0;
    end
end

//-----------------------------------------------------------------------------
// Bit unstuffer
//-----------------------------------------------------------------------------
logic [USB_STUFF_BITS_N-1:0] unstuff_shift;
logic                        unstuff_event;
logic                        unstuff_error;

always_ff @(posedge clk or posedge rst)
begin
    if (rst)
        unstuff_shift <= '0;
    else if (dec_nrzi_valid)
        unstuff_shift <= {unstuff_shift[USB_STUFF_BITS_N-2:0], dec_nrzi_bit};
end

assign unstuff_event = (unstuff_shift == '1);
assign unstuff_error = unstuff_event && (dec_nrzi_bit != 1'b0);

//-----------------------------------------------------------------------------
// Data bitstream control
//-----------------------------------------------------------------------------
logic  data_bit;
logic  data_bit_valid;
bus8_t data_shift;
logic  detect_sync;

assign data_bit = dec_nrzi_bit;
assign data_bit_valid = dec_nrzi_valid && (!unstuff_event || line_idle) && (!tx_active);

always_ff @(posedge clk or posedge rst)
begin
    if (rst)
        data_shift <= '1;
    else if (detect_eop)
        data_shift <= '1;
    else if (data_bit_valid)
        data_shift <= {data_bit, data_shift[7:1]};
end

// SYNC and Idle detection
assign detect_sync = (data_shift == USB_SYNC_VAL);

always_ff @(posedge clk or posedge rst)
begin
    if (rst)
        line_idle <= 1'b1;
    else if (detect_sync && line_idle)
        line_idle <= 1'b0;
    else if (detect_eop)
        line_idle <= 1'b1;
end

//-----------------------------------------------------------------------------
// Receive state machine
//-----------------------------------------------------------------------------
enum logic [2:0] {
    RX_WAIT_S,
    STRIP_SYNC_S,
    RX_DATA_S,
    STRIP_EOP_S,
    ERROR_S,
    ABORT_S,
    TERMINATE_S,
    XXX_S = 'x
} fsm_state, fsm_next;

always_ff @(posedge clk or posedge rst)
begin
    if (rst)
        fsm_state <= RX_WAIT_S;
    else
        fsm_state <= fsm_next;
end

always_comb
begin
    fsm_next = XXX_S;
    case (fsm_state)
        RX_WAIT_S : begin
            if (detect_sync && (!tx_active))
                fsm_next = STRIP_SYNC_S;
            else
                fsm_next = RX_WAIT_S;
        end

        STRIP_SYNC_S : begin
            fsm_next = RX_DATA_S;
        end

        RX_DATA_S : begin
            if (unstuff_error)
                fsm_next = ERROR_S;
            else if (detect_eop)
                fsm_next = STRIP_EOP_S;
            else
                fsm_next = RX_DATA_S;
        end

        STRIP_EOP_S : begin
            fsm_next = RX_WAIT_S;
        end

        ERROR_S : begin
            fsm_next = ABORT_S;
        end

        ABORT_S : begin
            if (line_idle)
                fsm_next = TERMINATE_S;
            else
                fsm_next = ABORT_S;
        end

        TERMINATE_S : begin
            fsm_next = RX_WAIT_S;
        end
    endcase
end

logic  [3:0] data_bit_cnt;
bus8_t       data_hold;
logic        data_valid;
logic        data_active;
logic        data_error;

always_ff @(posedge clk or posedge rst)
begin
    if (rst) begin
        data_bit_cnt <= '0;
        data_hold    <= '0;
        data_valid   <= 1'b0;
        data_active  <= 1'b0;
        data_error   <= 1'b0;
    end else begin
        case (fsm_state)
            RX_WAIT_S : begin
                data_bit_cnt <= '0;
            end

            STRIP_SYNC_S : begin
                data_active <= 1'b1;
            end

            RX_DATA_S : begin
                // count shifted data bits
                if (data_bit_valid) begin
                    if (data_bit_cnt == 'd8)
                        data_bit_cnt <= 'd1;
                    else
                        data_bit_cnt <= data_bit_cnt + 1;
                end
                // hold data when byte accumulated
                if (data_bit_cnt == 'd8) begin
                    data_hold <= data_shift;
                    data_valid <= 1'b1;
                end else
                    data_valid <= 1'b0;
            end

            ERROR_S : begin
                data_error  = 1'b1;
            end

            ABORT_S : begin
                data_valid  = 1'b0;
                data_error  = 1'b0;
            end

            TERMINATE_S : begin
                data_active = 1'b0;
            end
        endcase
    end
end

// Data valid should be a pulse for FS
logic data_valid_ff;
logic data_valid_pulse;

always_ff @(posedge clk or posedge rst)
begin
    if (rst) begin
        data_valid_ff <= 1'b0;
    end else begin
        data_valid_ff <= data_valid;
    end
end

assign data_valid_pulse = data_valid && (!data_valid_ff);

//-----------------------------------------------------------------------------
// Bus reset detection
//-----------------------------------------------------------------------------
logic line_reset;
bus8_t line_reset_cnt;

always_ff @(posedge clk or posedge rst)
begin
    if (rst)
        line_reset_cnt <= '0;
    else if (!line_reset && (line_state_hist[0] == USB_LS_SE0))
        line_reset_cnt <= line_reset_cnt + 'b1;
    else
        line_reset_cnt <= '0;
end

always_ff @(posedge clk or posedge rst)
begin
    if (rst)
        line_reset <= 1'b0;
    else if (!line_reset && (line_reset_cnt == '1))
        line_reset <= 1'b1;
    else if (line_reset && (line_state_hist[0] != USB_LS_SE0))
        line_reset <= 1'b0;
end

//-----------------------------------------------------------------------------
// Outputs registering stage
//-----------------------------------------------------------------------------
always_ff @(posedge clk or posedge rst)
begin
    if (rst) begin
        rx_data    <= '0;
        rx_valid   <= 1'b0;
        rx_active  <= 1'b0;
        rx_error   <= 1'b0;
        bus_reset  <= 1'b0;
    end else begin
        rx_data    <= data_hold;
        rx_valid   <= data_valid_pulse;
        rx_active  <= data_active;
        rx_error   <= data_error;
        bus_reset  <= line_reset;
    end
end

endmodule : usb_sie_rx

            STRIP_EOP_S : begin
                data_valid  = 'b0;


//==============================================================================
// SIE transmit side:
//   - NRZI encoder
//   - bit stuffer
//   - data to line states convert
//   - transmit FSM
//
//------------------------------------------------------------------------------
// [usb20dev] 2018 Eden Synrez <esynr3z@gmail.com>
//==============================================================================

import usb_pkg::*;

module usb_sie_tx (
    input  logic    clk,        // Clock
    input  logic    rst,        // Asynchronous reset

    // Frontend tx
    output logic    dp_tx,      // USB Data+ output
    output logic    dn_tx,      // USB Data- output
    output logic    tx_oen,     // USB Data output enable

    // SIE tx
    input  bus8_t   tx_data,    // USB data input bus
    input  logic    tx_valid,   // Transmit data on tx_data bus is valid
    output logic    tx_ready,   // UTM ready to load transmit data into holding registers
    output logic    tx_active   // Transmit state machine is active (from SYNC sending start to EOP sending end)
);

//-----------------------------------------------------------------------------
// Transmit state machine
//-----------------------------------------------------------------------------
logic data_hold_full;
logic data_oen;
logic data_se0;

enum logic [2:0] {
    TX_WAIT_S,
    SEND_SYNC_S,
    TX_DATA_LOAD_S,

always_ff @(posedge clk or posedge rst)
begin
    if (rst)
        fsm_state <= TX_WAIT_S;
    else
        fsm_state <= fsm_next;
end

always_comb
begin
    fsm_next = XXX_S;
    case (fsm_state)
        TX_WAIT_S : begin
            if (tx_valid)
                fsm_next = SEND_SYNC_S;
            else
                fsm_next = TX_WAIT_S;
        end

        SEND_SYNC_S : begin
            fsm_next = TX_DATA_LOAD_S;
        end

        TX_DATA_LOAD_S : begin
            if ((!tx_valid) && (!data_oen))
                fsm_next = TX_WAIT_S;
            else
                fsm_next = TX_DATA_WAIT_S;
        end

        TX_DATA_WAIT_S : begin
            if (!data_hold_full)
                fsm_next = TX_DATA_LOAD_S;
            else
                fsm_next = TX_DATA_WAIT_S;
        end
    endcase
end

logic        data_shift_last;
logic  [3:0] data_bit_cnt;
bus8_t       data_hold;
bus8_t       data_shift;
logic        data_bit_strobe;
logic        stuff_event;

always_ff @(posedge clk or posedge rst)
begin
    if (rst) begin
        data_bit_cnt    <= '0;
        data_hold       <= '0;
        data_shift      <= '0;
        data_oen        <= 1'b0;
        data_shift_last <= 1'b0;
        data_hold_full  <= 1'b0;
        tx_ready        <= 1'b0;
        data_se0        <= 1'b0;
    end else begin
        case (fsm_state)
            TX_WAIT_S : begin
                data_bit_cnt    <= 'd0;
                data_shift_last <= 1'b0;
            end

            SEND_SYNC_S : begin
                data_shift <= USB_SYNC_VAL;
            end

            TX_DATA_LOAD_S : begin
                if (tx_valid) begin
                    data_hold      <= tx_data;
                    data_hold_full <= 1'b1;
                    tx_ready       <= 1'b1;
                end else if (!tx_valid && data_oen) begin
                    data_hold       <= 'd0;
                    data_hold_full  <= 1'b1;
                    tx_ready        <= 1'b0;
                    data_shift_last <= 1'b1;
                end
            end

            TX_DATA_WAIT_S : begin
                tx_ready <= 1'b0;

                // data shifting on every strobe
                if (data_bit_strobe) begin
                    data_shift   <= data_shift >> 1;
                    data_bit_cnt <= data_bit_cnt + 1;
                    if (data_se0 && (data_bit_cnt == 'd12))
                        data_hold_full <= 1'b0;
                end else if (data_bit_cnt == 'd8) begin
                    data_shift     <= data_hold;
                    data_hold_full <= 1'b0;
                    if (!data_shift_last)
                        data_bit_cnt   <= 'd0;
                end

                // data output enable should be active till last se0 of eop have been transmitted
                if (data_bit_strobe)
                    data_oen <= (data_se0 && (data_bit_cnt == 'd12)) ? 1'b0 : 1'b1;

                // signalling that last bit of last byte transmitted and next will be se0 of eop
                if ((data_bit_cnt == 'd10) && data_shift_last && (!stuff_event)) begin
                    data_se0 <= 1'b1;
                end else if (data_bit_strobe && data_se0 && (data_bit_cnt == 'd12)) begin
                    data_se0 <= 1'b0;
                end
            end
        endcase
    end
end

logic       data_bit;
logic [1:0] data_bit_phase_cnt;
logic       data_bit_valid;

always_ff @(posedge clk or posedge rst)
begin
    if (rst)
        data_bit_phase_cnt <= '1;
    else if (!data_oen)
        data_bit_phase_cnt <= '1;
    else
        data_bit_phase_cnt <= data_bit_phase_cnt + 'b1;
end

assign data_bit_valid = (data_bit_phase_cnt == '1);

always_ff @(posedge clk or posedge rst)
begin
    if (rst)
        data_bit <= 1'b0;
    else if (!data_oen)
        data_bit <= 1'b0;
    else if (data_bit_strobe)
        data_bit <= data_shift[0];
end

assign data_bit_strobe = data_bit_valid && (!stuff_event);

//-----------------------------------------------------------------------------
// Bit stuffer
//-----------------------------------------------------------------------------
logic [USB_STUFF_BITS_N-1:0] stuff_shift;
logic                        stuff_bit;
logic                        stuff_bit_valid;

always_ff @(posedge clk or posedge rst)
begin
    if (rst)
        stuff_shift <= '0;
    else if (data_bit_valid)
        stuff_shift <= {stuff_shift[USB_STUFF_BITS_N-2:0], stuff_event? 1'b0 : data_bit};
end

assign stuff_event     = (stuff_shift == '1);

always_ff @(posedge clk or posedge rst)
begin
    if (rst) begin
        stuff_bit       <= 1'b0;
        stuff_bit_valid <= 1'b0;
    end else if (!data_oen) begin
        stuff_bit       <= 1'b0;
        stuff_bit_valid <= 1'b0;
    end else begin
        stuff_bit       <= stuff_event? 1'b0 : data_bit;
        stuff_bit_valid <= data_bit_valid;
    end
end

//-----------------------------------------------------------------------------
// NRZI encoder
//-----------------------------------------------------------------------------
logic enc_nrzi_bit;

always_ff @(posedge clk or posedge rst)
begin
    if (rst)
        enc_nrzi_bit <= 1'b1;
    else if (!data_oen)
        enc_nrzi_bit <= 1'b1;
    else if (stuff_bit_valid && (stuff_bit == 1'b0))
        enc_nrzi_bit <= ~enc_nrzi_bit;
end

//-----------------------------------------------------------------------------
// Data to line states converter
//-----------------------------------------------------------------------------
always_ff @(posedge clk or posedge rst)
begin
    if (rst)
        {dn_tx, dp_tx} <= USB_LS_J;
    else if (!data_oen)
        {dn_tx, dp_tx} <= USB_LS_J;
    else if (stuff_bit_valid && data_se0)
        {dn_tx, dp_tx} <= USB_LS_SE0;
    else if (stuff_bit_valid)
        {dn_tx, dp_tx} <= {!enc_nrzi_bit, enc_nrzi_bit};
end

// Output enable expander - to drive J one bit time after EOP transmitted
logic [3:0] data_oen_ff;

always_ff @(posedge clk or posedge rst)
begin
    if (rst)
        data_oen_ff <= '0;
    else
        data_oen_ff <= {data_oen_ff[2:0], data_oen};
end

always_ff @(posedge clk or posedge rst)
begin
    if (rst)
        tx_oen <= 1'b0;
    else
        tx_oen <= |data_oen_ff;
end

assign tx_active = tx_oen;

endmodule : usb_sie_tx


localparam USB_RAW_BITS  = USB_RAW_BYTES*8;

//-----------------------------------------------------------------------------
// Connections
//-----------------------------------------------------------------------------
logic dp_tx, dn_tx;
wire  dp_rx, dn_rx;

pullup  (phy.dp);
pulldown(phy.dn);

assign phy.dp = dp_tx;
assign phy.dn = dn_tx;
assign dp_rx = phy.dp;
assign dn_rx = phy.dn;

initial
begin
    send_raw_nondrive();
end

//-----------------------------------------------------------------------------
// Raw line control tasks
//-----------------------------------------------------------------------------
task wait_interpacket_delay;
begin
    #`USB_PERIOD_DEL;
    #`USB_PERIOD_DEL;
    #`USB_PERIOD_DEL;
    #`USB_PERIOD_DEL;
    #`USB_PERIOD_DEL;
    #`USB_PERIOD_DEL;
end
endtask : wait_interpacket_delay

task send_raw_bit(
    input logic dp,
    input logic dn
);
bit jit_sel;
begin
    jit_sel = $urandom_range(0,1);

    if (jit_sel) begin
        dp_tx <= dp;
        #`USB_PHASE_DEL dn_tx <= dn;
    end else begin
        dn_tx <= dn;
        #`USB_PHASE_DEL dp_tx <= dp;
    end

    #`USB_PERIOD_DEL;
end
endtask : send_raw_bit

task send_raw_nondrive;
begin
    send_raw_bit(1'bz, 1'bz);
end
endtask : send_raw_nondrive

task send_raw_k;
begin
      send_raw_bit(0, 1);
end
endtask : send_raw_k

task send_raw_j;
begin
      send_raw_bit(1, 0);
end
endtask : send_raw_j

task send_raw_se0;
begin
      send_raw_bit(0, 0);
end
endtask : send_raw_se0

task send_raw_packet(
    input logic [USB_RAW_BITS-1:0] data,
    input int len
);
bit enc_nrzi_bit;
int stuff_bit_cnt;
begin
    //Sync
    send_raw_k();
    send_raw_j();
    send_raw_k();
    send_raw_j();
    send_raw_k();
    send_raw_j();
    send_raw_k();
    send_raw_k();

    enc_nrzi_bit = 0;
    stuff_bit_cnt = 1;

    for (int i = 0; i < len*8; i++) begin
        // NRZI encoding
        if (!data[i])
            enc_nrzi_bit = !enc_nrzi_bit;
        send_raw_bit(enc_nrzi_bit, !enc_nrzi_bit);

        // Bit stuffing
        if (data[i])
            stuff_bit_cnt++;
        else
            stuff_bit_cnt = 0;

        if (stuff_bit_cnt >= USB_STUFF_BITS_N) begin
            stuff_bit_cnt = 0;
            enc_nrzi_bit = !enc_nrzi_bit;
            send_raw_bit(enc_nrzi_bit, !enc_nrzi_bit);
        end
    end

    // EOP
    send_raw_se0();
    send_raw_se0();
    send_raw_j();
    send_raw_nondrive();
    wait_interpacket_delay();
end
endtask : send_raw_packet

task receive_raw_packet (
    output logic [USB_RAW_BITS-1:0] data,
    output int len
);
usb_line_state_t [7:0]   line_state_hist;
int                      unstuff_cnt;
int                      bit_cnt;
logic [USB_RAW_BITS-1:0] bit_data;
begin
    bit_cnt = 0;
    bit_data = '0;
    line_state_hist = {8{USB_LS_J}};

    // wait for sync pattern
    while (line_state_hist != USB_SYNC_PATTERN) begin
        line_state_hist = {line_state_hist[6:0], usb_line_state_t'({dn_rx, dp_rx})};
        #`USB_PERIOD_DEL;
    end

    unstuff_cnt = 1; // SYNC has 1 in the end

    // get data
    while (line_state_hist[2:0] != USB_EOP_PATTERN) begin
        line_state_hist = {line_state_hist[6:0], usb_line_state_t'({dn_rx, dp_rx})};

        if (line_state_hist[2:0] == USB_EOP_PATTERN) begin
            if (line_state_hist[3] == USB_LS_SE0)
                $display("%0d, W: %m: Warning, EOP must have 2 se0 bits!", $time);
            break;
        end
        else if (line_state_hist[0] == USB_LS_SE0) begin
            //continue;
        end else if (unstuff_cnt == USB_STUFF_BITS_N) begin
            if (line_state_hist[0] == line_state_hist[1])
                $display("%0d, W: %m: Warning, should be '0' after 6 '1's!", $time);
            unstuff_cnt = 0;
        end else begin // regular data bit
            bit_cnt = bit_cnt + 1;
            // NRZI decoding and bit stuffing control
            if (line_state_hist[0] == line_state_hist[1]) begin
                bit_data = {1'b1, bit_data[USB_RAW_BITS-1:1]};
                unstuff_cnt = unstuff_cnt + 1;
            end else begin
                bit_data = {1'b0, bit_data[USB_RAW_BITS-1:1]};
                unstuff_cnt = 0;
            end
        end

        #`USB_PERIOD_DEL;
    end

    // shift lsb to the array bottom
    bit_data = bit_data >> (USB_RAW_BITS - bit_cnt);

    data = bit_data;
    len  = bit_cnt/8;
    if (bit_cnt%8 != 0)
        $display("%0d, W: %m: Warning, number of bits is not multiple of 8!", $time);

    wait_interpacket_delay();
end
endtask : receive_raw_packet

task send_reset;
begin
    send_raw_se0();
    #10ms;
    send_raw_j();
    send_raw_nondrive();
end
endtask : send_reset

logic [4:0]  crc5 = '1;
task step_crc5(
    input  [7:0] dbyte,
    output [4:0] crc_o
);
const bit [4:0] crc5_poly = 5'b00101;
begin
    for (int i = 0; i < 8; i++)
    begin
        if (crc5[4] ^ dbyte[i])
            crc5 = (crc5 << 1) ^ crc5_poly;
        else
            crc5 = crc5 << 1;
    end
    crc_o = crc5;
end
endtask : step_crc5

task valid_crc5(
    input [4:0] crc5,
    output      valid
);
const bit [4:0] crc5_res  = USB_CRC5_VALID;
begin
    valid = (crc5_res == crc5);
end
endtask : valid_crc5

logic [15:0]  crc16 = '1;
task step_crc16(
    input  [7:0] dbyte,
    output [15:0] crc_o
);
const bit [15:0] crc16_poly = 16'b1000000000000101;
begin
    for (int i = 0; i < 8; i++)
    begin
        if (crc16[15] ^ dbyte[i])
            crc16 = (crc16 << 1) ^ crc16_poly;
        else
            crc16 = crc16 << 1;
    end
    crc_o = crc16;
end
endtask : step_crc16

task valid_crc16(
    input [15:0] crc16,
    output      valid
);
const bit [15:0] crc16_res = USB_CRC16_VALID;
begin
    valid = (crc16_res == crc16);
end
endtask : valid_crc16

endmodule : usb_host_beh

// USB FS 12.000 Mb/s +-0.25% (+-208ps)
localparam USB_PERIOD = 83333;   // ps
localparam USB_JIT    = 100;     // ps
`define USB_PERIOD_DEL  ((USB_PERIOD + ($urandom_range(0, USB_JIT*2) - USB_JIT))/1000.0)


        while(!sie_bus.tx_ready)
            @(posedge clk);
    end
    @(posedge clk);
    sie_bus.tx_valid = 1'b0;
end
endtask : send_data

task receive_data(
    output logic [SIE_RAW_BYTES-1:0][7:0] data,  // Data bytes
    output int                            len    // Data bytes total
);
begin
    data = 0;
    len  = 0;
    wait(sie_bus.rx_active);
    while(sie_bus.rx_active) begin
        @(posedge clk);
        if (sie_bus.rx_active && sie_bus.rx_valid) begin
            data[len] = sie_bus.rx_data;
            len++;
        end
        if (sie_bus.rx_error)
            $display("%0d, W: %m: Warning, rx_error is active!", $time);
    end
end
endtask : receive_data

task detect_reset;
begin
    while(!sie_bus.reset)
        @(posedge clk);
    $display("%0d, I: %m: Bus reset detected", $time);

    while(sie_bus.reset)
        @(posedge clk);
    $display("%0d, I: %m: Bus reset released", $time);
end
endtask : detect_reset

endmodule : usb_sie_vip

        sie_bus.tx_data  = data[i];
        sie_bus.tx_valid = 1'b1;


    .rst    (~tb_rst_n),
    .data   (dut_data),
    .wr     (dut_wr),
    .clear  (dut_clear),
    .busy   (dut_busy),
    .crc    (dut_crc)
);

usb_host_beh host_beh (
    .phy (usb_fe.phy)
);

//`define STOP_TIME  100ms   // Time when test stops
`define TEST_DESCR "CRC test: compare CRC5 and CRC16 calculation on Host and Device"
`define DATA_TOTAL 16

//-----------------------------------------------------------------------------
// Testbench body
//-----------------------------------------------------------------------------
logic [4:0] crc5_val;
logic [7:0] data_in [`DATA_TOTAL-1:0];
logic [4:0] data_crc5_host [`DATA_TOTAL-1:0];
logic [4:0] data_crc5_dev [`DATA_TOTAL-1:0];

initial
begin : tb_body
    tb_err = 0; // no errors

    //Reset
    wait(tb_rst_n);

    //Test start
    #100ns tb_busy = 1;

    for (int i = 0; i < `DATA_TOTAL; i++) begin
        data_in[i] = $urandom();
    end

    $display("%0d, I: %m: Host calculate CRC5", $time);
    for (int i = 0; i < `DATA_TOTAL; i++) begin
        #10ns host_beh.step_crc5(data_in[i], crc5_val);
        #1ns  data_crc5_host[i] = host_beh.crc5;
    end

    $display("%0d, I: %m: Device calculate CRC5", $time);
    for (int i = 0; i < `DATA_TOTAL; i++) begin
        @(posedge tb_clk);#1ns;
        dut_data = data_in[i];
        dut_wr = 1'b1;
        @(posedge tb_clk);#1ns;
        dut_wr = 1'b0;
        @(posedge tb_clk);
        wait(!dut_busy);
        @(posedge tb_clk);#1ns;
        data_crc5_dev[i] = dut_crc;
    end

    $display("%0d, I: %m: Compare results", $time);
    for (int i = 0; i < `DATA_TOTAL; i++) begin
        if (data_crc5_dev[i] != data_crc5_host[i])
            tb_err++;
    end

    //Test end
    #3us tb_busy = 0;
end

`include "../testbenches/tb_footer.svh"


        for (int i = 0; i < tx_len; i++) begin
            if(tx_data[i] !== rx_data[i]) begin
                $display("%0d, E: %m: Error, packets are not equal!", $time);
                $display("\ttx_data[%0d] = 0x%0x; rx_data[%0d] = 0x%0x", i, tx_data[i], i, rx_data[i]);
                is_data_eq = 0;
            end
        end
    endfunction : is_data_eq

endclass : packet_tester_t

initial
begin : tb_body
    packet_tester_t ptester = new;

    tb_err = 0; // no errors

    //Reset
    wait(tb_rst_n);

    //Test start
    #100ns tb_busy = 1;

    $display("%0d, I: %m: VIP --> SIE --> Host", $time);
    for (int i = 0; i < `RND_CYCLES; i++) begin : crv_tx
        ptester.randomize();
        $display("%0d, I: %m: Cycle %0d, %0d bytes to transmit", $time, i, ptester.tx_len);

        fork
            tb.sie_vip.send_data(ptester.tx_data, ptester.tx_len);
            tb.host_beh.receive_raw_packet(ptester.rx_data, ptester.rx_len);
        join

        if (!ptester.is_len_eq())
            tb_err++;
        else if (!ptester.is_data_eq())
            tb_err++;
    end : crv_tx

    $display("%0d, I: %m: VIP <-- SIE <-- Host", $time);
    for (int i = 0; i < `RND_CYCLES; i++) begin : crv_rx
        ptester.randomize();
        $display("%0d, I: %m: Cycle %0d, %0d bytes to receive", $time, i, ptester.tx_len);

        fork
            tb.host_beh.send_raw_packet(ptester.tx_data, ptester.tx_len);
            tb.sie_vip.receive_data(ptester.rx_data, ptester.rx_len);
        join

        if (!ptester.is_len_eq())
            tb_err++;
        else if (!ptester.is_data_eq())
            tb_err++;
    end : crv_rx

    $display("%0d, I: %m: Host applying Reset", $time);
    fork
        tb.host_beh.send_reset();
        tb.sie_vip.detect_reset();
    join

    //Test end
    #3us tb_busy = 0;
end

`include "../testbenches/tb_footer.svh"


    .clk_48m (tb_clk),
    .rst     (~tb_rst_n),
    .fe_ctrl (usb_fe.ctrl)
);

usb_host_beh host_beh (
    .phy (usb_fe.phy)
);


    $stop;
end

`endif //STOP_TIME

endmodule : tb


logic tb_rst_n = 0;

always
begin
    #(`CLK_PERIOD/2);
    tb_clk <= ~tb_clk;
end

initial
begin
    #(`RST_DELAY_TIME) tb_rst_n <= 1;
end

// To be continued in tb.sv file ...


/*

*/

class rcc_agent extends uvm_agent;
	`uvm_component_utils(rcc_agent)

	uvm_analysis_port #(rcc_transaction) mon2scb;

	rcc_sequencer rcc_seqr;
	rcc_driver rcc_drvr;
	rcc_monitor rcc_mtr;

	function new(string name, uvm_component parent);
		super.new(name, parent);
	endfunction: new

	function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		mon2ref = new("mon2ref", this);
		mon2scb = new("mon2scb", this);

		rcc_seqr = rcc_sequencer::type_id::create(.name("rcc_seqr"), .parent(this));
		rcc_drvr = rcc_driver::type_id::create(.name("rcc_drvr"), .parent(this));
		rcc_mtr = rcc_monitor::type_id::create(.name("rcc_mtr"), .parent(this));

	endfunction: build_phase

	function void connect_phase(uvm_phase phase);
		super.connect_phase(phase);

		// connect the analysis port of monitor to analysis port of agent
		rcc_mtr.mon2ref.connect(mon2ref);
		rcc_mtr.mon2scb.connect(mon2scb);

		// connect driver seq_item_port to sequencer seq_item_export
		rcc_drvr.seq_item_port.connect(rcc_seqr.seq_item_export);

	endfunction: connect_phase

endclass: rcc_agent


/*
* Assertion module for the RCC block
*
*/

module rcc_assertions(rcc_if intf);

  always begin
    //forever begin
      @(posedge intf.clk)
        if(intf.reset == 1)
           assert (intf.dout == 8'hff)
           else
           //$stop;
           $display("ERROR: dout is not correctly initialized to reset value: %h", intf.dout);

        assert(intf.dout_flag == 1'b1)
        else
        $display("%b ERROR: dout_flag is not correctly initialized at reset", intf.dout_flag);

  end

// adding the concurrent assertions

    property din_rcc_clk;
      @(posedge intf.clk)

    end

// need to use non-overlapped operator //
/*    property digclk_doutflag;
      @(posedge intf.clk)
        disable iff (intf.reset) $changed(intf.dout_flag) |=> $rose(intf.digit_clk);
      endproperty

    dout_flag_check: assert property (digclk_doutflag) else begin
                    $error("ERROR-------------- digit clock %t", $time);
    end*/

endmodule //assertion

        disable iff (intf.reset) $rose(intf.rcc_clk) |=> !$isunknown(intf.din);
    endproperty


class rcc_configuration extends uvm_object;
	`uvm_object_utils(rcc_configuration)

	function new(string name = "");

	endfunction: new

endclass: rcc_configuration


/*
* Driver communicates with the DUT at pin level.
* It receives the stimulus from the sequencer in form of transactions
* Within Driver synchroization betwen sequencer and driver takes place via handshaking
*/

class rcc_driver extends uvm_driver#(rcc_transaction);
	`uvm_component_utils(rcc_driver)

	// virtual interface declaration
	virtual rcc_if vif;

    uvm_analysis_port #(rcc_transaction) din_cov;
    uvm_analysis_port #(rcc_transaction) addrs_cov;

    rcc_transaction cov_din_tx;
    rcc_transaction cov_addr_tx;

	function new(string name, uvm_component parent);
		super.new(name, parent);

	endfunction

	function void build_phase(uvm_phase phase);
		super.build_phase(phase);

            din_cov = new("din_cov", this);
            addrs_cov = new("addrs_cov", this);

            cov_din_tx = rcc_transaction::type_id::create("cov_din_tx", this);
            cov_addr_tx = rcc_transaction::type_id::create("cov_addr_tx", this);

	        // get the virtual interface handle that was stored in the uvm_config_db
	        // and assign it to the local vif field

			`uvm_fatal("No Vif", {"virtual interface must be set for: ", get_full_name(), ".vif"});

	endfunction: build_phase

  bit [3:0] g_address, digcnt, qcnt;
  bit [15:0] g_din;

	// using sequence_item_port connect with sequencer
	// get the transaction and then drive the DUT
	task run_phase(uvm_phase phase);

		reset(); // reset task
		forever
			begin

				rcc_transaction rcc_tx;
				seq_item_port.get_next_item(rcc_tx); // get the transaction from sequencer
				digcnt = rcc_tx.digcnt;
                               seq_item_port.item_done();

				repeat(digcnt)
					begin
						repeat(4) // wait for 4 clock edges //
					  		@(posedge vif.clk);

						g_address = 0;

                                           seq_item_port.get_next_item(rcc_tx);
						g_din = rcc_tx.g_din;
					seq_item_port.item_done();

					cov_din_tx.g_din = g_din; // for din coverage
					din_cov.write(cov_din_tx);

						repeat(8) // for 8 registers write the data //
							begin

								write_it;
                                                              g_address = g_address +1;
							//if(rcc_tx.randomize())
							seq_item_port.get_next_item(rcc_tx);
                                                             //  g_din = rcc_tx.g_din;
							seq_item_port.item_done();

							seq_item_port.get_next_item(rcc_tx);
                                                               g_din = rcc_tx.g_din;
							seq_item_port.item_done();
							//$display("input: %h", g_din);

                                		cov_din_tx.g_din = g_din; // for din coverage
                                		din_cov.write(cov_din_tx);

							end
                                		//cov_din_tx.g_din = g_din; // for din coverage
                                		//din_cov.write(cov_din_tx);
						write_it;
						g_address = 0;

						repeat(160)
						  @(posedge vif.clk);
					end //digcnt

					//if(rcc_tx.randomize())
                                   seq_item_port.get_next_item(rcc_tx);
					qcnt = rcc_tx.qcnt;
                                    seq_item_port.item_done();

					repeat(qcnt)
						begin
							repeat(4)
							  @(posedge vif.clk);

							g_address = 0;
							g_din = 0;

                                                       //seq_item_port.get_next_item(rcc_tx);
							//rcc_tx.g_din = g_din;
                                                      // seq_item_port.item_done();

							repeat(8)
								begin
								  write_it;
								  g_address = g_address +1;
								end

							write_it;
							g_address = 0;

							repeat(160)
							  @(posedge vif.clk);

						end //qcnt
					//seq_item_port.item_done(); // transaction complete

					end //forever loop

endtask // run_phase

	task write_it();
	@(posedge vif.clk);
	vif.address <= g_address;
	vif.din <= g_din;

 cov_addr_tx.g_address = g_address; // for addrs coverage
 addrs_cov.write(cov_addr_tx);

	@(posedge vif.clk);
	vif.rcc_clk <= 1;
	@(posedge vif.clk);
	vif.rcc_clk <= 0;
	@(posedge vif.clk);
	vif.din <= 'bz;
        endtask

  //bit [8:0] [15:0] temp;
  // reset task, Resets the interface signals to default/initial values
  task reset();

    vif.test_mode = 0;
    vif.scan_in0 = 0;
    vif.scan_in1 = 0;
    vif.scan_en = 0;

    vif.din = 16'h0000;
    vif.address = 4'h0;

    vif.rcc_clk = 1'b0;
    vif.reset = 1'b0;
// assertion checks
    //vif.dout = 8'haa;
   // vif.dout_flag = 1'b0;

    @(negedge vif.clk);
    @(negedge vif.clk);
    vif.reset = 1;

  $display("----------- RESET START------");
    @(negedge vif.clk);
    @(negedge vif.clk);

    vif.reset = 0;
  $display("------------ RESET ENDS-----------");
  endtask

endclass: rcc_driver

if(!uvm_config_db#(virtual rcc_if)::get(this, "", "vif", vif))


class rcc_environment extends uvm_env;
	`uvm_component_utils(rcc_environment)

	rcc_agent       rcc_agnt;  // DUT out
        ref_pred        rcc_ref_pred; // Ref out
	rcc_scoreboard  rcc_scb;      // compare both outputs
        rcc_in_coverage    rcc_cov;
    rcc_out_coverage    rcc_out_cov;

	function new(string name, uvm_component parent);
		super.new(name, parent);
	endfunction: new

	function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		rcc_agnt = rcc_agent::type_id::create(.name("rcc_agnt"), .parent(this));
		rcc_scb  = rcc_scoreboard::type_id::create(.name("rcc_scb"), .parent(this));
		rcc_ref_pred  = ref_pred::type_id::create(.name("rcc_ref_pred"), .parent(this));

        rcc_out_cov  = rcc_out_coverage::type_id::create(.name("rcc_out_cov"), .parent(this));

	endfunction: build_phase

	// Connect_phase
	function void connect_phase(uvm_phase phase);
		super.connect_phase(phase);

		rcc_agnt.mon2scb.connect(rcc_scb.out_dut);
		rcc_agnt.mon2ref.connect(rcc_ref_pred.analysis_export);
        rcc_ref_pred.ref2scb.connect(rcc_scb.out_ref);

        rcc_agnt.rcc_drvr.din_cov.connect(rcc_cov.cov_din); // coverage analysis connection
        rcc_agnt.rcc_drvr.addrs_cov.connect(rcc_cov.cov_addr); // coverage analysis connection

		rcc_agnt.mon2scb.connect(rcc_out_cov.out_dut_cov); // coverage analysis connection
        rcc_ref_pred.ref2scb.connect(rcc_out_cov.out_ref_cov); // coverage analysis connection

	endfunction

endclass: rcc_environment

rcc_cov  = rcc_in_coverage::type_id::create(.name("rcc_cov"), .parent(this));


/*
* RCC interface
*/
interface rcc_if;

  bit reset, clk;

  bit rcc_clk;

  logic [3:0] address;

  bit digit_clk;

  logic [7:0] dout;

  logic dout_flag;

  bit test_mode;

  bit scan_in0, scan_in1, scan_en;

  bit scan_out0, scan_out1;

endinterface:rcc_if


class rcc_in_coverage extends uvm_component;
    `uvm_component_utils(rcc_in_coverage)

    virtual rcc_if vif;

    uvm_analysis_export #(rcc_transaction) cov_din;
    uvm_analysis_export #(rcc_transaction) cov_addr;

    uvm_tlm_analysis_fifo #(rcc_transaction) cov_din_fifo;

    rcc_transaction seq_din;
    rcc_transaction seq_addr;

    rcc_transaction seq_in;
    rcc_transaction seq_ad;

    covergroup din_rtl; // @(posedge vif.clk);
        cover_point_din_rtl: coverpoint seq_in.g_din {option.auto_bin_max = 65535;}
    endgroup

    covergroup addr_rtl; //@(posedge vif.rcc_clk);

        cover_point_addr_rtl: coverpoint seq_ad.g_address{ bins addrs_0 = {0};
                                                  bins addrs_1 = {1};
                                                  bins addrs_2 = {2};
                                                  bins addrs_3 = {3};
                                                  bins addrs_4 = {4};
                                                  bins addrs_5 = {5};
                                                  bins addrs_6 = {6};
                                                  bins addrs_7 = {7};
                                                  ignore_bins ig = {[8:15]};
                                                 }

    endgroup

    function new(string name = "", uvm_component parent);
        super.new(name, parent);
            din_rtl = new();
            addr_rtl = new();
    endfunction

    function void build_phase(uvm_phase phase);
        super.build_phase(phase);

              if(!uvm_config_db#(virtual rcc_if)::get(this, "", "vif", vif))
	      `uvm_fatal("No Vif", {"virtual interface must be set for: ", get_full_name(), ".vif"});

            seq_in = rcc_transaction::type_id::create("seq_in", this);
            seq_ad = rcc_transaction::type_id::create("seq_ad", this);

            cov_din = new("cov_din", this);
            cov_addr = new("cov_addr", this);

            cov_din_fifo = new("cov_din_fifo", this);
            cov_addr_fifo = new("cov_addr_fifo", this);

            seq_din = rcc_transaction::type_id::create("seq_din", this);
            seq_addr = rcc_transaction::type_id::create("seq_addr", this);
    endfunction

    function void connect_phase(uvm_phase phase);
        cov_din.connect(cov_din_fifo.analysis_export);
        cov_addr.connect(cov_addr_fifo.analysis_export);
    endfunction

    task run_phase(uvm_phase phase);
            fork
                din_coverage();
                addr_coverage();
            join_none

    endtask

    task din_coverage;
        forever begin
        @(posedge vif.clk)
        cov_din_fifo.get(seq_din);
        seq_in.g_din = seq_din.g_din;
      //$display("cov input : %h\n", seq_in.g_din);
        din_rtl.sample();
       // $display("input cov: %f\n", din_rtl.get_coverage());
        end
    endtask

    task addr_coverage;
        forever begin
        @(posedge vif.rcc_clk)
        cov_addr_fifo.get(seq_addr);
        seq_ad.g_address = seq_addr.g_address;
        //$display("cov input : %h\n", seq_ad.g_address);
        addr_rtl.sample();
       // $display("input address cov: %f\n", addr_rtl.get_coverage());
       end

    endtask

endclass


// Monitor Class
// It receives the response from the DUT and converts the signal level response into transactions
// sends the transactions to various other testbench components

class rcc_monitor extends uvm_monitor;
	`uvm_component_utils(rcc_monitor)

	// declare the interface
	virtual rcc_if vif;
	rcc_transaction rcc_tx;

	// Analysis Ports
	uvm_analysis_port#(rcc_transaction) mon2ref;
	uvm_analysis_port#(rcc_transaction) mon2scb;

	function new(string name, uvm_component parent);
		super.new(name, parent);
	endfunction: new

	function void build_phase(uvm_phase phase);
		super.build_phase(phase);

	mon2ref = new("mon2ref", this);
	mon2scb = new("mon2scb", this);

	// get the virtual interface handle from config database
	if(!uvm_config_db#(virtual rcc_if)::get(this, "", "vif", vif))

	endfunction: build_phase

bit [15:0] array_data [9];
integer i= 0;
integer j;

	task run_phase(uvm_phase phase);
		fork
		sample_input();
		sample_output();
		join

	endtask

	task sample_input;
		forever
			begin
			rcc_transaction data_collect;
			data_collect = rcc_transaction::type_id::create("data_collect", this);
				@(posedge vif.rcc_clk)
					data_collect.g_din = vif.din;
				mon2ref.write(data_collect);

			end //forever loop
	endtask // sample_input

	task sample_output;
		forever
			begin

				rcc_transaction data_collect_out;
				data_collect_out = rcc_transaction::type_id::create("data_collect_out", this);

				@(posedge vif.digit_clk)
					data_collect_out.dout = vif.dout;
			// Send the data through analysis port
				mon2scb.write(data_collect_out);

			end //forever loop
	endtask // sample_output

endclass

`uvm_fatal("No Vif", {"virtual interface must be set for: ", get_full_name(), ".vif"});


// Monitor takes the input from the reference model via a mailbox and checks DUT with ref. model
//

class rcc_out_coverage extends uvm_component;
	`uvm_component_utils(rcc_out_coverage)

	virtual rcc_if vif;

	uvm_analysis_export #(rcc_transaction) out_dut_cov;
    uvm_analysis_export #(rcc_transaction) out_ref_cov;

	uvm_tlm_analysis_fifo #(rcc_transaction) dut_cov_fifo;
	uvm_tlm_analysis_fifo #(rcc_transaction) ref_cov_fifo;

	rcc_transaction dut_trans_cov;
    rcc_transaction ref_trans_cov;

    bit count_min = 0;
    int watermark_count = 10;

	bit [7:0] refmdl_dout;
	bit [7:0] rtl_dout;

      //declare coun for rtl digits
  int dut_1 = 0;
  int dut_2 = 0;
  int dut_3 = 0;
  int dut_a = 0;
  int dut_4 = 0;
  int dut_5 = 0;
  int dut_6 = 0;
  int dut_b = 0;
  int dut_7 = 0;
  int dut_8 = 0;
  int dut_9 = 0;
  int dut_c = 0;
  int dut_star = 0;
  int dut_0 = 0;
  int dut_pound = 0;
  int dut_d = 0;

  //declare coun for ref model digits
  int ref_1 = 0;
  int ref_2 = 0;
  int ref_3 = 0;
  int ref_a = 0;
  int ref_4 = 0;
  int ref_5 = 0;
  int ref_6 = 0;
  int ref_b = 0;
  int ref_7 = 0;
  int ref_8 = 0;
  int ref_9 = 0;
  int ref_c = 0;
  int ref_star = 0;
  int ref_0 = 0;
  int ref_pound = 0;
  int ref_d = 0;

    // coverage
 covergroup output_coverage; // @(posedge vif.digit_clk);

    cover_point_dout_rtl: coverpoint rtl_dout { bins dout_rtl_0 = {48}; // 48 is decimal 0
                                               bins dout_rtl_1 = {49};
                                               bins dout_rtl_2 = {50};
                                               bins dout_rtl_3 = {51};
                                               bins dout_rtl_4 = {52};
                                               bins dout_rtl_5 = {53};
                                               bins dout_rtl_6 = {54};
                                               bins dout_rtl_7 = {55};
                                               bins dout_rtl_8 = {56};
                                               bins dout_rtl_9 = {57};
                                               bins dout_rtl_a = {97};
                                               bins dout_rtl_b = {98};
                                               bins dout_rtl_c = {99};
                                               bins dout_rtl_d = {100};
                                               bins dout_rtl_star = {42};
                                               bins dout_rtl_pound = {35};
                                               ignore_bins ig_rtl = {[1:34], [36:41], [43:47], [58:96],[101:255]};
                                              }

      cover_point_out_ref: coverpoint refmdl_dout { bins out_ref_0 = {48};
                                               bins out_ref_1 = {49};
                                               bins out_ref_2 = {50};
                                               bins out_ref_3 = {51};
                                               bins out_ref_4 = {52};
                                               bins out_ref_5 = {53};
                                               bins out_ref_6 = {54};
                                               bins out_ref_7 = {55};
                                               bins out_ref_8 = {56};
                                               bins out_ref_9 = {57};
                                               bins out_ref_a = {97};
                                               bins out_ref_b = {98};
                                               bins out_ref_c = {99};
                                               bins out_ref_d = {100};
                                               bins out_ref_star = {42};

                                               ignore_bins ig_ref = {[1:34], [36:41], [43:47], [58:96],[101:255]};
                                              }

       // cross coverage between ASCII digit produced by RTL and ref model
       cross_rtl_ref_out: cross cover_point_dout_rtl, cover_point_out_ref
       {
         bins cross_out_0 = binsof (cover_point_dout_rtl) intersect {48};
        bins cross_out_1 = binsof (cover_point_dout_rtl) intersect {49};
         bins cross_out_2 = binsof (cover_point_dout_rtl) intersect {50};
         bins cross_out_3 = binsof (cover_point_dout_rtl) intersect {51};
         bins cross_out_4 = binsof (cover_point_dout_rtl) intersect {52};
         bins cross_out_5 = binsof (cover_point_dout_rtl) intersect {53};
         bins cross_out_6 = binsof (cover_point_dout_rtl) intersect {54};
         bins cross_out_7 = binsof (cover_point_dout_rtl) intersect {55};
         bins cross_out_8 = binsof (cover_point_dout_rtl) intersect {56};
         bins cross_out_9 = binsof (cover_point_dout_rtl) intersect {57};
         bins cross_out_a = binsof (cover_point_dout_rtl) intersect {97};
         bins cross_out_b = binsof (cover_point_dout_rtl) intersect {98};
         bins cross_out_c = binsof (cover_point_dout_rtl) intersect {99};
         bins cross_out_d = binsof (cover_point_dout_rtl) intersect {100};
         bins cross_out_star = binsof (cover_point_dout_rtl) intersect {42};
         bins cross_out_pound = binsof (cover_point_dout_rtl) intersect {35};

        }

  endgroup //output_coverage

 // cross coverage for digit counts
  covergroup cross_digit_count; // @(posedge vif.digit_clk);

    cross_ref_1: coverpoint ref_1{option.auto_bin_max = 1;}
    cross_ref_2: coverpoint ref_2{option.auto_bin_max = 1;}
    cross_ref_3: coverpoint ref_3{option.auto_bin_max = 1;}
    cross_ref_4: coverpoint ref_4{option.auto_bin_max = 1;}
    cross_ref_5: coverpoint ref_5{option.auto_bin_max = 1;}
    cross_ref_6: coverpoint ref_6{option.auto_bin_max = 1;}
    cross_ref_7: coverpoint ref_7{option.auto_bin_max = 1;}
    cross_ref_8: coverpoint ref_8{option.auto_bin_max = 1;}
    cross_ref_9: coverpoint ref_9{option.auto_bin_max = 1;}
    cross_ref_0: coverpoint ref_0{option.auto_bin_max = 1;}
    cross_ref_a: coverpoint ref_a{option.auto_bin_max = 1;}
    cross_ref_b: coverpoint ref_b{option.auto_bin_max = 1;}
    cross_ref_c: coverpoint ref_c{option.auto_bin_max = 1;}
    cross_ref_d: coverpoint ref_d{option.auto_bin_max = 1;}
    cross_ref_star: coverpoint ref_star{option.auto_bin_max = 1;}
    cross_ref_pound: coverpoint ref_pound{option.auto_bin_max = 1;}

    cross_rtl_1: coverpoint dut_1{option.auto_bin_max = 1;}
    cross_rtl_2: coverpoint dut_2{option.auto_bin_max = 1;}
    cross_rtl_3: coverpoint dut_3{option.auto_bin_max = 1;}
    cross_rtl_4: coverpoint dut_4{option.auto_bin_max = 1;}
    cross_rtl_5: coverpoint dut_5{option.auto_bin_max = 1;}
    cross_rtl_6: coverpoint dut_6{option.auto_bin_max = 1;}
    cross_rtl_7: coverpoint dut_7{option.auto_bin_max = 1;}
    cross_rtl_8: coverpoint dut_8{option.auto_bin_max = 1;}
    cross_rtl_9: coverpoint dut_9{option.auto_bin_max = 1;}
    cross_rtl_0: coverpoint dut_0{option.auto_bin_max = 1;}
    cross_rtl_a: coverpoint dut_a{option.auto_bin_max = 1;}
    cross_rtl_b: coverpoint dut_b{option.auto_bin_max = 1;}
    cross_rtl_c: coverpoint dut_c{option.auto_bin_max = 1;}
    cross_rtl_d: coverpoint dut_d{option.auto_bin_max = 1;}
    cross_rtl_star: coverpoint dut_star{option.auto_bin_max = 1;}
    cross_rtl_pound: coverpoint dut_pound{option.auto_bin_max = 1;}

    digit_1: cross cross_ref_1, cross_rtl_1;
    digit_2: cross cross_ref_2, cross_rtl_2;
    digit_3: cross cross_ref_3, cross_rtl_3;
    digit_4: cross cross_ref_4, cross_rtl_4;
    digit_5: cross cross_ref_5, cross_rtl_5;
    digit_6: cross cross_ref_6, cross_rtl_6;
    digit_7: cross cross_ref_7, cross_rtl_7;
    digit_8: cross cross_ref_8, cross_rtl_8;
    digit_9: cross cross_ref_9, cross_rtl_9;
    digit_0: cross cross_ref_0, cross_rtl_0;
    digit_a: cross cross_ref_a, cross_rtl_a;
    digit_b: cross cross_ref_b, cross_rtl_b;
    digit_c: cross cross_ref_c, cross_rtl_c;
    digit_d: cross cross_ref_d, cross_rtl_d;
    digit_star: cross cross_ref_star, cross_rtl_star;
    digit_pound: cross cross_ref_pound, cross_rtl_pound;

  endgroup // cross_digit_count

    function new(string name= "", uvm_component parent);
		super.new(name, parent);
			output_coverage = new();
			cross_digit_count = new();
	endfunction

	function void build_phase(uvm_phase phase);
		super.build_phase(phase);
        	out_dut_cov = new("out_dut_cov", this);
        	out_ref_cov = new("out_ref_cov", this);

   			dut_cov_fifo = new("dut_cov_fifo", this);
			ref_cov_fifo = new("ref_cov_fifo", this);

			dut_trans_cov = rcc_transaction::type_id::create("dut_trans_cov", this);
			ref_trans_cov = rcc_transaction::type_id::create("ref_trans_cov", this);

	// get the virtual interface handle that was stored in the uvm_config_db
	// and assign it to the local vif field
			if(!uvm_config_db#(virtual rcc_if)::get(this, "", "vif", vif))
				`uvm_fatal("No Vif", {"virtual interface must be set for: ", get_full_name(), ".vif"});

	endfunction: build_phase

	function void connect_phase(uvm_phase phase);
		out_dut_cov.connect(dut_cov_fifo.analysis_export);
		out_ref_cov.connect(ref_cov_fifo.analysis_export);
	endfunction: connect_phase

	task run_phase(uvm_phase phase);
		forever begin
			@(posedge vif.digit_clk)
				dut_cov_fifo.get(dut_trans_cov);
                ref_cov_fifo.get(ref_trans_cov);

                rtl_dout = dut_trans_cov.dout;
                refmdl_dout = ref_trans_cov.dout;

              //  $display("rtl_dout; %c\n", rtl_dout);
               // $display("refmdl_dout; %c", refmdl_dout);

				if(rtl_dout == "1")      dut_1 += 1;
				else if(rtl_dout == "2") dut_2 += 1;
				else if(rtl_dout == "3") dut_3 += 1;
				else if(rtl_dout == "a") dut_a += 1;
				else if(rtl_dout == "4") dut_4 += 1;
				else if(rtl_dout == "5") dut_5 += 1;
				else if(rtl_dout == "6") dut_6 += 1;
				else if(rtl_dout == "b") dut_b += 1;
				else if(rtl_dout == "7") dut_7 += 1;
				else if(rtl_dout == "8") dut_8 += 1;
				else if(rtl_dout == "9") dut_9 += 1;
				else if(rtl_dout == "c") dut_c += 1;
				else if(rtl_dout == "*") dut_star += 1;
				else if(rtl_dout == "0") dut_0 += 1;
				else if(rtl_dout == "#") dut_pound += 1;
				else if(rtl_dout == "d") dut_d += 1;

				if(refmdl_dout == "1")      ref_1 += 1;
				else if(refmdl_dout == "2") ref_2 += 1;
				else if(refmdl_dout == "3") ref_3 += 1;
				else if(refmdl_dout == "a") ref_a += 1;
				else if(refmdl_dout == "4") ref_4 += 1;
				else if(refmdl_dout == "5") ref_5 += 1;
				else if(refmdl_dout == "6") ref_6 += 1;
				else if(refmdl_dout == "b") ref_b += 1;
				else if(refmdl_dout == "7") ref_7 += 1;
				else if(refmdl_dout == "8") ref_8 += 1;
				else if(refmdl_dout == "9") ref_9 += 1;
				else if(refmdl_dout == "c") ref_c += 1;
				else if(refmdl_dout == "*") ref_star += 1;
				else if(refmdl_dout == "0") ref_0 += 1;
				else if(refmdl_dout == "#") ref_pound += 1;
				else if(refmdl_dout == "d") ref_d += 1;

				cross_digit_count.sample;
				output_coverage.sample;

/*$display("--------- dut_1  count     = %d", dut_1 );
$display("--------- dut_2  count     = %d",  dut_2 );
$display("--------- dut_3  count     = %d",  dut_3 );
$display("--------- dut_a  count     = %d",  dut_a );
$display("--------- dut_4  count     = %d",  dut_4 );
$display("--------- dut_5  count     = %d",  dut_5 );
$display("--------- dut_6  count     = %d",  dut_6 );
$display("--------- dut_b  count     = %d",  dut_b );
$display("--------- dut_7  count     = %d",  dut_7 );
$display("--------- dut_8  count     = %d",  dut_8 );
$display("--------- dut_9  count     = %d",  dut_9 );
$display("--------- dut_c  count     = %d",  dut_c );
$display("--------- dut_d  count     = %d",  dut_d );
$display("--------- dut_star count   = %d",  dut_star );
$display("--------- dut_0 count      = %d",  dut_0 );
$display("--------- dut_pound count  = %d\n",  dut_pound );

$display("--------- Count from REF Model ---------------\n");

$display("--------- ref_1  count     = %d",  ref_1 );
$display("--------- ref_2  count     = %d",  ref_2 );
$display("--------- ref_3  count     = %d",  ref_3 );
$display("--------- ref_a  count     = %d",  ref_a );
$display("--------- ref_4  count     = %d",  ref_4 );
$display("--------- ref_5  count     = %d",  ref_5 );
$display("--------- ref_6  count     = %d", ref_6 );
$display("--------- ref_b  count     = %d",  ref_b );
$display("--------- ref_7  count     = %d", ref_7 );
$display("--------- ref_8  count     = %d", ref_8 );
$display("--------- ref_9  count     = %d",  ref_9 );
$display("--------- ref_c  count     = %d",  ref_c );
$display("--------- ref_d  count     = %d",  ref_d );
$display("--------- ref_star count   = %d",  ref_star );
$display("--------- ref_0 count      = %d",  ref_0 );
$display("--------- ref_pound count  = %d",  ref_pound );*/

//$display("--------- dut_9  count     = %d",  dut_9 );
// Watermark check

      if(dut_1 >= watermark_count && dut_2 >= watermark_count && dut_3 >= watermark_count && dut_4 >= watermark_count && dut_5 >= watermark_count && dut_6 >= watermark_count && dut_7 >= watermark_count &&dut_8 >= watermark_count && dut_9 >= watermark_count && dut_0 >= watermark_count && dut_a >= watermark_count && dut_b >= watermark_count && dut_c >= watermark_count && dut_d >= watermark_count && dut_star >= watermark_count && dut_pound >= watermark_count)

begin

             count_min = 1;

end

    if(count_min)
    begin

      $display("------Watermark of", " %d ",watermark_count, "reached for every digit----------");
      $finish;

    end

			end// forever

	endtask

endclass


	import uvm_pkg::*;
//`include "rcc_assertions.sv"
	`include "rcc_sequencer.sv"
        `include "rcc_in_coverage.sv"
        `include "rcc_out_coverage.sv"
	`include "rcc_driver.sv"
	`include"rcc_monitor.sv"
	`include "rcc_agent.sv"
	`include "ref_pred.sv"
	`include "rcc_scoreboard.sv"
	`include "rcc_config.sv"
	`include "rcc_environment.sv"
	`include "rcc_test.sv"

endpackage: rcc_pkg


// Checker receives the output from the Ref. Model and the output from DUT
// It compares both the outputs
// In this way it perform DUT vs Ref model verification

class rcc_scoreboard extends uvm_scoreboard;
	`uvm_component_utils(rcc_scoreboard)

	uvm_analysis_export #(rcc_transaction) out_dut;

	uvm_tlm_analysis_fifo #(rcc_transaction) dut_fifo;
	uvm_tlm_analysis_fifo #(rcc_transaction) ref_fifo;

	//logic [7:0] dut_output, ref_output;

	virtual rcc_if vif;

		rcc_transaction dut_trans;
        rcc_transaction ref_trans;

	function new(string name= "", uvm_component parent);
		super.new(name, parent);
	endfunction

	function void build_phase(uvm_phase phase);
		super.build_phase(phase);
        out_dut = new("out_dut", this);
        out_ref = new("out_ref", this);

   		dut_fifo		= new("dut_fifo", this);
		ref_fifo		= new("ref_fifo", this);

dut_trans = rcc_transaction::type_id::create("dut_trans", this);
ref_trans = rcc_transaction::type_id::create("ref_trans", this);

	// get the virtual interface handle that was stored in the uvm_config_db
	// and assign it to the local vif field
		if(!uvm_config_db#(virtual rcc_if)::get(this, "", "vif", vif))
			`uvm_fatal("No Vif", {"virtual interface must be set for: ", get_full_name(), ".vif"});

	endfunction: build_phase

	function void connect_phase(uvm_phase phase);
		out_dut.connect(dut_fifo.analysis_export);
		out_ref.connect(ref_fifo.analysis_export);
	endfunction: connect_phase

	task run_phase(uvm_phase phase);

	   forever begin

            @(posedge vif.digit_clk)

                dut_fifo.get(dut_trans);
                ref_fifo.get(ref_trans);

    // using assertion to check the ref model and dut output
            assert(dut_trans.dout == ref_trans.dout)
               // $display("SUCCESS %c %c %d \n", dut_trans.dout, ref_trans.dout, $time);
                    else
                        $error("ERROR %c %c %d \n", dut_trans.dout, ref_trans.dout, $time);
	    end// forever

	endtask

endclass


/*
* UVM has separate class hierarchy for data (stimulus)
*/

/*
* We use transactions to communicate between components
* Its a higher abstraction layer than signal level information
*/

// user defined transaction extends uvm_sequence_item
class rcc_transaction extends uvm_sequence_item;
	`uvm_object_utils(rcc_transaction)

	rand bit [15:0] g_din;
	rand bit [3:0] digcnt;
	rand bit [3:0] qcnt;
	bit [7:0] dout; // need for output checking
         bit [3:0] g_address;

	function new(string name = "");
		super.new(name);
	endfunction: new

endclass: rcc_transaction

//---------------------------------------------------------------------------------------//

/*
* After we have created the transaction, we will create a sequence
*/

class rcc_sequence_child extends uvm_sequence#(rcc_transaction);
	`uvm_object_utils(rcc_sequence_child)

    rcc_transaction rcc_tx;

		super.new(name);
	endfunction: new

// body defines the behavior of the sequence
	task body();

			  rcc_tx = rcc_transaction::type_id::create("rcc_tx");
		//repeat(9)
		//begin
			start_item(rcc_tx);
                        if(rcc_tx.randomize())
			//assert(rcc_tx.randomize());
			finish_item(rcc_tx);

		//end
	endtask: body

endclass: rcc_sequence_child

class rcc_sequencer extends uvm_sequencer#(rcc_transaction);
	`uvm_object_utils(rcc_sequencer)

	function new(string name = "");
		super.new(name);
	endfunction: new

endclass: rcc_sequencer

// this is top level sequenc that we start from uvm test class, which inturn creates
//different sequence objects of child sequence.

class rcc_sequence extends uvm_sequence#(rcc_transaction);
	`uvm_object_utils(rcc_sequence)

    //rcc_transaction rcc_tx;
    rcc_sequencer rcc_seqr; // handle to sequencer

	function new(string name = "");
		super.new(name);
	endfunction: new

// body defines the behavior of the sequence
	task body();

     forever begin
        rcc_sequence_child seq;
        seq = rcc_sequence_child::type_id::create("seq"); // create child sequence object

       if(!seq.randomize())
            `uvm_error(get_type_name(), "Failed to randomize sequence")
//$display("First here: %d \n", $time);
        seq.start(rcc_seqr, this);
//$display("end here: %d \n", $time);
      end
	endtask: body

endclass: rcc_sequence


`include "uvm_macros.svh"
`include "rcc_pkg.sv"
`include "rcc_if.sv"

module test;

    import uvm_pkg::*;

	// this package includes all our class definetion
    import rcc_pkg::*;

    import "DPI" function int sim_start_time();
    import "DPI" function void start_day_date();
    import "DPI" function void digit_gen_time(int dig_time);

    int sim_start;
    int sim_finish_time, execution_time;

	// instantiate the interface in top level module
    rcc_if intf();

	// connect the DUT with the interface
	results_conv top(
		          .clk(intf.clk),
		          .reset(intf.reset),
		          .test_mode(intf.test_mode),
		          .rcc_clk(intf.rcc_clk),
		          .address(intf.address),
		          .din(intf.din),
		          .digit_clk(intf.digit_clk),
		          .dout(intf.dout),
	`ifdef NETLIST
		          .dout_flag(intf.dout_flag),
		          .scan_in0(intf.scan_in0),
		          .scan_in1(intf.scan_in1),
		          .scan_en(intf.scan_en)
	`else
		          .dout_flag(intf.dout_flag)
	`endif
		         );

    rcc_assertions ass_rcc(intf);

	initial begin
		//Registers the Interface in the configuration block so that other
		//blocks can use it
		// we pass the virtual interface using config database
		uvm_config_db #(virtual rcc_if)::set(null, "*", "vif", intf);

		// execute the test
		run_test();
	end

        initial begin
            $timeformat(-9, 2, "ns", 16);
            $set_coverage_db_name("results_conv");
            `ifdef SDFSCAN
               $sdf_annotate("sdf/results_cov_tsmc18_scan.sdf", test.top);
             `endif
        end

	initial begin
		intf.clk <= 1'b0;
	end

	always begin
		#25 intf.clk = ~intf.clk;
	end

// wall time
    initial begin
        sim_start = sim_start_time();
        $display("-----------------------------------------------------\n");

        $display("----------------SIMULATION STARTS AT: ----------------------------\n");
        start_day_date();
    end

// final block that is executed at the end of the simulation
// print other useful information like coverage results, finish wall time
    final begin
        sim_finish_time = sim_start_time();
        execution_time = sim_finish_time - sim_start;

         $display("-----------------------------------------------------\n");
        $display("Total Execution Time in days/hours/minutes/seconds is: ");
        digit_gen_time(execution_time);

     // print coverage at the end of simulation
        $display("din coverage for RTL = %f ", cov_in.din_rtl.get_coverage() );
        $display("address coverage for RTL = %f\n ", cov_in.addr_rtl.get_coverage());

        $display("RTL dout coverage for RTL = %f ", cov_out.output_coverage.cover_point_dout_rtl.get_coverage() );
        $display("Ref Model dout coverage for RTL = %f\n ", cov_out.output_coverage.cover_point_out_ref.get_coverage() );

        $display("dout cross coverage for RTL = %f ", cov_out.output_coverage.cross_rtl_ref_out.get_coverage() );

        $display("digit count cross coverage = %f\n ", cov_out.cross_digit_count.get_coverage() );

        $display("\n------------SIMULATION ENDS AT: -----------------");
        start_day_date();
        $display("----------------END------------------");
    end

endmodule

    // coverage instance for coverage result display
    rcc_in_coverage cov_in;


/*
* test case for the RCC. It extends uvm_test base class
* 1) it drives the sequence by connecting the sequence with sequencer
* 2) create the env block
*/

class rcc_test extends uvm_test;
	//register the class name with uvm factory
	`uvm_component_utils(rcc_test)

	//instantiate the env
	rcc_environment rcc_env;

	function new(string name = "rcc_test", uvm_component parent = null);
		super.new(name, parent);
	endfunction: new

	// UVM build_phase
	function void build_phase (uvm_phase phase);
		super.build_phase(phase);
		rcc_env = rcc_environment::type_id::create(.name("rcc_env"), .parent(this));
	endfunction: build_phase

	// UVM run_phase

		// declare instance of sequence
		rcc_sequence rcc_seq;
		// create the sequence
		rcc_seq = rcc_sequence::type_id::create("rcc_seq");
                 assert(rcc_seq.randomize());

		phase.raise_objection(.obj(this));
		// test will call start() method of the sequence to initialte the execution of the sequence
		// and the argument is the sequencer on which sequence is going to start
		rcc_seq.start(rcc_env.rcc_agnt.rcc_seqr, null);
		phase.drop_objection(.obj(this));

	endtask: run_phase
endclass: rcc_test


// class that receives the data from agent and pass it to C-reference model
// and gets output returned from C-ref model

    // dpi imports
    import "DPI-C" function int input_arg( int array[8]);

class ref_pred extends uvm_subscriber #(rcc_transaction);
    `uvm_component_utils(ref_pred)

    // Analysis port for sending the ref model output to scoreboard
    uvm_analysis_port#(rcc_transaction) ref2scb;

    integer i= 0;
    integer j = 0;
    integer array_data[8];

        rcc_transaction ref_output;
//ref_output = rcc_transaction::type_id::create("ref_output", this);

    function new(string name = "", uvm_component parent);
        super.new(name, parent);
    endfunction

    function void build_phase(uvm_phase phase);
        super.build_phase(phase);

        ref2scb = new("ref2scb", this);

ref_output = rcc_transaction::type_id::create("ref_output", this);

    endfunction

    function void write(rcc_transaction t);

		if(i <= 7) array_data[i] = t.g_din;

		    i = i+1;

		if(i == 9)
	    	begin
       			ref_out = input_arg(array_data);
//$display("-------ref_out : %c\n", ref_out);
                ref_output.dout = ref_out;
ref2scb.write(ref_output);
    // send the ref_out to scoreboard
   //ref2scb.write(ref_out);
	            i = 0;
		        for(j = 0; j< 8; j++)
                    begin
		                array_data[j] = 0;
		            end
            end

    endfunction

   /*task run_phase(uvm_phase phase);

   //      forever begin
//$display("----------array_data = %h", array_data[0]);
//$display("----------------HERE---------------------\n");
//$display("-----------------------ref_out: %c \n", ref_out);
         ref2scb.write(ref_output);
        // end
    endtask */

endclass

/*------ debug --------------------------------------
$display("----------array_data = %h", array_data[0]);
$display("----------array_data = %h", array_data[1]);
$display("----------array_data = %h", array_data[2]);
$display("----------array_data = %h", array_data[3]);
$display("----------array_data = %h", array_data[4]);
$display("----------array_data = %h", array_data[5]);
$display("----------array_data = %h", array_data[6]);
$display("----------array_data = %h", array_data[7]);
//$display("----------array_data = %h", array_data[8]);
--------------------------------------------------------*/