Though we use both code and functional coverage to sign-off the design verification, they are not the same. So, you need to understand what is code coverage and how it is used to improve the quality of verification.
What is code coverage?
Code coverage is the coverage data generated from the RTL code by simulator. Looking at this coverage, one can understand how the RTL source code has been exercised by the testbench. So, we need to enable the code coverage metrics like statement, branch, expression, state, arc, sequence, toggle, etc. before running the simulation. Then the simulator will generate code coverage data.
For example, statement coverage indicates how each RTL statement has been executed. If the RTL had 100 lines of source code, but only 90 lines had been executed by the testbench during simulation, then code coverage would be only 90%. In order to improve the coverage up to 100%, we analyze the coverage post simulation and identify the lines which haven’t been executed. Then, we include the test vectors in testbench to trigger/execute those missing statements. This is how we improve the testbench and achieve code coverage closure.
Basically, we use code coverage predominantly to measure the quality of the testbench. For example, very low coverage [10 to 40%] indicates the testbench needs more useful test vectors, good coverage [60-80%] indicates the testbench is good which can be improved further for the coverage closure and more coverage [90% and above] indicates the testbench is highly productive. This is how we can rate the testbench and measure the simulation progress.
We don’t aim for 100% coverage blindly for every metric. Generally, we aim for 100% code coverage for basic metrics like statement, branch, state, etc., but we redefine the threshold and try to achieve coverage for the advanced metrics. For example, we may aim for only 80% branch coverage, 60% FSM arc coverage or 70% toggle coverage. It depends on two main things – design complexity and time available [number of days/weeks/months allocated for simulation].
For example, if the expression has ‘n’ variables, then we would have to try 2^n combinations and the question is how long it would take to achieve 2^n. In this case, we redefine the coverage target based on the number of days available and at the same time how best we can verify and assure that the expression will not break. Maybe 60% coverage is the minimum needed to test the expression’s stability. Usually, experienced verification managers/leads will analyze everything [Time Vs Quality] and define the coverage targets as part of the verification/test plan for the verification engineers like you who would be writing testcases and running regression simulation.
Coverage closure is an iterative process. Though code coverage is meant for improving the testbench quality, still we would be able to identify the design bugs too as part of this iterative process, as shown in the figure below.
In the above example, && [logical AND] is used by mistake, instead of || [logical OR]. It’s a design bug. If the testbench had the test vectors only for TRUE – T[A:1, B:1] and FALSE – F[A:0, B:0] to cover both True and False conditions, can we catch this bug?
In this case, if we apply expression coverage, it would force us to try 2^2 [4 possibilities: 00,01,10,11] and catch this bug through missing test vectors [01 and 10], though branch coverage has already been 100%. This is how code coverage works.