Looking for the best & elegant way of handling the situation I have, in SVTB.
DUT has an input GMII interface and a CPU interface. Generator at GMII i/f generates packets in the routine say GMII.RUN(). So is CPU.RUN() does the initial configuration of DUT. There is ENV class, which has BUILD() routine. ENV.BUILD() builds GMII and CPU. ENV.RUN() forks GMII.RUN and CPU.RUN(). CPU.RUN() has functionality to monitor various registers at various points in time and varies from test to test.
GMII related code is in <>/behavioral/GMII, CPU is in <>/behavioral/CPU. test-case is in <>/test/basic/test_1.sv. In <>/test/basic directory, say there are abt 10 tests. How one can modify the CPU.RUN() from test-case which is embedded 2 levels below ? I do have the handle to ENV in my test-case.

I gather that you are not using OVM or UVM, although you seem to be using the same sort of structure.
One way to do this, is to make all the test cases extend a base class with a set of common methods. For the sake of argument lets call one of the methods run_cpu_test() and this is populated for each test with the code that you want to run. Then what you need to do is something like:
In the test_case:
class test_case extends test_case_base;
ENV env_h;
// Setting up a handle in the CPU for the test
function void setup_test;
env_h.CPU.test = this;
endfunction: setup_test
task run_cpu_test(virtual CPU_IF CPU);
// Stuff that does what ever with the CPU interface
endtask
endclass: test_case
In the CPU:
class CPU;
test_case_base test;
virtual CPU_IF CPU; //
task RUN();
test.run_cpu_test(CPU);
endtask: RUN
endclass
You don't describe how you order the creation of the classes and the control of the test case, but I assume that you'll see that there is a back-pointer to the test_case from the CPU.
You are right, I 'm using PURE SVTB with UVM/VMM like hierarchy, with no OVM/UVM. I think I better give my code snippets (it's not complete, but gives an idea of TB), for a better understanding. It could be verbose, will do my best to get the point across.
In this TB architecture, wondering the elegant way of controlling the CPU.RUN() in "test-case" as CPU.RUN() will have to change from test-to-test.
Initially, I was thinking of controlling the handle of CPU in test-case. So that CPU_EXTEND.RUN() coded in test-case and assigning the CPU_EXTEND handle to CPU base handle, make CPU_EXTEND.RUN(), run in ENV.RUN()
Appreciate your inputs in context to the TB arch I have
//File: env.sv class env cfg cfgPkt; genGmii genGmiiPkt cpu cpuDrv function new() { this.cfgPkt = new(); } function build() { genGmiiPkt = new(); genGmiiPktDrv = new(); // not captured here, as it is not relevant cpuDrv = new(); } function configure() { cfgPkt.randomize() } task reset() { cpudrv.reset(); gmiiDrrv.reset() } function run() { fork genGmiPkt.run cpuDrv.run join } endclass// File: obj_TestBase class obj_TestBase; env envPkt; virtual interface cpuPort virtual interface gmiiPort function new( input interface cpuPort, input interface gmiiPort); function preRun() { envPkt.configure() envPkt.build(); } function run() { envPkt.reset() envPkt.run() } endclass