Jump to content

Adding Contraints in Driver?


Recommended Posts

Suppose I have a transaction class as below:

class my_xfer extends uvm_sequence_item ;

   logic write ;
   rand logic[5:0] num_wdata_beats ;
   rand logic[5:0] num_rdata_beats ;

   constraint c_num_beats {
      (write == 0) -> (num_wdata_beats == 0) ;
      (write == 1) -> (num_rdata_beats == 0);
   }

endclass

Can I have the following code in the run phase of the corresponding driver class?

virtual task run;
  forever
  begin
    my_xfer tx;
    tx.write = virt_intf.req_write ; // This is a signal from the DUT indicating a write request
    #10
    seq_item_port.get_next_item(tx); 
    // If DUT sends a write request, will tx.num_rdata_beats be 0 after this call?
    ...............
    ...............
  end
endtask: run

I am curious whether this sort of control over the randomization is permitted from the driver side. I believe there are ways to do this using multiple sequences, but something like this appears to be lesser code overhead. I am just not sure whether it will work or not.

Link to comment
Share on other sites

This will result in undefined behavior, because the tx transaction isn't allocated before tx.write is assigned. Also, get_next_item takes an output parameter, and what you pass in will be overwritten, thus the assignment to tx.write will not have any effect. If the dut is expecting a write operation, you could try to have a monitor detect this and have your test send a write transaction to the driver.

Erling

Link to comment
Share on other sites

This will result in undefined behavior, because the tx transaction isn't allocated before tx.write is assigned. Also, get_next_item takes an output parameter, and what you pass in will be overwritten, thus the assignment to tx.write will not have any effect. If the dut is expecting a write operation, you could try to have a monitor detect this and have your test send a write transaction to the driver.

Erling

Erling, Thanks for your response.

I realized that tx hadn't been allocated sometime back, but I thought just doing a 'my_xfer tx = new();' before the assignment to the write signal would solve the problem.

I am trying to look through the UVM codebase to see where exactly the transaction is 'new'ed. I couldn't find that exactly, but it really would have been easier if the code checks for whether 'tx' is an already allocated object or not before creating it anew (sort of like a pass by reference argument for the get_next_item call).

As for having a monitor recognize the write transaction and directing the test accordingly.. the transaction which needs to be sent back to the DUT relies on a lot of other signals present in the interface.. Is there some example you could point me to with similar structure? Thanks!

Link to comment
Share on other sites

I am trying to look through the UVM codebase to see where exactly the transaction is 'new'ed.

