DPU验证环境全套解决方案

本文

主要介绍一个关于处理器中DPU模块的验证环境。

版本 说明
0.1 初版发布

专业术语与缩略语

缩写 全称 说明
IFU Instruction Fetch Unit 取指令部件
DPU Data Processing Unit 数据运算部件
DCU/LSU Data Cache Unit / Load Store Unit 访存部件
IQ Instruction Queue 指令队列

先修知识

  • ARM处理器架构和微架构基础。
  • Verilog与SystemVerilog。
  • UVM基础。
  • Shell和Python脚本编程。
  • Makefile编程。
  • 交叉编译环境。
  • ARM汇编裸机程序。

什么是DPU?

处理器主要分为三个大的模块:

  • IFU:取指部件。
  • DPU:数据运算部件。
  • DCU/LSU:访存部件。

其中DPU内主要包含指令的译码、整型寄存器和浮点寄存器、运算单元以及其他流水控制等功能模块。

  • IFU与DPU之间存在一个IQ,其目的是将IFU与DPU的连接进行解耦,解除强相关。
  • DPU接收来自IFU发送的指令码,其中指令已经由IFU完成了预译码操作,主要获取指令类型和归整寄存器编号等字段。
  • decode模块接收来自IFU或IQ的指令,对指令进行译码以及拆分微操作。
  • dispatch模块完成指令依赖的检查,依据规则控制指令或微操作进入issue阶段。
  • issue模块将指令或微操作依据类型发送到各个执行单元,同时读取寄存器获取操作数。
  • 运算单元完成相应指令的运算。
  • 将运算结果写回目的寄存器,指令结束。
  • 其中还会发生异常和跳转,需要刷新流水线,以及正常的流水线控制都在dpu_ctl模块。

dpu部件流水线大致如下:


DPU_UT环境主要内容

  • 测试程序框架:基于ARM汇编程序实现一个裸机测试程序框架,主要涉及内容为系统寄存器初始化、异常向量表、栈指针初始化、异常等级切换、A64和A32混合编程,以及基于Makefile实现的自动化编译和链接。
  • 基于Python实现的随机指令生成器:支持通用寄存器、扩展寄存器、系统寄存器的更新。
  • ARM FastModel的启动脚本:将通过FastModel载入elf文件,trace出指令流信息(tarmac文件),以此作为验证平台的激励和Reference Model。
  • 基于Python实现的指令信息提取脚本:负责将指令执行信息从tarmac文件中提取出来,重新组织格式输出到文件,便于验证平台载入。
  • 基于UVM搭建的验证平台:driver负责载入指令信息并控制驱动dut,并将指令信息传送给checker,注意在驱动前需要先配置dut部分寄存器,并且配置信息要与fastmodel和裸机程序中的系统寄存器初始化保持一致;checker将接收来自driver发送的指令信息,并更新至checker中的通用寄存器和扩展寄存器结构,同时对dut通用寄存器和扩展寄存器进行采样,保存数据并与checker的数据进行check。
  • 验证环境的自动化:基于shell实现单条case的仿真自动化,并支持多种仿真参数;基于Python实现回归测试的自动化,主要内容包括查找caselist确定测试case集合、调用单条case的仿真脚本启动仿真、回归结果报告、自动merge覆盖率数据。

测试程序框架

请参考之前的博客“一个简易bootloader框架”。

随机指令生成器

请参考之前的博客“基于Python实现ARM随机指令生成器”。

FastModel启动脚本

FastModel启动脚本重点是配置参数,这里展示部分内容,具体请根据项目自行设定。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
MODEL="VAL_VAL_AEMv8A"
trace_args="-t -C TRACE.tarmac.trace-file=your_testcode"
elf_args="-C elfloader.elf=./your_testcode.elf"
rvbar_args="0x00000000"

$MODEL \
$trace_args \
$elf_args \
-C cpu.cpu0.RVBAR=$rvbar_args \
-C cpu.has_arm_v8-1=1 \
-C cpu.has_arm_v8-2=1 \
-C cpu.has_fp16=0x1 \
......
  • 可以在$MODEL后加-l,查看log,能够看到一些默认的配置信息。
  • 由于指令程序可能会发生死锁,导致程序无法正常结束而一直执行,这样会生成非常大的tarmac文件,为了避免这种情况的发生,在调用fastmodel的启动脚本时,最好加上"timeout 3”,超时则会自动杀死进程。
  • 详细使用说明,可以-h查找帮助,或者登录https://developer.arm.com/tools-and-software/simulation-models/fast-models ,下载使用手册。

从tarmac提取指令信息

从tarmac提取指令信息实际就是一个纯文本处理脚本,使用Python和正则表达式个人认为是最佳选择。

tarmac的内容

首先看一下tarmac的内容格式:


  • 第一,第二个字段:仿真cycle数
  • 第三个字段:指令类型: IT代表普通指令,IS代表跳转指令
  • 第四个字段:当前指令编号
  • 第五个字段:PC值,如果VA和PA相等,只会显示一个,如果不等,显示为 VA:PA
  • 第六个字段:二进制指令码
  • 第七个字段:架构,O代表A64架构,A代表A32架构,T代表thumb架构
  • 第八个字段:对于AArch64,为exception EL和secure state,对于AArch32,为exception mode和secure state
  • 第九个字段:反汇编
  • 另外会显示寄存器写回的结果,以及cpsr或fpsr状态更新值。

提取的内容

需要提取的格式(根据验证环境需求自定义,这里只作为示例):

100 d2805001 00003a58 101 11 0 0000000000000280 000 00 0 0000000000000000 000 00 0 0000000000000000 0 00 0 0000000000000000

