中文 英语
系统与设计
的意见

持续集成

通过使用回归脚本实现虚拟平台,简化软件开发工作流程。

受欢迎程度

在本文中,我将解决开发定制集成电路(ic)软件的工程师经常遇到的经典问题:

在硬件团队给我一个可以工作的硅芯片之前,我如何测试我的软件?

这里没有“一刀切”的解决方案(看看我的宠物独角兽);相反,我详细介绍了一种易于使用但功能强大的方法来解决特定开发场景中的这个问题。

如果您正在为ic开发软件,并且您或您的团队做以下任何一项工作:

  • 可在Linux或Windows上开发
  • 利用持续集成
  • 浪费时间处理合并/集成问题

接着往下读。如果没有,请继续阅读,因为这种方法可以在各种情况下有效地使用。

背景与方法
持续集成是一种软件开发实践,其中团队成员至少每天合并他们的工作,并且每次合并都由自动构建过程检查以识别错误。这使得开发人员可以避免在尝试将大量编辑和交织的应用程序组合在一起时发生的可怕的“合并-提交地狱”,更不用说由于没有正确地对代码进行压力测试而导致错误的可能性了。看到“为什么CI很重要”文章将深入探讨为什么持续集成在开发软件时如此重要。然而,为集成电路开发软件,在选择在什么平台上运行相关软件方面增加了另一层复杂性。

在这个领域有很多选择:仿真、fpga、机架的硬件开发板、虚拟原型等。每种方法在特定的开发情况下都有其优点和缺点。为了实现持续集成,一个能够支持快速软件运行时、大型团队的廉价扩展以及与持续集成平台的轻松集成的解决方案是必不可少的。基于这些指标,我选择了虚拟原型作为硬件模型Arm的快速模型.请关注未来关于为什么这是最佳选择的博客,因为详细的解释超出了本博客的范围。

这里提出的解决方案是一种将虚拟原型模拟集成到现有持续集成框架(如Jenkins或Bamboo)中的方法。这使得IC软件开发人员不再担心物理硬件,而是将精力集中在编写优秀的代码上。的组件体系结构调试接口(CADI)-一个用于模拟控制和调试访问运行在Arm虚拟平台上的软件的API,允许python与Arm快速模型交互,并提供硬件和软件状态之间的连接。Python使用CADI命令,可以控制应用程序的执行、设置断点、读取和写入内存、访问寄存器、查看测试变量等等。然后可以利用这些信息创建全面的软件测试,并与现有的持续集成框架进行交互。本文将介绍如何使用python的CADI平台(巧妙地命名为PyCADI),包括(1)如何设置开发环境和(2)示例代码及其结果。

这里的示例用例是一个团队,该团队在Windows环境中使用持续集成来开发软件手臂Cortex-M4.为了启用自动化软件验证,他们在每个测试中使用一个变量来指示测试结果是标准的实践。这个变量被称为“test_pass”,默认值为0,如果测试没有错误通过,则设置为1。该技术帮助每个测试插入到团队的通用回归脚本中。

让我们开始吧。

环境设置
本节分为“仅第一次设置”和“可能重复出现的设置”,用于在运行测试之前可能需要发生的步骤,具体取决于目标是什么以及测试中哪些变量发生了变化。这个博客是在Windows上开发的,只需要对Linux用户进行微小的调整。

仅限首次安装
在快速模型上运行软件的第一步是获得一个快速模型!如果尚未下载,请单击下面的按钮以获取评估许可。Windows和Linux操作系统请按照下载包中提供的安装指南进行安装。

快速模型下载

PyCADI库需要python 2.7.13(我知道非常具体),因此确保安装了python 2.7.13(以及选择的IDE)。请参阅下面的按钮以下载正确的python版本。确保将“python”和“pip”添加到系统的Path环境变量中。注意:使用最新版本的python 2.7应该可以工作,但未经测试。请谨慎使用!

Python 2.7.13下载

为了在文件中的函数名和特定行上实现断点,调用python库pyelftools是必需的。一个简单的pip install命令就可以做到这一点:

  • PIP安装pyelftools

