本文
主要介绍一个关于处理器中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启动脚本重点是配置参数,这里展示部分内容,具体请根据项目自行设定。
|
|
- 可以在$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下的脚本。
|
|
UVM验证框架
验证环境结构
- dut的顶层模块是dpu
- 环境中目前只有一个agent,ifu_agent来模拟ifu的行为,为dut提供驱动,后续根据验证环境的完善情况,可能会增加dcu agent等其他与dut交互模块的agent
- 目前只有ifu interface和dpu interface,ifu interface是dut与ifu交互的接口信号,dpu interface是其他信号,dpu interface内对信号做了初始化,后续根据验证环境的完善情况,可能会增加dcu interface等其他与dut交互模块的interface
- 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在指令驱动前要先初始化系统寄存器和通用/扩展寄存器。
- Predecode模块例化在testbench,该模块大部分逻辑内容来自ifu design,验证环境中只是将predecode的逻辑操作进行了集成和取消了时序逻辑。
- Checker通过hdl_read读取通用寄存器、浮点寄存器和系统寄存器的信号,当写信号有效时,下一拍将寄存器值存入dut_queue;checker通过接收ifu agent发来的trans,获取指令的寄存器写回结果,存入ref queue;最后将dut queue和ref queue结果依次比对(通用寄存器和扩展寄存器单独组织)。
- other logic是testbench下的其他逻辑,比如时钟和复位逻辑,cycle_id逻辑,以及其他辅助debug的逻辑。
验证组件
- run_test是验证环境的顶层,除了作为环境顶层,还有主要功能是为sequencer指定sequence,每个testcase对应一个test,test为ifu_sequencer指定该testcase的sequence。
- env中例化ifu_agent和checker,以及完成组件间的连接。
- Checker完成结果检测,Checker有三个线程并行执行:接收driver发来的trans,获取指令结果,存入ref queue;读取dut寄存器写信号,获取dut的寄存器结果,存入dut queue;ref queue和dut queue弹出指令信息,结果比对。
- Ifu_agent例化ifu_sqr、ifu_driver、ifu_monitor。
- Ifu_moitor只对驱动信号进行检查。
- dut_sequencer为uvm环境框架固有组件,主要功能是启动sequence产生trans并将其发送给ifu_driver,这些由uvm自动完成,没有添加额外的功能。
- ifu_driver主要功能是组织激励和驱动dut,通过读取instr_datasheet文件,获取指令信息,再通过hdl_force和hdl_read方式获取predecode,最终驱动dut。另外还有寄存器初始化的控制。
- Ifu_trans是定义的随机数据以及随机约束,是环境组件间数据传送的包。这里定义instr_valid来实现指令随机发射的控制。
- 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
- env目录,验证环境目录。
- checker/main_checker.sv文件,实现reference和check功能,之所以叫main_check,因为该目录下可能还会有更多的check以及assert文件。
- coverage/demo_cov.sv文件,功能点的coverpoint都在此目录下,一般以 模块名_cov 命名。
- interface目录,内部包含ifu_if.sv和dpu_if.sv,其中dpu_if包含除ifu接口外其他接口信号,并进行了接口信号的初始化。
- filelist/flist文件,列出了设计和验证相关文件的filepath。
- setup目录,包含setup.bashrc,环境设置文件,主要设置环境变量和配置eda工具。
- testcase.list文件,列出了所有case信息,包括casename、testname和groupname。
- script目录,包含base_runtest.sh和super_runtest.py,base_runtest.sh是shell脚本,实现单条case仿真的参数化运行,super_runtest.py是python脚本,实现单次和回归测试的运行。
- Include目录,包含env_headfile.sv、env_flist.sv和timescale.sv。env_headfile.sv中定义一些关于模块路径的宏和一些teypedef定义;env_flist.sv以include方式列出所有验证环境文件;timescale.sv定义仿真时间精度。
- agents/ifu_agent/Predecoe目录,预译码模块,该模块下例化了ifu内部有关预译码逻辑模块。
- agents/ifu_agent/ifu_driver.sv文件,主要完成激励的组织和dut的驱动。
- agents/ifu_agent/Ifu_sequencer.sv文件,调用transaction和转发给driver。
- agents/ifu_agent/Ifu_sequence.sv文件,sequence的集合,其中base_sequence定义通用的变量和方法,作为其他sequence的父类。
- agents/ifu_agent/Ifu_monitor.sv文件,监测接口信号,做一些接口检查。
- agents/ifu_agent/Ifu_trans.sv文件,transaction,定义随机变量和约束。
- testcase目录,测试用例,包含a64、a32、t32,其中随机测试用例通过随机指令生成器获得。
- testbench/uvmtest.sv文件,test集合,其中base_test定义通用的变量和方法,作为其他test的父类。每个testcase对应一个test,在test中设置default_sequence。
- testbench/env_cfg.sv文件,环境的配置文件,包含一些全局静态变量,贯穿整个验证平台。(没有使用config_db来实现环境配置,原因是此环境后续不涉及与其他环境集成,这样实现简易且灵活)
- testbench/env_env.sv文件,env组件,例化checker和agent,并实现组件间连接。
- testbench/run_uvmtest.sv文件,uvm的启动入口,以及完成uvm_config_db的set操作。
- testbench/tb.sv文件,testbench顶层,例化dut和interface,产生时钟和复位,以及其他辅助debug的逻辑。
ifu_driver
|
|
main_checker
|
|
验证环境的自动化
- 支持单条case运行的bash脚本:base_runtest.sh
|
|
- 支持回归测试的python脚本:super_runtest.py
|
|
- 关于验证环境的自动化的使用:
- 所有case通过testcase.list维护,要跑的case必须包含在testcase.list内。
- 单条case测试时可直接使用super_runtest.py,详细参数可-h查询。
- 回归测试时,需要先运行一个单条case,若收集覆盖率必须加cov和fun_covc参数,待单条case执行完毕后,启动回归测试,注意此时simpath要与之前单条case一致,若收集覆盖率必须加cov和fun_covc参数。
- 可以理解为,回归前需要通过运行一个单条case来准备回归环境,好处在保证回归环境的稳定性,避免无效的提交。
文章原创,可能存在部分错误,欢迎指正,联系邮箱 cao_arvin@163.com。