SVUnit 101 For Designers

If you read my last post, Why Use SVUnit?, you’ll see that someone responding to my announcement about SVUnit v0.1 on verificationguild.com pointed out that I haven’t done an outstanding job of explaining why people would actually use SVUnit. Seems the last post took us a step in the right direction by explaining a little more about who can use and SVUnit and where they’d use it. You’ll see though that in the follow-up, krupan suggests I go a step further, which I agreed to… on one condition!

krupan: “I’m definitely intrigued by the prospect of a simple, easy to understand and work with framework for designers to use to test their verilog modules. UVM is not a good candidate for that. At all. I started watching the videos but so far I haven’t seen an example of that yet. Would it be possible for you to just post some code to your blog showing what that might look like? Sorry to be so difficult.”

me: “How about this… I’ll release a new version of SVUnit with an example of a module being tested along with a blog post with more explanation *if* you agree to download the new version and run the example to see it first hand. From there, you can decide for yourself whether or not we’re moving in the right direction with unit testing. Deal?”

krupan: Deal! Smile

So here we are with a long overdue example of how a designer can use SVUnit to unit test a module inside a design.

In a previous example that demonstrates SVUnit with UVM Express, I build an APB bus model. APB is simple yet somewhat relevant these days so I’ve chosen to go the same route for my module example. What I do is build a module with an APB slave connected to a memory while using SVUnit to test it. It’s not brain surgery but it does take us through all the necessary steps so I’m hoping it’ll be good for getting the point across.

First thing to do is create an empty module that we can use to generate the unit test template (i.e. create_unit_test.pl apb_slave.sv). All you need for that is this:

apb_slave.sv:
  module apb_slave();
  endmodule

With the empty module and the unit test template, the next step is to add connectivity between them. To do that, we obviously need to add the APB I/O to the module definition.

apb_slave.sv:
  module apb_slave
  #(
    addrWidth = 8,
    dataWidth = 32
  )
  (
    input clk,
    input rst_n,
    input [addrWidth-1:0] paddr,
    input pwrite,
    input psel,
    input penable,
    input [dataWidth-1:0] pwdata,
    output logic [dataWidth-1:0] prdata
  );
  endmodule

SVUnit uses virtual interfaces to speak between tests and UUT, so we also need to create the interface that gets passed into the unit test class. It’s pretty much a copy/paste of the module definition.

apb_slave_unit_test.sv:
  ...
  // virtual interface to the uut
  interface apb_slave_unit_test_if
  #(
    addrWidth = 8,
    dataWidth = 32
  )
  (
    input clk
  );
    logic rst_n;
    logic [addrWidth-1:0] paddr;
    logic pwrite;
    logic psel;
    logic penable;
    logic [dataWidth-1:0] pwdata;
    logic [dataWidth-1:0] prdata;
  endinterface
  ...

With everything we need for connectivity, now we can instantiate our UUT and connect it to the unit test class.

apb_slave_unit_test.sv
  ...
  // uut instance and connections to the virtual interface
  apb_slave my_apb_slave(.clk(clk),
                         .rst_n(my_apb_slave_if.rst_n),
                         .paddr(my_apb_slave_if.paddr),
                         .pwrite(my_apb_slave_if.pwrite),
                         .psel(my_apb_slave_if.psel),
                         .penable(my_apb_slave_if.penable),
                         .pwdata(my_apb_slave_if.pwdata),
                         .prdata(my_apb_slave_if.prdata));

  // virtual interface instance
  apb_slave_unit_test_if my_apb_slave_if(.clk(clk));
  ...

The connectivity is something I’d like to automate… but we’re not there yet. For now, hopefully manual will do :|.

With the tedious part done, it’s time to start writing tests and code. Before getting too far, I figured it would be nice to have a couple helper tasks to simplify the tests so I built 1 for write and read. They’re pretty similar so I’ll just copy in the write task.

apb_slave_unit_test.sv:
  task write(logic [7:0] addr,
             logic [31:0] data,
             logic back2back = 0,
             logic setup_psel = 1,
             logic setup_pwrite = 1);

    // if !back2back, insert an idle cycle before the write
    if (!back2back) begin
      @(negedge my_apb_slave_if.clk);
      my_apb_slave_if.psel = 0;
      my_apb_slave_if.penable = 0;
    end

    // this is the SETUP state where the psel,
    // pwrite, paddr and pdata are set
    //
    // NOTE:
    //   setup_psel == 0 for protocol errors on the psel
    //   setup_pwrite == 0 for protocol errors on the pwrite
    @(negedge my_apb_slave_if.clk);
    my_apb_slave_if.psel = setup_psel;
    my_apb_slave_if.pwrite = setup_pwrite;  
    my_apb_slave_if.paddr = addr;
    my_apb_slave_if.pwdata = data;
    my_apb_slave_if.penable = 0;

    // this is the ENABLE state where the penable is asserted
    @(negedge my_apb_slave_if.clk);
    my_apb_slave_if.pwrite = 1;
    my_apb_slave_if.penable = 1;
    my_apb_slave_if.psel = 1;
  endtask

