Monday, August 17, 2009, 09:04 AM - Hardcore verification
Due to an annoying spam comments attack I've decided to disable comments for a while. Please feel free to contact me directly with anything you would like to say at avidan_e at yahoo dot com.| permalink
Saturday, August 1, 2009, 08:13 AM - Hardcore verification
e „when“ inheritance has something in common with the internet, the cell-phone, the TV, the reality show and all of these other great inventions: Everybody that was born into the world after they were invented has a very hard time imagining life without it. This disbelief persists even in the face of overwhelming proofs that life can and do exist without these inventions, such as the shocking personal testimonies of people who used to have books in their living room instead of a HD flat screen. Going back to the verification world, people who have used “when” are often convinced that it is impossible to really verify a DUT without this feature, no matter how many one spin tape-outs to the contrary you present them with.
As one of the few people who have gone back in time from a world with “when” to a world without it, I think I have quite a comprehensive view with regards to the needs and requirements that “when” inheritance fulfils, the alternatives to “when” inheritance that SV/OVM has to offer, and the shortcomings of these alternatives. For the sake of my impatient readers, I’ll start with the bottom line: working with a language that doesn’t support “when”, will make your code uglier, and your debugging harder. However, it is definitely not the end of the world.
“when” inheritance is very strong where transactions of different types take place on a single channel completely at random. This somewhat lame description can be resumed in one word: packets. Packet traffic is often made of several types of packets that can appear on a single channel completely at random. When you’re dealing with this kind of traffic, using “when” inheritance will make your code cleaner and easier to debug.
In order to understand why, let us have a look at the two common solutions to generating such packet traffic in SV/OVM. In the first solution, often referred to as “the factory” solution, you would have a different class for each packet type. To generate the packet traffic, you have to go in two stages: First you randomly select the type of the packet you would like to generate next, then you generate an instance of the corresponding class. The code at the end of this entry (which can be used by OVM newbies also as a complete sequence example) demonstrates this solution.
At the height of the e-SV wars, some years ago, this solution was strongly promoted as a “when” replacement. Personally, I don’t like it at all, mainly because of (A) the two step randomization proecess, which limits the use of bidirectional constraints and (B) the fact that the type of the packet, which in my eyes is a property of the packet, is actually moved out of the packet to the factory that generates it (in the case of my example, the factory is located in the sequence). If you would like to constrain all packets to be of a certain type, you have to add a constraint to the factory, not to the packet as you would do in the case of “when”. This means that some of the packet properties are constrained through the packet object itself, while others are constrained through the factory. Ugly. It was also said at the time that this solution might lead to a “class explosion” if you have more then one “when” determinant. From my experience, this doesn’t happen too often in real life projects, so this objection belongs more in the theoretical realm.
If you would like to keep all the packet properties in one single packet class, like me, and still be able to generate different kinds of packets, at a distribution that can be easily constrained from the test by the user, the easiest solution is simply to have a packet class which is the aggregation of all the subtypes. This means that the packet class has all the fields of all the subtypes, but for a specific subtype, only some of these fields are actually valid/used. Obviously, the downside of this approach is that you have many garbage fields in your class, and this might make debugging more complex. When you open your packet in a watch window, you will have to search a bit for the fields that are actually relevant for you. However, this pain can be somewhat reduced by providing type-specific print functions.
So, as said above, verification without “when” is possible, at the price of some ugliness of code, or some extra effort in debugging and if this takes several thousand dollars off your license fees, it is worth considering. What prevents many e people from seeing this simple truth is, I believe the fact that “when” inheritance is very often used at places where it is not really required. Take for example the vr_ad register package: is it really required to define each register as a “when” subtype, or could they also be defined as normal derived classes of a register base class? In my opinion, “when” has little added value here. It is very rare that you just choose to read or write a random register, and if you know which register you’re reading or writing upfront, why don’t you just instantiate the register class you want? In this case, instantiating a specific class and randomizing it means almost no additional pain. Where the type and the transaction are completely random, “when” is a big help. Where the type is not random, and the transaction is, you can do just as well with normal OO.
You can read some more on "when" inheritance in SV here
Factory example:
package packet_pkg; import ovm_pkg::*; `include "ovm_macros.svh" typedef enum {A, B} packet_type_t; class packet_base extends ovm_sequence_item; `ovm_object_utils(packet_base); function new(string name = ""); super.new(name); endfunction endclass class packet_A extends packet_base; rand int a; `ovm_object_utils_begin(packet_A); `ovm_field_int(a, OVM_ALL_ON) `ovm_object_utils_end function new(string name = ""); super.new(name); endfunction endclass class packet_B extends packet_base; rand int b; `ovm_object_utils_begin(packet_B); `ovm_field_int(b, OVM_ALL_ON) `ovm_object_utils_end function new(string name = ""); super.new(name); endfunction endclass class packet_sequencer extends ovm_sequencer#(packet_base, packet_base); `ovm_sequencer_utils(packet_sequencer); function new(string name, ovm_component parent); super.new(name, parent); `ovm_update_sequence_lib endfunction endclass class packet_driver extends ovm_driver#(packet_base, packet_base); `ovm_component_utils(packet_driver) function new(string name, ovm_component parent); super.new(name,parent); endfunction task run(); packet_base next_packet; // This driver does nothing except for printing forever begin seq_item_port.get_next_item(next_packet); ovm_report_info("NEXT_PACKET",{"The next packet is:\n", next_packet.sprint()}); seq_item_port.item_done(next_packet); end endtask endclass class packet_env extends ovm_env; `ovm_component_utils(packet_env); packet_sequencer sequencer; packet_driver driver; function new(string name, ovm_component parent); super.new(name, parent); endfunction // new function void build(); super.build(); sequencer = packet_sequencer::type_id::create("packet_sequencer", this); driver = packet_driver::type_id::create("packet_driver", this); endfunction function void connect(); super.connect(); driver.seq_item_port.connect(sequencer.seq_item_export); driver.rsp_port.connect(sequencer.rsp_export); endfunction endclass class packet_seq_base extends ovm_sequence#(packet_base); `ovm_sequence_utils(packet_seq_base, packet_sequencer) // constrain this field to control packet_type rand packet_type_t packet_type; packet_A p_A; packet_B p_B; function new(string name = "packet_seq_base"); super.new(name); endfunction endclass endpackage package test_pkg; import ovm_pkg::*; import packet_pkg::*; `include "ovm_macros.svh" class specific_seq extends packet_seq_base; `ovm_sequence_utils(specific_seq, packet_sequencer) function new(string name = "specific_seq"); super.new(name); endfunction // new // in a specific sequence: // 1. randomize to choose the next packet // 2. use the sequencer built-in factory // to create the item, randomize and send it task body(); forever begin randomize(); case(packet_type) A : `ovm_do_with(p_A, {p_A.a == 5;}) B : `ovm_do_with(p_B, {p_B.b == 6;}) endcase #1; end endtask endclass class test extends ovm_test; `ovm_component_utils(test); packet_env env; function new(string name = "test", ovm_component parent=null); super.new(name,parent); endfunction virtual function void build(); super.build(); env = packet_env::type_id::create("env", this); set_config_string("env.sequencer","default_sequence", "specific_seq"); endfunction endclass endpackage module top; import ovm_pkg::*; import packet_pkg::*; import test_pkg::*; initial begin run_test(); end endmodule
| permalink
Saturday, July 18, 2009, 09:24 AM - Hardcore verification
OVM users tend to use FIFOs just about everywhere. This is largely due to two reasons: (A) using a FIFO sometime simplifies the OVM code that is required to create the actual connection and (B) This solution is frequently promoted in the OVM reference material. FIFOs, however, come with a significant price, which is often forgotten during coding, but then fires back painfully during debugging: they break the data flow. When you insert a FIFO between two elements you are actually creating, without even noticing it, two independent processes – one which does the put(), and another which does the get(). This means, for example, that if during debugging you want to follow your transaction across a FIFO, then you have to place one breakpoint on the put(), then, when execution stops, go to the other end of the FIFO, and place another breakpoint after the get(). Or it means that you won’t be able to use the function call stack window, to trace back the origins of a specific transaction. To see this live, just run the following example, and follow the instructions in the comments.
package vip_pkg; import ovm_pkg::*; `include "ovm_macros.svh" class tr extends ovm_object; int serial_num; `ovm_object_utils_begin(tr) `ovm_field_int(serial_num, OVM_ALL_ON); `ovm_object_utils_end function new(string name =""); super.new(name); endfunction endclass // a : put side of the fifo // b : get side of the fifo // env : instantiates the fifo and connects // it between a and b // The port at the beginning class a extends ovm_component; `ovm_component_utils(a) ovm_blocking_put_port#(tr) put_port; function new(string name, ovm_component parent); super.new(name,parent); put_port = new("put_port", this); endfunction task run(); int serial_num; forever begin tr tr_i; $cast(tr_i, create_object("tr", "tr_i")); tr_i.serial_num = serial_num++; // To follow a transaction place a breakpoint // on the line below. // When it breaks, go to "b" and place a // breakpoint at the designated point. // This is the only way you can follow a // transaction across a FIFO. put_port.put(tr_i); #1; end endtask endclass // The implementation at the end class b extends ovm_component; `ovm_component_utils(b) ovm_blocking_get_port#(tr) get_port; function new(string name, ovm_component parent); super.new(name,parent); get_port = new("get_port",this); endfunction task run(); tr tr_i; forever begin get_port.get(tr_i); // Place a breakpoint on the line below // to see the transaction come out of // the fifo. // Once it breaks, open the stack window. // You'll see that a is not anywhere there, // although that would be the most helpful // hint. tr_i.print(); end endtask endclass // The env connects the port to the export class env extends ovm_env; `ovm_component_utils(env) a a_i; b b_i; tlm_fifo#(tr) tlm_fifo_i; function new(string name, ovm_component parent); super.new(name,parent); endfunction function void build(); super.build(); $cast(a_i, create_component("a", "a_i")); $cast(b_i, create_component("b", "b_i")); tlm_fifo_i = new("tlm_fifo_i",this); endfunction // void function void connect(); super.connect(); a_i.put_port.connect(tlm_fifo_i.blocking_put_export); b_i.get_port.connect(tlm_fifo_i.blocking_get_export); endfunction endclass // env endpackage // vip package tests; import ovm_pkg::*; import vip_pkg::*; `include "ovm_macros.svh" class test extends ovm_test; `ovm_component_utils(test); env env_i; function new(string name = "test", ovm_component parent=null); super.new(name,parent); endfunction // new function void build(); super.build(); $cast(env_i, create_component("env", "env_i")); endfunction endclass endpackage // z module top(); import ovm_pkg::*; import vip_pkg::*; import tests::*; initial run_test(); endmodule
Note how much effort you need in order to trace a single transaction across one FIFO. If you have several instances of “a” and “b” this will become even worse. Also note, that between the break at the “put()” and the break at the “get()” you’ll have at least 1 delta delay difference. Much better to avoid such garbage delays…Where you really need a FIFO, this pain can’t be avoided. However, in the majority of cases you can simply use a normal port to imp connection as shown below:
package vip_pkg; import ovm_pkg::*; `include "ovm_macros.svh" class tr extends ovm_object; int serial_num; `ovm_object_utils_begin(tr) `ovm_field_int(serial_num, OVM_ALL_ON); `ovm_object_utils_end function new(string name =""); super.new(name); endfunction endclass // tr // a : the port at the beginning // b : the implementation at the end // The port at the beginning class a extends ovm_component; `ovm_component_utils(a) ovm_blocking_put_port#(tr) put_port; function new(string name, ovm_component parent); super.new(name,parent); put_port = new("put_port", this); endfunction task run(); int serial_num; forever begin tr tr_i; $cast(tr_i, create_object("tr", "tr_i")); tr_i.serial_num = serial_num++; put_port.put(tr_i); #1; end endtask endclass // The implementation at the end class b extends ovm_component; `ovm_component_utils(b) ovm_blocking_put_imp#(tr,b) put_imp; function new(string name, ovm_component parent); super.new(name,parent); put_imp = new("put_imp",this); endfunction task put(tr tr_i); tr_i.print(); endtask endclass // The env connects the port to the export class env extends ovm_env; `ovm_component_utils(env) a a_i; b b_i; function new(string name, ovm_component parent); super.new(name,parent); endfunction function void build(); super.build(); $cast(a_i, create_component("a", "a_i")); $cast(b_i, create_component("b", "b_i")); endfunction // void function void connect(); super.connect(); a_i.put_port.connect(b_i.put_imp); endfunction endclass // env endpackage // vip package tests; import ovm_pkg::*; import vip_pkg::*; `include "ovm_macros.svh" class test extends ovm_test; `ovm_component_utils(test); env env_i; function new(string name = "test", ovm_component parent=null); super.new(name,parent); endfunction // new function void build(); super.build(); $cast(env_i, create_component("env", "env_i")); endfunction endclass endpackage // z module top(); import ovm_pkg::*; import vip_pkg::*; import tests::*; initial run_test(); endmodule
If you place a breakpoint on the print() at “b” now, and open the stack window, you’ll see “a”, which is probably what you want.
The port to imp connection, however, can become complicated to use if “b” has to connect to two different data sources. This is common in scoreboards that have to look at DUT input data and DUT output data. The solution is simply to tweak the OVM imp class a little bit so that it sends its own name, along with the transaction, thereby enabling its parent component to know where the transaction came from. For example, if a scoreboard has “dut_input” and “dut_output” ports, then the transactions coming from the “dut_input” port will come along with a string whose value is, surprisingly, “dut_input”. Here are the tweaked versions of the two most popular imps, namely, blocking_put_imp and analysis_imp. You can easily rewrite in the same way any other OVM imp when required.
package multiple_port_imps_pkg; import ovm_pkg::*; `include "ovm_macros.svh" `define TLM_BLOCKING_PUT_MASK (1<<0) `define TLM_ANALYSIS_MASK (1<<8) class ovm_blocking_multiple_put_imp #(type T=int, type IMP=int) extends ovm_port_base #(tlm_if_base #(T,T)); local IMP m_imp; function new (string name, IMP imp); super.new (name, imp, OVM_IMPLEMENTATION, 1, 1); m_imp = imp; m_if_mask = `TLM_BLOCKING_PUT_MASK; endfunction // new virtual function string get_type_name(); return "ovm_blocking_multiple_put_imp"; endfunction // string task put (T t); m_imp.put(get_name(),t); endtask endclass class ovm_multiple_analysis_imp #(type T=int, type IMP=int) extends ovm_port_base #(tlm_if_base #(T,T)); local IMP m_imp; function new (string name, IMP imp); super.new (name, imp, OVM_IMPLEMENTATION, 1, 1); m_imp = imp; m_if_mask = `TLM_ANALYSIS_MASK; endfunction // new virtual function string get_type_name(); return "ovm_multiple_analysis_imp"; endfunction // string function void write (input T t); m_imp.write(get_name(),t); endfunction endclass endpackage
And now you can use these imps to connect “a1” and “a2” to “b”, without any FIFOs.
package vip_pkg; import ovm_pkg::*; import multiple_port_imps_pkg::*; `include "ovm_macros.svh" class tr extends ovm_object; int serial_num; `ovm_object_utils_begin(tr) `ovm_field_int(serial_num, OVM_ALL_ON); `ovm_object_utils_end function new(string name =""); super.new(name); endfunction endclass // tr // a1,a2 : the two ports at the beginning // b : the implementation at the end // The port at the beginning (both are identical) class a extends ovm_component; `ovm_component_utils(a) ovm_blocking_put_port#(tr) put_port; function new(string name, ovm_component parent); super.new(name,parent); put_port = new("put_port", this); endfunction task run(); int serial_num; forever begin tr tr_i; $cast(tr_i, create_object("tr", "tr_i")); tr_i.serial_num = serial_num++; put_port.put(tr_i); #1; end endtask endclass // The implementation at the end class b extends ovm_component; `ovm_component_utils(b) ovm_blocking_multiple_put_imp#(tr,b) put_imp_source1; ovm_blocking_multiple_put_imp#(tr,b) put_imp_source2; function new(string name, ovm_component parent); super.new(name,parent); put_imp_source1 = new("put_imp_source1",this); put_imp_source2 = new("put_imp_source2",this); endfunction task put(string port_name, tr tr_i); case(port_name) "put_imp_source1" : put_source1(tr_i); "put_imp_source2" : put_source2(tr_i); endcase endtask // put task put_source1(tr tr_i); tr_i.print(); endtask // put_source1 task put_source2(tr tr_i); tr_i.print(); endtask endclass // The env connects the port to the export class env extends ovm_env; `ovm_component_utils(env) a a1_i; a a2_i; b b_i; function new(string name, ovm_component parent); super.new(name,parent); endfunction function void build(); super.build(); $cast(a1_i, create_component("a", "a1_i")); $cast(a2_i, create_component("a", "a2_i")); $cast(b_i, create_component("b", "b_i")); endfunction // void function void connect(); super.connect(); a1_i.put_port.connect(b_i.put_imp_source1); a2_i.put_port.connect(b_i.put_imp_source2); endfunction endclass // env endpackage // vip package tests; import ovm_pkg::*; import vip_pkg::*; `include "ovm_macros.svh" class test extends ovm_test; `ovm_component_utils(test); env env_i; function new(string name = "test", ovm_component parent=null); super.new(name,parent); endfunction // new function void build(); super.build(); $cast(env_i, create_component("env", "env_i")); endfunction endclass endpackage // z module top(); import ovm_pkg::*; import vip_pkg::*; import tests::*; initial run_test(); endmodule
| permalink
Back Next





