OSVVM FSM Coverage Modelling
In my earlier post I discussed how you could get around the pre-VHDL-2008 scoping rules by using external names, in this post we will look at using an external name to help us write an OSVVM functional coverage model for the Blackjack state machine shown in figure 1:
The aim of our functional model is to create a coverage bin for each state transition so we can ensure that during our tests, every transition is exercised. We can do that by using cross-coverage, but more on that later. The first step would be to create a coverpoint for the state machine:
shared variable FSM_CP : CovPType; --FSM states coverpoint
This is created as a shared variable as it will be used by two processes in the testbench, firstly in the coverage model and secondly in the coverage reporter. So next we need to create the coverage model, as previously mentioned this will be created using cross coverage, using the AddCross method of FSM_CP:
FSM_CP.AddCross("Begin_g to Hit_me",
GenBin(BlackJack_type'POS(Begin_g)),
GenBin(BlackJack_type'POS(Hit_me)));
Note, that we use the POS attribute to give us the integer position of the state label in the type BlackJack, we are also using the name : string parameter to pass the transition name to AddCross, this will improve the reporting later on.
The complete coverage model for the FSM transitions is as follows:
COVERAGE_COLLECTOR: process
variable PREV_FSM_STATE : BlackJack_type;
begin
-- Initialise coverage model
Message("Creating state trans bins for FSM.");
FSM_CP.SetName("FSM Transistions");
-- Begin_g
FSM_CP.AddCross("Begin_g to Hit_me",
GenBin(BlackJack_type'POS(Begin_g)),
GenBin(BlackJack_type'POS(H1_Hit_me)));
-- Hit_me
FSM_CP.AddCross("Hit_me to Got_im",
GenBin(BlackJack_type'POS(H1_Hit_me)),
GenBin(BlackJack_type'POS(H1_Got_im)));
-- Got_im
FSM_CP.AddCross("Got_im to test16",
GenBin(BlackJack_type'POS(H1_Got_im)),
GenBin(BlackJack_type'POS(H1_test16)));
-- test16
FSM_CP.AddCross("test16 to Test21",
GenBin(BlackJack_type'POS(H1_test16)),
GenBin(BlackJack_type'POS(Test21)));
FSM_CP.AddCross("test16 to Hit_me",
GenBin(BlackJack_type'POS(H1_test16)),
GenBin(BlackJack_type'POS(H1_Hit_me)));
-- Test21
FSM_CP.AddCross("Test21 to BustState",
GenBin(BlackJack_type'POS(Test21)),
GenBin(BlackJack_type'POS(BustState)));
FSM_CP.AddCross("Test21 to HoldState",
GenBin(BlackJack_type'POS(Test21)),
GenBin(BlackJack_type'POS(HoldState)));
FSM_CP.AddCross("Test21 to TenBack",
GenBin(BlackJack_type'POS(Test21)),
GenBin(BlackJack_type'POS(TenBack)));
-- TenBack
FSM_CP.AddCross("TenBack to test16",
GenBin(BlackJack_type'POS(TenBack)),
GenBin(BlackJack_type'POS(H1_test16)));
-- Mark rest as illegal.
FSM_CP.AddCross( ALL_ILLEGAL, ALL_ILLEGAL);
wait until falling_edge(SYS_CLK);
PREV_FSM_STATE := FSM_STATE;
loop
-- Collect state transitions.
wait until falling_edge(SYS_CLK);
-- Integer vector used to represent transitions.
FSM_CP.ICover( (BlackJack_type'POS(PREV_FSM_STATE), BlackJack_type'POS(FSM_STATE)));
PREV_FSM_STATE := FSM_STATE;
end loop;
end process COVERAGE_COLLECTOR;
This functional coverage model relies on the signal FSM_STATE this is the value of the next state variable within the state machine. Obviously, we don’t have direct access to this, from the testbench it is down several levels of hierarchy, so we need to use an external name, in this case with an alias as below:
alias FSM_STATE is <<signal .bjack_tb.UUT.I6.BlackJack : BlackJack_type >> ;
Note, that for this to work the state type BlackJack_type must be in a package shared between the test bench and the state machine.
The final thing to complete the solution is to add a process to dump the functional coverage results to the simulation console at the end of simulation. It should be noted that if you are using an Aldec simulator (Active-HDL or Rivera-PRO) the coverage data can be added to the coverage database and can be used with your test plan.
COVERAGE_REPORT: process
begin
wait until END_TEST ;
for I in BlackJack_type'LOW to BlackJack_type'HIGH loop
Message( integer'image(BlackJack_type'POS(I)) & " -> & BlackJack_type'IMAGE(I) );
end loop;
Message("End of test.");
Message("TAP FSM Coverage " & TO_STRING(FSM_CP.GetCov) & "% Cycles " & integer'image(CYCLE) & "." );
Message("Generating Coverpoint report for TAP FSM.");
FSM_CP.writebin;
FSM_CP.WriteCovDb ("tap_osvvm_db.txt", OpenKind => WRITE_MODE );
wait;
end process COVERAGE_REPORT;
In this article, we have only looked at the items required to produce the coverage model for the state machine. The finished testbench will have additional functionality to generate clocks and resets as well as a stimulus generator, the latter could use simple directed tests or constrained random stimulus. In some cases, using directed tests to cover items defined in the test specification as one testbench then constrained random to collect the data for the unplanned and unexpected input transitions. The functional coverage for both these tests can then be combined to give an overall coverage figure.
This coverage model is quite low level as we are capturing changes within the design hierarchy, typically, we would want to capture functional coverage at the transaction level, but maybe that could be the subject of a later post.
To finish this article we have Figure 2, a block diagram of the finished testbench:
I hope you have enjoyed this post and found it useful. Keep safe until the next time!
2 Comments
Leave a Reply
You must be logged in to post a comment.
Miroslav Marinkovic
Hi David,
Thanks for nice post, it is quite useful.
I have a few comments:
1. If we want fewer lines of the code, we can combine Bins using concatenation &, for example :
— Test21
FSM_CP.AddCross(“Test21”, GenBin(BlackJack_type’POS(Test21)),
GenBin(BlackJack_type’POS(BustState)) & GenBin(BlackJack_type’POS(HoldState)) & GenBin(BlackJack_type’POS(TenBack)));
2. As an alternative to the approach in the post,
it is good idea to separate the coverage model and the coverage collector in two processes:
COVERAGE_MODEL: process
begin
— Begin_g
FSM_CP.AddCross(“Begin_g to Hit_me”,
GenBin(BlackJack_type’POS(Begin_g)),
GenBin(BlackJack_type’POS(H1_Hit_me)));
.
.
.
— Mark rest as illegal.
FSM_CP.AddCross(ALL_ILLEGAL, ALL_ILLEGAL);
wait;
end process COVERAGE_MODEL;
COVERAGE_COLLECTOR: process
constant COMBINATIONAL_DELAY : time := 1 ns;
alias current_state is <>;
alias next_state is <>;
begin
wait until rising_edge(clk);
wait for COMBINATIONAL_DELAY;
— update coverage information
v_fsm_transition_coverage.ICover( (BlackJack_type’POS(current_state), BlackJack_type’POS(next_state)) );
end process COVERAGE_COLLECTOR;
Regarding the external names used in COVERAGE_COLLECTOR process, it is assumed that RTL code of the FSM contains the process for FSM state register.
Something as:
STATE_REG: process(clk, reset_n)
begin
if reset_n = ‘0’ then
current_state <= Begin_g;
elsif rising_edge(clk) then
current_state <= next_state;
end if;
end process STATE_REG;
Best regards,
Miroslav
David Clift
Hi Miroslav,
Thank you for your kind comments and encouragement, I am glad my post was helpful, hopefully you will find my upcoming post useful as well.
With regards to your questions:
1. Could you use concatenation to shorten the code? Probably, I haven’t tried it. Typically I tend to write things out longhand so to speak, as you see here each stage is clearly separated. This helps with understandability and maintainability.
2. Using two processes once for the coverage model and another for the coverage collector, the important thing is to ensure all the collection bins are built at time zero, this happens when the process gets evaluated at time zero (All process run to their first wait). I am not sure of the benefit of splitting the coverage model into two parts.Maybe I will discuss this with Jim next time we speak
Keep safe
David…