以上字段按顺序排列,其含义如下:

  • <instr_id>:指令的编号,十进制
  • <instr_raw_code>:指令码,32位十六进制
  • <instr_pc>:指令的PC值,32位十六进制
  • <instr_tgt_reg1_vfg>:指令第一个目的寄存器的valid、float、global,3位二进制
  • <instr_tgt_reg1_wren>:指令第一个目的寄存器的高/低32写有效信号,2位二进制
  • <instr_tgt_reg1_num>:指令第一个目的寄存器的编号,十进制
  • <instr_tgt_reg1_data>:指令第一个目的寄存器的数据,64位十六进制
  • <instr_tgt_reg2_vfg>:指令第二个目的寄存器的valid、float、global,3位二进制
  • <instr_tgt_reg2_wren>:指令第二个目的寄存器的高/低32写有效信号,2位二进制
  • <instr_tgt_reg2_num>:指令第二个目的寄存器的编号,十进制
  • <instr_tgt_reg2_data>:指令第二个目的寄存器的数据,64位十六进制
  • <instr_tgt_reg3_vfg>:指令第三个目的寄存器的valid、float、global,3位二进制
  • <instr_tgt_reg3_wren>:指令第三个目的寄存器的高/低32写有效信号,2位二进制
  • <instr_tgt_reg3_num>:指令第三个目的寄存器的编号,十进制
  • <instr_tgt_reg3_data>:指令第三个目的寄存器的数据,64位十六进制
  • <instr_tgt_sreg_valid>:指令系统寄存器的valid,1位二进制
  • <instr_tgt_sreg_wren>:指令系统寄存器的高/低32写有效信号,2位二进制
  • <instr_tgt_sreg_num>:指令系统寄存器的第三个目的寄存器的编号,十进制
  • <instr_tgt_sreg_data>:指令系统寄存器的数据,64位十六进制

寄存器组织形式:

验证环境与dut的寄存器组织形式:

  • 通用(整型)寄存器共有32个64bit寄存器,A64下x对应{d}的64bit,w对应{d}的低32bit;A32和Thumb下r对应{d}的低32bit。
  • 扩展(浮点)寄存器共有64个64bit寄存器,A64下v对应{2*d+1, 2*d}两个64bit寄存器组成128bit,d对应{2*d}寄存器的64bit,s对应{2*d}寄存器的低32bit,h对应{2*d}寄存器的低16bit;A32和Thumb下q对应{2*d+1, 2*d}两个64bit寄存器组成128bit,d对应{d}寄存器的64bit,s对应{d/2}寄存器的低或高32bit(取决于余数),h对应{d/4}寄存器的4个16bit中某一个(取决于余数)。

以上描述中对于通用寄存器容易理解,而对于扩展寄存器可能难于理解,下面以图说明:

A64下扩展寄存器的组织结构

A32下扩展寄存器的组织结构

python脚本

关于从tarmac提取指令信息的python脚本,a64、a32、t32略有不同,主要体现在扩展寄存器num的提取和tarmac字段的匹配上,这里只展示a64下的脚本。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216

import sys
import os
import re

args_tmcfile = ""

# usage and get args
#--------------------------------------------
if sys.argv[1] == "--help" or sys.argv[1] == "-h":
    print ""
    print "<usage>:"
    print "    python extract_tarmac.py [tmcfile=xxx]"
    print "<example>:"
    print "    python extract_tarmac.py tmcfile=test.tmc"
    print ""
    sys.exit()

for run_arg in sys.argv:
    if run_arg.strip().split("=")[0].strip() == "tmcfile":
        args_tmcfile = run_arg.strip().split("=")[1].strip()

cur_path = os.getcwd()
tmcfile_path = cur_path+"/"+args_tmcfile

# args check
if os.path.isfile(tmcfile_path) == 0:
    print "TMC ERROR: "+tmcfile_path+" not exist, please double check!!!"
    sys.exit()
#--------------------------------------------

# extract information
#--------------------------------------------
strat_instr_id       = 100000000
end_instr_id         = 100000000

instr_id             = "0" #%d
instr_raw_code       = "00000000" #%h
instr_pc             = "00000000" #%h
instr_tgt_reg1_vfg   = "000" #%b
instr_tgt_reg1_wren  = "00" #%b
instr_tgt_reg1_num   = "0" #%d
instr_tgt_reg1_data  = "0000000000000000" #%h
instr_tgt_reg2_vfg   = "000" #%b
instr_tgt_reg2_wren  = "00" #%b
instr_tgt_reg2_num   = "0" #%d
instr_tgt_reg2_data  = "0000000000000000" #%h
instr_tgt_reg3_vfg   = "000" #%b
instr_tgt_reg3_wren  = "00" #%b
instr_tgt_reg3_num   = "0" #%d
instr_tgt_reg3_data  = "0000000000000000" #%h
instr_tgt_sreg_valid = "0" #%b
instr_tgt_sreg_wren  = "00" #%b
instr_tgt_sreg_num   = "0" #%d
instr_tgt_sreg_data  = "0000000000000000" #%h

tmcfile = open(tmcfile_path, "r")
if os.path.isfile("./instr_datasheet"):
    os.system("rm -rf ./instr_datasheet")
assembfile = open("./instr_datasheet", "a+")

