Day 24 — SystemVerilog OOP II: Extensible Transaction Hierarchy
Implement polymorphic read/write transactions, access control, parameterized classes, composition, manual factory creation and deep-copy verification.
Concept lesson
Understand the language feature before coding
Day 24 — SystemVerilog OOP II
Simulation status (28 Oct 2026): The commands below pass on Verilator 5.052. Icarus Verilog 12 does not support classes. On 28 Oct 2026 two lint warnings that stopped the Verilator build were fixed in the testbench. Details: Week 5 validation record (VALIDATION.md).
Duration: 2 h theory + 2 h guided + 2 h independent + 1 h debug.
Workspace: project_workspace/week5_systemverilog/day24_oop_ii
Guided work
- Implement base transaction and derived read/write transactions.
- Override
displayandcompare. - Demonstrate derived object via base handle and virtual dispatch.
- Implement a static instance counter.
Independent work
- Compare protected/local access.
- Parameterize a packet transaction.
- Compose a nested transaction envelope and implement independent deep copy.
- Add an abstract virtual base and a manual transaction factory.
Debug challenge
Remove virtual from a base method in a temporary copy, reproduce incorrect dispatch, restore it, and capture before/after evidence. Compare shallow handle assignment to clone() and deep_copy().
Commands (pass on Verilator 5.052)
mkdir -p sim logs
verilator --binary --timing --top-module day24_oop_ii tb/day24_oop_ii.sv -o day24_sim
./obj_dir/day24_sim | tee logs/day24.log
Submission
Source, simulator version, compile/run logs, DAY24 PASS, class diagram, bug evidence and corrected rerun.
Scope: Week 5 language/OOP only; no UVM or Week 6 transaction-based verification framework.
Visual explanation
Connect syntax with simulator behavior
Worked example
Read and run the supplied SystemVerilog source
Example source · Day 24
// Day 24 — advanced OOP. Requires simulator support for SV classes.
virtual class transaction;
protected int unsigned address;
local int unsigned tag;
static int created = 0;
function new(int unsigned a=0, int unsigned t=0);
address=a; tag=t; created++;
endfunction
pure virtual function string kind();
virtual function string display();
return $sformatf("%s addr=%0d tag=%0d",kind(),address,tag);
endfunction
virtual function bit compare(transaction rhs);
return rhs != null && kind()==rhs.kind() && address==rhs.get_address() && tag==rhs.get_tag();
endfunction
function int unsigned get_address(); return address; endfunction
function int unsigned get_tag(); return tag; endfunction
pure virtual function transaction clone();
endclass
class read_txn extends transaction;
function new(int unsigned a=0, int unsigned t=0); super.new(a,t); endfunction
virtual function string kind(); return "READ"; endfunction
virtual function transaction clone(); read_txn c=new(address,get_tag()); return c; endfunction
endclass
class write_txn extends transaction;
int unsigned data;
function new(int unsigned a=0, int unsigned t=0, int unsigned d=0);
super.new(a,t); data=d;
endfunction
virtual function string kind(); return "WRITE"; endfunction
virtual function string display(); return $sformatf("%s data=%0d",super.display(),data); endfunction
virtual function bit compare(transaction rhs);
write_txn w;
if (!$cast(w,rhs)) return 0;
return super.compare(rhs) && data==w.data;
endfunction
virtual function transaction clone(); write_txn c=new(address,get_tag(),data); return c; endfunction
endclass
class packet #(int WIDTH=8);
bit [WIDTH-1:0] payload;
function new(bit [WIDTH-1:0] p='0); payload=p; endfunction
endclass
class envelope;
transaction child;
function new(transaction t=null); child=t; endfunction
function envelope deep_copy();
envelope c=new(child==null ? null : child.clone()); return c;
endfunction
endclass
module day24_oop_ii;
function automatic transaction make_txn(bit is_write);
if(is_write) begin
write_txn w=new(5,2,99); return w;
end else begin
read_txn rd=new(5,2); return rd;
end
endfunction
initial begin
transaction t, copy_t;
write_txn w;
envelope a,b;
packet #(16) p;
int failures;
failures=0;
t=make_txn(1);
if(t.kind()!="WRITE") failures++;
if(!$cast(w,t)) failures++;
if(w.data!=99) failures++;
if(t.display()!="WRITE addr=5 tag=2 data=99") failures++;
copy_t=t.clone();
if(!t.compare(copy_t)) failures++;
w.data=100;
if(t.compare(copy_t)) failures++;
a=new(t); b=a.deep_copy();
if(a.child==b.child) failures++;
if(!a.child.compare(b.child)) failures++;
p=new(16'hCAFE);
if(p.payload!==16'hCAFE) failures++;
if(transaction::created<3) failures++;
// An intentionally nonvirtual base method would fail dynamic-dispatch tests.
if(failures!=0) $fatal(1,"DAY24 FAIL count=%0d",failures);
$display("DAY24 PASS");
$finish;
end
endmodule
Compile and run
mkdir -p sim logsverilator --binary --timing --top-module day24_oop_ii tb/day24_oop_ii.sv -o day24_sim./obj_dir/day24_sim | tee logs/day24.log
What to verify
- base/read/write transactions
- inheritance
- method override
- polymorphism
- virtual dispatch
- virtual class
- static member
- protected member
- local member
- parameterized class
- composition
- deep copy
- hierarchy compare
- manual factory
- override bug debug
Pass condition: All tests pass; derived behavior dispatches via base handle, nested copy remains independent, injected override defect is detected and corrected; output DAY24 PASS.
Hands-on practice
Complete the exercises in order
Written assignment
- Draw base/derived transaction UML and dispatch diagram.
- Explain polymorphism versus object slicing/aliasing in SV handle semantics.
- Document access-control behavior and class parameter choices.
- Capture failing override test, patch, passing regression, simulator/version.
Debug challenge
Introduce an omitted virtual override and show wrong base dispatch; correct the override, then prove deep copy does not alias nested objects.
Completion criteria
11 daily-plan activities and all 17 SystemVerilog lab topics mapped; virtual dispatch, factory creation, deep copy and override debug checks pass.
Knowledge check
Check your understanding
Why must a base method be virtual for dynamic dispatch through a base-class handle?
Virtual dispatch selects the derived implementation using the actual object type; nonvirtual dispatch uses the declared handle type.
What is the distinction between protected and local class properties?
Protected is accessible within the class and derived classes; local is restricted to its defining class.
Why must a deep-copy method copy a composed child object rather than just its handle?
Copying only the handle aliases nested mutable state, so mutations affect both objects.
What does a static class member represent?
One class-level value shared by all instances of that class specialization, rather than a separate value per object.
What does $cast check during a downcast?
Whether the run-time object type is compatible with the destination handle type.
Why use a virtual base class or pure virtual method?
To define a common contract that derived classes must implement.
Evidence to retain
- SV source and reproducible run command
- PASS/FAIL test log
- polymorphic dispatch proof
- deep-copy independence proof
- override bug before/after evidence
- class hierarchy explanation