最近需要进行基于板级的FPGA测试工作,由于需要联合四块不同的板卡同时进行工作,而每块板卡的寄存器访问方式又各不相同,所以进行测试工作时操作比价繁琐。现在,前期手动测试工作基本完毕,下一步是开发自动测试脚本,这里主要一个需求就是,为了使得所有的case格式可以比较统一且简单清晰,同时能够有详细的错误记录,需要在这个板级的自动测试环境上下点功夫。经过几天的努力和尝试,这个脚本环境基本上有了一个大概的框架。
由于设备相关,fpga的测试工作不能像使用开发板那样方便(jtag访问),必须通过设备软件系统进行寄存器的访问。各类板卡的访问环境又不尽相同,一套板卡有一套方法,所以对多块板卡进行联合测试的时候就显得非常繁琐。这里先介绍一下大致的设备系统结构。我们的设备有一个主控的板卡进行整个设备的控制,设备上又可以插多块子板卡,子板卡上也有板卡级别的主控cpu进行控制。主控板卡可以通过网口的方式连接到网络中,而每块板卡也可以通过主控板卡进行桥接访问。手动测试中,一般通过telnet的方式连接到我们的设备上。设备上有许多软件工具,实际上我们也是通过这些软件工具传输我们的命令进行相关的操作。
思考以后,觉得可以运用一下面向对象方法将一些操作分层,这样,经过封装以后,顶层的接口可以统一,底下的实现可以各不相同;同时,某些有共性的操作可以达到复用的效果,这样日后开发扩展就会比较方便。其实,tcl本身并不是一门面向对象的语言,不过幸好其扩展性很强,Itcl模块在一般的tcl版本中都直接集成了,可以通过Itcl实现面向对象的编程。
这里介绍整个脚本平台的构架和思路。
首先,从最低层的角度来看,我们可能需要通过telnet的方式连接到设备上的系统进行寄存器访问,也可能直接调用本地的应用进行寄存器的访问。这样,我就可以设计两种访问方式的类,一种是通过telnet方式访问设备主cpu的类,代码如下:
package require Itcl
#--------------------------------------------------------------------
#this class will set up one telnet handle(connect to EC)
#each time you make create an object of this Class or its subClass,
#tcl will set up an individual handle for each object
#--------------------------------------------------------------------
itcl::class EC {
private common EC_connect_num 0
protected variable host 135.252.101.1
protected variable telnet_port 23
protected variable user {root}
protected variable password {bugaosuni}
protected variable host_prompt "root@:~#"
protected variable chan_array
#-----------------------------------------------------------------
#set up telnet handle by constructor,so once you create object or
#subobject of this class, a diff telnet handle will be set up
#-----------------------------------------------------------------
constructor {} {
if [catch {set telnet_handle [telnet_login $host $telnet_port $user $password $host_prompt]} err] {
puts stderr "$err"
return
}
set chan_array(type) telnet
set chan_array(id) $telnet_handle
set chan_array(prompt) $host_prompt
#set chan_array(prompt) $chan_prompt
incr EC_connect_num
puts "establish one ec telnet connection..."
puts "..."
puts "Total telnet connection number of EC is: $EC_connect_num"
}
destructor {
incr EC_connect_num -1
close $chan_array(id)
puts "destructor one EC telnet connection"
}
}
(先吐槽一下,博客园的代码插入竟然不支持tcl...)
当通过EC这个类创建对象的时候,脚本会自动建立一个telnet的连接工作,,然后我们就可以通过返回的句柄进行进一步的读写操作。
第二个类的实现比较长,但抽象出来看其实很简单,继承EC这个类,创建对象的时候通过父类完成telnet的
package require Itcl#----------------------------------------------------------------
#this class inherit from EC,
#just change some global variable to fix my implementation,
#because wr_rd_cmd is protected method here, so user need wrap
#this class and use it
#----------------------------------------------------------------
itcl::class dbgCut {
inherit EC
protected variable app_pwd "/???/???/???"
protected variable dbgCut_prompt "???????>"
protected variable slot_num 2
protected variable dev "N/A"
private common 20p200_err_cnt 0
constructor {} {EC::constructor} {
puts "dbgCut"
set chan_array(prompt) $dbgCut_prompt
if [catch {set rdata [chan_issue_cmd chan_array $app_pwd ]} err] {
puts stderr "$err"
return
}
}
destructor {EC::destructor}
protected method io_write_cmd {addr val} {
set dbgCutThru_1 "!dbgCutThru (flts 0 otumach 1 "
set dbgCutThru_2 $slot_num
set dbgCutThru_3 ") "
set dbgCutThru $dbgCutThru_1$dbgCutThru_2$dbgCutThru_3
set bar "_2"
set val _$val
set tt1 "\"drvdbg kitewrite__"
set tt2 "\" "
return $dbgCutThru$tt1$addr$val$bar$tt2
}
protected method io_read_cmd {addr} {
set dbgCutThru_1 "!dbgCutThru (flts 0 otumach 1 "
set dbgCutThru_2 $slot_num
set dbgCutThru_3 ") "
set dbgCutThru $dbgCutThru_1$dbgCutThru_2$dbgCutThru_3
set bar "_2"
set tt1 "\"drvdbg kiteread__"
set tt2 "\" "
return $dbgCutThru$tt1$addr$bar$tt2
}
#copy from yinghuic, need check here
#public mm {args}
protected method rd_wr_cmd {args} {
#parray chan_array
##args check and assigned
set args_num [llength $args]
puts "$args"
puts "$args_num"
if { $args_num < 2 } {
##error
set msg_err "error: The number of arguments in proc mm should be no less than 2"
log_puts $msg_err
error $msg_err $::errorInfo
}
set args_1st [lindex $args 0]
set args_ind [string range $args_1st 0 1]
set args_rw [string range $args_1st 2 end]
if ![string equal $args_ind "--" ] {
##error
set msg_err "error: The indication of mm should be -- instead of $args_ind"
error $msg_err $::errorInfo
}
set args_addr [lindex $args 1]
##args_addr validated here
set args_num [expr $args_num-2]
if [string equal $args_rw "rdl" ] {
if {$args_num >1 } {
##error
set msg_err "error: more arguments than need in $args"
error $msg_err $::errorInfo
} else {
if {$args_num == 1} {
##length validate here
set args_len [lindex $args 2]
}
#set cmd "mm $args"
set cmd [io_read_cmd $args_addr]
if [catch {set rdata [chan_issue_cmd chan_array $cmd ]} err] {
set msg_err "error: $err"
error $msg_err $::errorInfo
}
log_puts $rdata
return $rdata
}
} elseif [string equal $args_rw "wrl" ] {
if {$args_num == 1} {
##args_data validate here ##send command to host
set args_data [lindex $args 2]
#set cmd "mm $args"
set cmd [io_write_cmd $args_addr $args_data]
if [catch {set rdata [chan_issue_cmd chan_array $cmd ] } err] {
set msg_err "error: $err"
error $msg_err $::errorInfo
}
log_puts $rdata
return $rdata
} else {
##error
set msg_err "error: more arguments than need in mm -- wrl $args_data"
error $msg_err $::errorInfo
}
} else {
##error
set msg_err "error: unkown command :$args_rw "
error $msg_err $::errorInfo
}
}
protected method reg_check {addr val} {
set read_str ""
set rd_addr ""
set rd_val ""
# 0 -- no err 1 -- err occur
set err_flag 0
set raw_data [rd_wr_cmd --rdl $addr]
regexp {dbgCut> kite read reg (0x[0-9a-zA-Z]+) on bar 2: data = (0x[0-9a-zA-Z]+)} $raw_data read_str rd_addr rd_val
set tmp_1 [format $addr]
set tmp_2 [format $rd_addr]
set tmp_3 [format $val]
set tmp_4 [format $rd_val]
if {($tmp_1 == $tmp_2) && ($tmp_3 == $tmp_4)} {
puts "read register address: $rd_addr, value is: $rd_val"
set err_flag 0
return [list $err_flag $rd_addr $rd_val]
} else {
log_puts "err> dev $dev, slot $slot_num ) reg_chk $addr is error!"
log_puts "err> expect : addr is $addr , data is $val"
log_puts "err> return val:read address is $rd_addr, read data is $rd_val"
set err_flag 1
incr 20p200_err_cnt
return [list $err_flag $rd_addr $rd_val]
}
}
public method check_20p200_error_cnt {} {
puts "total 20p200 err cnt is : $20p200_err_cnt"
return $20p200_err_cnt
}
protected method clear_total_20p200_error_cnt {} {
puts "the total 20p200 error count has been clear!!!"
set 20p200_err_cnt 0
}
}
第二个层次实际是将telnet以后的软件命令进行了一次封装,并提供了一些方法供外部以及子类使用,这里注意 common这个变量的使用,实际作用类似于c++中的静态变量,这里用静态变量作为err cnt,是因为可以将所有派生自该类的子类板卡的错误统计信息汇总起来。 将第二个层次抽象出来以后,再上一层的板卡类可以集成不同的位于第二层的父类,实现不同板卡不同的读写方式(接口是统一的,这样改动就会比较小,只需要修改继承关系就可以了。
太晚了,暂时写到这,办卡类的实现,以及后续自动平台功能扩展的架构构想放在后面一篇说吧。