Jump to content

tudor.timi

Members
  • Posts

    289
  • Joined

  • Last visited

  • Days Won

    33

Posts posted by tudor.timi

  1. This is a pretty uncommon scenario, that you won't be able to implement without some extra code. This reminds me of a situation a colleague of mine faced, where different registers shared the same address, but they were differentiated based on some extra bits in the bus transaction objects. In your case, you differentiate using another register. It might be problematic to map multiple registers to the same address (you get warnings, but I'm not sure if nothing else gets busted).

     

    The first thing I can think of is that you can define your IP regs in a separate block (as you probably already have). You can instantiate them in your main block and not map them. At the same time you can add another instance of the IP regs block and map that one. This will serve as the gateway to the "real" registers:

    class dut_reg_block extends uvm_reg_block;
      rand control_reg CONTROL;
      rand ip_reg_block IP;
    
      ip_reg_block IPs[4];
    
    virtual function void build();
        // ...
        default_map.add_reg(CONTROL, 'h0);
        default_map.add_submap(IP.default_map, 'h10);
    
        foreach (IPs[i]) begin
          IPs[i].build();
          // ... not mapped ...
        end
      endfunction
    
      // ...
    endclass
    

    The use model would be that when you call IP.DATA.write(...) or read(...), you would be accessing one of the register blocks in IPs, depending on the value set in CONTROL.MUX. You need to make this hookup. You can make it so that when IP.DATA gets written, the appropriate register gets updated, via a callback:

    class mux_regs_cbs extends uvm_reg_cbs;
      control_reg CONTROL;
      data_reg DATAs[4];
    
      virtual function void post_predict(uvm_reg_field fld, uvm_reg_data_t previous,
        inout uvm_reg_data_t value, input uvm_predict_e kind, uvm_path_e path,
          uvm_reg_map map
      );
        data_reg DATA = DATAs[CONTROL.MUX.get_mirrored_value()];
        uvm_reg_field real_fld = DATA.get_field_by_name(fld.get_name());
    
        if (kind == UVM_PREDICT_WRITE)
          real_fld.predict(value);
    
        // Won't work because 'predict(...)' is called after checking the read.
        //else if (kind == UVM_PREDICT_READ)
        //  value = real_fld.get_mirrored_value();
      endfunction
    endclass
    

    You also need to update the value of IP.DATA when reading, so that you can check what the DUT returns. You can't do this in the previous callback, since the post_predict(...) hook is called after the read data has already been checked and there isn't any pre_predict(...) hook. What you can do, however, is to update the value of IP.DATA whenever you're switching between registers (i.e. writing CONTROL):

    class mux_control_cbs extends uvm_reg_cbs;
      data_reg DATA;
      data_reg DATAs[4];
    
      virtual function void post_predict(uvm_reg_field fld, uvm_reg_data_t previous,
        inout uvm_reg_data_t value, input uvm_predict_e kind, uvm_path_e path,
          uvm_reg_map map
      );
        if (kind == UVM_PREDICT_WRITE)
          DATA.predict(DATAs[value].get_mirrored_value());
      endfunction
    endclass
    

    You need to add the callbacks to the appropriate registers:

    protected function void connect_ips();
        mux_regs_cbs regs_cbs = new();
        mux_control_cbs control_cbs = new();
    
        regs_cbs.CONTROL = CONTROL;
        foreach (regs_cbs.DATAs[i])
          regs_cbs.DATAs[i] = IPs[i].DATA;
    
        control_cbs.DATA = IP.DATA;
        foreach (control_cbs.DATAs[i])
          control_cbs.DATAs[i] = IPs[i].DATA;
    
        begin
          uvm_reg_field flds[$];
          IP.DATA.get_fields(flds);
          foreach (flds[i])
            uvm_reg_field_cb::add(flds[i], regs_cbs);
        end
    
        uvm_reg_field_cb::add(CONTROL.MUX, control_cbs);
      endfunction
    

    You can either do this in the register block itself or outside, if you're relying on generated code that you can't touch.

     

    The code I posted could be made more flexible, i.e. instead of hardcoding references to DATA, it could be made to loop over all registers of the IP reg block, etc. Maybe there are nicer ways of doing it as well. I'm most probably going to write a blog post on this in the future to detail this procedure and maybe investigate others, so stay tuned!

  2. A1: You can start your simulation with the +UVM_CONFIG_DB_TRACE plusarg to see messages for set(...)/get(...) in the log file.

     

    A2: You can have both use models. You can either tell the sequencer to start a sequence of a certain type by providing a type wrapper (like you did and the sequencer will instantiate it itself) or you can directly provide an instantiated sequence which you randomized/configured to your heart's desire (and the sequencer will start it). If you run your simulation with UVM_FULL (at least on the sequencer) you should see a message saying that it started the phase sequence and what type of sequence.

     

    A3: This error comes from start_item(...) when it can't figure out on which sequencer to start it. If you aren't starting any sequences yourself, the seq name there for the sequence probably comes from the phase sequence which is getting started. I assume you have some problems with your register model configuration (something like a sequencer not being set for the register map). Also, these built in register sequences need a register or a register block to know what to access. I don't see how you can pass the phase sequence a reference to either on of these without creating an instance first, setting the register field and only then setting it as a phase sequence.

  3. In Specman there was a special event that happend at the end of a delta cycle, tick_end, which you could use for this. In SV/UVM there is a task that simulates this, uvm_wait_for_nba_region(), which you could call once you get your IGMP packet. This way you'd be sure that the write_ip(...) would also get called by the time you return from the wait. It's not pretty, but it's pragmatic.

     

    Another thing you could do is define some field in your IGMP packet where you'd reference the originating transaction, in your case IP. You could make it of type uvm_sequence_item if you intend to be able to layer this protocol on top of anything else other than IP. Your monitor would handle assigning this field when it's also figuring out the values for other fields.

  4. @Alan I'm not sure if this is going to affect reporting. I think supplying a parent when creating an object will only set the context used by the factory. I had a quick look through the code and when calling uvm_report_*(...) inside an object, you'll actually be calling the global methods which route the report to the top component (uvm_root).

     

    You should try making your object inherit from uvm_report_object instead, as this class contains its own report handler. If you also set the context of the object (by specifying a parent or a full path), then you'll be able to distinguish it from other instances, even though it's not part of the component hierarchy.

  5. Do you need to know exactly how an engine works to be able to drive a car? The whole point of using libraries is that you don't need to care how they're implemented, rather how to use them. Coming back to the car analogy, the UVM user guide isn't a service manual, so it won't describe the parts the car is made of and how they're connected. It will tell you what buttons to press to turn on the radio, to turn on the AC, start the engine, etc.

  6. You're going to have to implement your own copying scheme. It might get tricky because of all the 'local' fields in the register classes, but there's no way around it. At the same time, you could just store the register values at a certain point in time and when you need to do something with them, you can unpack those values in a temporary register of that type to extract the individual fields.

  7. I don't really remember what the Verification Academy cookbook said (it's a long time since I've seen it). I can tell you how I've started writing my plans. For your small little feature, I'd structure it like this:

    ARP interface
    
    -- The DUT should reply to an ARP request addressed to it
    ---- <map some cover item where I show I did ARP requests to the DUT>
    
    -- The DUT should drop ARP requests not addressed to it
    ---- <map some cover item where I show I did ARP requests to some other device>
    
    -- The DUT should drop ARP replies
    ---- <map some cover item where I know I did ARP replies>
    

    The "--" notation above means that it's a child section. I'd have the one big section related to ARP processing, then split this into smaller sub-sections, one per requirement. I don't get what you mean by non-supported behavior. If you're referring to dropping replies and other requests, that's not non-supported behavior, just other requirements aside from the main one, that is should accept ARP requests addressed to it.

     

    I'd typically break down a specification like this to end up with a verification plan. That doesn't mean what I think of at that point is the final version. W.r.t. finding the bugs you aren't looking for, you're right, that's a big part of constrained random (the other being test automation). What I do when I find a bug in an area I wasn't expecting (i.e. I didn't have any coverage defined for) is to add some coverage in the general area to make sure I stress it enough and the bug doesn't reappear. Other times you just get ideas of more stuff you want to test out of the blue. A verification plan should be a living thing, like your testbench and tests.

  8. This has nothing to do with sequences or anything else. The Cadence simulator doesn't support using queues inside constraints. You'll have to use dynamic arrays. You don't need an additional field for the array size as you can just use tx_err_bytes.size() == 3. You do have to pass in the size of the array which is annoying, but there's no way around it.

  9. I'm guessing your device can only be in one mode at a time and this is configured via some fields in some configuration registers. It doesn't make sense to start sending OCTA frames when the device is configured for DUAL mode operation, hence there's no point in having mode a field in your sequence/item. When you configure your device for a certain mode (via some register/bus sequence) you need to reach into the configuration for your SPI agent and update that with the appropriate value (e.g. write registers to configure the device for QUAD, set the config field in the SPI agent to QUAD as well).

×
×
  • Create New...