概述
SOC验证环境一千家公司有一千家公司的做法。那么一个优秀的SOC验证环境应该具备哪些功能呢?
首先是SOC验证环境支持C和SV两种下激励的方式。
通过C code启动SOC环境是怎么启动的呢?这里涉及到CPU如何boot,对此很多转行的同学可能很难理解,在这里和大家做个简单的介绍。
我们知道CPU执行的是指令和数据,冯诺依曼结构是将指令和数据合并在一起存储,如8086。
哈弗结构是将指令和数据分开存储,如ARM系列。
无论是哪种结构CPU都是通过指令实现程序跳转或者数据读写。什么是指令?以RISCV为例,其指令格式如下
我们以简单的加法运算为例,比如我想做一个 rd= rs1+rs2 这样一个寄存器加法运算,伪代码就是 add rd,rs1,rs2 。通过上述RISCV的指令格式应该就是
funct7,funct3,opcode又是什么呢?通过查询 riscv 手册可以查到以下结果。
由此我们组成一个简单的加法的操作指令,CPU拿到这个操作指令之后就可以进行寄存器的加法运算。
事实上除了上述的寄存器操作指令,CPU还支持其他控制指令和数据处理指令。以riscv的标准指令集为例,其分别有以下6种类型。
R-format for register-register arithmetic/logical operations
I-format for register-immediate arith/logical operations and loads
S-format for stores
B-format for branches
U-format for 20-bit upper immediate instructions
J-format for jumps
大部分我们SOC对寄存器,memory的访问,数据的处理可以分解成上述6种类型的指令。由于底层的机器指令晦涩难懂,在存储访问和数据处理的时候,人们更倾向于用高级语言C来进行描述。C又是如何最终转换成机器指令送给CPU的呢?这就要讲到编译的过程。
从C语言到机器语言总共要经过3步,分别是编译,汇编和链接。
C编译成机器码要通过预处理,编译,汇编,链接四个步骤。这四个步骤由谁做的呢?答案是编译器。
编译器做的工作类似我们IC行业里面的综合。在IC设计中,门级电路特别复杂,特别是当逻辑门数比较多时,通过直接设计逻辑电路变得非常困难。由此产生了verilog。我们通过Verilog描述电路的功能,然后通过DC进行综合,让我们很方便的进行大规模数字逻辑电路的设计。
在软件层面,编译器做得事情和综合类似,编译器的编译的过程也分前端和后端。编译器的前端主要完成分析阶段读取源程序,这其中包括词法、语法和语法检查,生成中间源代码和符合表等。编译器的后端,综合阶段通过中间源代码表示和符号表生成目标程序。具体的编译器各个阶段做得事情,这里不做详细介绍,感兴趣的粉丝可以自己找资料学习。
C语言的编译器有很多种,在我们芯片行业,主要有GCC和LLVM。下面框图简单的描述了一个CPU编译器组成。
我们都知道CPU的执行效率和CPU的频率及指令流水处理的效率有关系。很少人知道CPU的执行效率还和编译器有很大关系。编译器对C代码进行优化可以大大的缩减代码量,提高执行效率。国内有不少公司专门做这方面的研究。
上面我们讲完编译器的相关知识,在集成SOC环境的时候,我们需要集成工具链用于编译C语言生成机器码。
机器码生成了,该怎么输入给CPU使用呢?
介绍CPU boot的过程。对此我们先看下一个哈弗结构的CPU的框架。
CPU通过指令总线从指令存储器里面读取指令进行操作,如果需要数据则从数据存储器里面读取数据处理。下面是一组真实的指令总线和数据总线。
[]
从上面的代码中可以看到,指令总线和数据总线都有地址和数据信号以及一些握手信号。在CPU reset之后,CPU会主动通过指令总线去某个特定的位置读取机器码。下图是CPU接口上一组输入信号,boot_addr_i 就是指令CPU reset 之后从哪里开始读代码。
由此我们可以通过将编译好的机器码放在mem里面,然后设置boot_addr_i 指定到该mem的位置,实现一个最小的CPU boot系统。
实际芯片的模块比这个最小系统复杂很多。存储介质上分为ROM,SRAM和Flash,一些大的芯片还有DDR等。
看到这些存储模块,有些人想了,是不是可以把机器码放在里面?这就要讲到boot的几种模式。
第一种将代码放在SRAM里面直接启动。具体操作的手段是将CPU的boot_addr_i 指定到SRAM的位置,在仿真开始的时候将code load 进 SRAM memory里面即可实现 从SRAM里面boot。SRAM boot的一个问题是每次关电,SRAM都会被清除,无法保存code。
第二种是将代码放在ROM里面,但是很不幸,ROM的空间非常小,如果将整段代码放在ROM里面不太现实。所以很多人将ROM 用作存储引导代码,即bootrom code。然后由bootrom code 跳转到SRAM或者flash执行的位置。
第三种是将代码放在Flash里面。通过bootrom code 从flash里面将代码搬到SRAM里面执行。另外一种方式是通过bootrom code 去配置flash 进入XIP状态,然后CPU直接从flash里面读取指令进行执行。通过这类方式boot的好处是掉电代码没有遗失,是真正产品应用的方式。
上面是三种在我们SOC验证中经常用到的boot方式,真实芯片的boot比这个复杂得多。
以ARM为例,系统boot会被分成三个步骤。
第一级bootloader:引导加载程序,即bootrom code,会选择哪种方式启动系统(EMMC,UART,SPI…)。第一级bootloader执行完之后会跳转到第二级bootloader。
第二级bootloader:用于硬件的初始化,比如初始化时钟,中断,看门狗等,这段代码放在SRAM中执行。执行完这个会跳到第三级bootloader。
第三级bootloader: 这个才是我们C代码的入口,也就是我们写的main函数在这里开始执行。
bootrom是一门比较复杂的系统工程,在真正产品应用中还需要考虑可靠性和安全性。但是对我们SOC验证来说,在系统里面集成最基础的三种boot方式即可满足大部分验证需求。
上期有朋友问我有没有简单的SOC验证环境,答案是有,在公众号后台回复"SOC验证"可获得一个最小系统的SOC验证环境。
https://github.com/openhwgroup/cv32e40p
上次说到CPU的boot,今天说说SOC环境的另外一种启动方式。用C启动SOC验证环境有几个问题。
一是CPU boot过程比较慢,每次仿真前都需要很长的一段初始化时间。
二是IP验证环境的测试用例无法直接复用到SOC环境里面。
对于小型的芯片用C做仿真还可以,但是对于大型的SOC芯片,用C做仿真效率有点低。基于上面两处不便,我们考虑能否用UVM直接接管CPU,然后通过SV/C直接下激励。
上述方法是可行的,为了让SOC环境跑的更快,用相应的BUS的agent接管 CPU的总线,将CPU bypass过去,然后通过UVM的环境调用不同agent的driver,实现给不同模块激励。
如上图所示,右边是一个SOC,CPU通过总线访问SPI,USB,PCIE,DDR,I2C等。在SOC验证环境里面,通过usb_agt 接管去往USB的总线,用pcie_agt接管去往PCIE的总线。当然也可以用chip_agt 接管CPU出来的总线。总而言之做法就是通过UVM去接管系统的总线。
这样我们可以bypass boot的过程,并且还可以实现IP的验证环境在SOC验证环境中复用。如果我们对C代码进行一些封装,还可以通过C访问SV从而实现C test在这类环境的复用。
通过UVM接管CPU,bypass boot的过程,我们仿真的速度可以加快不少,但是SOC还是非常大,在编译和仿真的时候,会消耗很多时间和内存。为了加速,我们将不用的module用empty的module代替,只保留接口信息,模块内部不实现,这样可以大大减少SOC环境的逻辑单元数和信号的翻转率,提升编译和仿真速度。
上图中,我们在测USB的时候会用到DDR,但是SPI,PCIE和I2C都不会用到,因此我们将这些模块用空的module代替。采用这些手段,这个SOC的验证环境就可以跑的比较快。
最后
以上就是背后帆布鞋为你收集整理的SOC验证环境的启动方式的全部内容,希望文章能够帮你解决SOC验证环境的启动方式所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复