UVM style suggestion needed for parameterized transactions/drivers

Hi uvm experts,
I am developing a UVM tb for a parameterized Digital FIR Filter. And would like some suggestions on how experienced UVM coders deal with parameterized DUT.

My I/Os are simple: clock, reset, sampled signal, FIR coefficients, and output signal. A quick example “pseudocode”


coefficient = {1,2,3,4} //4 taps
input = {1,1,1,1,...}
output = {1*1, 1*1+1*2, 1*1+1*2+1*3, 1*1+1*2+1*3+1*4,...} = {1,3,6,10,...} 

However, the width and the number of taps should be parameterized.
I have pondered on how to implement a parameterized testbench, and have some questions and preliminary thoughts. I would like to raise them to the forum readers to help me see if they are appropriate. I am pretty new to UVM, so could be entirely off track.

1. dealing with parameterized drivers
Interfaces for parameterized DUT is hard to pass to systemverilog class and maintain using uvm_config_db, so I created a maximum footprint interface nested inside the real DUT interface. At the “uvm_test” level, I do uvm_config_db::set, to pass down the “cfg” variable with parameters.


interface dut_if #(inputWidth=8,outputWidth=10,coefWidth=7firLength=31) (input clk);
	localparam adder_length = int'(firLength/2) + firLength%2;
	logic reset;
	logic [inputWidth-1:0] sample;
	logic [outputWidth-1:0] result;
	logic [coefWidth-1:0] coeff [adder_length-1:0];
	
	max_width_if internal_if(.clk(clk)); // remember to take care of input clk!!!
	assign reset = internal_if.reset;
	assign sample = internal_if.sample[inputWidth-1:0];	
	genvar i;
	generate
		for(i=0;i<adder_length;i=i+1)
			assign coeff[i] = internal_if.coeff[i][coefWidth-1:0];
	endgenerate
	assign internal_if.result = {{(`MAX_WIDTH-outputWidth){1'bz}},result[outputWidth-1:0]};
endinterface

and on the driver side, I do high impedance for the unused bits.


task run_phase(uvm_phase phase);
...
seq_item_port.get_next_item(req);
dut_max_width_vif0.coeff[1] = {{(`MAX_WIDTH-cfg.coef_width){1'bz}},req.coeff[1]};
dut_max_width_vif0.coeff[0] = {{(`MAX_WIDTH-cfg.coef_width){1'bz}},req.coeff[0]};
dut_max_width_vif0.sample = {{(`MAX_WIDTH-cfg.smp_width){1'bz}}, req.sample};
seq_item_port.item_done(req);

Is this a good coding style? the drivers/agents are seemingly reusable, but the test is not. For cases where I have a specific combination of coefficients that I desired to test, I can live with writing new tests. What if I just want a simple case where all inputs(signal samples + coefficients) are purely random? I cannot think of a way to make the test being reusable for different parameters.

2. variable lengths of transactions.
In the above case, the width of transaction entries change together with the DUT parameters. When I create a new sequencer, can I do something like:


sequencer = uvm_sequencer#(my_transaction#(10,10,3,4))::type_id::create("sequencer",this);

with a parameterized my_transaction class?


class my_transaction #(inputWidth,outputWidth,coefWidth,numOfTaps) extends uvm_sequence_items

Transactions never seems to be specifically instantiated from users perspective. Its type is passed to the sequencer, and the sequencer generates them 1 by 1. I am wondering if any methodology can use factory override to create different transactions??

3. deterministic sequences
For starters, we usually test on deterministic sequences instead pure random ones. If the DUT doesn’t work for deterministic ones, we probably don’t have to utilize UVM’s capability of randomizing with super elaborated test cases. To achieve that, I can only think of changing the sequences task body:


task body;
			
			if(starting_phase!=null) starting_phase.raise_objection(this);
				
			for (int i=1;i<=4;i++)
			begin
				req = my_transaction::type_id::create("req");
				start_item(req);
				if(!req.randomize()) 
					`uvm_error("","Randomize failed");
// overwriting the randomized entry in req
				req.sample = i;
				finish_item(req);
			end
			if(starting_phase!=null) starting_phase.drop_objection(this);
				
		endtask:body

But this requires me to rip out the sequence code for other sequences. Any suggestion to make the code more reusable?

thanks a lot in advance

In reply to chuncheng:

have you looked at this paper
http://verificationhorizons.verificationacademy.com/volume-7_issue-3/articles/stream/polymorphic-interfaces-an-alternative-for-systemverilog-interfaces_vh-v7-i3.pdf

In reply to vamacon:
Thanks a lot for the pointer. To rephrase, the author suggests setting the uvm_config_db with a handle to an “abstract virtual class”, instead of a virtual interface. The driver, is implemented with the method provide by the interface, instead of implementing the pin wiggle itself. This is very enlightening. Really appreciate the help!!