Monitor / driver bus functional model (BFM)

I’ve been using BFMs for my interfaces since I began learning SV/UVM around 3 years ago. We do not perform emulation, which I know is one selling point of using a BFM approach. In case you didn’t know, e.g. the driver BFM has a task which takes a transaction as an argument, and performs the pin wiggling (not the agent driver). Every “interface” instantiation in my top module is cluster of 3 SV interfaces, two of which are BFMs, and the other is simply a bundle of wires. Only the two BFMs are placed into the config DB, for use of the agents.

module dut_top;

LL_if           SysLnp_LL_Rx_if (sys50_if.clk, sys50_if.reset1);
LL_driver_bfm   SysLnp_LL_Rx_drv_bfm(SysLnp_LL_Rx_if);
LL_monitor_bfm  SysLnp_LL_Rx_mon_bfm(SysLnp_LL_Rx_if);
//...
module tb_top;
//...
initial begin
  uvm_config_db #( virtual LL_driver_bfm  )::set( null , "uvm_test_top", SYSLNP_RX_LL_DRV_NAME, dut_top.SysLnp_LL_Rx_drv_bfm ) ;
  uvm_config_db #( virtual LL_monitor_bfm )::set( null , "uvm_test_top", SYSLNP_RX_LL_MON_NAME, dut_top.SysLnp_LL_Rx_mon_bfm ) ;
end

Sometimes I think I would rather have simply a single SV interface (a “traditional” approach). It’s less verbose, there’s a single clocking block defined (instead of defining it both monitor and driver bfm), etc. Thoughts?

We do something similar too, but without any SV interfaces involved. One advantage we see with using module-based BFMs instead of SV interfaces is that we can capture parameter settings and pass them up to the agent via the configuration database. With parameterized SV interfaces, the agent classes would end up being parameterized as well, and we wanted to avoid this proliferation that would become a maintenance problem. With BFMs, the classes do not need to be parameterized more than necessary. Another advantage is that we can reuse existing BFMs that would already contain a good set of assertions and coverage, and we wrap it in a module that provides the linkage to the UVM layer.

In reply to manning999:

I guess your BFM are modelled as modules. SV interfaces behave more or less in the same way as modules do (with a few limitations). The main limitation is you cannot instantiate a module in an interface. But I believe your BFMs are not hierarchical. The key thing is you can implement in interfaces functions and task which can be provided to other blocks connected to. And you don’t have to parameterize if there is no need, but you can pass parameters through the hierarchy using the config_db. Wherea re the problems? Could you please elaborate a little bit more on them?

In reply to manning999:

One advantage we see with using module-based BFMs instead of SV interfaces is that we can capture parameter settings and pass them up to the agent via the configuration database.

I am having trouble picturing why this is the case (that you end up having a parameterized agent). I currently use some parameterized interfaces; I have parameters defined in a test parameters package file, I feed parameters to the SV interface, and my test also has access to these parameters, and can place them into the agent config object(s) (which gets pulled via database by agent during build phase).

In reply to bmorris:

If you have your parameters in a package I do not understand why you have to pass them through the hierarchy using config_db. Importing the whole package or only the parameters you need in a class or a module/interface is completely sufficient.

You guys are right in that you can capture the parameters in the interface and then pass it into the UVM world via the config DB. However, if the interface is parameterized, then the virtual interface handle must also be parameterized, and that would make the agent classes similarly parameterized. If I change the parameter set at the interface, I have to change it in all of the classes, so it can be a maintenance pain unless we do something like factor it out into a macro, which is not desirable. So, I don’t understand how a parameterized interface can be passed into an agent without this ripple effect in the coding.

We reuse BFM modules that contain tasks that have been proven from past experience to handle the pin-level activity of the bus to either drive onto it or sample it for the monitor path, along with a comprehensive set of assertions and coverage. It wouldn’t make sense for us to redo it. Since we cannot instantiate modules in interfaces, we wrap the BFM in another module where a concrete class delegates the pin-level operations to the BFM, and that class is passed to the agent via the config DB. The parameters are captured into a config object as well and that is also passed up the same way.

As for strictly importing a package, I suppose it could work fine, but with multiple instances of the same interface that would have different parameterizations, then you end up with instance-specific params to define in the package. The agent would have to select which set of params to use based on its instantiation. That doesn’t sound appealing to me.

In reply to manning999:

Of course it is costly to compile your module-based approach into a class-based approach. But I’m sure you would get also a lot of benefits, i.e. regarding reusebility etc.
Another approach would be to stay with the non-clas-based approach and defina your BFMs as interfaces. This would mean you have to change the keyword module into the keyword interface.
Hierarchies of interfaces are allowed.

In reply to manning999:

You guys are right in that you can capture the parameters in the interface and then pass it into the UVM world via the config DB. However, if the interface is parameterized, then the virtual interface handle must also be parameterized, and that would make the agent classes similarly parameterized. If I change the parameter set at the interface, I have to change it in all of the classes, so it can be a maintenance pain unless we do something like factor it out into a macro, which is not desirable. So, I don’t understand how a parameterized interface can be passed into an agent without this ripple effect in the coding.