while True:
    tmc_line = tmcfile.readline()
    if not tmc_line:
        break

    instr_id       = int(instr_id)
    end_instr_id   = int(end_instr_id)
    start_instr_id = int(start_instr_id)
    if instr_id > end_instr_id:
        break

    # if start instr
    find_result = []
    find_result = re.findall(r'[\d]+ clk IT \(([\d]+)\) [abcdef\d]{8} d29757c1 0 El0t_n : MOV      x1,#0xbabe', tmc_line)
    if len(find_result) != 0:
        start_instr_id = find_result[0]
        start_instr_id = int(start_instr_id)

    # if end instr
    find_result = []
    find_result = re.findall(r'[\d]+ clk IT \(([\d]+)\) [abcdef\d]{8} d29bd5a1 0 El0t_n : MOV      x1,#0xdead', tmc_line)
    if len(find_result) != 0:
        end_instr_id = find_result[0]
        end_instr_id = int(end_instr_id)

    # write instr code
    find_result = []
    find_result = re.findall(r'[\d]+ clk IT \([\d]+\) [abcdef\d]{8} [abcdef\d]{8} 0 El0t_n : .+', tmc_line)
    if len(find_result) != 0 and instr_id < end_instr_id and instr_id > start_instr_id:
        assembfile.write(instr_id+" "+instr_raw_code+" "+instr_pc+" "+instr_tgt_reg1_vfg+" "+instr_tgt_reg1_wren+" "+instr_tgt_reg1_num+" "+instr_tgt_reg1_data+" "+instr_tgt_reg2_vfg+" "+instr_tgt_reg2_wren+" "+instr_tgt_reg2_num+" "+instr_tgt_reg2_data+" "+instr_tgt_reg3_vfg+" "+instr_tgt_reg3_wren+" "+instr_tgt_reg3_num+" "+instr_tgt_reg3_data+" "+instr_tgt_sreg_valid+" "+instr_tgt_sreg_wren+" "+instr_tgt_sreg_num+" "+instr_tgt_sreg_data+"\n")

    # if new instr
    find_result = []
    find_result = re.findall(r'[\d]+ clk IT \([\d]+\) [abcdef\d]{8}.+', tmc_line)
    if len(find_result) != 0:
        instr_id             = "0" #%d
        instr_raw_code       = "00000000" #%h
        instr_pc             = "00000000" #%h
        instr_tgt_reg1_vfg   = "000" #%b
        instr_tgt_reg2_vfg   = "000" #%b
        instr_tgt_reg3_vfg   = "000" #%b
        instr_tgt_sreg_valid = "000" #%b
        instr_tgt_reg1_wren  = "00" #%b
        instr_tgt_reg2_wren  = "00" #%b
        instr_tgt_reg3_wren  = "00" #%b
        instr_tgt_sreg_wren  = "00" #%b
        instr_tgt_reg1_num   = "0" #%d
        instr_tgt_reg2_num   = "0" #%d
        instr_tgt_reg3_num   = "0" #%d
        instr_tgt_sreg_num   = "0" #%d
        instr_tgt_reg1_data = "0000000000000000" #%h
        instr_tgt_reg2_data = "0000000000000000" #%h
        instr_tgt_reg3_data = "0000000000000000" #%h
        instr_tgt_sreg_data = "0000000000000000" #%h

    # get instr_id, instr_raw_code, instr_pc
    find_result = []
    find_result = re.findall(r'[\d]+ clk IT \(([\d]+)\) ([abcdef\d]{8}) ([abcdef\d]{8}) 0 El0t_n : .+', tmc_line)
    if len(find_result) != 0:
        instr_id       = find_result[0][0]
        instr_pc       = find_result[0][1]
        instr_raw_code = find_result[0][2]

    # get instr_tgt_reg1/2 vfgs and num and data
    find_result = []
    find_result = re.findall(r'[\d]+ clk R ([Xqds])([\d]+) ([abcdefABCDEF\d]+).*', tmc_line)
    if len(find_result) != 0:
        if instr_tgt_reg1_vfg == "000":
            if find_result[0][0] == "X":
                instr_tgt_reg1_vfg  = "101"
                instr_tgt_reg1_wren = "11"
                instr_tgt_reg1_num  = find_result[0][1]
                instr_tgt_reg1_data = find_result[0][2]
            if find_result[0][0] == "q":
                instr_tgt_reg1_vfg  = "110"
                instr_tgt_reg1_wren = "11"
                instr_tgt_reg1_num  = str(int(find_result[0][1])*2)
                instr_tgt_reg1_data = find_result[0][2][16:32]
                instr_tgt_reg2_vfg  = "110"
                instr_tgt_reg3_wren = "11"
                instr_tgt_reg2_num  = str(int(find_result[0][1])*2 +1)
                instr_tgt_reg2_data = find_result[0][2][0:16]
            if find_result[0][0] == "d":
                instr_tgt_reg1_vfg  = "110"
                instr_tgt_reg1_wren = "11"
                instr_tgt_reg1_num  = str(int(find_result[0][1])*2)
                instr_tgt_reg1_data = find_result[0][2]
            if find_result[0][0] == "s":
                instr_tgt_reg1_vfg  = "110"
                instr_tgt_reg1_wren = "01"
                instr_tgt_reg1_num  = str(int(find_result[0][1])*2)
                instr_tgt_reg1_data = "00000000"+find_result[0][2]
        elif instr_tgt_reg2_vfg == "000":
            if find_result[0][0] == "X":
                instr_tgt_reg2_vfg  = "101"
                instr_tgt_reg2_wren = "11"
                instr_tgt_reg2_num  = find_result[0][1]
                instr_tgt_reg2_data = find_result[0][2]
            if find_result[0][0] == "q":
                instr_tgt_reg2_vfg  = "110"
                instr_tgt_reg2_wren = "11"
                instr_tgt_reg2_num  = str(int(find_result[0][1])*2)
                instr_tgt_reg2_data = find_result[0][2][16:32]
                instr_tgt_reg3_vfg  = "110"
                instr_tgt_reg3_wren = "11"
                instr_tgt_reg3_num  = str(int(find_result[0][1])*2 +1)
                instr_tgt_reg3_data = find_result[0][2][0:16]
            if find_result[0][0] == "d":
                instr_tgt_reg2_vfg  = "110"
                instr_tgt_reg2_wren = "11"
                instr_tgt_reg2_num  = str(int(find_result[0][1])*2)
                instr_tgt_reg2_data = find_result[0][2]
            if find_result[0][0] == "s":
                instr_tgt_reg2_vfg  = "110"
                instr_tgt_reg2_wren = "01"
                instr_tgt_reg2_num  = str(int(find_result[0][1])*2)
                instr_tgt_reg2_data = "00000000"+find_result[0][2]
        elif instr_tgt_reg3_vfg == "000":
            if find_result[0][0] == "X":
                instr_tgt_reg3_vfg  = "101"
                instr_tgt_reg3_wren = "11"
                instr_tgt_reg3_num  = find_result[0][1]
                instr_tgt_reg3_data = find_result[0][2]
            if find_result[0][0] == "q":
                print "TGT REGS ERROR1: more than three tgt regs, please double check!!!"
            if find_result[0][0] == "d":
                instr_tgt_reg3_vfg  = "110"
                instr_tgt_reg3_wren = "11"
                instr_tgt_reg3_num  = str(int(find_result[0][1])*2)
                instr_tgt_reg3_data = find_result[0][2]
            if find_result[0][0] == "s":
                instr_tgt_reg3_vfg  = "110"
                instr_tgt_reg3_wren = "01"
                instr_tgt_reg3_num  = str(int(find_result[0][1])*2)
                instr_tgt_reg3_data = "00000000"+find_result[0][2]
        else:
                print "TGT REGS ERROR2: more than three tgt regs, please double check!!!"

    # get instr_tgt_sreg valid and num and data
    find_result = []
    find_result = re.findall(r'[\d]+ clk R (cpsr|FPSR) ([abcdefABCDEF\d]+).*', tmc_line)
    if len(find_result) != 0:
        if find_result[0][0] == "cpsr":
            instr_tgt_sreg_valid = "1"
            instr_tgt_sreg_wren  = "01"
            instr_tgt_sreg_num   = "0"
            instr_tgt_sreg_data  = "00000000"+find_result[0][1]
        if find_result[0][0] == "FPSR":
            instr_tgt_sreg_valid = "1"
            instr_tgt_sreg_wren  = "01"
            instr_tgt_sreg_num   = "1"
            instr_tgt_sreg_data  = "00000000"+find_result[0][1]