可能重现的设置
可执行应用程序当然是测试所述可执行应用程序所必需的。对于软件应用程序开发人员,本博客不涉及如何创建应用程序的细节。注意要测试的任何应用程序的绝对路径,因为PyCADI需要定位它。

还必须提供表示终端硬件的快速模型。从头开始创建一个快速模型系统超出了本博客的范围,但请参阅位于\FastModelsPortfolio_\Docs下的参考指南,以获得如何使用快速模型的教程。在本例中,在System Canvas中创建了一个简单的Cortex-M4内核,它带有时钟源和内存。

为python脚本编译Fast Model系统的一个重要注意事项是构建设置。在系统画布中打开相关系统,导航到“项目>项目设置>目标”,并确保“CADI库”被选中。然后,当应用这些更改并选择“构建”时,System Canvas将生成一个“。在Linux中是“。dll”文件,在Windows中是“。dll”文件,它们的名称都是“cadi_system_”。使用下面截图中的设置构建,模型名称将是“cadi_system_Win64-Release-VC2013.dll”。

代码演练
现在,设置已经完成,模型+应用程序已经到手,让我们一行一行地进入示例。回想一下,团队在这里的目标是检查测试是否通过了他们的回归环境。变量“test_pass”应该默认为0,如果应用程序检出没有错误,则更改为1。因此,应该在测试开始和测试结束时检查' test_pass '。

首先,导入必要的模块,并为PyCADI的python设置相关路径。此路径设置主要用于基于Linux的系统,如果用于快速模型的工具来源不正确,则会失败。fm.debug是从PyCADI模块导入的,python elf工具是从之前安装的pyelftools python库中加载的。

进口sys, os#设置python路径为Fast Models试一试: sys.path.append (os.path.join (os.environ [“PVLIB_HOME”],“自由”“python27”))除了KeyError作为艾凡:打印“错误!确保你所有的来源都来自快速模型目录。试试这样说:“打印"$ source /FastModelsTools_11.0/source_all.sh"sys.exit ()进口fm.debugelftools.dwarf.descriptions进口describe_form_classelftools.elf.elffile进口ELFFile

接下来将指定测试变量,并使用硬编码的模型和应用程序的绝对路径。还指示了在测试期间跟踪的寄存器和变量。R15是一个感兴趣的寄存器,作为Cortex-M4的程序计数器,它将验证内存中的断点被击中的位置。变量是' test_pass '来检查测试是否通过,由它在虚拟平台内存中的位置指定。这样做是为了在整个测试过程中对变量进行一致的跟踪,将变量保存到程序不使用的位置,以避免重写。在本例中,该位置为十六进制地址' 0x20005000 '。要在初始化时将这个或任何变量放在软件代码中的那个位置,请使用unannotations语法而不是annotated语法(在C语言中):