A very important note to make here is that because I was doing TDD, my write method started out much simpler than what I have here. This is what I ended up with though after I got to the back-to-back transactions and the protection against protocol errors I chose to design in.

To build my APB slave, I ended up with 4 tests in total. I have a couple here so you can see they’re pretty simple.

apb_slave_unit_test.sv
  ...
  //************************************************************
  // Test:
  //   single_write_then_read
  //
  // Desc:
  //   do a write then a read at the same address
  //************************************************************
  `SVTEST(single_write_then_read)
    addr = 'h32;
    data = 'h61;

    write(addr, data);
    read(addr, rdata);
    `FAIL_IF(data !== rdata);
  `SVTEST_END(single_write_then_read)
  ...
  //************************************************************
  // Test:
  //   _2_writes_then_2_reads
  //
  // Desc:
  //   Do back-to-back writes then back-to-back reads
  //************************************************************
  `SVTEST(_2_writes_then_2_reads)
    addr = 'hfe;
    data = 'h31;

    write(addr, data, 1);
    write(addr+1, data+1, 1);
    read(addr, rdata, 1);
    `FAIL_IF(data !== rdata);
    read(addr+1, rdata, 1);
    `FAIL_IF(data+1 !== rdata);
  `SVTEST_END(_2_writes_then_2_reads)
  ...

There’s your basic write/read tests that even in a simple module like this can catch a bug or 2 that are almost guaranteed to cause you headaches down the road. With simple unit tests like these though, you get rid of them right away.

The last thing I’ll point out is the code I put in the setup method that the unit test template spits out. I’ve taken the approach that unit tests and functionality should be isolated from each other as much as possible. To do that, I set up each test by doing a hardware reset and setting the APB bus to the IDLE state.

For setup(), the create_unit_test.pl template generator gives you this:

  task setup();
    super.setup();
  endtask

…and it’s your job to turn it into this:

  task setup();
    super.setup();

    //-----------------------------------------
    // move the bus into the IDLE state
    // before each test
    //-----------------------------------------
    idle();

    //-----------------------------
    // then do a reset for the uut
    //-----------------------------
    my_apb_slave_if.rst_n = 0;
    repeat (8) @(posedge my_apb_slave_if.clk);
    my_apb_slave_if.rst_n = 1;
  endtask

Setup is not something you need to explicitly call in each test. SVUnit does that under the hood so you don’t have to worry about it.

That’s it. That’s all you need to unit test a simple APB slave module. If you want to see and run the entire example, including the UUT, you can go to the SVUnit page for instructions on how to download it. This APB slave example is in examples/modules/apb_slave.

How’d that go, designers? Reasonable?

-neil

2 thoughts on “SVUnit 101 For Designers

  1. Yes, I’d say that’s very reasonable. Good work for a 0.3 release. Although, I’m a verif guy, not a designer. I’m not sure all the designers on my team would take the time to pick something like this up on their own, but I’ll bet I could guide them through it initially and they would run with it.

    One thing that would help would be to have the basic steps that I got from the first video (when to run which perl script, when to run make, which file to create so that make will run my simulator, etc.) would have been more accessible if they were written down. I had to do a lot of pause and rewind on the video.

    Also, maybe for a future release, basic tar-file courtesy is to have it so when I untar the tar file, it creates one and only one directory, with everything else inside of that.

    Otherwise, good work at hiding a lot of complexity from me. Once I had generated the files it was pretty easy to create the interface, wire it up, and insert some test code in the correct place in the *unit_test.sv file

    1. Good to hear and thanks for the feedback. I do have the basic steps in the svunit-code/README though it probably wouldn’t hurt to make mention of that in the example dirs as well. And the tar-ball courtesy… bah! You’re right! I’ll turnaround a new release asap for that.

      Thanks again. If you get any feedback from your designers, good or bad, please keep me in the loop and I’ll do what I can to help out.

      -neil

Leave a Reply

Your email address will not be published. Required fields are marked *