Atomic Generators
Atomic generators select and randomize transactions based on user constraints. If you do not care about what sequence the transactions are generated, then atomic generators are a good choice.
The code below shows a pcie_config class which defines the read and write transactions. It also has two macros defined that create a channel and the atomic generator to drive the channel.
// filename: pcie_config.sv
class pcie_config extends vmm_data;
typedef enum {Read, Write} kind_e;
rand kind_e instruction;
rand bit [31:0] address;
rand bit [31:0] data;
…
endclass: pcie_config
`vmm_channel(pcie_config)
`vmm_atomic_gen(pcie_config, “PCIE Configuration Atomic Generator”)
If you are running functional coverage, there are often corner cases or a sequence of events that will require multiple simulation cycles to be covered. The time to get to these specific points in the state space can be drastically reduced by using scenario generators to generate the specific sequence of events.
Scenario Generators
Scenario generators are useful if you need to constrain your generator to generate a specific sequence of transactions.
Let’s say to configure your PCIE device you need to follow a series of steps.
1. Read the status register
2. Read the control register and
3. Write to the control register
In this case you can use your pcie_config class once again but now define a macro for its scenario generator instead of the atomic generator.
// filename: pcie_config.sv
`vmm_channel(pcie_config)
`vmm_scenario_gen(pcie_config, “ PCIE Configuration Scenario Generator”)
Here again a single channel will be connected to this scenario generator. We now need to define the scenario as shown below.
// filename: pcie_gen.sv
class pcie_control_scenario extends pcie_config_scenario;
// Variable to identify the scenario
int pcie_control_scenario_id
constraint pcie_control_scenario_items {
if($void(scenario_kind) == pcie_control_scenario_id) {
// number of elements in the scenario
length == 3;
// run scenario more than once
repeated == 0;
foreach(item[i])
if(i==0)
this.items[i].instruction == pcie_config:: Read(status_address);
else if(i==1)
this.items[i].instruction == pcie_config:: Read(control_address);
else if(i==2)
this.items[i].instruction == pcie_config:: Write(control_address);
}
}
endclass: pcie_gen
These are also called single stream scenarios. Scenario generators work well for block level testbenches, but if we need to control multiple blocks in the system level testbench to generate specific interactions to improve coverage multi-stream scenario generators should be used.
Multi-stream Scenario (MSS) Generators
In a typical chip there will be more than one type of peripheral. Ex: PCIE, USB, Ethernet. Each needs to be controlled separately with its own scenario generator. But there is often a time when the interface on the PCIE and the USB need to be controlled together. This is where MSS generators are useful. MSS generators can feed transactions to multiple channels, unlike single stream scenario generators and atomic generators which can feed only one.
Figure 1: Multi-Stream Scenarios
Let’s try to create a MSS with the PCIE scenario and the USB scenario together.
1. Use the macros to generate the pcie_config channel and scenario
// filename: pcie_config.sv
`vmm_channel(pcie_config)
`vmm_scenario_gen(pcie_config, “PCIE Configuration Scenario Generation”)
2. Use the macros to generate the usb_config channel and scenario
// filename: usb_config.sv
`vmm_channel(usb_config)
`vmm_scenario_gen(usb_config, “USB Configuration Scenario Generation”)
3. Define scenarios for the PCIE and USB that you want to control. An example for the PCIE control scenario is shown in the previous section.
4. Next extend your scenario from vmm_ms_scenario to put the above defined scenarios together and in the execute task define how to use the above defined scenarios. Directed stimulus for the ETH module can be reused from the block level testbench by encapsulating it in the MSS. The directed stimulus can be cut-and-pasted into the MSS or the MSS could directly call an external function to execute the stimulus.
// filename: my_ms_scenario.sv
class my_ms_scenario extends vmm_ms_scenario;
pcie_control_scenario pcie_control_scenario;
usb_control_scenario usb_control_scenario;
task execute();
fork begin
pcie_config_channel out_chan;
pcie_control_scenario.apply(out_chan);
end
begin
usb_config_channel out_chan;
usb_control_scenario.apply(out_chan);
end
begin
// Directed test code goes here
end
join
endtask: execute
endclass: my_ms_scenario
5. Of course in your top level testbench don’t forget to instantiate your multi-stream scenario and register the various channels that it will use to talk to the PCIE and USB peripherals. You will also need to register the multi-stream scenario generator.
// filename: top_tb.sv
vmm_ms_scenario_gen mss_gen;
my_ms_scenario my_ms_scenario;
pcie_config_chan pcie_config_chan;
usb_config_chan usb_config_chan;
mss_gen = new(“Multi-stream scenario generator”);
my_ms_scenario = new();
pcie_config_chan = new(“PCIE CONFIGURATION CHANNEL”, pcie_config_chan);
USB_config_chan = new(“USB CONFIGURATION CHANNEL”, usb_config_chan);
mss_gen.register_channel(“PCIE_SCENARIO_CHANNEL”, pcie_config_channel);
mss_gen.register_channel(“USB_SCENARIO_CHANNEL”, usb_config_channel);
mss_gen.register_ms_scenario(“MSS_SCENARIO”, my_ms_scenario);
mss_gen.stop_after_n_scenarios = 10;
As can be seen in Figure 1, you can combine scenarios to make multi-stream scenarios, or you can also have higher level multi-stream scenario generators calling other multi-stream scenario generators and directed tests.