// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

// ---------------------------------------------
// TileLink sequence item
// ---------------------------------------------

// use macro to write constraint in order to reuse in functions:
// get_exp_d_error and randomize_a_chan_with_protocol_error
`define chk_prot_a_opcode \
  a_opcode inside {Get, PutFullData, PutPartialData}

// For PutFullData message, mask needs to match with size
`define chk_prot_mask_w_PutFullData \
  a_opcode != PutFullData || $countones(a_mask) == (1 << a_size)

// mask must align with addr, same as below
//   a_addr[1:0] == 1 -> a_mask[0]   == 0;
//   a_addr[1:0] == 2 -> a_mask[1:0] == 0;
//   a_addr[1:0] == 3 -> a_mask[2:0] == 0;
`define chk_prot_mask_w_addr \
  MaskWidth'(a_mask << (4 - a_addr[SizeWidth-1:0])) == 0

// mask must be within enabled lanes
// prevent cases: addr: 0h, size: 1, mask: 'b1100; addr: 0h, size: 0, mask: 'b1000
`define chk_prot_mask_in_enabled_lanes \
  ((a_mask >> a_addr[SizeWidth-1:0]) >> (1 << a_size)) == 0

// Address must be aligned to the a_size (2 ** a_size bytes)
`define chk_prot_addr_size_align \
  (a_addr << (AddrWidth - a_size)) == 0

// max size is 2
`define chk_prot_max_size \
  a_size <= 2