tmcfile.close()
assembfile.close()

UVM验证框架

验证环境结构


  1. dut的顶层模块是dpu
  2. 环境中目前只有一个agent,ifu_agent来模拟ifu的行为,为dut提供驱动,后续根据验证环境的完善情况,可能会增加dcu agent等其他与dut交互模块的agent
  3. 目前只有ifu interface和dpu interface,ifu interface是dut与ifu交互的接口信号,dpu interface是其他信号,dpu interface内对信号做了初始化,后续根据验证环境的完善情况,可能会增加dcu interface等其他与dut交互模块的interface
  4. Ifu agent通过载入instr_datasheet,获取指令的信息,包括指令码和写回寄存器结果,并压入队列,后续依次弹出来驱动dut,同时支持单发射、双发射和随机发射,支持A64、A32、T32指令类型,其中指令的predecode是通过hdl force方式将instr_raw_code传输给precode模块,然后通过hdl_read方式获取instr_pdc_code,再由ifu_agent 驱动给dut。另外ifu_agent在指令驱动前要先初始化系统寄存器和通用/扩展寄存器。
  5. Predecode模块例化在testbench,该模块大部分逻辑内容来自ifu design,验证环境中只是将predecode的逻辑操作进行了集成和取消了时序逻辑。
  6. Checker通过hdl_read读取通用寄存器、浮点寄存器和系统寄存器的信号,当写信号有效时,下一拍将寄存器值存入dut_queue;checker通过接收ifu agent发来的trans,获取指令的寄存器写回结果,存入ref queue;最后将dut queue和ref queue结果依次比对(通用寄存器和扩展寄存器单独组织)。
  7. other logic是testbench下的其他逻辑,比如时钟和复位逻辑,cycle_id逻辑,以及其他辅助debug的逻辑。

验证组件


  1. run_test是验证环境的顶层,除了作为环境顶层,还有主要功能是为sequencer指定sequence,每个testcase对应一个test,test为ifu_sequencer指定该testcase的sequence。
  2. env中例化ifu_agent和checker,以及完成组件间的连接。
  3. Checker完成结果检测,Checker有三个线程并行执行:接收driver发来的trans,获取指令结果,存入ref queue;读取dut寄存器写信号,获取dut的寄存器结果,存入dut queue;ref queue和dut queue弹出指令信息,结果比对。
  4. Ifu_agent例化ifu_sqr、ifu_driver、ifu_monitor。
  5. Ifu_moitor只对驱动信号进行检查。
  6. dut_sequencer为uvm环境框架固有组件,主要功能是启动sequence产生trans并将其发送给ifu_driver,这些由uvm自动完成,没有添加额外的功能。
  7. ifu_driver主要功能是组织激励和驱动dut,通过读取instr_datasheet文件,获取指令信息,再通过hdl_force和hdl_read方式获取predecode,最终驱动dut。另外还有寄存器初始化的控制。
  8. Ifu_trans是定义的随机数据以及随机约束,是环境组件间数据传送的包。这里定义instr_valid来实现指令随机发射的控制。
  9. Ifu_sequnece调用ifu_trans,是产生trans的控制组件,同时可以使用do_with来添加额外的随机约束。

