Classes, Inheritance, and Polymorphism | FPGA Pour Tous
Summary
Classes, Inheritance, and Polymorphism
Model test transactions, understand handles, and extend behavior without copying an entire environment.
A test object, not a circuit
A class describes data and the methods that operate on it. It is dynamically allocated and normally not synthesizable. In a testbench, a class often represents a transaction, generator, or checking component.
class bus_transaction; rand bit write; rand bit [15:0] address; rand bit [31:0] data; function new(bit [15:0] address = '0); this.address = address; endfunction virtual function void display(); $display("write=%0b address=%04h data=%08h", write, address, data); endfunction
endclass
The class declaration defines a type. The object exists only after new:
tr is a handle, similar to a reference. Assigning tr to another handle does not copy the object. Both handles then refer to the same instance.
The constructor initializes the object. It should neither start a thread nor call randomize(): a timed run() method and the generator make those actions visible and controllable.
Inherit for a real specialization
A derived class receives the fields and methods of its base class.
class error_transaction extends bus_transaction; bit inject_parity_error; function new(); super.new(); inject_parity_error = 1'b1; endfunction virtual function void display(); super.display(); $display("inject_parity_error=%0b", inject_parity_error); endfunctionendclass
Inheritance fits when the derived class truly remains a form of the base class. Avoid a deep hierarchy created only to share a few lines of code.
Because display is virtual, the call uses the error_transaction implementation. A generator or driver can work with the base type while accepting specialized variants.
Without virtual, method selection would follow the handle type instead of the actual object type.
Copying and lifetime
A handle assignment copies only the reference. new original does create a new object, but handles stored inside that object remain shared: this is a shallow copy. A deep copy must recreate every mutable subobject as well.
For a transaction with no subobjects, an explicit method added to bus_transaction is sufficient:
virtual function bus_transaction copy(); bus_transaction result = new(); result.write = write; result.address = address; result.data = data; return result;endfunction
If the transaction contains a header handle, for example, result.header = header would remain shallow. Call header's copy method and apply the same rule to queues or arrays of handles. The copy, compare, pack, and unpack methods must all process the same fields.
The blueprint pattern
A mailbox transports handles. Reusing and then rerandomizing the same object while the driver is processing it therefore changes a transaction that is already “in flight.” A robust generator keeps a template object, or blueprint, and sends an independent copy:
task run(); bus_transaction blueprint = new(); repeat (100) begin if (!blueprint.randomize()) $fatal(1, "Randomization failed"); gen_to_drv.put(blueprint.copy()); endendtask
Creating a new object on every iteration also prevents sharing, but resets the history of a randc field. Randomizing the blueprint and then copying it preserves both that history and the independence of the transactions sent.
Downcasting safely
A base handle can receive a derived object directly. The reverse operation requires $cast, and its result must be checked:
error_transaction err;if (!$cast(err, base_handle)) $fatal(1, "The transaction is not an error_transaction");
Do not hide this cast in an assertion when it is required for operation: a simulation configuration can disable assertion evaluation.
Objects are reclaimed when no handle refers to them. Circular references and unnecessarily retained handles make long-running environments harder to debug.
Key takeaways
A class variable is a handle and new creates the object.
Copying a handle does not duplicate the object.
A deep copy also recreates mutable subobjects.
Randomize the blueprint, then pass an independent copy.
Inheritance represents a real specialization of the base type.
A virtual method lets the actual object type select the implementation.
Classes belong in testbenches, not ordinary synthesizable RTL.