The driver is not supposed to participate in the creation of the transaction, just receive it and transform it to pin activity. The transaction is usually created by a sequence (and new'ed by the transaction proxy).

As for having a monitor recognize the write transaction and directing the test accordingly.

Your test environment could monitor the signals in the interface, and post the collected information by means of analysis port notifications or similar. This could be used to update an internal state of the dut. Sequences can then be sent to the driver as a response to a dut state change, or sequences could be sent at any time based on knowledge about the dut state, and not based on direct pin access. Please post more info if you believe there is something special about your dut that does not fit into this way of thinking.

Erling

Link to comment
Share on other sites

Erling,

Thanks for your time and help.

Yes, I am theoretically aware that there are roundabout ways to make sure that a transaction is created based on the DUT state (such as the monitor / analysis ports). Currently, I am in the beginning stages of creating the verif environment and need to deliver a basic test to the RTL designer ASAP -- so, there are no plans to have monitors or analysis ports for the next couple of weeks. I believe it should be possible to get a UVM testbench up and running with just the interface, transaction, driver, sequencer, agent and environment code.

The DUT I am dealing with is not very complicated. The 'BFM' I am trying to create has to respond to memory requests from the DUT. Each request has a req_vld with a req_write signal. req_addr and req_byte_cnt are other signals in the interface valid at the same time as req_vld. For write requests, the BFM needs to send wdata_nxt after some delay back to the DUT (as many wdata_nxt as necessary based on a pre-determined bus width and the req_addr and req_byte_cnt signals) and for read requests, the BFM needs to send the appropriate number of rdata_vlds along with rdata.

I need to be able to control the rdata generated in the sequence one level up at a later point (probably with a `uvm_do_with). In my transaction class, the rdata is : rand logic [63:0] rdata[] and I have a constraint rdata.size() == num_rdata_beats. The issue I am facing is: How do I get the num_rdata_beats (calculated from the interface signals in the driver) to the constraint when calling get_next_item ? I know that I can do it in the sequencer with the `uvm_do_with, but, again, the issue becomes: How do I get the num_rdata_beats to the sequencer? As far as I know, the interface signals are available only in the driver and not in the sequencer.

Link to comment
Share on other sites

How do I get the num_rdata_beats to the sequencer?

If you absolutely don't want to have a monitor facility at this time, you could add a uvm_blocking_peek_imp in the driver, for example:

class MyDrv extends...

  typedef struct {...} Info;
	
  uvm_blocking_peek_imp#(Info, MyDrv) info_peek;
		
  function new(string name, uvm_component parent=null);
    super.new(name, parent);
    ...
    info_peek = new("info_peek", this);
  endfunction: new

  task peek(output Info info);
    @(posedge req_vld);
    fill_in(info); // req_addr, req_byte_cnt, whatever
  endtask: peek

  ...

endclass: MyDrv

Add an info_peek port to the sequencer:

class MySqr extends uvm_sequencer #(MyTrans);

  uvm_blocking_peek_port#(MyDrv::Info) info_peek;

  function new (string name, uvm_component parent);
    super.new(name, parent);
    info_peek = new("info_peek", this);
  endfunction: new

endclass: MySqr

In the agent, connect the peek port to the peek imp:

class MyAgt extends uvm_agent;

  MyDrv m_drv;
  MySqr m_sqr;
  
  function void connect_phase(uvm_phase phase);
    if (get_is_active() == UVM_ACTIVE) begin
      m_drv.seq_item_port.connect(m_sqr.seq_item_export);
      m_sqr.info_peek.connect(m_drv.info_peek);
    end
  endfunction: connect_phase

  ...
  
endclass: MyAgt

You can then await info from the driver in a sequence, for example:

class MySeq extends uvm_sequence #(MyTrans);

  MySqr m_sqr;
	
  task pre_body();
    super.pre_body();
    $assert($cast(m_sqr, get_sequencer()));
  endtask: pre_body
	
  task body();
    MyDrv::Info info;
    MyTrans trans;
    forever begin
      ...
      m_sqr.info_peek(info);
      trans = MyTrans::type_id::create();
      start_item(trans);
      $assert(trans.randomize() with { info somehow });
      finish_item(trans);
      ...
    end
  endtask: body
	
endclass: MySeq

There may be all kinds of errors in the code above, but it could be something to start with.

Erling

Link to comment
Share on other sites

Erling,

I am trying to understand your code better and taking further help from the link below to understand TLM further:

Example 4.7 : http://low-powerdesign.com/article_Cadence-UVM_101010.html

My questions are:

1. What is wrong with implementing the communication using something simple like uvm_blocking_put_port ?

2. In my situation, the driver is the one producing the information (the request details) and it is the sequencer which is consuming it. Shouldn't the _imp be part of the sequence? If I use the blocking_put method, can I directly connect the sequencer with the driver instead of traversing the full hierarchy?

3. If I use the peek method suggested by you, wouldn't successive req_vlds with different req_types end up confusing the sequencer (it might see 2 read requests when there was actually 1 read and 1 write?)? Am I misunderstanding the peek method here?

Thanks

Ganesh

Link to comment
Share on other sites

1. What is wrong with implementing the communication using something simple like uvm_blocking_put_port ?

2. In my situation, the driver is the one producing the information (the request details) and it is the sequencer which is consuming it. Shouldn't the _imp be part of the sequence? If I use the blocking_put method, can I directly connect the sequencer with the driver instead of traversing the full hierarchy?

If the driver is pushing the info, how should the sequencer deal with it? With the peek method there is already a sequence waiting to respond to the driver info.

3. If I use the peek method suggested by you, wouldn't successive req_vlds with different req_types end up confusing the sequencer (it might see 2 read requests when there was actually 1 read and 1 write?)? Am I misunderstanding the peek method here?

Not sure I understand what you mean. When the sequence does the peek, it will get info on the first req_vld, respond to it, and then peek again for the next req_vld. For this to work, though, there can't be another req_vld before the previous request has been serviced.

Erling

Link to comment
Share on other sites

Erling,

Thanks for the detailed clarifications. I brushed up my TLM understanding, and things are much more clear now.

I am trying out the code suggested by you, and NCSim doesn't seem to like the :: reference to the class member (keeps insisting that the class name is a typedef), but I got around that by making the structure to peek into a global for that package.

Now, for this line in the sequence body:

m_sqr.info_peek(info);

I am getting a compile error:

Expecting a task name [10.2.2(IEEE)]

I am not sure what is to be done in this case. Do you have any suggestions? info_peek is a uvm_blocking_peek_port in the sequencer.

Edit: After some more searches, I put in a m_sqr.info_peek.peek(info), and that compile error seems to have gone away for now.

Edited by ganeshts
Found a probable solution to the issue
Link to comment
Share on other sites

Erling, Thank you very much for all your help. I got the setup working.

The only thing I had to fix was accessing the port in the sequence through the p_sequencer hierarchy path. (There were lots of errors when a sequencer was declared inside a sequence as given in the example you typed out).

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...