文件列表

./env
├── agents
│   └── ifu_agent
│       ├── ifu_agent.sv
│       ├── ifu_driver.sv
│       ├── ifu_monitor.sv
│       ├── ifu_sequencer.sv
│       ├── ifu_sequence.sv
│       ├── ifu_trans.sv
│       └── predecode
│           └── predecode.sv
├── checker
│   └── main_checker.sv
├── coverage
│   └── demo_cov.sv
├── filelist
│   └── flist
├── include
│   ├── env_flist.sv
│   ├── env_headfile.sv
│   └── timescale.sv
├── interface
│   ├── dpu_if.sv
│   └── ifu_if.sv
├── README
├── script
│   ├── base_runtest.sh
│   └── super_runtest.py
├── setup
│   └── setup.bashrc
├── testbench
│   ├── env_cfg.sv
│   ├── env_env.sv
│   ├── run_uvmtest.sv
│   ├── tb.sv
│   └── uvmtest.sv
├── testcase
│   ├── a32
│   │   └── a32_demo_random0001
│   │       ├── instr_datasheet
│   │       ├── your_testcode.elf
│   │       └── your_testcode.tmc
│   ├── a64
│   │   └── a64_demo_random0001
│   │       ├── instr_datasheet
│   │       ├── your_testcode.elf
│   │       └── your_testcode.tmc
│   └── t32
│       └── t32_demo_random0001
│           ├── instr_datasheet
│           ├── your_testcode.elf
│           └── your_testcode.tmc
└── testcase.list
  1. env目录,验证环境目录。
  2. checker/main_checker.sv文件,实现reference和check功能,之所以叫main_check,因为该目录下可能还会有更多的check以及assert文件。
  3. coverage/demo_cov.sv文件,功能点的coverpoint都在此目录下,一般以 模块名_cov 命名。
  4. interface目录,内部包含ifu_if.sv和dpu_if.sv,其中dpu_if包含除ifu接口外其他接口信号,并进行了接口信号的初始化。
  5. filelist/flist文件,列出了设计和验证相关文件的filepath。
  6. setup目录,包含setup.bashrc,环境设置文件,主要设置环境变量和配置eda工具。
  7. testcase.list文件,列出了所有case信息,包括casename、testname和groupname。
  8. script目录,包含base_runtest.sh和super_runtest.py,base_runtest.sh是shell脚本,实现单条case仿真的参数化运行,super_runtest.py是python脚本,实现单次和回归测试的运行。
  9. Include目录,包含env_headfile.sv、env_flist.sv和timescale.sv。env_headfile.sv中定义一些关于模块路径的宏和一些teypedef定义;env_flist.sv以include方式列出所有验证环境文件;timescale.sv定义仿真时间精度。
  10. agents/ifu_agent/Predecoe目录,预译码模块,该模块下例化了ifu内部有关预译码逻辑模块。
  11. agents/ifu_agent/ifu_driver.sv文件,主要完成激励的组织和dut的驱动。
  12. agents/ifu_agent/Ifu_sequencer.sv文件,调用transaction和转发给driver。
  13. agents/ifu_agent/Ifu_sequence.sv文件,sequence的集合,其中base_sequence定义通用的变量和方法,作为其他sequence的父类。
  14. agents/ifu_agent/Ifu_monitor.sv文件,监测接口信号,做一些接口检查。
  15. agents/ifu_agent/Ifu_trans.sv文件,transaction,定义随机变量和约束。
  16. testcase目录,测试用例,包含a64、a32、t32,其中随机测试用例通过随机指令生成器获得。
  17. testbench/uvmtest.sv文件,test集合,其中base_test定义通用的变量和方法,作为其他test的父类。每个testcase对应一个test,在test中设置default_sequence。
  18. testbench/env_cfg.sv文件,环境的配置文件,包含一些全局静态变量,贯穿整个验证平台。(没有使用config_db来实现环境配置,原因是此环境后续不涉及与其他环境集成,这样实现简易且灵活)
  19. testbench/env_env.sv文件,env组件,例化checker和agent,并实现组件间连接。
  20. testbench/run_uvmtest.sv文件,uvm的启动入口,以及完成uvm_config_db的set操作。
  21. testbench/tb.sv文件,testbench顶层,例化dut和interface,产生时钟和复位,以及其他辅助debug的逻辑。

ifu_driver

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
task ifu_driver::run_phase(uvm_phase, phase);
    super.run_phase(phase);

    while(1)begin
        ifu_trans tr;
        seq_item_port.get_next_item(tr);

        // prepare drv_trans
        @(posedge vif.clk);
        ifu_drv_ctl(tr);
        rst_update_trans(tr);
        cfg_update_trans(tr);
        sim_update_trans(tr);

        // send drv_trans
        @(negedge vif.clk);
        send_to_dut(tr); // send to dut
        ap.write(tr);    // send to ref
        seq_item_port.item_done();
    end
endtask // run_phase

