System Verilog
System Verilog questions
SV problem solving questions
Some SV problem solving questions
Question1
---
Q1: 3 parallel processes are there. Write a SV code such that after any 2 processes finish, you come out of the threads and end execution
---
//Method 1 by use of disable_fork
class abc;
process process1, process2, process3;
fork : LABEL
begin
process1 = process::self();
process2 = process::self();
process3 = process::self();
end
join_none
if(process1.status === process::FINISHED && process2.status === process::FINISHED) || (process1.status === process::FINISHED && process3.status === process::FINISHED) || (process2.status === process::FINISHED && process3.status === process::FINISHED)
disable_fork : LABEL
endclass : abc
//Method 2 by use of semaphore
class abc;
semaphore s;
process process1, process2, process3;
//initialise semaphore with 0 key
function new();
s = new(0); //initially with 0 keys
endfunction:new
task multiple_process();
fork
//process 1 ( within 1 begin-end will be treated as 1 process, execute sequentially)
begin
process1 = process::self();
s.put(1);
end
//process 2
begin
process2 = process::self();
s.put(1);
end
//process 3
begin
process3 = process::self();
s.put(1);
end
join_none
s.get(2); //when you get 2 keys end execution by disabling threads
disable_fork;
s = null; //remove semaphore
endtask : multiple_process
endclass : abc
//method 3 by use of wait statements
class abc;
process process1, process2, process3;
int count;
task multiple_process();
fork
begin
//P1
process1 = process :: self();
count++;
end
begin
//P2
process2 = process :: self();
count++;
end
begin
//P3
process3 = process :: self();
count++;
end
join_none
wait(count === 2); //level sensitive
disable_fork;
endtask : multiple_process
endclass : abc
Question2
---
Q2: There is a signal wdata[63:0] representing data-bus
There is a signal wstrb[7:0] representing strobe/valid byte of the data-bus
Constraint is when a valid byte is present in wdata, it has to be either 8'b11111111 or 8'b10101010
Write a SV code/constraint to generate suitable transactions
---
class abc;
rand bit [7:0] wstrb;
rand bit [63:0] wdata;
rand bit [7:0] valid_byte;
constraint valid_byte { valid_byte inside {8'b11111111,8'b10101010};}
constraint valid_wdata { foreach(wstrb[ii])
if(wstrb[ii])
wdata[8*ii+:8] inside {valid_byte};
}
constraint valid_strobe { foreach(wstrb[ii])
wstrb[ii] inside {0,1};
}
constraint solver1 { solve wstrb before wdata;}
constraint solver2 { solve valid_byte before wstrb;}
endclass : abc
Question3
---
Q3: There are 4 strings RED, GREEN, BLUE, YELLOW and a sequence of numbers {0,1,2,3,4,5,6,7,8,9}. A card datatype contains both a string sequence and a number sequence which is a subset of the former.
Write SV code/constraint such that everytime you pick 3 cards, they get same string and a consecutive sequence of numbers as its' items
---
//One possible solution(maybe)
class abc;
rand string string_key[];
rand bit[3:0] number_value[];
constraint number_value_init {
number_value.size() == 3;
foreach(number_value[ii])
number_value[ii] == ii; //consecutive
foreach(number_value[ii])
number_value[ii] inside {[0:9]};
}
constraint string_key_init {
string_key.size() == 3;
foreach(string_key[ii])
foreach(string_key[jj])
if(ii!=jj)
string_key[ii] == string_key[jj];//all elements same
foreach(string_key[ii])
string_key[ii] inside {"RED", "GREEN", "BLUE", "YELLOW"};
}
constraint solver {solve string_key before number_value;}
//create a card datatype --> union is close
typedef struct packed
{
string_key;
number_value;
}
card_t;
rand card_t card[]; //take an array of cards
endclass: abc
Question4
---
Q4: Suppose you are writing a TB for a memory model which has the following signals
input wr_en = write_enable
input rd_en = read_enable
input addr = address
input wdata = write data
input cs = chip select
input valid = valid transaction on input side
input wstrb = indicating which are valid byte lanes in wdata
output rdata = read data
Write the basic code (just the scratch template) for the sequence item and possible driving logic (write + read) and also the sampling function/logic for wdata on basis of wstrb which can be used in monitor
---
//Definitions
`ifndef vip_def
`define vip_def
`define ADDR_WIDTH = 4
`define DATA_WIDTH = 32
`endif
//Sequence item
class mem_seq_item extends uvm_sequence_item;
`uvm_object_utils(mem_seq_item)
rand bit [`ADDR_WIDTH-1:0] addr;
rand bit [`DATA_WIDTH-1:0] wdata;
rand bit wr_en;
rand bit rd_en;
rand bit [`DATA_WIDTH/8 - 1:0] wstrb; //max width = number byte lanes in data bus
bit [`DATA_WIDTH-1:0] rdata;
//constructor
function new(string name = "mem_seq_item");
super.new(name);
endfunction
//constraints
//wr_en and rd_en should be mutually exclusive
constraint wr_rd_exc {wr_en != rd_en; }
//wstrb bits should be either 0 or 1
constraint wstrb_valid { foreach (wstrb[ii]) wstrb[ii] inside {0,1}; }
endclass : mem_seq_item
//driver
class mem_driver extends uvm_driver#(mem_seq_item);
`uvm_component_utils(mem_driver) //registration
virtual mem_if vif; //virtual intf local handle
//constructor
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction:new
//build phase
//code template + get vif
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(virtual mem_if) :: get(this, "", "vif", vif))
`uvm_error("mem_driver", "failed to get vif for mem_driver")
endfunction : build_phase
//run_phase
virtual task run_phase(uvm_phase phase);
forever begin
seq_item_port.get_next_item(req); //req is an in built handle for seq item mem_seq_item
drive(req);
seq_item_port.item_done();
end
endtask : run_phase
virtual task drive(req);
vif.wr_en <= 0;
vif.rd_en <= 0;
vif.valid <= 0;
vif.wstrb <= 0;
vif.cs <= 1; //select the chip
@(posedge vif.clk);
vif.addr <= req.addr; //give the addr
if(req.wr_en)begin
vif.wr_en <= req.wr_en;
vif.wstrb <= req.wstrb;
vif.wdata <= req.wdata;
@(posedge vif.clk); //wait 1 more clock
end
if(req.rd_en)begin
vif.rd_en <= req.rd_en;
@(posedge vif.clk); //wait 1 clock to make rd_en 0
vif.rd_en <= 0;
@(posedge vif.clk);//Wait 1 more to get read data
req.rdata <= vif.rdata;
end
endtask : drive
endclass : mem_driver
//function for sampling wdata on basis of wstrb in monitor
function logic[`DATA_WIDTH-1:0] sample_data_on_strb
(
input bit [`DATA_WIDTH-1:0] wdata,
input bit [`DATA_WIDTH/8-1:0] wstrb
);
logic [`DATA_WIDTH-1:0] sampled_data;
for (int i=0, j=0; i<`DATA_WIDTH/8; i++)
if(wstrb === 1'b1)begin
sampled_data[8*j+:8] = wdata[8*i+:8];
j++;
end
return sampled_data;
endfunction : sample_data_on_strb
//Ex- wdata[31:0] = abcdef9a (hex)
// wstrb[3:0] = 1100 (binary)
//i = 0, wstrb[0]=0, skip
//i = 1, wstrb[1]=0, skip
//i = 2, wstrb[2]=1, wdata[8*2:8*2+8-1] = sampled_data[8*0:8*0+8-1] = cd (hex), j=1
//i = 3, wstrb[3]=1, wdata[8*3:8*3+8-1] = sampled_data[8*1:8*1+8-1] = ab (hex), j=2
Question5
---
Q5: Explain the concept of polymorphism by code
class parent;
virtual function void print();
$display("this is parent");
endfunction
endclass:parent
class child extends parent;
function void print();
$display("this is child");
endfunction
endclass
module poly;
intial begin
//case 1
begin
parent p;
child c;
p = new;
c = new;
p.print(); //Ans - "this is parent"
c.print(); //Ans - "this is child"
//In this case different forms of the method print(of parent class) is done by naming the parent method as virtual
//So basically when we call the child print method it first determines the type of handle the child has.
//Now child class is pointing to no one else and is of type child and so it goes up the hierarchy and finds that the called function type to be
//virtual in parent. So it comes down and calls child implementation
end
//case 2
begin
parent p;
child c;
c = new;
p = c; //This is legal child instance can be copied to parent since it contains subclass of type child only
p.print(); //Ans - "this is child"
c.print(); //Ans - "this is child"
//When we call parent print method it looks for the handle type it is containing. It is containing c/child type handle.
//so child function is called
//When we call child print method it does the same as case 1
end
//case3
begin
parent p;
child c;
/*
p = new;
c = p;
Above block is illegal you cannot assign parent to child because child does not contain a subclass of type parent!
*/
//So we need dynamic casting as below
child c_temp;
c = new; //create child instance
p = c; //parent now contains child
//c_temp = p; //Static casting or copying will fail due to static type checking at compile time
$cast(c_temp, p); //Dynamic casting will be a success due to dynamic type checking at run time
p.print();//Ans- "this is child"
c_temp.print();//Ans - "this is child"
//Now p is pointing to child. So child function will be called because of virtual method
//c_temp is pointing to parent which in turn points to child, so child function will be called because of virtual method
end
end
endmodule
Stretch Question on above
---
Q5.1: If you declare the child method as also virtual what will happen
Ans - Child methods are by default virtual even if they are not explicitly mentioned/declared, provided the parent's method is virtual
So for all the cases i.e., 1,2,3 prints will remain the same
Stretch Question on above
---
Q5.2: If you don't put virtual keyword in both child and parent what will happen
Now methods will be called solely depending on class type
If function/method names are same in parent and child, child method will override parent method
And even if a parent class is pointing to child instance
or a child handle is pointing to parent which in turn is pointing to child instance....none of that matters
because when you will call the methods it will be called solely on class types i.e.,
visibility of methods is limited to the type of class it is, and not on the type of handle it is pointing to
So
For case 1,2,3:
print1 - "this is parent"
print2 - "this is child"
Question6
---
Q6: Suppose you are given to code AXI master agent which will send aligned address in AWADDR according to AXI spec. Code the basic sequence item and constraint needed for it which will be simulator/emulator friendly
class axi_sequence_item extends uvm_sequence_item;
....//all the necessary stuffs
rand logic [`AXI_ADDRESS_WIDTH -1 :0] AWADDR;
//burst_size will indicate how many bytes of data bus are used in each tranfers/beats(clk) according to AXI spec
typedef enum logic[2:0] //because AxSIZE[2:0]
{
1BYTE = 3'b000,
2BYTE = 3'b001,
4BYTE = 3'b010,
8BYTE = 3'b011,
16BYTE = 3'b100,
32BYTE = 3'b101,
64BYTE = 3'b110,
128BYTE = 3'b111
}
burst_size;
rand burst_size burst_size_t;
constraint generate_burst_aligned_address{
burst_size_t === 1BYTE -> AWADDR = AWADDR;//2**0 = 1
burst_size_t === 2BYTE -> AWADDR[0] = 1'b0;//2**1 = 2
burst_size_t === 4BYTE -> AWADDR[1:0] = 2'b0;//2**2 = 4
burst_size_t === 8BYTE -> AWADDR[2:0] = 3'b0;//2**3 = 8
burst_size_t === 16BYTE -> AWADDR[3:0] = 4'b0;//2**4 = 16
burst_size_t === 32BYTE -> AWADDR[4:0] = 5'b0;//2**5 = 32
burst_size_t === 64BYTE -> AWADDR[5:0] = 6'b0;//2**6 = 64
burst_size_t === 128BYTE -> AWADDR[6:0] = 7'b0;//2**7 = 128
}
constraint solver1 {solve burst_size_t before AWADDR};
....
endclass
You can solve the above by using modulo or (%) operator but when synthesized for running in emulation platform it will have more hardware cost.
Also above method is beneficial in terms of software operation cost.
Question7(High level)
---
Q7: Suppose your AXI master is not sending burst aligned addresses for transfers. Provided you are given Burst size and address, how will you determine how many bytes it is unaligned to burst size.
package abc;
//burst_size will indicate how many bytes of data bus are used in each tranfers/beats(clk)
enum logic[2:0] //because AxSIZE[2:0]
{
1BYTE = 3'b000,
2BYTE = 3'b001,
4BYTE = 3'b010,
8BYTE = 3'b011,
16BYTE = 3'b100,
32BYTE = 3'b101,
64BYTE = 3'b110,
128BYTE = 3'b111
}
burst_size;
function byte calculate_unaligned_offset(
input bit[AXI_ADDR_WIDTH] address,
input bit[2:0] burst_size);
byte unaligned_offset;
case(burst_size)
1BYTE : unaligned_offset = 0;
2BYTE : unaligned_offest = byte'(address[0]); //Static cast to byte size
//i.e., take the axsize no of bits in lsb and fill remaining msbs to 0s
4BYTE : unaligned_offset = byte'(address[1:0]);
8BYTE : unaligned_offset = byte'(address[2:0]);
16BYTE :unaligned_offset = byte'(address[3:0]);
....
endcase
return unaligned_offset;
endfunction : unaligned_offset
endpackage
You can solve the above by using modulo or (%) operator but when synthesized for running in emulation platform it will have more hardware cost.
Also above method is beneficial in terms of software operation cost.
Question8
---
Q8: Which datatype in SV will you use while modelling a big (say.,512MB) RAM
When the size of the collection is unknown or the data space is sparse,
an associative array is used, which does not have any storage allocated
until it is used. That means, it is dynamically allocated, but has non-contiguous elements.
Associative array’s index expression is not restricted to integral expressions, but can be of any type.
In RAM ,huge amount of data needs to be accessed , it's inefficient to declare the size in compile time,
because many spaces may left unused, so it is good to declare as associative array
Question9
---
Q9: Which SV datatype does uvm_config_db uses to implement it's internal configuration database support
and type overriding of elements at runtime
To build uvm_config_db we likely need a <key,value> pair lookup table which is accessible from anywhere and with any index type.
It should also have static "set" and "get" methods. So let us use associative array datatype for implementing the same.
"class_name :: set" & "class_name :: get" methods are used to store and retrieve information from database repectively.
//Rough implementation
class uvm_config_db #(type T = int); //Type is dynamic, default type is integer
//declare a static assoc-array
static T db[string]; //return type of assoc-array is dynamic, default is int type
//Why static, because change of assoc-array by one method call will be visible to all
//define static "set" method
static function void set(input string name, input T value); //set method takes the string index
//and value to be stored in assoc-array(config_db) in that index
db[name] = value;
endfunction : set
//define static "get" method
static function void get(input string name, ref T value); //get method takes the string index
//and value to be obtained from assoc-array(config_db) from that index. value will be obtained as a reference/pointer
value = db[name];
endfunction : get
//define utility print function
static function void print();
$display("config_db%s", $typename(T)); //print the type of return type of config_db
foreach(db[i])
$display("db[%s]=%p", i, db[i]); //print key, value
endfunction : print
endclass : uvm_config_db
//Let's use above config class
class test;
int i,j;
endclass : test
//
module abc;
int i=2;
int val;
real pi=3.14
//take handle of test class
test test_inst;
initial begin
//call set method
uvm_config_db#(int) :: set("i", i);
//Above will create following entry within config_db class
//db["i"] = 2;
uvm_config_db#(real) :: set("pi", pi);
//Above will create following entry within config_db class
//db["pi"] = 3.14;
//take the instance of test class
test_inst = new();
test_inst.i = 8; //i=8 in test class instance
test_inst.j = 6; //j=6 in test class instance
uvm_config_db#(test) :: set("test_inst", test_inst);
//Above will create following entry within config_db class
//db["test_inst"] = test_inst; // or pointer to test_inst
uvm_config_db#(int) :: get("i", val);
//Above will get the following entry value from within config_db class and return to user
//val = db["i"] = 2;
$display("get value of i from db is %0d", val); //will display 2
uvm_config_db#(int) :: print();
//Will print following display
//config_dbint
//db[i] = 2
uvm_config_db#(real) :: print();
//Will print following display
//config_dbreal
//db[pi] = 3.14
uvm_config_db#(test) :: print();
//Will print following display
//config_dbtest
//db[test_inst] = test_inst
end
endmodule:abc
//Actual call to set and get methods in uvm are of the following format
//set from topmost component
string global_value = "soham";
uvm_config_db #(string) :: set(null, "uvm_test_top.*", "string_key", global_value);
//Return 1- success or 0-failure
//get in sub components
string local_value;
uvm_config_db #(string) :: get(this, "", "string_key", local_value);
Question10
Q10: What is the need for a virtual interface in System verilog?
---
* System verilog interface is static in nature, wheras classes are dynamic in nature. Because of this reason, it is not allowed to declare the interface within classes but it is allowed to refer to or point to the interface.
* A virtual interface is a variable of an interface type that is used in classes to provide access to physical interface signals.
* Classes are dynamic and so created at run time, while interfaces are static which is created at compile time. So if you instantiate a physical interface within a class, it will throw compilation error while compilation.
Question11
Q11: Write a SV constraint to generate unique elements in an array (very popular but quite old question)
//Method 1
class abc;
rand bit[`DATA_WIDTH-1:0] data[];
constraint unique_array_constraint
{ foreach(data[i])
foreach(data[j])
if(i!==j)
data[i]!=data[j];
}
endclass:abc
//Method 2
class abc;
rand bit[`DATA_WIDTH-1:0] data[];
constraint unique_array_constraint
{
unique {data};
}
endclass:abc
//Method 3 - Though it is a solution it generates pseudo-random elements., i.e.,
//predicatble to a certain extent. It is not true random
class abc;
rand bit[`DATA_WIDTH-1:0] data[];
constraint unique_array_constraint
{
foreach(data[i])
data[i] = i; //or i*i
}
function post_randomize();
data.shuffle();
endfunction : post_randomize
endclass:abc
Question11
Q11: What can be basic verification scenarios for a NOC with 2 masters accessing 4 slaves. The NOC can handle single protocol.
---
* A bare minimum interconnect VIP should have master active/passive agents connected to the interface and slave IP components as DUT of different bus protocols. The active agents will drive transactions and passive agents will throw any error associated with protocol/timing
* It should have a common interconnect/NOC scoreboard and a coverage monitor
* It should be able to handle multiple protocols
* It should be able to handle setting different transaction attributes(like cacheable, bufferable, prot, priority etc.,) on each channel
* It should be able to handle reconfigurable address mapping and routing on the fly by use of sempahores
* It should have proper response code checking for
- Unmapped addresses
- Unsecured accesses to secured memories
- Transactions to a closed path (for power off or other reasons)
- Custom policy
* It should be able to handle cache coherency
- Communication between coherent masters - An additional coherency scoreboard can be developed comprising of masters which access shared data.This access should be properly communicated to all the concerned masters via coherency protocol and returned data must come from memory or cache
- Proper passing of shared data between coherent masters
- Accurate master ACE responses in regards to cache states
**Probable testcases**
* Check single master accessing all slaves
* Check multiple master trying to get access of bus at same time. Master 1 of higher priority(say) should take hold of bus first and complete transaction. Immediately after it's completion, master 0 of lower priority(say) should finish it's transaction
* Master should access non-existent slaves with unmapped address and check bus signal behaviour
* Master should try to do both write and read in read only slave and check bus signal behaviour
* ...
Question12
Q12: How to know which master has initiated transaction to or from which slave transaction is coming from, in case of a multimaster and multislave scenario
As we know in AXI, ID field is associated with an atomic transaction. So from transaction only, we will not be able to decipher which master has issued it or which slaves' response is it. We can put a ID associated with each master & slave in User signal of AXI in transaction for this.
//master a monitor
class master_a_mon extends uvm_monitor;
transaction trans;
uvm_analysis_port#(transaction) ap1;
//new
ap1 = new("ap1", this);
//run_phase
trans = transaction::type_id::create("trans",this);
ap1.write(trans);
...
endclass: master_a_mon
/*-------------------------------------------------
---------------------------------------------------
-------------------------------------------------*/
//master b monitor
class master_b_mon extends uvm_monitor;
transaction trans;
uvm_analysis_port#(transaction) ap2;
//new
ap2 = new("ap2", this);
//run_phase
trans = transaction::type_id::create("trans",this);
ap2.write(trans);
...
endclass: master_b_mon
//scoreboard
`uvm_analysis_imp_decl(_port_a)
`uvm_analysis_imp_decl(_port_b)
class scoreboard extends uvm_scoreboard;
uvm_analysis_imp_port_a #(transaction, scoreboard) imp_a;
uvm_analysis_imp_port_b #(transaction, scoreboard) imp_b;
//Queues for respective masters
transaction master_a[$];
transaction master_b[$];
int trans_cnt_mon_a;
int trans_cnt_mon_b;
//new
imp_a = new("imp_a", this);
imp_b = new("imp_b", this);
virtual function void write_port_a(transaction trans);
transaction sb_a;
$cast( sb_a, trans.clone() );
trans_cnt_mon_a++;
case(sb_a.user)
A: begin
master_a.push_back[sb_a];
end
B: begin
master_b.push_back[sb_a];
end
endcase
endfunction
virtual function void write_port_b(transaction trans);
transaction sb_b;
$cast( sb_b, trans.clone() );
trans_cnt_mon_b++;
case(sb_b.user)
A: begin
master_a.push_back(sb_b);
end
B: begin
master_b.push_back(sb_b);
end
endcase
endfunction
endclass : scoreboard
//Environment
class environment extends uvm_env;
//instances
//connect phase
function void connect_phase(uvm_phase phase);
master_a_mon.ap1.connect(scoreboard.imp_a);
master_b_mon.ap2.connect(scoreboard.imp_b);
endfunction
...
endclass
Question13
Q13: What is the difference between m_sequencer and p_sequencer
* m_sequencer is default sequencer and p_sequencer is typecast to m_sequencer
* m_sequencer is a handle of type uvm_sequencer_base which is available by default in a sequence
It is determined by the following:
- the sequencer handle provided in the start method
- the sequencer used by the parent sequence
- the sequencer that was set using the set_sequencer method
* The real sequencer on which a sequence is running would normally be derived from the uvm_sequencer_base class. Hence to access the real sequencer on which sequence is running , you would need to typecast the m_sequencer to the physical sequencer which is generally called p_sequencer
* Since p_sequencer is a typed-specific sequencer pointer, it is generally created by registering the sequence to the sequencer using macro (`uvm_declare_p_sequencer). The p_sequencer is used to access the sequencer properties.
//An example
//Let's say a sequence wants to access a component(monitor_a., say) which is available with its' sequencer
class sequencer_c extends uvm_sequencer;
monitor_a monitor_a_inst1;
endclass : sequencer_c
class sequence_c extends uvm_sequence
sequencer_c p_sequencer;
//or
`uvm_declare_p_sequencer(sequencer_c)
monitor_a monitor_a_inst2;
task pre_body();
//Typecast the m_sequencer base type to p_sequencer
if( !$cast(p_sequencer, m_sequencer) )
begin
`uvm_error("sequence_c:","Sequencer type mismatch ... cast failed")
end
//Now you can access after typecasting
monitor_a_inst2 = p_sequencer.monitor_a_inst1;
endtask : pre_body
endclass: sequence_c
Question14
Q14: What is the difference between virtual sequencer and a normal sequencer
Virtual sequencer contains pointers to real physical sequencers where sequences can run upon. It is not bothered with data driving part. It does not communicate with real physical drivers. It's job is only to have the handles of child sequencers and point them to actual physical sequencers of agents respectively.
Stretch Question on above
Q14: What is the use of virtual sequencer in your SoC Testbench
* It contains handles of the sequencers which are located in different agents
* If stimulus generation across different interfaces has to be synchronized, it is done by Virtual sequencer and virtual sequences
* In practical if you have two IPs in an SoC and you want to have stimulus control, virtual sequencer, virtual sequences helps to achieve it
//Consider seqr1 and seqr2 are handles of seqrs which belongs to agents seq1_agent and seq2_agent respectively within environment "env"
class v_seqr extends uvm_sequencer;
`uvm_component_utils(v_seqr)
//Handles of seqrs
type1_seqr seqr1;
type2_seqr seqr2;
//constructor
function new(string name="v_seqr", uvm_component parent);
super.new(name,parent);
endfunction:new
endclass:v_seqr
//Instantiate v_seqr within virtual sequence "virtual_base_seq"
class base_virtual_seq extends uvm_sequence#(uvm_sequence_item);
`uvm_object_utils(base_virtual_seq)
type1_seqr seqr1;
type2_seqr seqr2;
v_seqr v_seqr_inst;
//or you can use `uvm_declare_p_sequencer macro
//constructor
function new(string name="base_virtual_seq");
super.new(name);
endfunction:new
task body();
if(!$cast(v_seqr_inst, m_sequencer)) begin
`uvm_error(get_full_name(), "v_seqr pointer cast failed")
end
//After casting assign the null pointers to actual child sequencers pointers present inside v_seqr
this.seqr1 = v_seqr.seqr1;
this.seqr2 = v_seqr.seqr2;
endtask:body
endclass:base_virtual_seq
//Let's define virtual sequence
class v_seq extends base_virtual_seq;
`uvm_object_utils(v_seq)
type1_seqr seqr1;
type2_seqr seqr2;
//virtual sequencer handle
v_seqr v_seqr_inst;
//constructor
function new(string name="v_seq");
super.new(name);
endfunction:new
task body();
//call parent body to assign sub_sequence handles correctly
super.body();
//take handles of actual sequences
//for time being, assume there are sequence classes of below names
type1_seq seq1;
type2_seq seq2;
//create seqs
seq1 = type1_seq::type_id::create("seq1");
seq2 = type2_seq::type_id::create("seq2");
//start the sequences
repeat(10)begin
seq1.start(seqr1);
seq2.start(seqr2);
end
endtask:body
endclass:v_seq
//Environment will contain v_seqr, agents, and make connection of v_seqr to actual agent seqrs
...
function void connect_phase(uvm_phase phase);
v_seqr.seqr1 = seq1_agent.m_sequencer;
v_seqr.seqr2 = seq2_agent.m_sequencer;
endfunction:connect_phase
...
//Test will contain v_seq and env
...
virtual task run_phase(uvm_phase phase);
//create v_seq
v_seq_inst = v_seq::type_id::create("v_seq");
phase.raise_objection(this);
//start v_seq on v_seqr
v_seq_inst.start(env.v_seqr_inst);
phase.drop_objection(this);
endtask:run_phase
...
Question15
Q15: What is the difference between a reg, logic in SystemVerilog
* A reg is a data type that can model a storage element or a state. They can be synthesized to FF, latch or combinational circuit (They might not be synthesizable !!!)
* They need to be driven by an always block and cannot be driven by continuous assignment statement.
Eg.,
module abc(clk,in,a);
input clk;
input in;
reg a;
always_ff@(posedge clk);
begin
a <= in; //allowed
end
assign a = in; //not allowed
endmodule:abc
//////
*Logic can be both driven by assign block, output of a port and inside a procedural block like this
//Eg.,
logic a;
assign a = b ^ c; // wire style
always (c or d) a = c + d; // reg style
MyModule module(.out(a), .in(xyz)); // wire style
//////
* A reg can be used to model both sequential and combinational logic.
* A logic is a new data type in SystemVerilog that can be used to model both wires and state information (reg).
* It also is a 4 state variable like reg and hence can hold 0, 1, x and z values.
* If a wire is declared as a logic (wire logic), then it can be used to model multiple drivers and the last assignment will take the value.
* So, Logic data type doesn’t permit multiple driver. It has a last assignment win behavior in case of multiple assignment (which implies it has no hardware equivalence). Reg/Wire data type give X if multiple driver try to drive them with different value. Logic data type simply assign the last assignment value.
Q16: What is the difference between a bit and logic data type?
* bit is a 2-state data type that can take only values 0 and 1, while logic is a 4-state data type which can take values 0, 1, x, and z.
* 2-state variables will help in a small simulation speed up but should not be used if it is used to drive or sample signals from RTL design in which uninitialized and unknown values will be missed.
Q17: Write a code for randomizing the size of a 2D matrix or a multi dimentional array
SystemVerilog multi-dimensional arrays are more like arrays of arrays. That means you have to deal with each dimension separately, and each elements that is an array needs to be sized.
class abc;
rand bit [11:0] width[]; //to store width
rand bit [11:0] height[]; //to store height
rand bit[11:0] md_array [][]; //multidimensional array
constraint width_and_height
{
width.size() inside {[100:5]};
height.size() inside {[200:700]};
}
constraint width_height_and_elements
{
//first assign the size of the first dimension of md_array
md_array.size == width.size();
//Then for each sub-array in first dimension assign their size
foreach(md_array[ii])
{
md_array[ii].size() == height.size();
}
//Now assign constraint to each elements of md_array
foreach(md_array[ii][jj])
{
md_array[ii][jj] inside {[1:255]};
}
}
constraint solver
{
solve width before height;
solve height before md_array;
}
endclass : abc