/* unsigned int test_pass;* /#定义test_pass (* ((无符号整型*) 0 x20005000))

变量' test_pass '与代码中典型定义的变量相同,唯一的区别是它现在有一个显式定义的内存地址位置0x20005000。

最后,三种不同类型的断点被初始化:(1)在一个十六进制地址(程序内存中的位置),(2)在函数的开始(本例中为SCS_init),以及(3)在文件的行号处。请注意,在文件行号上设置断点只在它是可断点的情况下才有效,这听起来很明显,但不允许在空行、只有括号的行等上设置。如果主流调试器可以在那里设置断点,那么PyCADI也可以。

#路径规格model_path =“C: \ \ zaclas01 \ itm_integration_windows \用户模型\ cadi-Win64-Release-VC2013 \ cadi_system_Win64-Release-VC2013.dll”app_path =“C: \ \用户zaclas01 \ itm_integration_windows \程序\ itm_app \ startup_Cortex-M4.axf '#变量和寄存器检查cpu_reg =“R15”app_var =“0 x20005000”BPT_on_file_line =[设置断点]“c: 100”BPT_on_function = [“SCS_init”BPT_on_memory = [“0 x122”

需要注意的是,将函数名和行号匹配到程序内存中各自位置的方法是使用易于使用的python工具,使用我的解决方案完成的。PyCADI本身支持在以下情况下设置断点:当程序执行到达内存地址时,当访问内存位置时,以及当访问寄存器时。在函数名和行号上设置断点需要进行一些映射,本博客中展示了一个可重用的示例。

在下面的代码片段中,python开始与模型的CADI端口进行交互。debug模块创建一个模型对象,从中获得一个目标对象“cpu”(该模型中的第一个也是唯一的cpu),并将指定的应用程序加载到cpu上。

#加载模型model = fm.debug.LibraryModel(model_path) #获取cpu cpu = model.get_cpus()[0] #加载应用到cpu cpu.load_application(app_path)

现在开始设置断点。这里的所有断点都是使用' cpu.add_bpt_prog() '命令设置的,当程序执行到达程序内存中的地址时停止。此功能内置于PyCADI的fm.debug模块中。由于所有的断点都需要一个已知的地址,因此需要进行更多的处理来将函数名或文件+行号映射到内存中相应的地址。pyelftools库通过获取格式化的DWARF调试信息在应用程序文件中:

使用Python ELF工具获取DWARF调试信息打开(app_pathrb的作为f: elffile = elffile (f) dwarfinfo = elffile.get_dwarf_info()

利用这个对象“dwarfinfo”,我创建了函数来将函数名映射到内存地址,并将文件+行号映射到内存地址。下面是从函数名到内存的映射,以上面的' dwarfinfo '作为输入,其中包含要映射的函数列表:

defdecode_funcname(dwarfinfo,BPT_on_function): func_lineno_dic ={} #检查DWARF信息中的所有die,查找包含给定地址的地址范围的子程序条目。dwarfinfo.iter_CUs ():CU.iter_DIEs ():试一试如果死。标签= =“DW_TAG_subprogram”: lowpc = DIE.attributes[“DW_AT_low_pc”) value# DWARF v4 in section 2.17 describes how to interpret the DW_AT_high_pc attribute based on the class of its form. highpc_attr = DIE.attributes[“DW_AT_high_pc”highpc_attr_class = descripbe_form_class (highpc_attr_form)如果highpc_attr_class = =“地址”: highpc = highpc_attr.valueelifhighpc_attr_class = =“不变”: highpc = lowpc + highpc_attr.value其他的: highpc =“无效的highPC类”func_name = DIE.attributes[“DW_AT_name”) valueBPT_funtBPT_on_function:如果func_name == BPT_funt: #存储在字典func_lineno_dic[func_name] =(十六进制(低pc),十六进制(高pc))除了KeyError:继续返回func_lineno_dic

下面是将文件+行号连接到内存的代码,使用相同的' dwarfinfo '作为输入,以及要映射的文件+行号条目列表:

defdecode_filelinenum(dwarfinfo,BPT_on_file_line): hex_to_filelinenum_dic ={} #遍历DWARF信息中的所有行程序,寻找一个描述给定地址的行程序。dwarfino . iter_cus(): #查看行程序找到文件/行地址lineprog = dwarfino . line_program_for_cu (CU)如果lineprog = =没有:继续条目lineprog.get_entries(): #我们感兴趣的是那些分配了新状态的条目如果entry.state没有一个entry.state.end_sequence:继续#提取相关数据file_name = lineprog[“file_entry”] [entry.state。file - 1].name line_numb = str(entry.state.line) file_linenum = file_name+':'+line_numb address = hex(entry.state.address) #检查file_linenum是否应该被监控:BPT_flnBPT_on_file_line:如果file_linenum == BPT_fln: #一个文件/一行可以跨越多条指令;输入第一个事件如果不是file_linenumHex_to_filelinenum_dic: Hex_to_filelinenum_dic [file_linenum] = address返回hex_to_filelinenum_dic

有了这两个函数,只需几行代码就可以创建所需的断点。所有断点都被添加到断点列表中,以便稍后处理。

#跟踪列表bpts中设置的所有断点=[]#在指定的十六进制位置上设置bptshex_locBPT_on_memory: bpt = cpu.add_bpt_prog(int(hex_loc,16)) bpt .append(bpt) #创建指定函数的dic及其起始和结束addr #设置函数开始时的bpt func_dic = decode_funcname(dwarfinfo,BPT_on_function)关键func_dic: bpt = cpu.add_bpt_prog(int(func_dic[key][0],16)) bts .append(bpt) #创建指定文件和行号及其位置的dic #在映射位置上设置bpt filelinenum_dic = decode_filelinenum(dwarfinfo,BPT_on_file_line)关键Filelinenum_dic: BPT = cpu.add_bpt_prog(int(Filelinenum_dic [key],16)) BPT .append(BPT)

我们一直等待的时刻终于到来了:运行应用程序!此示例设置为运行模型,直到遇到断点或直到3秒后没有断点,以先发生者为准。下面是代码流的简单分解:

  1. 如果遇到断点:
    1. 选中的断点被标识并打印出来
    2. 获得并打印出指定的变量(' test_pass ')
    3. 获取并打印指定寄存器(' R15 ')
    4. 模型又开始运行了
  2. 如果5秒过去了:
    1. 获得并打印出指定的变量(' test_pass ')
    2. 获取并打印指定寄存器(' R15 ')
    3. 测试结束

下面的代码示例以阻塞模式运行应用程序;应用程序也可以在非阻塞模式下运行,以允许在应用程序运行时进行额外的计算。超时时间也可以更改。

1:试一试: model.run(超时= 3)除了: #在超时时,没有击中断点,模型将挂起在应用程序模型的末尾。打破#测试由于断点而停止,识别它双极性晶体管双极性晶体管:如果bpt.is_hit:打印"触及断点%s "获取变量状态var_status = cpu.read_memory(int(var_check,16),count=1打印'变量%s值为:%s'% (“test_pass”#获取寄存器状态reg_status = str(十六进制(cpu.read_register(reg_check)))打印'寄存器%s值为:%s'% (“R15”reg_status) #回到开始循环,重新启动模型#模型运行完成  #----------------------------------------------------------------- # 变量状态var_status = cpu.read_memory (int (var_check、16),数= 1)[0]打印'变量%s值为:%s'% (“test_pass”#获取寄存器状态reg_status = str(十六进制(cpu.read_register(reg_check)))打印'寄存器%s值为:%s'% (“R15”reg_status)打印“监控完整、干净的出口”

这就是示例代码的全部内容!虽然简单易消化,结果是强大的。下面是运行上述代码时的输出(为了清晰起见,包含了一些额外的打印语句):

在这里,可以清楚地看到' test_pass '确实开始时设置为0,并且在测试完成后返回1,表示测试成功。此状态可以以各种不同的方式发送到其他应用程序或持续集成平台,例如创建验证文本文件或将状态返回给调用PyCADI脚本的另一个程序。

总结
使用快速模型的PyCADI为集成电路软件开发人员提供了一种轻量级而强大的方法来简化持续集成过程。与涉及硬件板、fpga或硬件模拟器机架的解决方案不同,具有快速模型的PyCADI价格便宜、简单且快速。它还可以有效地与现有的持续集成平台集成。最终,利用这个虚拟硬件脚本系统可以更强大地使用持续集成实践,从而减少合并冲突,加快上市时间。所有这些都会带来更愉快的工作流程和更多的银行存款。

此外,由于这种编写脚本的方法是用python完成的,因此在编写脚本时,python的所有模块和资源都可用。这为集成其他环境和分析数据提供了无限的可能性。有PyCADI的完整API文档其中详细介绍了所有可能的fm.debug对象参数和设置,但为了方便起见,我整理了一个有用命令的快速“备忘单”,以便立即开始使用PyCADI和Fast Models。这张表,以及一个样品皮质m4模型,是免费提供在下面的zip文件夹。使用这个zip文件夹中的文件以及Fast Models、python和您自己的应用程序,您可以在几个小时内开始改进您的持续集成系统。

立即下载,快乐编码。现在就下载编码快乐!



留下回复


(注:此名称将公开显示)

Baidu