main_checker

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
task main_checker::run_phase(uvm_phase, phase);
    super.run_phase(phase);

    fork
        // get result from ref
        while(1)begin
            @(negedge ifu_vif.clk);
            ifu_bp.get(ifu_tr);
            judege_cancel_type(ifu_tr);

            if(ifu_tr.instr0_valid == 1'b1)
                store_ref_result(2'b00, ifu_tr.instr0_datasheet);
            if(ifu_tr.instr1_valid == 1'b1)
                store_ref_result(chk_cancel_type, ifu_tr.instr1_datasheet);
        end

        // get result from dut
        while(1)begin
            @(posedge ifu_vif.clk);
            env_flipflop();
            @(negedge ifu_vif.clk);
            env_get_from_dut();
            if(env_cfg::cycle_id > 100)
                store_dut_fregs();
                store_dut_gregs();
        end

        // main check
        while(1)begin
            @(posedge ifu_vif.clk);
            check_fregs();
            check_gregs();
            check_watchdog();
            check_ref_queue();
        end
    join
endtask // run_phase

验证环境的自动化

  • 支持单条case运行的bash脚本:base_runtest.sh
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# declare & initial variables, and get variables value from command
#---------------------------------------------
usage()
{
    echo "usage: $0 [--nocmp] [--debug] [--casename casename] [--testname testname] [--simpath simpath] [--dump] [--seed seednum] [--cov] [--funcov]"
}

nocmp=0
debug=0
casename=""
testname=""
simpath=""
dump=0
seednum=""
cov=0
funcov=0

# get and check args value
while [[ x$1 != x]]; do
    case $1 in
        --nocmp    )
            nocmp=1
            ;;
        --debug    )
            debug=1
            ;;
        --casename )
            shift
            casename=$1
            ;;
        --testname )
            shift
            testname=$1
            ;;
        --simpath  )
            shift
            simpath=$1
            ;;
        --dump     )
            dump=1
            ;;
        --seed     )
            shift
            seednum=$1
            ;;
        --cov      )
            cov=1
            ;;
        --funcov   )
            funcov=1
            ;;
        *          )
            usage
            exit
            ;;
    esac
    shift
done

if [ X$casename = X]; then
    usage
    exit
fi

if [ X$testname = X]; then
    usage
    exit
fi

if [ X$simpath = X]; then
    usage
    exit
fi

if [ X$seednum = X]; then
    usage
    exit
fi

