Classes can be parameterized just like Verilog modules, allowing you to create reusable templates with configurable data widths, depths, and types. The factory pattern centralizes object creation: a factory method creates different transaction types based on a string or enum, eliminating scattered new() calls throughout the testbench.
A parameterized class accepts parameters specified at instantiation:
class fifo #(int WIDTH=32, int DEPTH=16);
logic [WIDTH-1:0] data [DEPTH];
int wr_ptr, rd_ptr;
function new();
wr_ptr = 0; rd_ptr = 0;
endfunction
function void put(logic [WIDTH-1:0] item);
data[wr_ptr] = item;
wr_ptr = (wr_ptr + 1) % DEPTH;
endfunction
endclass
// Instantiation with overrides:
fifo #(.WIDTH(64), .DEPTH(32)) f32 = new();
fifo #(.WIDTH(8)) f8 = new();
A factory method creates objects based on input parameters, centralizing object creation logic. Instead of scattered conditionals throughout the testbench, all creation logic lives in one place:
// WITHOUT factory (scattered): if (type == "read") tr = new read_transaction(); else if (type == "write") tr = new write_transaction(); else if (type == "burst") tr = new burst_transaction(); // WITH factory (centralized): tr = transaction_factory::create(type);
// Base transaction class
class transaction;
rand logic [15:0] addr;
rand logic [7:0] data;
virtual function void display();
$display(" addr=%h data=%h", addr, data);
endfunction
endclass
class read_transaction extends transaction;
logic [7:0] read_data;
virtual function void display();
super.display();
$display(" + read_data=%h", read_data);
endfunction
endclass
class write_transaction extends transaction;
logic [3:0] byte_enable;
virtual function void display();
super.display();
$display(" + byte_enable=%b", byte_enable);
endfunction
endclass
class burst_transaction extends transaction;
int unsigned burst_len;
virtual function void display();
super.display();
$display(" + burst_len=%0d", burst_len);
endfunction
endclass
// ---- FACTORY CLASS ----
class transaction_factory;
// Factory method: creates objects based on type string
static function transaction create(string tx_type);
case (tx_type)
"read": begin
read_transaction rd = new();
return rd;
end
"write": begin
write_transaction wr = new();
return wr;
end
"burst": begin
burst_transaction br = new();
br.burst_len = 4;
return br;
end
default: begin
$fatal(1, "Unknown transaction type: %s", tx_type);
end
endcase
endfunction
endclass
// ---- TESTBENCH USING FACTORY ----
module factory_demo;
initial begin
transaction q[$];
// Create different types using factory
q.push_back(transaction_factory::create("read"));
q.push_back(transaction_factory::create("write"));
q.push_back(transaction_factory::create("burst"));
q.push_back(transaction_factory::create("read"));
// All stored in base class queue, factory handled creation
foreach (q[i]) begin
$display("Transaction %0d:", i);
q[i].display();
end
$finish;
end
endmoduleCombine both for maximum flexibility:
class fifo #(int WIDTH=32, int DEPTH=16);
// ... FIFO implementation
endclass
class fifo_factory;
static function fifo create_fifo(string config);
case (config)
"small": return new fifo#(.WIDTH(8), .DEPTH(16));
"medium": return new fifo#(.WIDTH(32), .DEPTH(64));
"large": return new fifo#(.WIDTH(64), .DEPTH(256));
endcase
endfunction
endclass
class name #(type PARAM=default); — parameters override at instantiation.Classes can have type or value parameters like Verilog modules. Example: class fifo #(int WIDTH=32) specifies a parameter with a default value. At instantiation: fifo #(.WIDTH(64)) f = new() overrides the default.
A static method that creates and returns objects based on input parameters. Instead of scattered conditionals, the factory centralizes all object creation logic in one place.
When many different transaction types or object configurations are created throughout the testbench. The factory eliminates scattered new() calls and makes adding new types easier.