Thursday, June 26, 2014

SystemVerilog Constraints from Above

After reading the title, some of you might be asking yourselves "What are constraints from above?". Constraints from above (CFAs) are an e term. As Reuven Naveh explains in this post on Team Specman's blog, CFAs have the following properties:

  • They constrain a do-not-generate field or its descendants.
  • The constraint is declared not in the type in which the field is declared, but at a higher level.
  • The field is later generated on-the-fly.

For those of you who opened the link, they look remarkably similar to in-line constraints, but in modern e CFAs are more complicated than what that article presents. We won't go into that as this is a SystemVerilog post.

Why are CFAs useful? To best answer that question, let's look at an example. Let's say we have a trivial sequence item that models accessing a communication device:

typedef enum { CONFIG, TX_SHORT, TX_LONG, RX_SHORT, RX_LONG, SHUTDOWN } mode_e;

class some_item extends uvm_sequence_item;
  `uvm_object_utils(some_item)
  
  rand mode_e mode;
  
  function new(string name = "some_item");
    super.new(name);
  endfunction // new

endclass // some_item

We also have some sequence that operates with these items:

class some_sequence extends uvm_sequence #(some_item);
  `uvm_object_utils(some_sequence)
  
  rand int unsigned num_items;
  
  constraint max_items_c {
    num_items >= 5;
    num_items <= 10;
  }
  
  function new(string name = "some_sequence");
    super.new(name);
  endfunction // new
  
  task body();
    start_item(req);
    if (!req.randomize() with { mode == CONFIG; })
      `uvm_error("RANDERR", "Randomization error")
    finish_item(req);

    // start num_items comms
    repeat (num_items) begin
      start_item(req);
      if (!req.randomize() with { mode inside { TX_SHORT, TX_LONG, RX_SHORT, RX_LONG }; })
        `uvm_error("RANDERR", "Randomization error")      
      finish_item(req);
    end

    start_item(req);
    if (!req.randomize() with { mode == SHUTDOWN; })
      `uvm_error("RANDERR", "Randomization error")
    finish_item(req);
  endtask // body
  
endclass // some_sequence

We need to send an initial configuration command to set up the device. We follow up with the main test, throwing a few communication commands at the DUT and end it all with a shutdown. Pretty easy stuff up to now. Next, what if we want to have another sequence that restricts our traffic to only short communication commands? This already hints at class inheritance because a short communication sequence is just a specialization of our current sequence.

What disturbs us is the inline constraint on req, because we hardcoded the values that we want our communication commands to take. The most naïve and inefficient approach would be to just re-implement the body() task. In this case it's not a big deal, because we don't do much aside from running our traffic, right? Aside from the traffic loop which we need to slightly change, It's just two little 4-line blocks we need to copy. But what happens if a new command gets added and we have to run that as well prior to starting our traffic? We would have a second place we have to patch. Not good.

What we could also do is spin-off our traffic loop to it's own method and override that. This could work, but it's so C++. The little e angel on my shoulder is telling me: "This would be a great time to use a constraint from above.".

Instead of hardcoding the constraint inside the with block, we can put a constraint on req in some_sequence's scope:

class some_sequence extends uvm_sequence #(some_item);
  // ...
  rand some_item req;
    
  constraint only_comms_c {
    req.mode inside { TX_SHORT, TX_LONG, RX_SHORT, RX_LONG };
  };
  
  // ...
endclass // some_sequence

Now, we don't want to randomize the whole sequence, because that would mess up the num_items field. Fortunately, SystemVerilog provides an easy way to randomize just a subset of an object's fields. The mechanism is called in-line random variable control. Here it is in action, randomizing just the req field:

class some_sequence extends uvm_sequence #(some_item);
  // ...
  
  task body();
    // ...

    // start num_items comms
    repeat (num_items) begin
      start_item(req);
      if (!this.randomize(req))
        `uvm_error("RANDERR", "Randomization error")
      req.set_item_context(this, get_sequencer);      
      finish_item(req);
    end
    
    // ...
  endtask // body
  
endclass // some_sequence

What is happening here is that we are randomizing inside some_sequence's scope.The solver will take all of the constraints defined in some_item together with the one we defined above on req.mode, but it will only update req. Think of it like disabling randomization for all fields except for req via calls to rand_mode(0).