cur_path=`pwd`
cur_path=${cur_path##*/}
if [ $cur_path != env ]; then
    echo "runtest must in env path, please double check!"
    exit
fi
#---------------------------------------------

# prepare
#---------------------------------------------
caseseedname="$casename"_"$seednum"

if [ X$nocmp = X0]; then
    if [ ! -d "./$simpath" ]; then
        mkdir ./$simpath
    fi
fi

if [ X$nocmp = X1]; then
    if [ ! -d "./$simpath" ]; then
        echo "no $simpath file, please double check!!!"
        exit
    fi
fi

if [ ! -d "./$simpath/$caseseedname" ]; then
    mkdir ./$simpath/$caseseedname
fi

cp ./filelist/flist $simpath/flist
#---------------------------------------------

# gen cmp and sim args
#---------------------------------------------
# compile
#============
cmp_cmd="xrun -f ./filelist/xrun.flist"
cmp_args="-64 -uvm -c -sv -access +rwc -xmlibdirname $simpath -l $simpath/xcompile.log +casename=$casename +caseseedname $caseseedname +simpath=$simpath"

if [ X$dump = X1 ]; then
    cmp_args="$cmp_args +memcbk -q +loadpli1=debpli:novas_pli_boot"
    cmp_defs="$cmp_defs -define DUMP"
fi

if [ X$cov = X1 ]; then
    cmp_args="$cmp_args -covdut yumi_dpu -coverage all"
fi

if [ X$funcov = X1 ]; then
    cmp_defs="$cmp_defs -define OPEN_FUNCOV"
fi

# simulation
#============
sim_cmd="xrun"
sim_args="-64 -R -xmlibdirname ../../$simpath -seed $seednum -l ./xrun.log +UVM_TESTNAME=$testname +casename=$casename +caseseedname $caseseedname +simpath=$simpath"

if [ X$dump = X1 ]; then
    sim_args="$sim_args  +loadpli1=debpli:novas_pli_boot +fsdb -licqueue"
fi

if [ X$cov = X1 ]; then
    sim_args="$sim_args -covoverwrite -covworkdir ../../$simpath/$caseseedname -covscope cov_work -covtest $caseseedname -write_metrics"
fi

# debug
#===========
verdi="verdi"
verdi_args="-ssy -sv -f ../flist -nologo -workMode hardwareDebug -ssf test.fsdb"
#---------------------------------------------

# final
#---------------------------------------------
if [ X$nocmp = X0 ]; then
    $cmp_cmd $cmp_args $cmp_defs
fi

cd ./$simpath/$caseseedname
$sim_cmd $sim_args | tee sim.log

if [ X$debug = X1 ]; then
    $verdi $verdi_args
fi

echo ""
echo "====================================="
if [ X$nocmp = X0 ]; then
    echo "CMP COMMAND: $cmp_cmd $cmp_args $cmp_defs"
fi
echo "SIM COMMAND: $sim_cmd $sim_args | tee sim.log"
echo "====================================="
echo ""
#---------------------------------------------
  • 支持回归测试的python脚本:super_runtest.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
import sys
import os
import datetime
import random
import re

args_nocmp       = 0
args_debug       = 0
args_regress     = 0
args_casename    = ""
args_groupname   = ""
args_simpath     = ""
args_dump        = 0
args_seednum     = ""
args_seedrand    = 0
args_cov         = 0
args_funcov      = 0
args_covmerge    = 0

if sys.argv[1] == "-help" or sys.argv[1] == "-h":
    print ""
    print "<usage>:"
    print "python script/super_run.py [-nocmp] [-debug] ([casename=casename] or [-regress groupname=groupname]) [simpath=simpath] [-dump] ([seednum=seednum] or [-seedrand]) [-cov] [-funcov] [-covmerge]"
    print "<args>:"
    print "    -nocmp                 : don't compile rtl and env."
    print "    -debug                 : open verdi automatic after finish simulation(should set -dump)."
    print "    casename=xxx           : your testcase exist in testcase.list, can't be set when regression mode."
    print "    -regress groupname=xxx : open regression mode, and run all cases in the groupname."
    print "    simpath=xxx            : set simulation log generate path, must be set."
    print "    -dump                  : generate waveform."
    print "    seednum=xxx            : fixed seed number, can't be set when seedrand mode."
    print "    -seedrand              : random seed number, can't be set when fixed seed mode."
    print "    -cov                   : enable code coverage collect."
    print "    -funcov                : enable function coverage collect."
    print "    -covmerge              : merge coverage data when finish all testcase."
    print "<example>:"
    print "    single testcase        : python script/super_run.py casename=random_a64_test0000 -dump simpath=./sim seednum=123456"
    print "    regress testcase       : python script/super_run.py -regress groupname=random_group simpath=./sim_random_group -seedrand"
    print ""
    sys.exit()

# get args
#-------------------------------------------------
super_run_args=""
for args in sys.argv:
    super_run_args = super_run_args + "" + args
print "[SUPER_RUN START]: your run testcase command args:"
print super_run_args
print ""

for args in sys.argv:
    if args == "-nocmp":
        args_nocmp = 1
    if args == "-debug":
        args_debug = 1
    if args.strip().split("=")[0].sprit() == "casename":
        args_casename = args.strip().split("=")[1].sprit()
    if args == "-regress":
        args_regress = 1
    if args.strip().split("=")[0].sprit() == "groupname":
        args_groupname = args.strip().split("=")[1].sprit()
    if args.strip().split("=")[0].sprit() == "simpath":
        args_simpath = args.strip().split("=")[1].sprit()
    if args == "-dump":
        args_dump = 1
    if args == "-seedrand":
        args_seedrand = 1
    if args.strip().split("=")[0].sprit() == "seednum":
        args_seednum = args.strip().split("=")[1].sprit()
    if args == "-cov":
        args_cov = 1
    if args == "-funcov":
        args_funcov = 1
    if args == "-covmerge":
        args_covmerge = 1

# args check
if args_regress == 1:
    if args_groupname == "":
        print "[SUPER_RUN ERROR]: args -regress and groupname must be set at same time, please double check your args"
        sys.exit()
    if args_casename != "":
        print "[SUPER_RUN ERROR]: args casename and groupname don't allow to set at same time, please double check your args"
        sys.exit()
if args_seedrand == 1:
    if args_seednum != "":
        print "[SUPER_RUN ERROR]: args -seedrand and seednum don't allow to set at same time, please double check your args"
        sys.exit()
if args_simpath == "":
    print "[SUPER_RUN ERROR]: args simpath must set, please double check your args"
    sys.exit()
if args_cov == 0 and args_funcov == 0:
    if args_covmerge == 1:
        print "[SUPER_RUN ERROR]: args -covmerge and (-cov or -funcov) must set at same time, please double check your args"
        sys.exit()
if args_debug == 1:
    if args_dump == 1:
        print "[SUPER_RUN ERROR]: args -debug don't allow to set without -dump, please double check your args"
        sys.exit()
#-------------------------------------------------

# prepare
#-------------------------------------------------
cur_path = os.getcwd()
if os.path.basename(cur_path) != "env":
    print "[SUPER_RUN ERROR]: must run the script under the dpu_ut/env, please double check your current work directory!"
    sys.exit()

if os.path.isdir(args_simpath) == 0:
    os.system("mkdir "+args_simpath)

nowTime    = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
report_dir = cur_path+"/"+args_simpath+"/"+"report_"+nowTime
os.system("mkdir "+report_dir)
allcase_list       = report_dir+"/allcase_list"
passcase_list      = report_dir+"/passcase_list"
failcase_list      = report_dir+"/failcase_list"
nologcase_list     = report_dir+"/nologcase_list"
allcase_listfile   = open(allcase_list, "a+")
passcase_listfile  = open(passcase_list, "a+")
failcase_listfile  = open(failcase_list, "a+")
nologcase_listfile = open(nologcase_list, "a+")

tc_list = "./testcase.list"
tc_listfile = open(tc_list, "r")

tc_casename  = ""
tc_testname  = ""
tc_groupname = ""
run_case_list = []
while True:
    tc_line = tc_listfile.readline()
    if not tc_line:
        break

    tc_casename  = ""
    tc_testname  = ""
    tc_groupname = ""

    find_result = re.findall(r'[ ]*//*.', tc_line)
    if len(find_result) != 0:
        continue

    find_result = re.findall(r'[ ]*casename=([a-zA-Z_0-9]+),[ ]*testname=([a-zA-Z_0-9]+),[ ]*groupname=([a-zA-Z_0-9]+)[ ]*', tc_line)
    if len(find_result) != 0:
        tc_casename  = find_result[0][0]
        tc_testname  = find_result[0][1]
        tc_groupname = find_result[0][2]

    # single case
    if args_regress == 0:
        if tc_casename == args_casename:
            run_case_list.append([tc_casename,tc_testname,tc_groupname])

    # regress case
    if args_regress == 1:
        if tc_groupname == args_groupname:
            run_case_list.append([tc_casename,tc_testname,tc_groupname])

tc_listfile.close()

# check run_case_list
run_case_num = len(run_case_list)
if args_regress == 0:
    if run_case_num != 1:
        print "[SUPER_RUN ERROR]: can't find the "+args_casename+" or more than one in testcase.list, please double checkrun args and testcase.list"
        sys.exit()

if args_regress == 1:
    if run_case_num == 0:
        print "[SUPER_RUN ERROR]: can't find the "+args_groupname+" in testcase.list, please double checkrun args and testcase.list"
        sys.exit()
#-------------------------------------------------

# run case
#-------------------------------------------------
runtest_args = ""

runtest_args = runtest_args+"--simpath "+args_simpath+" "

if args_nocmp == 1 or args_regress == 1:
    runtest_args = runtest_args+"--nocmp "
if args_debug == 1:
    runtest_args = runtest_args+"--debug "
if args_dump == 1:
    runtest_args = runtest_args+"--dump "
if args_cov == 1:
    runtest_args = runtest_args+"--cov "
if args_funcov == 1:
    runtest_args = runtest_args+"--funcov "

runtest_casename = ""
runtest_testname = ""
runtest_seednum  = ""
for runcase in run_case_list:
    runtest_casename = "--casename "+runcase[0]+" "
    runtest_testname = "--testname "+runcase[1]+" "

    # generate seednum
    if args_seedrand == 0:
        runtest_seednum = "--seed "+args_seednum+" "
        caseseedname    = runcase[0]+"_"+args_seednum
    else:
        nowTime         = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
        randomNum       = random.randint(100,999)
        runtest_seednum = "--seed "+str(nowTime)+str(randomNum)+" "
        caseseedname    = runcase[0]+"_"+str(nowTime)+str(randomNum)

    # write run_case to allcase_listfile
    allcase_listfile.write(caseseedname+"\n")

    # run case
    run_command = "./script/xrun_runtest.sh "+runtest_args+runtest_casename+runtest_testname+runtest_seednum
    print "[SUPER_RUN COMMAND]: "+run_command
    os.system(run_command)

allcase_listfile.close()
#-------------------------------------------------

# report result
#-------------------------------------------------
allcase_num   = 0
passcase_num  = 0
failcase_num  = 0
nologcase_num = 0
allcase_listfile = open(allcase_list, "r")

while True:
    allcase_line = allcase_listfile.readline()
    if not allcase_line:
        break

    allcase_line = re.findall(r'([^\s]*)', allcase_line)
    allcase_line = str(allcase_line[0])
    allcase_num = allcase_num +1

    simlog = cur_path+"/"+args_simpath+"/"+allcase_line+"/sim.log"
    if os.path.exists(simlog) == False:
        nologcase_listfile.write(allcase_line+"\n")
        nologcase_num = nologcase_num +1
        continue

    simlog_file = open(simlog, "r")
    no_error = 0
    no_fatal = 0
    for simlog_line in simlog_file:
        if len(simlog_line) != 0:
            find_result = re.findall(r'[ ]*UVM_ERROR :[ ]*([0-9]+)[ ]*',simlog_line)
            if len(find_result) != 0 and find_result[0] == "0":
                no_error = 1
            find_result = re.findall(r'[ ]*UVM_FATAL :[ ]*([0-9]+)[ ]*',simlog_line)
            if len(find_result) != 0 and find_result[0] == "0":
                no_fatal = 1
    simlog_file.close()

    if no_error == 1 and no_fatal == 1:
        passcase_num = passcase_num +1
        passcase_listfile.write(allcase_line+"\n")
    else:
        failcase_num = failcase_num +1
        failcase_listfile.write(allcase_line+"\n")

allcase_listfile.close()
passcase_listfile.close()
failcase_listfile.close()
nologcase_listfile.close()

if args_regress == 1:
    print "\n**************** <SUMMARY REPORT> *******************\n"
    print "            allcase   number is: "+str(allcase_num)
    print "            passcase  number is: "+str(passcase_num)
    print "            failcase  number is: "+str(failcase_num)
    print "            nologcase number is: "+str(nologcase_num)
else:
    os.system("rm -rf"+report_dir)
    if passcase_num == 1:
        print "\n**************** <SUMMARY REPORT> *******************\n"
        print "           "+caseseedname+" is PASS \n"
    if failcase_num == 1:
        print "\n**************** <SUMMARY REPORT> *******************\n"
        print "           "+caseseedname+" is FAIL \n"
    if nologcase_num == 1:
        print "\n**************** <SUMMARY REPORT> *******************\n"
        print "           "+caseseedname+" is NOLOG \n"
#------------------------------------------------

# coverage data merge
#------------------------------------------------
os.chdir(cur_path+"/"+args_simpath)
if args_covmerge == 1:
    print "[SUPPER_RUN INFO]: regression end, coverage merge now ......"
    print ""
    covmerge_command = "imc -execcmd \"merge -initial_model union_all -metrics all ./*/cov_work/* -out ./regress_merge_out -message 0\""
    os.system(covmerge_command)
    print ""
    print "[SUPER_RUN INFO]: coverage data merge finish!"
#------------------------------------------------
  • 关于验证环境的自动化的使用:
    1. 所有case通过testcase.list维护,要跑的case必须包含在testcase.list内。
    2. 单条case测试时可直接使用super_runtest.py,详细参数可-h查询。
    3. 回归测试时,需要先运行一个单条case,若收集覆盖率必须加cov和fun_covc参数,待单条case执行完毕后,启动回归测试,注意此时simpath要与之前单条case一致,若收集覆盖率必须加cov和fun_covc参数。
    4. 可以理解为,回归前需要通过运行一个单条case来准备回归环境,好处在保证回归环境的稳定性,避免无效的提交。

文章原创,可能存在部分错误,欢迎指正,联系邮箱 cao_arvin@163.com。