DUT Verification

Overview of the general verification process

This section introduces the general process of verifying a DUT based on Picker.

The goal of the open verification platform is functional verification, which generally involves the following steps:

1. Determine the verification object and goals

Typically, the design documentation of the DUT is also delivered to the verification engineer. At this point, you need to read the documentation or source code to understand the basic functions, main structure, and expected functionalities of the verification object.

2. Build the basic verification environment

After fully understanding the design, you need to build the basic verification environment. For example, in addition to the DUT generated by Picker, you may also need to set up a reference model for comparison and a signal monitoring platform for evaluating subsequent functional points.

3. Decompose functional points and test points

Before officially starting the verification, you need to extract the functional points and further decompose them into test points. You can refer to: CSDN: Chip Verification Series - Decomposition of Testpoints

4. Construct test cases

With the test points, you need to construct test cases to cover the corresponding test points. A test case may cover multiple test points.

5. Collect test results

After running all the test cases, you need to summarize all the test results. Generally, this includes line coverage and functional coverage. The former can be obtained through the coverage function provided by the Picker tool, while the latter requires you to judge whether a function is covered by the test cases through monitoring the behavior of the DUT.

6. Evaluate the test results

Finally, you need to evaluate the obtained results, such as whether there are design errors, whether a function cannot be triggered, whether the design documentation description is consistent with the DUT behavior, and whether the design documentation is clearly described.


Next, we will introduce the general verification process usingMMIO read and write of Nutshell Cache as an example:

1 Determine the verification object and goals:
The MMIO read and write function of the Nutshell Cache. MMIO is a special type of IO mapping that supports accessing IO device registers by accessing memory addresses. Since the register state of IO devices can change at any time, it is not suitable to cache it. When receiving an MMIO request, the Nutshell cache will directly access the MMIO memory area to read or write data instead of querying hit/miss in the ordinary cache line.

2 Build the basic verification environment:
We can roughly divide the verification environment into five parts: env

1. Testcase Driver:Responsible for generating corresponding signals driven by test cases 2. Monitor:Monitors signals to determine whether functions are covered and correct 3. Ref Cache:A simple reference model 4. Memory/MMIO Ram:Simulates peripheral devices to simulate corresponding cache requests 5. Nutshell Cache Dut:DUT generated by Picker

In addition, you may need to further encapsulate the DUT interface to achieve more convenient read and write request operations. For details, refer to Nutshll cachewrapper.

3 Decompose functional points and test points
Nutshell cache can respond to MMIO requests, further decomposing into the following test points:

Test Point 1:MMIO requests will be forwarded to the MMIO port Test Point 2:The cache will not issue burst transfer requests when responding to MMIO requests Test Point 3:The cache will block the pipeline when responding to MMIO requests

4 Construct test cases: The construction of test cases is simple. Knowing that the MMIO address range of the Nutshell cache obtained through Creating DUTis 0x30000000~0x7fffffff, we only need to access this memory range to obtain the expected MMIO results. Note that to trigger the test point of blocking the pipeline, you may need to initiate requests continuously.
Here is a simple test case:

# import CacheWrapper here

def mmio_test(cache: CacheWrapper):
	mmio_lb	= 0x30000000
	mmio_rb	= 0x30001000
	
	print("\n[MMIO Test]: Start MMIO Serial Test")
	for addr in range(mmio_lb, mmio_rb, 16):
		addr &= ~(0xf)
		addr1 = addr
		addr2 = addr + 4
		addr3 = addr + 8

		cache.trigger_read_req(addr1)
		cache.trigger_read_req(addr2)
		cache.trigger_read_req(addr3)

		cache.recv()
		cache.recv()
		cache.recv()
		
	print("[MMIO Test]: Finish MMIO Serial Test")

5 Collect test results

'''
    In tb_cache.py
'''

# import packages here

class TestCache():
    def setup_class(self):
        color.print_blue("\nCache Test Start")

        self.dut = DUTCache("libDPICache.so")
        self.dut.init_clock("clock")

        # Init here
        # ...

        self.testlist = ["mmio_serial"]
    
    def teardown_class(self):
        self.dut.finalize()
        color.print_blue("\nCache Test End")

    def __reset(self):
        # Reset cache and devices
            
    # MMIO Test
    def test_mmio(self):
        if ("mmio_serial" in self.testlist):
            # Run test
            from ..test.test_mmio import mmio_test
            mmio_test(self.cache, self.ref_cache)
        else:
            print("\nmmio test is not included")

    def run(self):
        self.setup_class()
        
        # test
        self.test_mmio()

        self.teardown_class()
    pass

if __name__ == "__main__":
	tb = TestCache()
	tb.run()

Run:

    python3 tb_cache.py

The above is only a rough execution process, for details refer to:Nutshell Cache Verify

6 Evaluate the running results
After the run is complete, the following data can be obtained: Line coverage: line_cov

Functional coverage: func_cov

It can be seen that the preset MMIO functions are all covered and correctly triggered.

Last modified September 12, 2024: Fix typo (4b0984f)