For some reason, the call to randomize() allocates a new object, even though the standard explicitly states that "[randomize] does not allocate any class objects" (IEEE Std. 1800-2012). I've noticed this behavior on two different simulators and can't explain it (in a stripped down example this doesn't happen; the object is randomized in-place). This new sequence item doesn't have it's context (parent sequence and sequencer) set, hence the call to set_item_context(...).

If we want to create a sequence that starts only short communication commands sequences, it's enough to extend some_sequence and add another constraint saying that req should be either TX_SHORT or RX_SHORT:

class some_other_sequence extends some_sequence;
  `uvm_object_utils(some_other_sequence)

  function new(string name = "some_other_sequence");
    super.new(name);
  endfunction // new

  constraint only_short_c {
    req.mode inside { TX_SHORT, RX_SHORT };
  }
  
endclass // some_other_sequence

Short and sweet, just like in e (well, almost, if it weren't for all of that boilerplate code for factory registration and the trivial constructor).

We got points 2 and 3 of Reuven's quote down, but what about point 1? The cool thing about this approach is that it could also work for non-random fields, with the caveat that the semantics of constraints are slightly different for e than for SystemVerilog. We could just as well not tag req as random, but we would need to take care that it's initial value is legal with respect to the constraints (or we would need to tag the constraints as soft). This is because SystemVerilog checks that even constraints on just state variables also hold, whereas (I think) e discards them.

We could do crazy stuff like disable the constraint inside the constructor and re-enable it inside post_randomize(). This would ensure that when the initial randomization of the whole sequence was executed our CFA doesn't lead to a contradiction. After re-enabling the constraint we could randomize only req (also in post_randomize()) to protect ourselves from another randomization of the sequence. You know what? Too complicated, forget it. I'll gladly call what we did here a CFAs even though it doesn't apply to a state variable, if it makes things simpler.

As usual, the examples (and more test code) can be found on SourceForge. Feel free to download it and experiment. See you next time!

20 comments:

  1. Very nice, Tudor! I'll keep this trick in mind. I do have one question though. I didn't understand why you had to call req.set_item_context(this, get_sequencer). Isn't the context set when the sequence item is sent through the sequencer to the driver? I've never set it before and the context always shows up nicely when printed.

    ReplyDelete
    Replies
    1. Hi, thanks a lot for the nice words!

      start_item(req) sets the context of the item, but for some reason, when we do this.randomize(req), a new req is created (although the standard states that randomize() does not create new items). The jury's still out on this one whether it's a bug, because this happens on two different simulators. The new item that gets created doesn't have its context set, so we have to set it again. What we can also do if we don't need late randomization is:

      if (!this.randomize(req)) // creates a new item and assigns it to req
      `uvm_error("RANDERR", "Randomization error")
      start_item(req); // sets the context of the item
      finish_item(req); // waits until the item is finished

      Delete
    2. I see, makes sense. I probably never ran across this issue before because I rarely randomize an object by passing it as an argument to the randomize function.

      Good stuff!

      Delete
  2. Hi Tudor,

    I recently came across your blog, it seems pretty interesting. Keep up the good work!

    Just to mention a minor typo mistake in the traffic loop where the `uvm_error should run when the randomize function returns zero. ;)

    Thanks,

    Artemios.

    ReplyDelete
    Replies
    1. Hi, thanks a lot for the kind words! And also thanks for pointing out the typo; it's fixed now :)

      Delete
  3. Hi Tudor,
    your blog is pretty knowledgeable!
    Maybe you should name the new item differently. It is a bit confusing to name it 'req'.

    ReplyDelete
    Replies
    1. Hi Cedric, thanks for the compliment.

      Do you mean it's confusing because it overrides the 'req' field defined in uvm_sequence? It stands for "request". I chose the term "req" as it is used in multiple places in UVM and I thought it would actually make it clearer.

      Delete
    2. Exactly, 'req' is already the current default item. So override it makes confusion, especially for people not familiar with UVM or using it time-to-time.
      By the way, it's also strange that the overriding declaration does not raise an error at compilation. How does it work behind the scene ?

      Delete
    3. This is a "feature" of SystemVerilog that not many people know. I stumbled upon it by mistake some years back. If you define the same field in a sub-class that the super-class already has, it will be overridden. I don't know the exact semantics of what happens in that case (with any code in the super-class that was using this field), but I was thinking of doing a blog post on the topic at some point.

      The field "req" is not used at all in uvm_sequence and I liked the name so I overrode it. I'm pretty sure the code would work also without me defining 'req' at all, as the call to randomize(req) will force it to be treated as a random field (even though it isn't tagged as random in uvm_sequence).

      Delete
    4. And a follow up question from my side: Did you ever use this 'req' field up to now? If so, for what exactly? I'm not really sure what it's supposed to do.

      Delete
    5. Thank you for the tip (subclass overwrite).
      I only use it as the default item to randomize and send the transaction.
      That's why your name choice confused me and I think that for a beginner it is tricky to understand your article.
      If you call it "nested_item", it'll be clearer but maybe I mislead.

      Delete
  4. As soon as I saw that you hadn't declared it anywhere, I peeked in the BCL to go find it. My only guess is that it's just a convenience declaration; I'll start using it now, as it saves me a line of code ;)

    An aside.. I use RivieraPro, and I had to explicitly create req in order to use it; it didn't assign an object just by calling randomize().

    examples works well. Constraints from above!... (angels trumpets heard in distance)

    ReplyDelete
    Replies
    1. In SV, objects always need to be created before they get used, including when randomizing. That part just isn't shown (it's part of the '//...' block in the code sample).

      Delete
  5. I literally just ran into this in my sim, fixed it, and then came back to your example to see if you had written something. Of course you had.

    ReplyDelete
  6. So this is meant to be used with a transaction that is created during the new function, and then randomized over and over?
    Versus created in the new function, and then also created over and over during the loop.

    ReplyDelete
    Replies
    1. If by "this" you mean "constraints from above", you shouldn't think of it in terms of when the items are created. I'd use this approach to allow for constraint tweaking where this isn't possible using the factory. For example, for the communication case from above you can't set a type override on some_item to another class that only generates SHORT_TX or SHORT_RX, because that would cause constraint violations with the CONFIG and SHUTDOWN inline constraints.

      An instance override might be possible if you make sure to name the items differently so they have different contexts. At the same time it would also be possible to create separate classes for CONFIG items and SHUTDOWN items and create those, leaving the possibility to set a type override. Back when I wrote this post I didn't think much about alternatives. Now that I've got more experience I can see that there are many ways to achieve the same effect and it's nice to know all the tools at your disposal.

      Delete
  7. Hi Tudor,

    Your blogs are really good and informative.

    In this current example, why the below constraint is not affecting the CONFIG / SHUTDOWN?

    constraint only_comms_c {
    req.mode inside { TX_SHORT, TX_LONG, RX_SHORT, RX_LONG };
    };

    ReplyDelete
    Replies
    1. For CONFIG/SHUTDOWN, we're calling 'reg.randomize() with ...". This means it's randomizing the "reg" object and it only considers constraints defined inside its class. The scope getting randomized is the transaction.

      For the other cases notice that we're calling "this.randomize(req)". This means that we're randomizing the sequence (this), but only the "req" field. The scope that's getting randomized is the sequence, so all constraints here there get considered as well.

      Have a look at sections 18.11 In-line random variable control and 18.5.9 Global constraints of the IEEE 1800-2012 standard.

      Delete
  8. Hi Tudor,

    Can you please solve follwing issue.
    class base;
    int aa;
    endclass

    class trn extends uvm_sequence_item;
    base class_array[3];

    rand int idx;
    rand int aa_in_base;

    function new (string name = "trn");
    super.new(name);
    foreach(class_array[i]) begin
    class_array[i] = new;
    class_array[i].aa = i;
    end
    endfunction

    constraint const_a {
    idx == 1;
    aa_in_base == class_array[idx].aa;
    }
    endclass


    Randomization is failed...

    ReplyDelete
  9. unfortunately, my sim is failing in the test, when I randomize the seq because the constraint is accessing a null pointer (the transaction does not exist). I guess there is no automatic object creation. I have to create the transaction in the new() function of the sequence to avoid this error.

    ReplyDelete