The difficulty in solving this problem comes from the lack of visibility into DUT’s internal states, which means the solution should count only on the events/packets driven/monitored on the DUT’s interfaces. That being said it means the protection mechanism is not cycle-accurate and the resource locking and freeing is done in a “pessimistic” way (i.e. lock resources early and free them late).
The complete solution can be downloaded from GitHub.
The Device Under Test
Let’s consider a simple DUT and verification environment like the one in Fig. 1.
One should keep in mind that the following specification covers a particular kind of design and it is not aiming to be valid for any DUT.
MESSAGE packets have a fixed size and they are written into the RAM at specific addresses. They can be buffered until RAM interface is available for MESSAGE write operations.
READ packets are forwarded as read requests on RAM bus, the RAM response is buffered until input interface becomes available and then sent back to input agent as a READ RESPONSE packet. In my case, the DUT prioritizes the accesses to RAM interface in favor of READ packets. The DUT is expected to receive READ packets at a very low pace.
The DUT contains two FIFO’s for different data paths:
- the FIFO_MSGS stores up to 16 MESSAGE packet descriptors of fixed size
- the FIFO_RESP stores the payload of READ responses (i.e. can vary from one READ to another), up to 4096 Bytes
The rest are UVM components or sequences.
The Solution(s)
The solution can be implemented in at least two ways:
Aspect | Solution 1 | Solution 2 |
---|---|---|
Singleton | SystemVerilog class | Inherits uvm_object |
Resource Counting | SystemVerilog semaphore | uvm_objection |
To keep it short this post details only the Solution 1 implementation, while the Solution 2 implementation will be presented in a follow-up post. Both implementations can be downloaded from GitHub.
Step 1. Define enumeration items that identify the two FIFOs.
typedef enum {FIFO_MSGS, FIFO_RESP} fifo_t;
Step 2. Implement the singleton object using a SystemVerilog class.
class fifo_protection;
// singleton instance
static local fifo_protection m_inst;
// Constructor
protected function new();
endfunction
// Retrieves the singleton instance
static function fifo_protection get();
if(m_inst == null)
m_inst = new();
return m_inst;
endfunction
endclass
Step 3. Add the semaphores
Add the semaphore list which contains one semaphore for each FIFO. Add other utility fields which enable the semaphores or specify their resource limit.
// list of semaphores: one for each FIFO
semaphore semaphores[fifo_t];
// enable flag that allows us to enable/disable fifo protections
bit sm_en[fifo_t];
// list of fifo limits
int sm_limit[fifo_t];
Step 4. Add semaphore initialization function
function void init_protection(fifo_t kind, int limit, bit en);
semaphores[kind] = new(limit);
sm_limit[kind] = limit;
sm_en[kind] = en;
endfunction
// Enables/disables the given FIFO semaphore
function void set_enable(fifo_t kind, bit en);
sm_en[kind] = en;
endfunction
Step 5. Add resource locking/freeing API
function void free(fifo_t kind, int nof_keys);
if (!sm_en[kind])
`uvm_warning("FIFO_PROT_FREE_WRN", $sformatf("The semaphore %s isn't enabled.",kind.name()))
semaphores[kind].put(nof_keys);
endfunction
task lock(fifo_t kind, int nof_keys);
if (!sm_en[kind])
`uvm_warning("FIFO_PROT_LOCK_WRN", $sformatf("The semaphore %s isn't enabled.",kind.name()))
semaphores[kind].get(nof_keys);
endtask
function int try_lock(fifo_t kind, int nof_keys);
if (!sm_en[kind])
`uvm_warning("FIFO_PROT_TRYLOCK_WRN",$sformatf("The semaphore %s isn't enabled.",kind.name()))
return semaphores[kind].try_get(nof_keys);
endfunction
Step 6. Add semaphore status API
function int get_resource_count(fifo_t kind);
get_resource_count = 0;
for (int i=1; i<=sm_limit[kind]; i++) begin
if (semaphores[kind].try_get(i) == 0)
break;
get_resource_count = i;
semaphores[kind].put(i);
end
endfunction
function bit are_all_free();
int is_free[$];
fifo_t ft = ft.first();
are_all_free = 1;
forever begin
are_all_free &= (get_resource_count(ft) == sm_limit[ft]);
if ( ft == ft.last )
break;
ft = ft.next;
end
endfunction
Step 7. Add debug API
function string dump();
fifo_t ft = ft.first();
dump = "";
forever begin
dump=$sformatf("%s%s=%d/%d, ", dump, ft.name(), get_resource_count(ft), sm_limit[ft]);
if ( ft == ft.last )
break;
ft = ft.next;
end
dump=$sformatf("fifo_protection available resources: %s", dump);
endfunction
Step 8. Create a global variable for the singleton
fifo_protection fifo_prot_ston = fifo_protection::get();
Step 9. Initialize protections
One can initialize protections for various FIFOs in the build_phase() of one's verification environment:
fifo_prot_ston.init_protection(FIFO_MSGS, 16, 1);
fifo_prot_ston.init_protection(FIFO_RESP, 4096, 1);
The limit argument (2nd argument) represents the upper limit of the FIFO.
The granularity is either packets or bytes/words/etc:
- FIFO_MSGS contains packet descriptors of fixed size so the semaphore limit represents packets (in our case 16 packets)
- FIFO_RESP contains whole packets whose size can vary so the limit argument represents bytes/words/etc (in our case 4096 Bytes)
Step 10. Lock and free resources
In order to protect the FIFOs there are two important steps:
- lock resources before the packet gets into its FIFO
- free resources after the data flow corresponding to the FIFO is checked
The points in time when the resources are locked/freed are critical for this mechanism to provide protection against overflow, so one should do a data flow analysis to determine the right moments( see more here). In Fig. 2 I represented graphically the data flows for READ and MESSAGE packets, the calls to scoreboard and to the fifo_protection object in order to ease the understanding of data flows and of the points in time were the resources are locked/freed.
Fig. 2. Data Flow Analysis
For MESSAGE data flow one needs to:
- lock 1 resource just before sending a MESSAGE (in MESSAGE sequence, just before `uvm_do)
- release 1 resource as soon as the RAM write access is verified against the original MESSAGE packet
// lock resources in MESSAGE sequence, just before `uvm_do
fifo_prot_ston.lock(FIFO_MSGS, 1);
`uvm_do(msg_item)
……………………
// free resources in scoreboard, right after the RAM-write access is verified
fifo_prot_ston.free(FIFO_MSGS, 1);
Similarly for READ data flow one needs to:
- lock “number of READ bytes” resources as soon as the input monitor detects a READ request. Given my particular example the resource locking could be done later than MESSAGE resource lock.
- Free “number of READ bytes” resources after scoreboard checks the READ response going out on the input interface
// lock resources in scoreboard as soon as a READ request is detected on the input interface by the input monitor
fifo_prot_ston.lock(FIFO_RESP, read_req.size);
……………………
// free resources in scoreboard, right-after the READ-response on input interface is verified
fifo_prot_ston.free(FIFO_RESP, read_resp.size);
How to disable the protection
There might be scenarios that require to overfill one or more of the FIFOs. In that case it is sufficient to disable the semaphores for the given FIFO using the dedicated API:
fifo_prot_ston.set_enable(FIFO_RESP, 0);
It is recommended to disable one FIFO at a time and have different tests for each FIFO overflow scenario.
To be continued...until then feel free to comment or present your own approach for this kind of problems.
2 Responses
Hi Stefan,
I think the fact that all your functions take ‘kind’ as an argument is a hint that you have multiple objects trying to come out. Instead of using the singleton, you could have a ‘fifo_protection’ class that is instantiated multiple times. There’s no need to define an enumeration to identify the FIFO being accessed and the approach can be extended to any number of FIFOs (without using parameterization of the ‘kind’ type or any other tricks).
Is the idea behind the singleton to allow for easier debug? This can probably still be achieved by having a granular API for each FIFO protection object and a simply looping over all objects.
Hi Tudor,
You’re right: I could get a similar effect by using a list of semaphore/objection-wrapper objects (i.e. fifo_protection as you called them) instantiated multiple times either in a list or in various verification components/environment.
Why I went the path presented in the post? Well, well, here we go:
A side comment regarding resource usage cross coverage: it’s not cycle-accurate, but it’s good enough for scenario debug purposes.