Layered transactions using parameterized inheritance

I am working on a testbench that requires building layered transactions. I have looked at various tutorials/papers that talks about different techniques (eg layered sequences, layered agents, layered sequencers, etc) of building layered transactions. Some of which seems like an overkill at least for my application. I am considering using parameterized inheritance for layering transactions (example below). I haven’t found any paper that suggests using this technique. So, before I embark on a journey to build these transactions, I am wondering if there are any pitfalls or complications of implementing transactions this way?

class enet_item extends uvm_sequence_item;
   `uvm_object_utils(enet_item)

   // Enet fields
   rand logic [47:0] da;
   rand logic [47:0] sa;
   rand logic [15:0] type;  
   ...
endclass

**//### ipv4_item**
class ipv4_item #(type T = enet_item) extends T;
   `uvm_object_param_utils(ipv4_item#(T))

   // IPv4 fields
   rand logic [3:0] ver;
   rand logic [3:0] ihl;
   rand logic [7:0] tos;
   ....
endclass

//### ipv6_item
class ipv6_item #(type T = enet_item) extends T;
   `uvm_object_param_utils(ipv6_item#(T))

   // IPv6 fields
   rand logic [3:0] ver;
   rand logic [7:0] traffic_class;
   rand logic [7:0] flow_label;
   ....
endclass

//### tcp_item
class tcp_item #(type T = ipv4_item) extends T;
   `uvm_object_param_utils(tcp_item#(T))

   // TCP fields
   rand logic [15:0] src_port;
   rand logic [15:0] dest_port;
   ....
endclass

//## Create Specific Transaction Types
typedef ipv4_item#(enet_item) ipv4_txn;
typedef ipv6_item#(enet_item) ipv6_txn;
typedef tcp_item#(**ipv4_txn**) tcp_ipv4_txn;
typedef tcp_item#(**ipv6_txn**) tcp_ipv6_txn;

//### Instantiate and use the transactions
 class my_sequence extends uvm_sequence #(enet_item);
      `uvm_object_utils(my_sequence)
      ...

      task body();
         randcase
              1 : begin
                    tcp_ipv4_txn txn;
                    txn = tcp_ipv4_txn::type_id::create("txn_ipv4_txn");
                    txn.randomize() with ...;
                    start_item(txn);
                    finish_item(txn);
              end

              1 : begin
                    tcp_ipv6_txn txn;
                    txn = tcp_ipv6_txn::type_id::create("txn_ipv6_txn");
                    txn.randomize() with ...;
                    start_item(txn);
                    finish_item(txn);
              end
      endtask
endclass

First, is this a valid syntax, writing ** ipv6_txn ** as a parameter of parametrized class?

Just wondering, why not to create a list under the base ethernet packet class which will be the headers container?
This might enable you the layering, am I wrong?

You are right. It’s not a valid syntax as I quickly found out soon after posting my question. I thought about creating a list…but couldn’t come up with a good way to structure the list so that I can control header fields at each layer easily when generating my transactions. For eg, if I create a list of type “hdr_base” from which each layer header extends, I’d have to generate each header class first and then copy handles of each header to the list in the base transaction class. I’d have to do the same thing if any of the layers also has a trailer. But if I can use inheritance (ie enet_item ← ipv4_item ← tcp_item), I would have access to all l2/l3/l4 header fields and I can use inline constraints to build each header all at once. Additionally, I was trying to make my transaction classes generic so that I can reuse them to create different transactions with different lower layer using parameterization (but I now know why this wouldn’t fly).

But I’ll explore the list based approach again. If you have figured out a good way to generate layered protocol transactions using header/trailer list, please share.

Thanks!

Don’t have an available code which can generate a layered protocol transactions using a header/trailer list.
Moreover, I don’t posses enough protocol knowledge of all the rules, constraints on all possible headers and its fields’ values, across all the layers (I think it also depends on the Ethernet protocol and the “Parse graph” you need to support and the various use cases of your product).
Would be happy to learn if you have some good references for studying these rules and what is supposed to be L2/L3/L4 header and connections between the header fields.
So please share.

I found next git repository while searching possible implementations:

Try go over the code there, maybe it could help you to to start with something.