Ok I gotcha, you are using the abstract/concrete Class method instead of virtual interfaces. I’ve never used it before.

Yes, I follow! I was confusing in what I wrote… My “wire bundle” SV interface is generally parameterized (set bus widths, num of devices, etc), but I’ve never had parameterized BFMs (which are the only vIFs I place into the interface). So long story short, I’ve neither used parameterized interfaces that are used by agents, nor had a legacy BFM to plug into our custom busses. Sorry for the confusion.

As for strictly importing a package, I suppose it could work fine, but with multiple instances of the same interface that would have different parameterizations, then you end up with instance-specific params to define in the package.

Let’s say you have 2 parameterizations of the same legacy BFM. For each instance, you will have a: 1. concrete object handle 2. collection of parameters for that unique parameterization. Where do you actually define the numbers/parameters for each BFM instance?

In reply to chr_sue:

I believe he is using the abstract/concrete Class approach because his BFMs are parameterized. It prevents the agents from being parameterized as well. Not the agents themselves, but the virtual interface handles they would have to call.

In reply to bmorris:

Yes, my bad, should’ve been up-front about the name of the method - abstract/concrete. And, not a problem about any hint of confusion. We do have BFMs that will live on and will unlikely undergo a major re-architecture such as to convert to interfaces, so we have to adapt.

The actual parameter settings are made at the BFM module instances. The values can be params themselves that are instance-specific and defined/maintained in a package for the testbench, like what chr_sue suggested. For example, the package might have BUS1_DATAW=64 and BUS2_DATAW=512, and those parameters would be used to override the BFM’s DATAW parameter at their respective instantiations in the module testbench. Hope that’s clear.

In reply to manning999:

Right on! That is where I was headed. You might have…

package test_params_pkg;
  parameter BUS1_DATAW = 64;  
  parameter BUS2_DATAW = 512; 
  //...
endpackage

And then you pipe those values into each instantiation of your wrapper modules, which in turn pipe down to your BFM module. Then the concrete object provides only a (non-parameterized) functional API to the transactors. This is the first time it’s really clicked for me; sweet! thanks for poking my brain.

So that only difference that leaves is where, and with what the database is populated. I believe you are letting your protocol wrapper modules self-populate the concrete class handles into the database.

uvm_config_db #( BusType_abstract_c )::set(
null , “uvm_test_top”, “BUS_API_H”, BusType_concrete_c ) ;

Using this setup, my test_base would perform a db get(), and populate a handle in an agent configuration object.

So far so good. The parameters themselves though, I don’t believe you need to use the database to deliver these to their final destination. Your same parameter package can be shared with your test, which can simply inject them into your agent’s config object (which then gets put into db).

I’m always looking for ways to skimp out of using the database for performance (not that it makes much difference for a bunch of one-shot parameters).

In reply to chr_sue:

You are correct; the parameters are never directly placed into database. they are pulled by the test from the imported package, and placed into a configuration object. This object is placed into the database.

In reply to bmorris:

Why are you doing this, storing parameters available from a package into the database? It is not needed because you can import this package or the parameters required in any component or object.

You can certainly import the test package, and the env can assign the values to each agent instance without going through the config DB. Probably would look something like


class my_env extends uvm_env;
    :
    my_agent m_agent1;
    my_agent m_agent2;
    :
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);

        m_agent1.dataw = BUS1_DATAW;
        m_agent2.dataw = BUS2_DATAW;
        :
    endfunction : build_phase
    :
endclass : my_env

With the config DB, you can encapsulate the agent/bus configuration in an object. Passing it through the config DB only involves passing the object handle, so I think that any performance impact is very negligible. The object can also have methods to provide an API to the agent for using the param values to configure itself, separating intimate knowledge of the parameter set at the bus interface from the configuration knobs of the agent. I believe this sort of approach can offer greater flexibility, scalability, and resuability.

Not saying one way is more correct than the either. It all depends on the user’s particular needs. I personally lean more towards object-oriented methods.

In reply to chr_sue:

This can work, but you are taking a hit on flexibility; consider this…

The component retrieves it’s configuration object via a dB get(); the config object’s members can be set to take a default value. The test can do the following actions to a component config object: randomize, manually populate values (from a parameters package), or do nothing (default behavior); the component will function in any of those 3 scenarios. A simple package import only supports static values.

Also at integration level, if you had multiple agents of the same type, I don’t see how they would know how to pull different sets parameters for a particular “configuration”.

In reply to manning999:

The kicker is that the config object the agent/component must be fully populated (at least all parameters that are used during the agent’s build_phase), and available to the agent before the agent is constructed. The agent must know, e.g., if it must build a sequencer and driver. Once it’s built, there’s no way for the env, or any other component to set variables to affect it’s build phase.

The dB solves this issue because the agent automatically populates it’s configuration object at the beginning of the build_phase.