UVM Example Reference 1


UVM Example Reference 1

uvm_ref_flow_1.2是不错的学习材料,从UT到SOC的环境都可以参考这里面的代码来写,至少可以当做代码的模板来用。

和我之前一般看到的做法不同,monitor被完全实现在了transaction level,signal level的东西放在了一个叫collector的class里面。

还有一个特别值得说明是,在apb_slave_seq_lib.sv中的simple_response_seq通过其p_sequencer的tlm接口addr_trans_port来生成包,不错的应用方法。

  class simple_response_seq extends uvm_sequence #(apb_transfer);
     // ...
     virtual task body();
	`uvm_info(get_type_name(), "Starting...", UVM_MEDIUM)
	forever begin
	   p_sequencer.addr_trans_port.peek(util_transfer);
	   // ...
	end
     endtask : body
  endclass : simple_response_seq

为了让sequencer能看到发包的情况,建立了一条tlm通道。从总线到apb_collector,再到apb_monitor,最后到apb_slave_sequencer。

<img src="../../images/uvm_ref_flow1.png" class="img-thumbnail" width="60%" >

用事件来在同一个class的两个task之间做同步和信息传递,用peek的tlm通路来层层传递。

  class apb_slave_sequencer extends uvm_sequencer #(apb_transfer);
     // ...
     uvm_blocking_peek_port#(apb_transfer) addr_trans_port;
     // ...
  endclass : apb_slave_sequencer

  class apb_monitor extends uvm_monitor;
     // ...
     // Allows the sequencer to look at monitored data for responses
    uvm_blocking_peek_imp#(apb_transfer,apb_monitor) addr_trans_export;

    // Allows monitor to look at collector for address information
    uvm_blocking_peek_port#(apb_transfer) addr_trans_port;

    event trans_addr_grabbed;
  endclass : apb_monitor

    // UVM run_phase
  task apb_monitor::run_phase(uvm_phase phase);
    forever begin
      addr_trans_port.peek(trans_collected);
      `uvm_info(get_type_name(), $sformatf("Address Phase Complete: %h[%s]", trans_collected.addr, trans_collected.direction.name() ), UVM_HIGH)
      -> trans_addr_grabbed;
    end
  endtask : run_phase

  // FUNCTION: peek - Allows the sequencer to peek at monitor for responses
  task apb_monitor::peek(output apb_transfer trans);
    @trans_addr_grabbed;
    trans = trans_collected;
  endtask : peek

  class apb_collector extends uvm_component;
     // ...
     // TLM Port - Allows sequencer access to transfer during address phase
    uvm_blocking_peek_imp#(apb_transfer,apb_collector) addr_trans_export;
    event addr_trans_grabbed;
  endclass : apb_collector

     // UVM run_phase()
  task apb_collector::run_phase(uvm_phase phase);
      @(posedge vif.preset);
      `uvm_info(get_type_name(), "Detected Reset Done", UVM_LOW)
      collect_transactions();
  endtask : run_phase

  // collect_transactions
  task apb_collector::collect_transactions();
    forever begin
      @(posedge vif.pclock iff (vif.psel != 0));
      void'(this.begin_tr(trans_collected,"APB_COLLECTOR","UVM Debug","APB collector transaction inside collect_transactions()"));
      trans_collected.addr = vif.paddr;
      trans_collected.master = cfg.master_config.name;
      trans_collected.slave = cfg.get_slave_name_by_addr(trans_collected.addr);
      case (vif.prwd)
	1'b0 : trans_collected.direction = APB_READ;
	1'b1 : trans_collected.direction = APB_WRITE;
      endcase
	@(posedge vif.pclock);
      if(trans_collected.direction == APB_READ)
	trans_collected.data = vif.prdata;
      if (trans_collected.direction == APB_WRITE)
	trans_collected.data = vif.pwdata;
      -> addr_trans_grabbed;
      @(posedge vif.pclock);
      if(trans_collected.direction == APB_READ) begin
	  if(vif.pready != 1'b1)
	    @(posedge vif.pclock);
	trans_collected.data = vif.prdata;
	end
      this.end_tr(trans_collected);
      item_collected_port.write(trans_collected);
      `uvm_info(get_type_name(), $sformatf("Transfer collected :\n%s",
		trans_collected.sprint()), UVM_MEDIUM)
	`ifdef HEAP
	runq.push_back(trans_collected);
	`endif
       num_transactions++;
      end
  endtask : collect_transactions

  task apb_collector::peek(output apb_transfer trans);
    @addr_trans_grabbed;
    trans = trans_collected;
  endtask : peek

