Wednesday, November 14, 2012

Sytemverilog UVM testbench for Xilinx EMAC

I used Xilinx's coregen to generate the  Virtex 5 Ethernet Tri-MAC, which is essentially the hardened MAC along with some soft-logic. Coregen also generates a simple verilog testbench, which injects 3 packets. The MAC interface is GMII, which is an 8 bit - 125 MHz interface for Gigabit ethernet. The ethernet packets are 802.3.

The verilog testbench is fairly limited in functionality and the ethernet packet data structure is described using a module. Needless to say it is clunky and difficult to make enhancements. I figured anyone wishing to build a more complex packet processor, would need a more sophisticated testbench.

I decided to build one in System verilog, using the UVM class framework. Thanks in part to the EDA vendors,   people don't believe you've built a testbench unless its in UVM.

Essentially I use a system verilog class to represent an ethernet 802.3 packet,  build drivers and monitors to inject and watch for packets, also use a checker to verify the packets coming out (a self-checking testbench). UVM provides the class framework.

Here's the breakdown of the steps I followed.

Edited the demo_tb.v  removing the configuration_tb and emac0_phy_tb instance, creating tb_uvm.v Moved the clock and reset generation blocks from configuration_tb.v to tb_uvm.v.
For UVM drivers and monitor to access signals, system verilog interfaces instances are created and stored in a UVM resource database (dB). This happens in lines 107 through 130.
The test itself "default_run_test" is launced in line 131.
tb_uvm.v
  1 //-----------------------------------------------------
  2 // Systemverilog+UVM testbench to exercise a 
  3 // Xilinx coregen Virtex5 Trimac with a GMII interface
  4 //-----------------------------------------------------
  5 
  6 `timescale 1ps / 1ps
  7 
  8 import uvm_pkg::*;
  9 
 10 module tb_uvm;
 11 
 12    reg        reset;
 13 
 14   // EMAC0
 15   wire        tx_client_clk_0;
 16   wire [7:0]  tx_ifg_delay_0; 
 17   wire        rx_client_clk_0;
 18   wire [15:0] pause_val_0;
 19   wire        pause_req_0;
 20 
 21   // GMII wires
 22   wire        gmii_tx_clk_0;
 23   wire        gmii_tx_en_0;
 24   wire        gmii_tx_er_0;
 25   wire [7:0]  gmii_txd_0;
 26    reg            gmii_rx_clk_0;
 27   wire        gmii_rx_dv_0;
 28   wire        gmii_rx_er_0;
 29   wire [7:0]  gmii_rxd_0;
 30 
 31   // Clock wires
 32    reg            host_clk;
 33   reg         gtx_clk;
 34   reg         refclk;
 35 
 36    reg       config_busy;
 37 
 38   //---------------------------------------------------
 39   // Wire up Device Under Test
 40   //---------------------------------------------------
 41   v5_emac_v1_8_example_design dut
 42     (
 43     // Client Receiver Interface - EMAC0
 44     .EMAC0CLIENTRXDVLD               (),
 45     .EMAC0CLIENTRXFRAMEDROP          (),
 46     .EMAC0CLIENTRXSTATS              (),
 47     .EMAC0CLIENTRXSTATSVLD           (),
 48     .EMAC0CLIENTRXSTATSBYTEVLD       (),
 49 
 50     // Client Transmitter Interface - EMAC0
 51     .CLIENTEMAC0TXIFGDELAY           (tx_ifg_delay_0),
 52     .EMAC0CLIENTTXSTATS              (),
 53     .EMAC0CLIENTTXSTATSVLD           (),
 54     .EMAC0CLIENTTXSTATSBYTEVLD       (),
 55 
 56     // MAC Control Interface - EMAC0
 57     .CLIENTEMAC0PAUSEREQ             (pause_req_0),
 58     .CLIENTEMAC0PAUSEVAL             (pause_val_0),
 59 
 60      // Clock wire - EMAC0
 61     .GTX_CLK_0                       (gtx_clk),
 62 
 63     // GMII Interface - EMAC0
 64     .GMII_TXD_0                      (gmii_txd_0),
 65     .GMII_TX_EN_0                    (gmii_tx_en_0),
 66     .GMII_TX_ER_0                    (gmii_tx_er_0),
 67     .GMII_TX_CLK_0                   (gmii_tx_clk_0),
 68     .GMII_RXD_0                      (gmii_rxd_0),
 69     .GMII_RX_DV_0                    (gmii_rx_dv_0),
 70     .GMII_RX_ER_0                    (gmii_rx_er_0),
 71     .GMII_RX_CLK_0                   (gmii_rx_clk_0),
 72 
 73 
 74     .REFCLK                          (refclk),
 75         
 76     // Asynchronous Reset
 77     .RESET                           (reset)
 78     );
 79 
 80   //-----------------------------------------------
 81   // Flow Control is unused in this demonstration
 82   //-----------------------------------------------
 83   assign pause_req_0 = 1'b0;
 84   assign pause_val_0 = 16'b0;
 85 
 86   // IFG stretching not used in demo.
 87   assign tx_ifg_delay_0 = 8'b0;
 88 
 89   //---------------------------------------------
 90   // Clock drivers
 91   //--------------------------------------------
 92 
 93   // Drive GTX_CLK at 125 MHz
 94   initial                 // drives gtx_clk at 125 MHz
 95   begin
 96     gtx_clk <= 1'b0;
 97     #10000;
 98     forever
 99     begin    
100       gtx_clk <= 1'b0;
101       #4000;
102       gtx_clk <= 1'b1;
103       #4000;
104     end
105   end
106 
107    //Systemverilog interface instances to pass onto the
108    //uvm testbench drivers and monitors
109    gmii_mon_intf tx_mon_intf( .clk(gmii_tx_clk_0),
110                           .data(gmii_txd_0),
111                           .valid(gmii_tx_en_0),
112                           .err(gmii_tx_er_0) );
113 
114    gmii_mon_intf rx_mon_intf( .clk(gmii_rx_clk_0),
115                           .data(gmii_rxd_0),
116                           .valid(gmii_rx_dv_0),
117                           .err(gmii_rx_er_0) );
118 
119    gmii_drv_intf drv_intf( .clk(gmii_rx_clk_0),
120                        .data(gmii_rxd_0),
121                        .valid(gmii_rx_dv_0),
122                        .err(gmii_rx_er_0),
123                        .config_busy(config_busy) );      
124    
125    initial
126      begin
127     //Store interface handles in resource db
128     uvm_resource_db #(virtual gmii_mon_intf )::set("tx_mon", "intf", tx_mon_intf,null);
129     uvm_resource_db #(virtual gmii_mon_intf )::set("rx_mon", "intf", rx_mon_intf,null);
130     uvm_resource_db #(virtual gmii_drv_intf )::set("drv", "intf", drv_intf,null);   
131     run_test("default_run_test");
132      end
133 
134   // Drive refclk at 200MHz
135   initial
136     begin
137     refclk <= 1'b0;
138     #10000;
139     forever
140     begin
141       refclk <= 1'b1;
142       #2500;
143       refclk <= 1'b0;
144       #2500;
145     end
146   end
147 
148   // drives gmii_rx_clk at 125 MHz for 1000Mb/s operation
149   initial    
150   begin
151     gmii_rx_clk_0 <= 1'b0;
152     #20000;
153     forever
154     begin
155       #4000;
156       gmii_rx_clk_0 <= 1'b1;
157       #4000;
158       gmii_rx_clk_0 <= 1'b0;
159     end
160   end
161    
162   // hostclk freq = 1/3 gtx_clk freq 
163   initial
164     begin
165        host_clk <= 1'b0;
166        #2000;
167        forever
168      begin   
169         host_clk <= 1'b1;
170         #12000;
171         host_clk <= 1'b0;
172         #12000;
173      end
174     end
175 
176 //Reset
177    initial
178      begin 
179     $display("timing checks invalid");
180 
181     reset <= 1'b1;
182     config_busy <= 0;
183 
184     #200000
185       config_busy <= 1;
186 
187     $display("reset design");
188 
189     reset <= 1'b1;
190     #4000000
191       reset <= 1'b0;
192     #200000;
193      
194     $display("timing checks valid");
195     #15000000;
196     #100000     ;
197     config_busy <= 0;
198   end 
199 
200 endmodule
So what are these system verilog interface thingys anyway ?
Lets take a look at the interface for the GMII driver. Lines 2 through 6 specify the port and direction, we'll be driving the data, valid, err signals, hence they are outputs. clk is an input, driven by the clock generator in the top level testbench, tb_uvm.v.  The default clocking block specifies that the outputs and inputs are to be sampled with respect to the rising clock edge.
gmii_drv_intf.sv
 1 interface gmii_drv_intf   (
 2     input clk, 
 3     output [7:0] data, 
 4     output    valid, 
 5     output    err,
 6     input     config_busy) ;
 7 
 8 default clocking pclk @(posedge clk);
 9 endclocking
10 
11 endinterface
In a similar fashion, for a monitor we have (gmii_mon_intf.sv).
gmii_mon_intf.sv
 1 interface gmii_mon_intf (
 2        input clk, 
 3        input [7:0] data, 
 4        input valid, 
 5        input err) ;
 6 
 7 default clocking pclk @(posedge clk);
 8 endclocking
 9 
10 endinterface

Notice that a passive monitor does not drive any signals, so everything is an input.
There are many ways to describe interfaces, you may see the direction being set by modports as opposed to the direction being set in the interface port declaration list. So you can actually combine the driver and monitor in one interface and have modports distinguish between driver and monitor. I will defer stuff like that to post discussing interfaces.

At this point, building the hooks to access the RTL is finished. We jump over to the test side to define the packet, driver, monitor, checker and put everything together in a test.

The ethernet packet is defined in the class  below. All data objects derive from uvm_sequence_item in UVM. I'll point out some nifty features of System verilog. A packet can have a variable length payload, from 46 to 1500 bytes. System verilog provides several features to transform a packet to a stream of bytes (remember thats what goes on the wire) and vice versa.  Line 7 defines a queue of bytes, calling the pack() method on the packet will fill bytestream. The "real" packet fields are between lines 9 and 16. The data field on line 14 is variable length and so is described as a queue. The constraint on line 22, chooses a payload length and also sizes the data byte queue. The uvm_object_utils macros setup print,copy, pack and unpack methods. The built-in pack and unpack methods do not work for us, since we have a variable length field. The pack() and unpack() methods use the system verilog streaming operator, which allows us to do in one line what the UVM custom packing methods would take several lines to do. In UVM custom packing methods must be defined in do_pack() and do_unpack(). In another post I will compare the two ways.
ethernet_pkt.v

  1 //-------------------------------------------------
  2 // Ethernet 802.3 packet
  3 //-------------------------------------------------
  4 class ethernet_pkt extends uvm_sequence_item ;
  5 
  6 static int pkt_count = 0;
  7 byte bytestream[$] ;
  8 
  9 rand bit [55:0] preamble ;
 10 rand byte sfd ;
 11 rand bit [47:0] da;
 12 rand bit [47:0] sa ;
 13 rand bit [15:0] length;
 14 rand byte data[$];
 15 rand bit [31:0] fcs;
 16 rand bit [31:0] calc_fcs;
 17 
 18 rand int unsigned payload_length ;
 19 
 20 constraint preamble_c { preamble == 56'h55555555555555; }
 21 constraint sfd_c { sfd == 8'hd5; }
 22 constraint payload_length_c { 
 23    payload_length inside {[46:1500]}; 
 24    data.size() == payload_length; }
 25 
 26 `uvm_object_utils_begin(ethernet_pkt)
 27 `uvm_field_int( preamble, UVM_ALL_ON | UVM_NOPACK );
 28 `uvm_field_int( sfd, UVM_ALL_ON | UVM_NOPACK );
 29 `uvm_field_int( da, UVM_ALL_ON | UVM_NOPACK );
 30 `uvm_field_int( sa, UVM_ALL_ON | UVM_NOPACK );
 31 `uvm_field_int( length, UVM_ALL_ON | UVM_NOPACK | UVM_DEC );
 32 `uvm_field_queue_int( data, UVM_ALL_ON | UVM_NOPACK );
 33 
 34 `uvm_field_int( fcs, UVM_ALL_ON | UVM_NOPACK | UVM_NOCOMPARE );
 35 `uvm_field_int( payload_length, UVM_ALL_ON |
 36             UVM_NOPACK | UVM_DEC | UVM_ABSTRACT | UVM_NOCOMPARE);
 37 `uvm_field_int( calc_fcs, UVM_ALL_ON | 
 38             UVM_NOPACK | UVM_ABSTRACT | UVM_NOCOMPARE);
 39 `uvm_object_utils_end
 40 
 41 function new(string name="ethernet_pkt");
 42       super.new(name);
 43       pkt_count++;
 44 endfunction // new
 45 
 46 function void post_randomize();
 47       length = payload_length ;
 48       do_calc_fcs();
 49       fcs = calc_fcs ;
 50 endfunction // void
 51 
 52 task  do_calc_fcs() ;
 53       byte unsigned byte_array[];
 54       int unsigned num_bytes ;
 55       bit [31:0] tmp ;
 56       
 57       this.pack_bytes( byte_array );
 58       num_bytes = byte_array.size();
 59 
 60       calc_fcs = 32'hffffffff;
 61       for (int i=8;i<num_bytes-4;i++)
 62     calc_fcs = nextCRC32_D8( byte_array[i],calc_fcs);
 63       //Invert
 64       calc_fcs = ~calc_fcs ;
 65       //Bit reverse
 66       calc_fcs = { << {calc_fcs} };
 67       //Byte reverse
 68       calc_fcs = { << 8{calc_fcs}};
 69 endtask
 70 
 71 task pack();
 72       bytestream = { >> {preamble,sfd,da,sa,length,data,fcs}};
 73 endtask // pack
 74 
 75 task unpack() ;
 76       { >> {preamble,sfd,da,sa,length,data,fcs}} = bytestream ;
 77       do_calc_fcs();
 78 endtask // unpack
 79 
 80 function bit [31:0] nextCRC32_D8 (bit [7:0] Data, bit [31:0] crc);
 81 
 82     reg [7:0] d;
 83     reg [31:0] c;
 84     reg [31:0] newcrc;
 85 
 86     d = Data;
 87     c = crc;