class tl_seq_item extends uvm_sequence_item;

  rand bit [AddrWidth - 1   : 0] a_addr;
  rand bit [DataWidth - 1   : 0] a_data;
  rand bit [MaskWidth - 1   : 0] a_mask;
  rand bit [SizeWidth - 1   : 0] a_size;
  rand bit [2:0]                 a_param;
  rand bit [SourceWidth - 1 : 0] a_source;
  rand bit [OpcodeWidth - 1 : 0] a_opcode;

  rand bit [2:0]                 d_param;
  rand bit [DataWidth - 1   : 0] d_data;
  rand bit [SourceWidth - 1 : 0] d_source;
  rand bit [SizeWidth - 1   : 0] d_size;
  rand bit [OpcodeWidth - 1 : 0] d_opcode;
  rand bit                       d_error;
  rand bit [DUserWidth - 1 : 0]  d_user;
  rand bit                       d_sink;

  // host mode delays
  rand int unsigned               a_valid_delay;
  rand int unsigned               a_valid_len;

  // device mode delays
  rand int unsigned               d_valid_delay;
  rand int unsigned               d_valid_len;

  // param is reserved for future use, must be zero
  constraint param_c {
    a_param == 0;
    d_param == 0;
  }

  constraint no_d_error_c {
    soft d_error == 0;
  }

  constraint a_valid_len_c {
    soft a_valid_len inside {[1:10]};
  }

  constraint d_valid_len_c {
    soft d_valid_len inside {[1:10]};
  }

  constraint valid_delay_c {
    soft a_valid_delay inside {[0:50]};
    soft d_valid_delay inside {[0:50]};
  }

  constraint d_opcode_c {
    d_opcode inside {AccessAckData, AccessAck};
  }

  constraint a_opcode_c {
    `chk_prot_a_opcode;
  }

  // mask must be contiguous, e.g. 'b1001, 'b1010 aren't allowed
  constraint mask_contiguous_c {
    $countones(a_mask ^ {a_mask[MaskWidth-2:0], 1'b0}) <= 2;
  }

  constraint mask_w_PutFullData_c {
    `chk_prot_mask_w_PutFullData;
  }

  constraint mask_w_addr_c {
    `chk_prot_mask_w_addr;
  }

  constraint mask_in_enabled_lanes_c {
    `chk_prot_mask_in_enabled_lanes;
  }

  constraint addr_size_align_c {
    `chk_prot_addr_size_align;
  }

  // size can't be more than 2
  constraint max_size_c {
    `chk_prot_max_size;
  }

  `uvm_object_utils_begin(tl_seq_item)
    `uvm_field_int  (a_addr,              UVM_DEFAULT)
    `uvm_field_int  (a_data,              UVM_DEFAULT)
    `uvm_field_int  (a_mask,              UVM_DEFAULT)
    `uvm_field_int  (a_size,              UVM_DEFAULT)
    `uvm_field_int  (a_param,             UVM_DEFAULT)
    `uvm_field_int  (a_source,            UVM_DEFAULT)
    `uvm_field_int  (a_opcode,            UVM_DEFAULT)
    `uvm_field_int  (d_param,             UVM_DEFAULT)
    `uvm_field_int  (d_source,            UVM_DEFAULT)
    `uvm_field_int  (d_data,              UVM_DEFAULT)
    `uvm_field_int  (d_size,              UVM_DEFAULT)
    `uvm_field_int  (d_opcode,            UVM_DEFAULT)
    `uvm_field_int  (d_error,             UVM_DEFAULT)
    `uvm_field_int  (d_sink,              UVM_DEFAULT)
    `uvm_field_int  (d_user,              UVM_DEFAULT)
  `uvm_object_utils_end

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

  virtual function string convert2string();
    string str;
    string a_opcode_name, d_opcode_name;
    tl_a_op_e a_op_e;
    tl_d_op_e d_op_e;
    if ($cast(a_op_e, a_opcode)) a_opcode_name = a_op_e.name();
    else                         a_opcode_name = $sformatf("Invalid, value: %0d", a_opcode);
    if ($cast(d_op_e, d_opcode)) d_opcode_name = d_op_e.name();
    else                         d_opcode_name = $sformatf("Invalid, value: %0d", d_opcode);

    str = {$sformatf("a_addr = 0x%0h ", a_addr),
           $sformatf("a_data = 0x%0h ", a_data),
           $sformatf("a_mask = 0x%0h ", a_mask),
           $sformatf("a_size = 0x%0h ", a_size),
           $sformatf("a_param = 0x%0h ", a_param),
           $sformatf("a_source = 0x%0h ", a_source),
           $sformatf("a_opcode = %0s ", a_opcode_name),
           $sformatf("d_data = 0x%0h ", d_data),
           $sformatf("d_size = 0x%0h ", d_size),
           $sformatf("d_param = 0x%0h ", d_param),
           $sformatf("d_source = 0x%0h ", d_source),
           $sformatf("d_opcode = %0s ", d_opcode_name),
           $sformatf("d_error = %0b ", d_error),
           $sformatf("d_user = %0b ", d_user),
           $sformatf("d_sink = %0b ", d_sink)};
    return str;
  endfunction

  function void disable_a_chan_randomization();
    a_addr.rand_mode(0);
    a_data.rand_mode(0);
    a_mask.rand_mode(0);
    a_size.rand_mode(0);
    a_param.rand_mode(0);
    a_source.rand_mode(0);
    a_opcode.rand_mode(0);
  endfunction

  // calculate d_error based on values of a_chan
  function bit get_exp_d_error();
    return !(`chk_prot_addr_size_align) || !(`chk_prot_max_size) ||
           !(`chk_prot_a_opcode) || !(`chk_prot_mask_w_PutFullData) ||
           !(`chk_prot_mask_w_addr) || !(`chk_prot_mask_in_enabled_lanes);
  endfunction

  function void disable_a_chan_protocol_constraint();
    a_opcode_c.constraint_mode(0);
    mask_contiguous_c.constraint_mode(0);
    mask_w_PutFullData_c.constraint_mode(0);
    mask_w_addr_c.constraint_mode(0);
    mask_in_enabled_lanes_c.constraint_mode(0);
    addr_size_align_c.constraint_mode(0);
    max_size_c.constraint_mode(0);
  endfunction

  // randomly disable constraint_mode for a chan
  // at least one constraint_mode needs to be disabled to make sure protocol is violated
  function void randomize_a_chan_with_protocol_error();
    bit cm_a_opcode, cm_mask_w_PutFullData;
    bit cm_mask_w_addr, cm_mask_in_enabled_lanes, cm_addr_size_align, cm_max_size;
    `DV_CHECK_FATAL(std::randomize(cm_a_opcode, cm_mask_w_PutFullData,
                                   cm_mask_w_addr, cm_mask_in_enabled_lanes,
                                   cm_addr_size_align, cm_max_size) with {
                                   // at least one constraint_mode is off
                                   !(cm_a_opcode && cm_mask_w_PutFullData &&
                                   cm_mask_w_addr && cm_mask_in_enabled_lanes &&
                                   cm_addr_size_align && cm_max_size);
                                   })
    a_opcode_c.constraint_mode(cm_a_opcode);
    mask_w_PutFullData_c.constraint_mode(cm_mask_w_PutFullData);
    mask_w_addr_c.constraint_mode(cm_mask_w_addr);
    mask_in_enabled_lanes_c.constraint_mode(cm_mask_in_enabled_lanes);
    addr_size_align_c.constraint_mode(cm_addr_size_align);
    max_size_c.constraint_mode(cm_max_size);
    `DV_CHECK_FATAL(
        // at least one `chk_prot_* is violated
        randomize() with {!cm_a_opcode              && !(`chk_prot_a_opcode)              ||
                          !cm_mask_w_PutFullData    && !(`chk_prot_mask_w_PutFullData)    ||
                          !cm_mask_w_addr           && !(`chk_prot_mask_w_addr)           ||
                          !cm_mask_in_enabled_lanes && !(`chk_prot_mask_in_enabled_lanes) ||
                          !cm_addr_size_align       && !(`chk_prot_addr_size_align)       ||
                          !cm_max_size              && !(`chk_prot_max_size);})
  endfunction

  // Find whether this seq item is a read or a write.
  virtual function bit is_write();
    return (a_opcode inside {PutFullData, PutPartialData});
  endfunction

  // Check if d_opcode is appropriate for the given a_opcode. This check assumes that the
  // response packet has been appended with the corresponding request.
  virtual function bit check_opcodes(bit throw_error = 1'b1);
    // for read, return AccessAckData; for write or error opcode, return AccessAck
    tl_d_op_e exp_d_opcode = a_opcode == Get ? tlul_pkg::AccessAckData : tlul_pkg::AccessAck;
    check_opcodes = (d_opcode == int'(exp_d_opcode));
    if (!check_opcodes && throw_error) begin
      `uvm_error(`gfn, $sformatf("d_opcode: %0d & exp_d_opcode: %0d mismatch",
                                  d_opcode, exp_d_opcode))
    end
  endfunction

  // Check if packet parameters are ok.
  virtual function bit is_ok(bit throw_error = 1'b1);
    is_ok = 1'b1;
    is_ok &= check_opcodes(throw_error);
    // addr and data channels should have the same source
    is_ok &= (a_source == d_source);
    if (!is_ok && throw_error)
      `uvm_error(`gfn, $sformatf("a_source: 0x%0h & d_source: 0x%0h mismatch", a_source, d_source))
  endfunction

endclass

`undef chk_prot_a_opcode
`undef chk_prot_mask_w_PutFullData
`undef chk_prot_mask_w_addr
`undef chk_prot_mask_in_enabled_lanes
`undef chk_prot_addr_size_align
`undef chk_prot_max_size