再看一下连接关系。

  // UVM connect_phase
  function void apb_env::connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    // Get the virtual interface if set via get_config
    if (!uvm_config_db#(virtual apb_if)::get(this, "", "vif", vif))
      `uvm_error("NOVIF",{"virtual interface must be set for: ",get_full_name(),".vif"})
    bus_collector.item_collected_port.connect(bus_monitor.coll_mon_port);
    bus_monitor.addr_trans_port.connect(bus_collector.addr_trans_export);
    master.monitor = bus_monitor;
    master.collector = bus_collector;
    foreach(slaves[i]) begin
      slaves[i].monitor = bus_monitor;
      slaves[i].collector = bus_collector;
      if (slaves[i].is_active == UVM_ACTIVE)
	slaves[i].sequencer.addr_trans_port.connect(bus_monitor.addr_trans_export);
    end
  endfunction : connect_phase

Emacs verilog mode


Emacs的verilog mode

是不是觉得verilog的语法,有一些啰嗦呢,实例的例化、信号连接工作太无聊呢?来来来,试试Emacs。 有人说Emacs老土的编辑器了,现在没有人用了吧,还真不是,其强大的扩展性(可折腾性),真是IC验证人的出门在外必备良药啊。 那么在哪里能安装呢?来这里找

如果是windows版本,需要设置一下环境变量。方法是,控制面板->系统->高级系统设置->环境变量->新建->变量名是HOME->变量值是C:\Users\heyao\documents。

根据自己的需要选择合适的变量值,用来存放用户的.emacs.d文件夹以及.emacs文件,我选择的是C:\Users\heyao\documents。.emacs.d主要是用来放置一下emacs的插件,.emacs是用户自己的初始化设置。

先不折腾这个配置,打开emacs,敲入下面代码:

module fanout (/*AUTOARG*/)
   input i;
   output [31:0] o;
   wire [31:0] o = {32{i}};
endmodule

保存,(快捷键CTRL+x CTRL+s,为了便于说明,用C-x C-s来表示),命名文件为fanout.v。

然后,用户C-c C-a,代码就变成了如下:

module fanout (/*AUTOARG*/
   // Outputs
   o,
   // Inputs
   i
   )
   input i;
   output [31:0] o;
   wire [31:0] o = {32{i}};
endmodule

我们再C-x 3,并排打开窗口,然后C-x C-f,新建一个文件ex_inst.v,如下:

module ex_inst (/*AUTOARG*/)
   input i;
   output [31:0] o;
   inout      io;

   fanout fanout (/*AUTOINST*/);
endmodule

然后,C-c C-a,代码就变成了如下:

module ex_inst (/*AUTOARG*/
   // Outputs
   o,
   // Inouts
   io,
   // Inputs
   i
   )
   input i;
   output [31:0] o;
   inout      io;

   fanout fanout (/*AUTOINST*/
	  // Outputs
	  .o            (o[31:0]),
	  // Inputs
	  .i            (i));
endmodule

可以看到Emacs自动帮忙做了实例化。

Learn some SQL


Learn some SQL

SQL(structured query language)可以理解为一种标准语言,不管底层最终的database是MySQL还是sqlite,都是支持这个标准语言的。虽然在脚本语言中,有DBI(database interface) module,比如Perl里的DBIx::Class,为用户操作数据库提供了便利的wrapper,但是了解一些基本的SQL操作,很有必要。 推荐两个非常棒的SQL教学视频,比如:sqlite3 tutorialMySQL

再回到我最近看的MojoExample源代码上来。Schema::Result::Blog.pm,有一段代码是这样:

__PACKAGE__->has_many(
  "blog_tags",
  "Schema::Result::BlogTag",
  { "foreign.blog" => "self.id" },
  { cascade_copy => 0, cascade_delete => 0 },
);

就是说一个Blog有多个BlogTag,table blog_tags的column成员blog映射到table blogs的column成员id上。 再来看Schema::Result::BlogTag.pm的一段:

__PACKAGE__->belongs_to(
  "blog",
  "Schema::Result::Blog",
  { id => "blog" },
  { is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" },
);

这个代码不够友好,参考这个帖子versioning-with-dbic 后,我们把这一段改一下:

__PACKAGE__->belongs_to(
  "blog",
  "Schema::Result::Blog",
  { "foreign.id" => "self.blog" },
  { is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" },
);

这样就清晰多了,table blogs的column成员id映射到table blog_tags的column成员blog上。

进到MojoExample目录下,看一下现有的数据库长什么样:

sqlite3 test.db

发几个命令熟悉一下,最后一条是JOIN命令,另外还有LEFT JOIN,RIGHT JOIN等,去w3school上看吧。

<img src="../../images/sqlite3.png" class="img-thumbnail" width="60%" >

My, Our and Local in Perl


My, Our and local

"my"比较常见,"our"和"local"偶尔会出现。查了好多文档,比如perldoc中的perlmod说明,也是比较模糊,而且又提到"lexical"变量的说法,更是让人一头雾水。下面这个文档比较清晰,值得一读。它从Perl发展的历史角度来讲述区别,同时解释了package变量和"lexical"变量的含义。 Coping with Scoping

举一个例子,加深理解。

package abc;
use strict;
use warnings;

$VERSION = 0.1;

sub get_ver {$VERSION}

package main;
print "step 1: $abc::VERSION\n";
print "step 2: ". abc::get_ver();

如上面代码所示,我们想在"package abc"中使用具有全局属性的package变量"$VERSION",但是由于用了"use strict",所以上面一段代码编译会报如下错。

Global symbol "$VERSION" requires explicit package name (did you forget to declare "my $VERSION"?) at a.pl line 7.
Global symbol "$VERSION" requires explicit package name (did you forget to declare "my $VERSION"?) at a.pl line 10.
Execution of a.pl aborted due to compilation errors.

根据提示我们可以修改成下面这样,在package abc内部使用自己的变量也要加上package名称做前缀:

package abc;
use strict;
use warnings;

$abc::VERSION = 0.1;

sub get_ver {$abc::VERSION}

package main;
print "step 1: $abc::VERSION\n";
print "step 2: ". abc::get_ver();

为了不这样麻烦,出现了our函数,这样我们就能更优雅一点的使用package变量了:

package abc;
use strict;
use warnings;

our $VERSION = 0.1;

sub get_ver {$VERSION}

package main;
print "step 1: $abc::VERSION\n";
print "step 2: ". abc::get_ver();

Uvm Reporter Mechanism


UVM Reporter Mechanism

UVM的log打印分为6个等级,如下图所示:

<img src="../../images/uvm_verbosity.png" class="img-thumbnail" width="60%" >

由下至上,打印的等级逐级升高。假设在一个task里面有如下的代码,那么即将打印的是后三条。

`uvm_info("ID0", "this is debug message", UVM_DEBUG)
`uvm_info("ID1", "this is full message", UVM_FULL)
`uvm_info("ID2", "this is high message", UVM_HIGH)
`uvm_info("ID3", "this is medium message", UVM_MEDIUM)
`uvm_info("ID4", "this is low message", UVM_LOW)
`uvm_info("ID5", "this is none message", UVM_NONE)

<img src="../../images/uvm_log.png" class="img-thumbnail" width="60%" >

因为仿真器默认的verbosity是UVM_MEDIUM,所以ID0~ID2的信息没有打印出来。可以通过命令行来修改打印的verbosity,比如:

+uvm_set_verbosity=component_name,id,verbosity,phase_name,optional_time

`uvm_warning、`uvm_error、`uvm_fatal可以看作是verbosity都是UVM_NONE的`uvm_info,所以它们是默认打印的。但是它们的行为可以通过命令行改变,比如默认的`uvm_fatal是要立刻停止仿真的,但是可以通过+uvm_set_action来改变它。

+uvm_set_action=component,id,severity,action

这里可以做简单的练习: my simple test

Uvm Factory Mechanism


UVM的Factory机制

验证环境要具有比较好的通用性,以及适应性。比如某一种情况下,需要我们发的transaction的某一些位域发生改变,或者dut接口上一些特定行为需要我们改变driver。UVM的factory机制为我们处理这些情况,提供了非常方便的方法。 典型的验证环境是这样的:

<img src="../../images/uvm_env.png" class="img-thumbnail" width="60%" >

很可能我们想要把发的transaction变成bad_transaction,那么我们就要重写一个bad_sequence,然后再写一个bad_driver,test也要重写。下面的代码请感受一下:

class bad_transaction extends transaction;
  bit bad;

function new(string name);
  super.new(name);
  bad = 1;
endfunction

endclass

class bad_sequence extends sequence;
  bad_transaction m_bad_transaction;
function new(string name);
  super.new(name);
  m_bad_transaction = bad_transaction::new("m_bad_transaction"); 
endfunction


endclass

class bad_driver extends driver;
bad_transaction m_bad_transaction;
...
endclass

Factory机制是指,UVM从两张表里寻找用户注册的信息,然后在实例化具体的组件时做判断到底实例化哪一个class的组件,优先从override注册表里找,然后是class类型注册表。

<img src="../../images/uvm_factory.png" class="img-thumbnail" width="60%" >

这样,如果是irun,我们只需要在仿真时的tcl里做override,就可以最大程度的复用之前的代码。

uvm_factory -override -by_type transaction bad_transaction 

当然,需要我们对我们的原始代码做小的调整。`uvm_object_utils帮助我们完成class在factory中的注册,这样在整个test中,其实例化的包是bad_transaction。

class transaction extends uvm_sequence_item;
  `uvm_object_utils(transaction)
  ...
endclass

class bad_transaction extends transaction;
  `uvm_object_utils(bad_transaction)
  bit bad;

function new(string name);
  super.new(name);
  bad = 1;
endfunction

endclass

class sequence extends uvm_sequence;
  transaction m_transaction;
function new(string name);
  super.new(name);
  m_transaction = transaction::type_id::create("m_transaction");
endfunction

endclass

Indirect Register Access


UVM register model为我们的寄存器验证提供了很大的帮助,多种集成register model的方法,尤其是使用layered register sequencer的方法,方便了间接访问寄存器的验证。

uvm_users_guide_1.1的例子分析

参考第5.9.2.3小节,

// translation sequence type
typedef uvm_reg_sequence #(uvm_sequence #(apb_rw)) reg2apb_seq_t;

class block_env extends uvm_env;
  block_reg_model regmodel;
  uvm_sequencer#(uvm_reg_item) reg_seqr;
  apb_agent apb;
  reg2apb_seq_t reg2apb_seq;

  virtual function void connect();
    if (regmodel.get_parent() == null) begin
      regmodel.default_map.set_sequencer(reg_seqr,null);
      reg2apb_seq = reg2apb_seq_t::type_id::create("reg2apb_seq",,get_full_name());
      reg2apb_seq.reg_seqr = reg_seqr;
      reg2apb_seq.adapter =
      reg2apb_adapter::type_id::create("reg2apb",,get_full_name());
      regmodel.set_auto_predict(1);
    end
  endfunction

  virtual task run();
    reg2apb_seq.start(apb.sequencer);
  endtask
endclass

中间层sequencer上的sequence item类型是uvm_reg_item,也即是寄存器的读或者写操作了,其是class。 在test里,start translation sequence。

class my_test extends uvm_test;
  block_env env;
  virtual function void run();
    my_reg_sequence seq = my_reg_sequence::type_id::create("seq",this);
    seq.start(env.reg_seqr);
  endfunction
endclass

实际问题

在我的实际应用中,是想把一个寄存器操作,转化为多个寄存器的读和写。这里,我把间接寄存器放在了一个独立的register map上(名为UnAddressedModules),而不是default map。

class wifi_t extends uvm_reg_block;
...
  uvm_reg_map wifi_top;
  uvm_reg_map UnAddressedModules;
...
  virtual function void build();
    wifi_top = create_map("wifi_top", 'h0, 4, UVM_LITTLE_ENDIAN, 1);
    UnAddressedModules = create_map("UnAddressedModules", 'h0, 4, UVM_LITTLE_ENDIAN, 1);
    default_map = wifi_top;
...    
endclass

在env中,集成register model以及构建layer sequence。

virtual function void build_phase(uvm_phase phase);
    reg_seqr = uvm_sequencer#(uvm_reg_item)::type_id::create("reg_seqr", this);

    rdb = wifi_t::type_id::create("rdb", this);
    rdb.configure(null,"");
    rdb.build();
    rdb.reset();
    rdb.lock_model();
  endfunction

  virtual function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);

    vsequencer.ahb_master_sqr = ahb_master_uvc.sequencer;
    vsequencer.reg_seqr       = reg_seqr;
    rdb.default_map.set_sequencer(vsequencer.ahb_master_sqr, reg2ahb);
    rdb.default_map.set_auto_predict(1);

    rdb.UnAddressedModules.set_sequencer(reg_seqr,null); // note that do not set adapter here
    rdb.UnAddressedModules.set_auto_predict(1);

    reg_tl_seq = reg_tl_seq_t::type_id::create("reg_tl_seq",,get_full_name());
    req_tl_seq.reg_seqr = reg_seqr; // set upstream sequencer
    req_tl_seq.rdb = this.rdb;
    req_tl_seq.adapter = reg2ahb;
    reg_tl_seq.phy_bank_cfg = env_cfg.phy_bank_cfg;

  endfunction

  virtual task main_phase(uvm_phase phase);
    super.main_phase(phase);
    env_cfg.print();
    reg_tl_seq.start(vsequncer.ahb_master_sqr);
  endtask

回过头来,参考uvm_reg_sequence.svh。

virtual task body();
    ...
    forever begin
      uvm_reg_item reg_item;
      reg_seqr.peek(reg_item);
      do_reg_item(reg_item);
      reg_seqr.get(reg_item);
      #0;
    end
  endtask

所以,我自己的sequence为下面的代码,因为不是uvm_reg_sequence的子类,所以需要手动实现上面body类似代码。

virtual task body();
  ...
  forever begin
    uvm_reg_item req;
    reg_seqr.peek(req);
    select_bank(0);
    if (req.kind == UVM_READ) begin
      read_data(req);
    end
    else begin
      write_data(req);
    end
    reg_seqr.get(req);
    #0;
    req.end_tr();
  end

注意,reg_seqr没有对应的driver与之相连,所以要手动end_tr,否则sequence在发出第一个包后,卡住不动,因为没有consume掉这个transaction。可以参考代码uvm_reg_map.svh。

task uvm_reg_map::do_write(uvm_reg_item rw);
  ...
  rw.parent.start_item(rw,rw.prior);
  rw.parent.finish_item(rw);
  rw.end_event.wait_on();// wait rw to be consumed
endtask

总结

层次化的集成register model的方法,处理间接访问寄存器,可行有效,但要注意一些小细节,比如关于transaction生命周期的。

JSON Validator


JSON Validator

XML表达信息比较详尽,格式清晰。其实做代码的generator等脚本的时候,也会通过JSON来传递用户的定制参数的。比如cfg.json的内容如下:

{
    "timescale":"1ns/1ps",
    "dut":{
	"name":"mydut",
	"top_file":"~/mydut.v",
	"filelist":"~/mydut.f"
    },
    "tbp":"demo",
    "env":{
	"name":"bus",
	"agent":[
	    {
		"name":"abc",
		"active":1,
		"off":0,
		"interface":{
		    "ifclk":"clk",
		    "ifrst":"rst",
		    "signal":[
			{
			    "name":"ahb_burst",
			    "bind":"ahb_burst_o"
			},
			{
			    "name":"ahb_valid",
			    "bind":"ahb_valid_o"
			}
		    ]
		}
	    }
	]
    }   
}

同样的,如何在第一时间去check用户输入的json信息是否是正确的?这时候就用到了json-schema文件作为一种validation的标准。假设top.schema.json的内容如下:

{
    "type": "object",
    "properties":{
	"timescale":{
	    "type": "string"
	},
	"dut":{
	    "type":"object",
	    "properties":{
		"name":{
		    "type":"string"
		},
		"top_file":{
		    "type":"string"
		},
		"filelist":{
		    "type":"string"
		}
	    },
	    "required":["name","top_file","filelist"]
	},
	"env":{
	    "type":"object",
	    "properties":{
		"name":{
		    "type":"string"
		},
		"agent":{
		    "type":"array",
		    "items":{
			"type":"object",
			"properties":{
			    "name":{
				"type":"string"
			    },
			    "active":{
				"type":"integer"
			    },
			    "off":{
				"type":"integer"
			    },
			    "interface":{
				"type":"object",
				"properties":{
				    "ifclk":{
					"type":"string"
				    },
				    "ifrst":{
					"type":"string"
				    },
				    "signal":{
					"type":"array",
					"items":{
					    "type":"object",
					    "properties":{
						"name":{
						    "type":"string"
						},
						"bind":{
						    "type":"string"
						}
					    },
					    "required":["name"]
					},
					"minItems":1
				    }
				},
				"required":["signal"]
			    }
			},
			"required":["name"]
		    },
		    "minItems":1
		}
	    }
	}			
    },
    "required":["dut"]
}

我们用perl来处理JSON文件的validation过程,如下:

use JSON qw(decode_json);
use JSON::Validator qw(validate_json);

my $validator = JSON::Validator->new;

open(FILE, "top.schema.json") or die "Can not open file top.schema.json";
my @lines = <FILE>;

my $schema = decode_json(join("\n",@lines));

close(FILE); 
$validator->schema($schema);

open(FILE, "cfg.json") or die "Can not open file cfg.json";
@lines = <FILE>;
close(FILE);
my $json_to_be_validated = decode_json(join("\n",@lines));

# Validate your data
@errors = $validator->validate($json_to_be_validated);

# Do something if any errors was found
die "@errors" if @errors;

通过对比可以发现虽然json数据格式简单,但是json的validation格式很不友好,比XML的DTD格式杂乱的多。

XML Validator


XML Validator

当我在做uvm环境的generator脚本的时候,是通过XML来传递用户的定制参数的。比如cfg.xml的内容如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE top
SYSTEM "top.dtd.xml"
>
  <top>
    <timescale value="1ns/1ps" />
    <dut name="mydut" top_file="~/mydut.v" filelist="~/mydut.f" />
    <tbp name="demo" />
    <env name="bus" >
      <agent name="abc" active="1" off="0" >
	<interface name="aa" >
	  <ifclk name="clk" />
	  <ifrst name="rst" />
	  <signal name="ahb_burst" bind="ahb_burst_o" />
	  <signal name="ahb_valid" bind="ahb_valid_o" />
	</interface>
      </agent>
    </env>
  </top>

那么如何在第一时间去check用户输入的xml信息是否是正确的?这时候就用到了DTD文件作为一种validation的标准。假设top.dtd.xml的内容如下:

<?xml version="1.0" encoding="UTF-8" ?>
  <!-- The top tag is the root tag. -->
<!ELEMENT top (timescale?,dut,tbp?,env) >

<!ELEMENT timescale EMPTY >
  <!ATTLIST timescale value CDATA #REQUIRED>

<!ELEMENT dut EMPTY>
  <!ATTLIST dut name CDATA #REQUIRED>
  <!ATTLIST dut top_file CDATA #REQUIRED>
  <!ATTLIST dut filelist CDATA #REQUIRED>


<!ELEMENT tbp EMPTY>
  <!ATTLIST tbp name CDATA #REQUIRED>

<!ELEMENT env (agent+)>
  <!ATTLIST env name CDATA #REQUIRED>

<!ELEMENT agent (interface)>
  <!ATTLIST agent name CDATA #REQUIRED>
  <!ATTLIST agent active CDATA #REQUIRED>
  <!ATTLIST agent off CDATA #REQUIRED>

<!ELEMENT interface (ifclk*,ifrst*,signal+)>
  <!ATTLIST interface name CDATA #REQUIRED>

<!ELEMENT ifclk EMPTY>
  <!ATTLIST ifclk name CDATA #REQUIRED>

<!ELEMENT ifrst EMPTY>
  <!ATTLIST ifrst name CDATA #REQUIRED>

<!ELEMENT signal EMPTY>
  <!ATTLIST signal name CDATA #REQUIRED>
  <!ATTLIST signal bind CDATA #IMPLIED>

我们用perl来处理XML文件的validation过程,如下:

use XML::LibXML;

my $parser = XML::LibXML->new;

$parser->validation(1);

$parser->parse_file("cfg.xml");