By Example: Test-driven Development of Verification IP

I’ve been putting a lot of time into developing new examples for SVUnit lately and as of wednesday last week, I’ve finished another to show how people can use SVUnit to do test-driven development of verification IP.

This particular example involves development of an APB master BFM. APB isn’t the most complicated of bus protocols but it’s a very good subject for an example like this because the code and tests are easy to understand (there’s an APB example in the latest UVM release also, presumably for the same reason).

UPDATE: for people looking at UVM Express that was announced by Mentor Graphics on Feb 22, this is my interpretation of how TDD can be applied at the lowest layer development of the BFM. Examples showing the addition of functional coverage and completion of an agent that includes sequence generation are still to come. You can see UVM Express on the Mentor website by going here.

In terms of requirements and documentation for our BFM, I’ll describe what’s going on with a couple of user stories:

There’s 2 stories that aptly describe this APB master verification IP. The ability to write/read memory of a peripheral and that’s pretty much it.

Here’s the acceptance criteria for both stories:

Because there’s 101 ways to build an APB BFM, the acceptance criteria here is important for narrowing things down. We’re looking for something equally useful for designers and verifiers so we’re not going to use anything fancy like UVM and we’re not going to use a transaction interface or anything constrained-random-like (which always seems to be the impulse of verification engineers). We’re just going to have IP with a simple method user interface on 1 side and pins on the other that could be connected to a peripheral. Arguably, a systemverilog interface is best for this so that’s the direction I went.

I was missing some necessary features for SVUnit so before I got started, I had to add support for generating unit test templates for systemverilog interfaces. I based the template off what I had already done for the module unit test template so it didn’t take long to get that done. With 1 test to verify the template output and 2 others to verify it compiled and simulated in both Questa and VCS, I could move on to the actual example.

The first tests I added to the unit test template were synchronous and asynchronous reset tests. No, they weren’t present on a story card but I thought this would be a good place to start anyway because they’re useful and would require all the pins of the interface but next to no functionality. Here’s one of those tests (fyi… `MST_EXPECT is a macro that checks each of the output ports on the interface. I expect all to be 0 after the reset):

The first time I ran this test, it failed – as I expected – in compilation because none of the pins nor the reset method existed in the interface yet. I added the pins and an empty method and ran it again. That ended in a runtime failure – again as I expected – because the reset method was empty and my FAIL_IF assertions were firing (here, FAIL_IF is hidden in the MST_EXPECT but you’ll see it in the next test). Add the reset functionality in the interface method, run the test a 3rd time and voila… my first passing test and my first complete loop through the TDD cycle:

The next test I wrote had to do with testing the functionality of the select pin for a write, which takes me into implementation of our first user story. Here’s that test:

You see here that I’m calling the write method on the interface and in parallel I’m checking the functionality of the select pin. Running this the first time ends of course with a compilation error because the write method doesn’t exist yet. I add an empty write method, run it again and see the failures from my FAIL_IF assertions again… which is exactly where I want to be. Now I add the code required to control the select. That means adding a state machine that takes the interface from IDLE to SETUP to ENABLE and back to IDLE while the select pin is active in SETUP and ENABLE but inactive in IDLE. Here’s the code I added to do that:

You’ll notice that my definition of just enough includes the write method, the FSM, the logic to control the select for a single write and some synchronization. I have nothing more than that because that’s all I need to pass my test. Run SVUnit again and now I have 2 passing tests: I know I can reset my interface and I know the select works properly for a single write.

One last example to drive it home… the next test I wrote was still focused on the select pin but was verifying it would remain active for 4 cycles in the case of back-to-back write transactions. Here’s the test:

That failed because the BFM couldn’t yet handle the transition from ENABLE back to SELECT. Here’s the update to the BFM that I had to make to pass the test:

Several more baby steps through the TDD cycle eventually led to a completed ABP BFM. Instead of boring you with the rest of the code, I’ll leave you with some statistics of what I ended up with after I was finished.

My APB BFM ended up being ~200 lines long

It includes methods for writing and reading that designers and verifiers would both find easy to use

I ended up with 26 tests in a unit test template that was ~550 lines long

I had 2 tests for each port on the interface to verify it behaved correctly for single read/write transactions and back-to-back read/write transactions

SVUnit tells me in about 4 seconds (of which about 3.5 sec is spent in compilation) whether or not my APB BFM is working properly

Including the addition of interface support to SVUnit, this whole thing took me about 5 hours… from 8pm to 1:30am with a half hour break for tea at midnight 🙂

I’ll summarize by saying that as a beginner, I still find it a little tricky to get into TDD mode where I think in terms of tests first. Once I’m there though, I find it’s a very practical way to write code. And the best part is that you know the code is working as you write it as opposed to debugging it after the fact.

If you’re interested in seeing and running the results, this APB BFM example is currently available to SVUnit early adopters in the examples directory. If you’re not an SVUnit early adopter but you’re interested in becoming one, you can get a hold of me at neil.johnson@agilesoc.com.

3 thoughts on “By Example: Test-driven Development of Verification IP”

Excellent post, and spot-on with regard to unit testing and TDD of HDL. The initial thought I had on the topic is «how do I apply TDD to my RTL», but as you say: TDD is very well suited to the verification and simulation code, which gives you a solid foundation for conventional tests of the RTL design (constraint random, direct etc.).