118       newcrc[29] = d[4] ^ c[27] .......
119       newcrc[30] = c[28] ^ d[0] ^ ..........
120       newcrc[31] = c[29] ^ c[23] ^ d[2];
121 
122     return(newcrc);
123       
124 endfunction // nextCRC32_D8
125 
126 endclass

The gmii_driver class takes a packet (from a UVM sequencer), packs it into a bytstream and sends it on the wire. Line 12 shows the declaration of an interface handle, always preceded by the keyword virtual. In Line 20, we retrieve the gmii_drv_intf instance from the resource dB. The key lookup for this dB is defined by concatenating the first and second arguments. get_name() returns a string, that was used as the name argument when the driver object is created. The driver, monitor and checker objects are created (or new'd) in a parent class. UVM suggests an environment class. For this example, the test class acts as the parent.
 1 `ifndef __GMII_DRIVER__
 2 `define __GMII_DRIVER__
 3 
 4 import uvm_pkg::*;
 5 
 6 `include "ethernet_pkt.sv"
 7 
 8 class gmii_driver extends uvm_driver #(ethernet_pkt) ;
 9 
10 `uvm_component_utils(gmii_driver)
11 
12 virtual gmii_drv_intf  intf ;
13 
14 function new(string name, uvm_component parent);
15    super.new(name,parent);
16 endfunction
17    
18 function void build_phase( uvm_phase phase);
19    super.build_phase(phase);
20    uvm_resource_db #(virtual gmii_drv_intf)::read_by_name(this.get_name(), "intf", intf,null);
21 endfunction
22 
23 function void connect_phase(uvm_phase phase) ;
24       super.connect_phase(phase);
25 endfunction
26 
27 task main_phase(uvm_phase phase);
28    fork
29       begin
30       ethernet_pkt pkt ;
31       intf.valid = 0;
32       intf.data = 0;
33       intf.err = 0;
34      
35       //Wait for reset to finish
36       wait ( intf.config_busy == 0 );
37       wait ( intf.config_busy == 1 );
38       wait ( intf.config_busy == 0 );
39       
40       forever
41         begin
42            //Get packet from sequencer
43            seq_item_port.get_next_item(pkt);
44            
45            pkt.pack() ;
46 
47            //Drive each byte from packed bytestream
48            foreach ( pkt.bytestream[i] )              
49                      begin
50                 @(intf.pclk);
51                 intf.valid = 1 ;
52                 intf.err = 0;
53                 intf.data = pkt.bytestream[i] ;                  
54              end
55               @(intf.pclk);
56            intf.valid = 0;
57            
58            seq_item_port.item_done();
59            
60            //Inter packet gap
61            repeat (200) @(intf.pclk) ;
62               
63         end // forever begin
64       end // fork begin
65    join_none
66 endtask 
67    
68 endclass
69   
70 `endif
The gmii monitor watches the gmii interface, builds an ethernet packet and sends it up to higher layer for processing. In our case this is the checker. The communication is through the UVM analysis port declared in line 12 and new'd in line 20. All the action is defined in the UVM main_phase task.
 1 `ifndef __GMII_MONITOR__
 2 `define __GMII_MONITOR__
 3 
 4 import uvm_pkg::*;
 5 
 6 `include "ethernet_pkt.sv"
 7 
 8 class gmii_monitor extends uvm_monitor ;
 9 
10 `uvm_component_utils(gmii_monitor)
11 
12 uvm_analysis_port #(ethernet_pkt) mon_export ;
13 
14 virtual gmii_mon_intf  intf ;
15 int unsigned pkt_count ;
16 
17 function new(string name, uvm_component parent);
18    super.new(name,parent);
19    pkt_count = 0;
20    mon_export = new("mon_export",this);
21 endfunction
22 
23 function int unsigned get_pkt_count();
24    return pkt_count ;
25 endfunction // int
26 
27 function void build_phase( uvm_phase phase);
28    super.build_phase(phase);
29    uvm_resource_db #(virtual gmii_mon_intf)::read_by_name(this.get_name(), "intf", intf,null);
30 endfunction
31 
32 function void connect_phase(uvm_phase phase) ;
33    super.connect_phase(phase);
34 endfunction
35 
36 task main_phase(uvm_phase phase);
37    fork
38       int count ;
39       ethernet_pkt pkt ;
40       
41       forever
42      begin
43         count = 0;
44         @(intf.pclk);
45         if ( intf.valid == 1 )
46           begin
47              `uvm_info(this.get_name(),"SOP",UVM_NONE);
48              pkt = new("pkt");
49              do
50                begin
51                   pkt.bytestream.push_back(intf.data);
52                   count = count + 1;
53                   
54                   @(intf.pclk);
55                   if ( intf.valid == 0 ) 
56                        begin
57                           `uvm_info(this.get_name(),"******EOP",UVM_NONE);
58                           pkt.unpack();
59                           mon_export.write( pkt );
60                           pkt = null ;
61                           count = 0;
62                           pkt_count++;
63                        end
64                   end
65              while ( count );
66           end
67      end
68    join_none
69 endtask 
70 
71 endclass
72   
73 `endif //  `ifndef __GMII_MONITOR__
The gmii checker uses ethernet pkt object fifos to pick up packets from the Rx and Tx interface monitors.Then compares the packets. All the fields should match, once the source and destination fields are swapped, which is what the example design does.The communication fifos are defined in lines 10-11,with the fancy uvm_tlm_analysis_fifo name. (TLM = Transaction Level Model). The packet compare method gets created auto-magically in the ethernet packet class, with the UVM object util macro set.
 1 `ifndef __GMII_CHECKER__
 2 `define __GMII_CHECKER__
 3 
 4 `include "ethernet_pkt.sv"
 5 
 6 class gmii_checker extends uvm_agent ;
 7 
 8 `uvm_component_utils(gmii_checker)
 9 
10 uvm_tlm_analysis_fifo #(ethernet_pkt) rx_mon_fifo ;
11 uvm_tlm_analysis_fifo #(ethernet_pkt) tx_mon_fifo ;
12 
13 function new (string name="gmii_checker", uvm_component parent);
14    super.new(name,parent);
15    rx_mon_fifo = new("rx_mon_fifo",this);
16    tx_mon_fifo = new("tx_mon_fifo",this);      
17 endfunction // new
18 
19 function void build();
20    super.build();
21 endfunction // void
22 
23 task run();
24    ethernet_pkt rx_pkt ;
25    ethernet_pkt tx_pkt ;
26    bit [47:0] swp ;
27    fork
28       forever
29      begin
30         tx_mon_fifo.get(tx_pkt);
31         //The design swaps the source and destination addresses
32         //checker does the same.
33         swp = tx_pkt.da ;
34         tx_pkt.da = tx_pkt.sa ;
35         tx_pkt.sa = swp;
36         rx_mon_fifo.get(rx_pkt);
37         
38         if ( tx_pkt.compare(rx_pkt) )
39           begin
40              `uvm_info(this.get_name(),"**** PKT MATCH ******",UVM_NONE);
41              tx_pkt.print();
42           end
43         else
44           `uvm_error(this.get_name(),"*** PKT MISMATCH *****");
45      end
46    join_none
47    
48 endtask // run
49 
50 endclass
51   
52 `endif //  `ifndef __GMII_CHECKER__
UVM has the concept of stimulus sequences, which are essentially streams of uvm_sequence_items, in our case this would be a stream of ethernet packets. Nothing precludes you from sending a packet to a driver using another mechanism such as sytem verilog's built-in mailbox. Indeed for simple stimulus streams, sequences are overkill. The glue that gets a sequence into a driver is the sequencer, which you will see is defined in the test class. The body() task is what defines how the sequence is built.
 1 `ifndef __DEFAULT_PKT_SEQUENCE__
 2 `define __DEFAULT_PKT_SEQUENCE__
 3 
 4 `include "ethernet_pkt.sv"
 5 
 6 class default_pkt_sequence extends uvm_sequence #(ethernet_pkt);
 7 
 8 int cnt = 10;
 9 ethernet_pkt pkt ;
10 
11 `uvm_object_utils_begin(default_pkt_sequence)
12 `uvm_field_int(cnt,UVM_ALL_ON);
13 `uvm_object_utils_end
14 
15 function new(string name = "default_pkt_sequence");
16    super.new(name);
17 endfunction // new
18 
19 task body();
20    repeat(cnt)
21      begin
22      pkt = new("pkt");
23      start_item(pkt);
24      
25      pkt.randomize() with { payload_length == 46; } ;
26 
27        finish_item(pkt);
28 end
29 endtask // body
30 
31 endclass
32   
33 `endif
34 
At the test, finally ! For this example, I've used the test to create all the testbench objects and glue stuff together. UVM purists will want a UVM agent that brings the driver+monitor+sequencer, then a UVM environment class that instantiates an agent, followed by a test class that instantiates the environment class. The testbench objects are created in lines 35-40. The first argument in the create or new functions give each object its name. In the connect function on line 45, we get the sequencer to talk to the driver and the monitors to the checker. The main_phase starts the test. UVM uses an objection mechanism to keep a phase alive. Once all objects have dropped objections in a phase, that phase is terminated. You will notice that the driver, monitor and checker did not raise an objection. Without the test raising an objection in the main phase, the main phase would basically terminate immediately giving us a "do-nothing" test. For our simple case, the test is kept alive by waiting for the same number of packets that were sent in. I plan to discuss other methods, timeouts etc in a future post.
 1 //-------------------------------------------------
 2 // Test: Bringing everything together
 3 //-------------------------------------------------
 4 import uvm_pkg::*;
 5 
 6 `include "gmii_monitor.sv"
 7 `include "gmii_driver.sv"
 8 `include "default_pkt_sequence.sv"
 9 `include "gmii_checker.sv"
10 
11 class default_run_test extends uvm_test ;
12 
13 `uvm_component_utils(default_run_test)
14 
15 gmii_monitor tx_mon ;
16 gmii_monitor rx_mon ;
17 gmii_driver drv ;
18 gmii_checker checker ;
19 
20 uvm_sequencer #(ethernet_pkt) seqr ;
21 
22 default_pkt_sequence seq ;
23 
24 time countdown_interval ;
25 
26 function new(string name, uvm_component parent);
27       super.new(name,parent);
28       drv_mbox = new(1);
29       `uvm_info("", "Called default_run_test::new", UVM_NONE);
30       countdown_interval = 1000ns;
31 endfunction: new
32 
33 function void  build_phase(uvm_phase phase);
34    super.build_phase(phase);
35    tx_mon = gmii_monitor::type_id::create("tx_mon",this);
36    rx_mon = gmii_monitor::type_id::create("rx_mon",this);
37    drv = gmii_driver::type_id::create("drv",this);
38    seqr = new("seqr",this);
39    seq = default_pkt_sequence::type_id::create("seq",this);
40    checker = new("checker",this);
41 
42    uvm_top.enable_print_topology = 1;
43 endfunction
44 
45 function void connect();
46    drv.seq_item_port.connect(seqr.seq_item_export);
47    tx_mon.mon_export.connect( checker.tx_mon_fifo.analysis_export );
48    rx_mon.mon_export.connect( checker.rx_mon_fifo.analysis_export );
49 endfunction // void
50 
51 task main_phase(uvm_phase phase);
52    ethernet_pkt pkt ;
53    int num_pkts = 3;
54    
55    phase.raise_objection(this);
56 
57    //Send pkt sequence
58    seq.start(seqr);
59 
60    //Loop until all pkts Tx by DUT
61    while(tx_mon.get_pkt_count() < num_pkts )
62      #(countdown_interval);
63    
64    phase.drop_objection(this);
65    
66 endtask // main_phase
67 
68 endclass
That pretty much describes all the components of the testbench. The complete source code tarball for the testbench is on github.
You can browse at : https://github.com/austinchipworks/sv_uvm_xil_emac_tb For a local copy: git clone https://github.com/austinchipworks/sv_uvm_xil_emac_tb

Monday, November 5, 2012

Correct by Construction Verilog RTL: Rule summary

Follow on to original post at:
http://siliconbootcamp.blogspot.com/2012/11/writing-correct-by-construction-verilog.html

Naming Conventions
All signals have suffixes of the form:

 _[r,w].[c,d].{#} : choose 1 of the letters in each [] and concatenate to build suffix

[r,w]: r=>register, w=>wire
[c,d]: c=>control,  d=>datapath
{#} : Optional. Reflects register stage number
      Eg: pop_rc1, is a cycle  delayed from pop_rc
            pop_rc2, is 2 cycles delayed from pop_rc


RULES
1. In the combinatorial block :
   1.1 Only *_w* signals must be on LHS. (get values assigned to them)
   1.2 All signals must have a default assignment, before the case
   1.3 Control signals that pulse must have a default assignment of 0
   1.4 Signals that hold their value, must have a default assignment to their registered equivalent.
       *_w* = *_r* ;
       Eg: push_pending_wc = push_pending_rc ;
   1.5 Conditional statements can use *_w* or *_r* signals.
   1.6 A conditional statement cannot use a signal in a its conditional expression and also have an assignment to it.
       Eg: if ( eom_detected_wc & fifo_full_rc )
       eom_detected_wc = 0;
       (this will create a combinational loop).


2. In the clocked block:
   2.1 All Control signals must have a reset value.,
   2.2 All Datapath signals need not have a reset value, like those registers in the middle of a pipeline.
   2.3 Only *_r* signals must be on LHS.
   2.4 Signal assignments will be of the form *_r* <= *_w* ;


GUIDELINES
1. In the combinatorial block :
   1.1 Generally datapath signals will "hold" values, whereas control signals will not.
   1.2 If a default assignment is missed, the synthesis tool will warn you about latches begin inferred.
  

Friday, November 2, 2012

Xilinx Coregen and EDK


 Adding Xilinx Coregen macros to EDK designs

1. Generate coregen macro, enable generation of .v files. The netlist will be a .ngc file

2. Look at .v file, will have a synthesis translate_off  and on directive around the behavioral simulation model

3. Synthesis will take any module with an io port definition and treat it as a blackbox.

   eg black box definition:

             module foo ( clk , rst, datain, dataout) ;

                input clk, rst;

                input datain ;

                output dataout;

             //If you want to include behavioral simulation code here 

             //do so within synthesis translate off and on

             // XST and synplicity should recognize this

             //synthesis translate_offf

              ....

              ....

              ....

             //synthesis translate_on

 

             endmodule

4. Synthesize using XST. This will create an .ngc file

5. In pcores/<ip_name>/data directory create a .bbd file

          This is just a comma separated list of the coregen.ngc files

          eg: cam_v6_1.ngc, srlfifo39.ngc

6. In the .mpd file add

          OPTION STYLE = MIX

          OPTION RUN_NGCBUILD = TRUE

7. Create pcores/<ip_name>/netlist directory and copy the .ngc files listed in step (5) here.

8. Invoke EDK build flow.
 

Xilinx commands and Filetypes

Xilinx commands and filetypes One-pager

===========================================

Xilinx File Types 

.ngc : Netlist file


.ngo : Similar to .ngc, output of Coregen, EDIF2NGD
 

.ncd : Mapped, placed , routed file
 

.ucf : User constraint file, read by NGDBUILD .
 

.pcf : Physical constraints file, output by MAP and used by PAR.

 
==========================================

NGCBUILD: Merges multiple .ngc (synthesized netlist files) into a single .ngc file. Useful for chipscope core insertion.

syntax:

ngcbuild -i <top_level_input_file>.ngc  <output_file>.ngc  

Options:

-sd : specify source directories? if all .ngc files not in the same directory as <top_level_input_file>.ngc

 
NGDBUILD: Reads (multiple) .ngc files to create and single .ngd file. This file is the entry point for MAP,PAR.
 

MAP: Reads .ngd file, output mapped .ncd file and optionally .pcf files.
 

PAR: Place and route, reads mapped .ncd file and outputs routed .ncd file

 
TRCE: Trace, reads .ncd file and what else.

========================================

Chipscope Core Inserter

Once you have .cdc file, can run inserter from the command line.

syntax:

inserter -insert <file.cdc> <input_file>.ngc <output_file>.ngc

======================================

Example windows script:
 
rem Clean up the results directory

rmdir /S /Q results

mkdir results

echo 'Synthesizing HDL example design with XST';

xst -ifn xst.scr

move xilinx_pci_exp_ep.ngc .\results\endpoint_blk_plus_v1_9_top.ngc

cd results

echo 'Running ngdbuild'

rem   ngdbuild -verbose -uc ..\xilinx_pci_exp_blk_plus_1_lane_ep_xc5vlx50t-ff1136-1.ucf endpoint_blk_plus_v1_9_top.ngc -sd ..\..\..\..\..\

ngdbuild -verbose -uc ..\xupv5-lx110t_pcie_x1_plus.ucf endpoint_blk_plus_v1_9_top.ngc -sd ..\..\..\..\
 

echo 'Running map'

map -timing -ol high -xe c -pr b -o mapped.ncd endpoint_blk_plus_v1_9_top.ngd mapped.pcf

echo 'Running par'

par -ol high -xe c -w mapped.ncd routed.ncd mapped.pcf

echo 'Running trce'

trce -u -v 100 routed.ncd mapped.pcf

echo 'Running design through netgen'

netgen -sim -ofmt verilog -ne -w -tm xilinx_pci_exp_ep -sdf_path ..\..\implement\results routed.ncd 

echo 'Running design through bitgen'

bitgen -w routed.ncd

=================================

command to create ace file.

impact -batch pcie_ace.cmd

pcie_ace.cmd >>

setMode -acecf

addCollection -name "ML509"

addDesign -version 6 -name "cfg4"

addDeviceChain -index 0

setCurrentDesign -version 6

setCurrentDeviceChain -index 0

addDevice -p 1 -file "./pcie_dma_top.bit"

generate -active ML509

quit

<< 

From xapp859 for ml505

bitgen -g ConfigRate:20 <file>

Xilinx EDK and Synplify


 Using Synplify to synthesize pcore logic in EDK
 
Pcores are user defined custom cores to hook up to to a Microblaze or PPC based embedded system in Xilinx FPGAs. Synplify often produces better results than XST (the Xilinx synthesis tool) both in terms of timing and area. 

1. Generate pcore, enable the generate in verilog option
2. This will create user_logic.v
 
3. Create a synplify project file for user_logic.v and all modules underneath it.

4. In synplify go to File->New, then select new project file. 

5. Add user_logic.v and other modules referenced by it. Synplify will also treat modules with only IO declaration as a blackbox. These blackbox modules will be resolved in the ngcbuild step.

6. Go under implementation options, select the right xilinx part no.

7. Most important under implementation options, disable automatic IO insertion.

8. Run in synplify

9. Synplify generates an .edf file

10. Copy to pcores<ip_name>/netlist directory and run edif2ngd user_logic.edf, rename output from user_logic.ngo to user_logic.ngc.

11. In pcores/hdl/verilog, edit user_logic.v to remove all code between the IO declaration and endmodule. Synthesis will take any module with an io port definition and treat it as a blackbox.

            eg black box definition:

             module foo ( clk , rst, datain, dataout) ;

                input clk, rst;

                input datain ;

                output dataout;

  
             //If you want to include behavioral simulation code here 

             //do so within synthesis translate off and on

             // XST and synplicity should recognize this

             //synthesis translate_offf

              ....

              ....

              ....

             //synthesis translate_on

             endmodule         

12. Edit the pcores/<ip_name>/data/.pao  file , if need be , it should only reference user_logic.v (in addition to the the other edk libraries and vhdl wrapper).

13. In pcores/<ip_name>/data directory create a .bbd file

          This is just a comma separated list of the coregen.ngc files, with a first line saying Files

          eg bbd file:
 
          # This is a comment, must have Files keyword below

          Files

          srlfifo39.ngc , user_logic.ngc 

14. In the .mpd file add

          OPTION STYLE = MIX

          OPTION RUN_NGCBUILD = TRUE

15. Invoke EDK build flow. 

16. If it fails MAP , it could be because synplify has not optimized away unconnected inputs. Create dummy connection to the problem LUTs. Rerun synplify.

17. Complete build.

This has been tested on the sample ml509_dpi  design with fifo read logic.

It ran into a problem with MAP. Issue was traced to the Intr_Event not being driven, drove it with a dummy register and then it worked.

(should try a simply tie off and see)

Sample synplify project file:

#-- Synplicity, Inc.

#-- Version C-2009.06-SP1

#-- Written on Wed Apr 14 12:30:27 2010
 

#project files

add_file -verilog "./srlfifo39.v"

add_file -verilog "./tag_data_splitter.v"

add_file -verilog "./tag_parser.v"

add_file -verilog "./user_logic.v"

 
#implementation: "rev_1"

impl -add rev_1 -type fpga

#device options

set_option -technology Virtex5

set_option -part XC5VLX110T

set_option -package FF1136

set_option -speed_grade -1

set_option -part_companion ""

 

#compilation/mapping options

set_option -use_fsm_explorer 0

set_option -top_module "user_logic"

 

# sequential_optimization_options

set_option -symbolic_fsm_compiler 1

 

# Compiler Options

set_option -compiler_compatible 0

set_option -resource_sharing 1

 
# mapper_options

set_option -frequency auto

set_option -write_verilog 0

set_option -write_vhdl 0

 
# Xilinx Virtex2

set_option -run_prop_extract 1

set_option -maxfan 10000

set_option -disable_io_insertion 1

set_option -pipe 1

set_option -update_models_cp 0

set_option -retiming 0

set_option -no_sequential_opt 0

set_option -fixgatedclocks 3

set_option -fixgeneratedclocks 3

# Xilinx Virtex5

set_option -enable_prepacking 1 

#VIF options

set_option -write_vif 1

#automatic place and route (vendor) options

set_option -write_apr_constraint 1

 
#set result format/file last

project -result_file "./rev_1/user_logic.edf"

#

#implementation attributes

set_option -vlog_std v2001

set_option -project_relative_includes 1

impl -active "rev_1"