ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Tessent MemoryBIST SBI库文件自动化生成与验证实践

Tessent MemoryBIST SBI库文件自动化生成与验证实践 做SoC DFT这些年Tessent MemoryBIST的Shared Bus InterfaceSBI库文件一直是个耗时大户。对不熟悉的人来说它不过是几个描述“怎么通过串行总线访问片上存储器BIST控制器”的文本文件但对真正跑过Insertion的人来讲这套库文件决定了你的BIST逻辑能不能正确挂上、仿真能不能按时跑通。只要一个memory的时钟域写错、一个地址位宽不对后面全盘重来。这篇文章我打算把它做成一份真正可上手的实践笔记围绕库文件自动化生成与验证这条主线讲清楚SBI库文件里到底有什么、脚本该怎么设计、验证怎么落地以及那些只有反复踩坑才会注意到的细节。适合正在用Tessent做MemoryBIST的DFT工程师也适合刚接手SBI接口、被一堆.tdf文件和memory wrapper搞到头疼的验证人员。1. 项目背景Shared Bus Interface库文件为什么值得花精力做自动化1.1 SBI到底在解决什么问题Shared Bus Interface简称SBI是Tessent MemoryBIST里很常见的一种接口方式。它的核心思想是不用给每个存储器的BIST控制器都拉一堆独立控制引脚而是让多个controller挂到同一条共享串行总线上通过给每个controller分配唯一地址来分时访问。SBI协议分成两级。第一级叫ICInterface Controller负责跟片外或系统侧的总线主机打交道把串行命令收进来按协议解析成内部并行信息。第二级叫PDProcedure Decoder挂在IC下面每个被访问的BIST控制器都有一段PD逻辑用来认领属于自己的命令并驱动对应的存储器做写、读、比较等操作。打个比方IC就像快递总站的分拣员把包裹按地址分到各个片区PD则是每个仓库门口的收货员只签收写着自己仓库号的包裹。这个“地址”和“仓库号”正是库文件里最容易出错的地方。1.2 手工维护库文件的真实痛点我在前一个项目里接手过一套纯手工改出来的SBI库。设计里有40多个memory实例覆盖4个时钟域每个memory的深度、位宽、时钟极性都不完全一样。最痛苦的是每次新增一个memory得同时改IC文件里的地址分配、PD文件里的decode逻辑、interface文件里的端口声明还要重新跑一遍仿真确认没改坏。整个过程没有任何脚本辅助全靠肉眼找。这种手工方式有几个致命问题一致性很难保证。IC里分配的地址和PD里decode的地址如果对不上仿真阶段才会暴露而排查这种问题往往要花掉大半天。拼写和命名错误频发。库文件里的信号名、实例名一旦跟memory wrapper里的端口差一个字母Tessent在Insertion阶段就直接报错而且报错信息经常说得云里雾里。时钟域处理全靠人肉记忆。同一个BIST时钟可能在IC、PD、memory behavior model三处出现改了一处漏了另一处是常有的事。版本管理混乱。手工修改很容易出现“本地跑得好好的传到服务器上就挂了”的情况。所以当我在新项目里看到60多个memory实例、6个时钟域的时候第一反应就是这活必须交给脚本而且要连验证一起自动化不能只搞个生成脚本草草了事。1.3 自动化方案要达成的目标我给自己定的目标是只维护一张memory list表格脚本根据这张表生成全套SBI库文件和验证环境然后自动跑完静态检查和仿真验证最后输出一份check report。整个流程的核心诉求有三点可重复同一脚本跑十遍结果完全一致。可追溯每个生成的文件都带版本头注释注明来源和生成时间。可验证生成后必须自动完成语法检查、库加载检查和RTL仿真不能只生成不验证。这套方案做下来新增一个memory的时间从原来的平均3到4小时压缩到10分钟以内而且基本告别了“改A忘B”的低级错误。下面我把具体的库文件结构和自动化流程展开讲。2. 库文件组成与核心设计拆解2.1 一套SBI库文件里到底有什么Tessent MemoryBIST用到的SBI库文件按用途可以分成三类接口定义文件、控制器行为文件、仿真辅助文件。以实际项目中最常见的组合为例p1500_shared_io_interface.tdf这是共享IO接口的定义文件声明了外部总线与IC之间的所有端口包括串行数据输入输出、移位时钟、更新信号等。Tessent在生成wrapper的时候会去读这个文件确定接口方向。ic_interface.tdf定义IC的行为。里面包含了从串行命令到内部并行指令的转换逻辑以及对外交互的握手协议。这个文件写得好不好直接决定仿真里命令能不能顺畅地“进门”。pd_interface.tdf定义PD的行为。每个被访问的BIST控制器对应一段decode逻辑根据命令中的地址和操作码决定是否响应以及具体要执行什么读写操作。memory behavior model就是存储器本身的仿真模型文件通常以Verilog形式提供。Tessent在仿真验证时需要用它来确认MemoryBIST逻辑与存储器接口时序匹配。时钟和复位定义文件描述BIST时钟、接口时钟、复位信号之间的关系这个文件影响Insertion时Tessent对时钟域的处理。很多人问这些文件不能直接用Mentor自带的例子吗能但自带例子大多是单时钟、单memory的demo结构真实SoC里往往有多个时钟域、多种memory类型直接套用几乎必挂。比较稳妥的方式是以自带例子为模板在上面做定制化修改然后用脚本去管理这些改动点。2.2 协议规则与字段解析SBI库文件里最核心的字段不外乎下面这几个端口信号包括串行输入SI、串行输出SO、移位时钟、更新使能等。命名规则必须与Tessent顶层端口映射保持一致否则生成wrapper时连不上。操作码opcode决定当前命令是写配置、读状态还是触发BIST。不同opcode的位宽不一定要相同但IC和PD必须用同一套约定。地址字段用来区分挂在同一条SBI总线上的不同controller。地址一旦重复后面的controller永远收不到命令而且仿真里很难第一时间发现。procedure id用来区分同一个PD下的不同操作比如先启动BIST再移位读取签名。这里要特别提醒地址字段和procedure id的位宽在设计阶段就要跟SoC系统侧的软件约定好。如果后面固件或测试程序里准备用某个地址去触发某个controller但库里配置成了另一个地址整个系统联调时会非常难看。2.3 设计取舍模板生成与命名规范在决定自动化方案时我比较过“完全从零生成”和“基于固定模板生成”两种路线。完全从零生成看起来自由但对脚本的要求很高每改一个微小的协议变动就得动大量代码逻辑维护成本不低。而且一旦生成出来的文件风格跟Tessent标准库差异太大工具加载时容易出现奇怪的不兼容问题。我最终选的是“模板生成”路线先手工做好一套经过验证的基准模板模板里保留所有固定结构和标准协议包只把每个memory的差异化信息抽出来比如实例名、地址分配、时钟域选择、位宽参数作为可替换的变量。脚本做的事情本质上就是按表格内容做批量替换和裁剪。命名规范这块也踩过坑。第一版脚本生成的文件名用的是混合大小写结果在Linux环境下跟tcl脚本里的引用对不上折腾了很久。后来统一改成全小写加下划线比如ic_interface_${clock_domain}.tdf所有实例名、信号名、标签全部小写开头既避免了大小写敏感问题也方便在仿真波形里按统一前缀过滤信号。3.1 输入文件怎么设计自动化脚本的地基是memory list。我用的是一张CSV表格每行描述一个memory实例字段包括mem_name存储器实例名比如u_mem_array_amem_type存储器类型名对应型号库里的模型名depth和width深度和位宽mem_clk_domain存储器本身时钟所在域bist_clk_domainBIST控制器时钟所在域cen_polarity和wen_polarity片选和写使能极性soc_bus_addr在SBI总线上分配的地址CSV的好处是Excel直接能编辑版本管理里diff也比较友好。为了减少解析出错我要求表头不能改字段用英文逗号分隔所有字段不允许出现空格。脚本在解析时会先做字段数量检查一旦某行字段数不对立刻报错并给出行号避免把错误一路带到生成文件里。一个典型的输入示例大概长这样mem_name,mem_type,depth,width,mem_clk_domain,bist_clk_domain,cen_polarity,wen_polarity,soc_bus_addr u_sram_a,ts1n28_hp_2port,16384,32,clk_sys,clk_sys,active_low,active_low,0x10 u_sram_b,ts1n28_hp_2port,32768,16,clk_fast,clk_sys,active_low,active_low,0x11 u_sram_c,ts1n28_hp_1port,8192,64,clk_ddr,clk_ddr,active_low,active_low,0x12从这张表能看出来mem时钟域和BIST时钟域不一定相同。u_sram_b这个实例存储体挂在clk_fast上但BIST控制器逻辑挂在clk_sys上这种跨时钟域的情况是脚本处理的重点。3.2 生成脚本的完整流程脚本用Perl实现主要因为这玩意儿在EDA环境里最不缺而且处理正则替换非常顺手。整体流程分四步解析CSV构建memory信息哈希表。按固定顺序生成目录结构和模板文件包括标准库目录、用户定义库目录、仿真目录。根据每个memory的参数填充IC文件、PD文件和interface文件。非本设计需要的procedure不生成减掉不必要的冗余逻辑。生成后续验证用的Tcl脚本和仿真测试bench模板并自动执行第一轮静态检查。流程上有一个关键点所有文件在写盘之前全部先拼到一个字符串缓冲里最后一次性写入。这么做的原因是为了避免“写到一半报错留下残缺文件”下次跑脚本的时候旧文件残留导致误判。3.3 核心代码实现讲解解析那部分比较常规没什么好细说的。真正值得拿出来讲的是两个子程序一个负责生成PD文件另一个负责生成do文件。PD文件生成的核心逻辑是根据CSV里的soc_bus_addr字段生成一段case语句把总线地址映射到对应的memory实例。代码大概长这样sub gen_pd_decode { my ($mems) _; my $pd // auto-generated by gen_sbi_lib.pl\n; $pd . module pd_interface\n; $pd . (\n; $pd . input wire sbi_shift_clk,\n; $pd . input wire sbi_update_clk,\n; $pd . input wire [7:0] sbi_addr,\n; $pd . output reg [3:0] mem_sel\n; $pd . );\n; $pd . always (*) begin\n; $pd . case (sbi_addr)\n; foreach my $m ($mems) { $pd . 8h$m-{addr} : mem_sel 4d$m-{index};\n; } $pd . default : mem_sel 4d0;\n; $pd . endcase\n; $pd . end\n; $pd . endmodule\n; return $pd; }这里有个细节值得展开地址统一用16进制书写并且强制补足到8位即使地址本身只需要5位。这么做是为了让生成的PD文件在grep和人工review时更容易对齐避免出现0x1和0x10这种一眼看不清的地址。do文件生成的核心逻辑也很简单就是把每个验证步骤打包成可重复执行的脚本。Tessent的命令在不同版本上多少有差异但这个流程骨架是通用的# run_sbi_verify.tcl # Step 1: 加载memory库和接口库 read_cell_library ./lib/ts1n28_hp_2port.lib read_memory_library ./mem_lib/ts1n28_hp_2port.lib read_interface_library ./sbi_lib/p1500_shared_io_interface.tdf # Step 2: 执行库一致性和接口检查 check_interface -verbose # Step 3: 执行标准的SBI库验证流程 verify_sbi_library -work_dir ./verify_run \ -log ./sbi_verify.log写这段脚本的时候我通常会把所有路径改成相对路径避免不同环境之间路径不兼容。另外每个步骤都加上-log参数方便出错时直接翻日志。3.4 目录结构和命名规则生成的目录结构也花过心思。现在固定用这套组织方式sbi_gen/ input/ mem_list.csv output/ lib/ mem_lib/ rtl/ tb/ scripts/ reports/lib下面是统一的接口库文件mem_lib下面是memory行为模型rtl里放的是生成的PD、IC模型tb里放自动生成的testbenchscripts里放do文件reports里放每次验证生成的log和report。所有脚本生成的文件开头都带一段注释头记录生成时使用的CSV版本、脚本版本、生成时间。这样哪次跑出来的结果不对能很快定位是输入变了还是脚本版本变了节省大量扯皮时间。4. 验证流程设计不能只生成不验证4.1 三级验证体系生成脚本只是第一步真正的重头戏是验证。我把验证拆成三个层级每一级定位不同的问题从低到高逐层通过第一级是静态检查。脚本跑完以后自动对生成的文件做一轮文本层面的检查。检查内容包括端口信号是否存在、CSV里的字段是否全部出现在PD文件中、地址是否有重复、实例名是否与memory list一致。这轮检查速度最快能在几十秒内把最弱智的问题全部拦下来。第二级是Tessent库加载验证。把生成的接口库交给Tessent让它正式读一遍确认文件语法、接口方向、库结构都没有问题。这一步能发现那些“看起来正确但工具不认”的隐藏问题。常见错误比如端口方向写反、信号名与标准库冲突等等。第三级是RTL仿真验证。也是最耗时间、最接近真实工作状态的一环。我会生成一个小的SoC级testbench里面例化一个SBI总线master行为模型再接上生成好的PD和IC连接然后对每个memory实例执行一轮完整BIST操作检查test_done能否按时拉高、bist_fail是不是保持低电平。4.2 仿真testbench怎么搭testbench模板我维护了一份通用版本每次生成时只替换memory实例相关参数。核心结构大概是这样的思路module tb_sbi_top; // 总线master行为模型 sbi_master u_master ( .sbi_clk (sbi_clk), .sbi_rst_n (sbi_rst_n), .sbi_io (sbi_io), ... ); // 被测memory wrapper u_mem_wrapper dut ( .sbi_io (sbi_io), .mem_clk (mem_clk), .mem_cen (mem_cen), .mem_wen (mem_wen), ... ); // 简要测试流程 initial begin init_system(); // 对每个地址依次触发BIST foreach (test_addr_list[i]) begin trigger_bist(test_addr_list[i]); wait (test_done[i] 1); check_fail_flag(i); end $finish; end endmoduletestbench的关键在于触发BIST的方式必须跟最终SoC系统软件操作一致先移位写入配置命令再发启动命令然后等待test_done信号拉高。仿真波形里我会重点看SBI总线上命令是否在正确的采样窗口被识别以及PD那边的mem_sel是否选中了正确的memory。4.3 验证结果怎么判定一处跑完所有实例的test_done都正常拉高、bist_fail全程保持低电平并且没有定时器超时我才敢说这轮库文件生成是合格的。每次验证结束脚本会把每个用例的结果汇总到一张表里格式类似下面这样memory实例地址时钟域test_donebist_fail结果u_sram_a0x10clk_sys100.2us0PASSu_sram_b0x11clk_sys98.6us0PASSu_sram_c0x12clk_ddr102.4us0PASS看到表格里出现FAIL的时候我会先把对应memory的波形单独拉出来看对照PD文件里的地址decode逻辑逐拍检查。实测下来绝大多数失败问题都能在20分钟内定位完成。5. 常见问题与排查实录5.1 IC接口缺失或加载报错现象Tessent读接口库时直接报IC interface找不到或者提示IC端口数量不对。这类问题十有八九出在ic_interface.tdf的端口声明与p1500_shared_io_interface.tdf不一致。比如共享IO里定义了一个额外的test模式下才用的辅助端口但IC文件里没加这个端口工具加载时就会因数量不匹配而报错。我的排查思路是先打开两个文件把端口列表按顺序排列并逐行对比重点看方向input/output/inout是否一致。自动化脚本里我加了一道预处理检查自动提取两个文件的端口列表做diff不一致就直接让脚本失败退出不再继续生成后续内容。5.2 仿真时部分memory的BIST命令始终不触发现象testbench跑起来大部分memory都能按时test_done但某个memory的test_done一直等不到。这种问题第一怀疑对象是地址冲突。某个新增memory的地址跟已有memory重复了PD里的case语句先match到前面的实例后面的实例永远得不到触发机会。我在脚本里专门写了一个地址唯一性检查模块在生成PD文件之前先遍历一遍整张CSV表如果发现有重复地址直接报错列出冲突的两个实例名。这一步在小规模设计里可能显得多余但上了规模之后真的是救命功能。5.3 跨时钟域导致的采样错误现象仿真总线上命令看起来都发对了但PD就是收不到正确的操作码波形上能看到移位时钟有毛刺或者数据在变化时被采样了。跨时钟域是SBI库里最难调的部分。比如memory时钟是clk_fast但BIST控制器逻辑是clk_sys采样的两者相位关系如果不固定移位数据就容易采错。我处理这类问题的办法是在库文件里明确把两个时钟的相位关系定义好。如果设计里允许尽量让BIST控制器跟memory公用同一个时钟域从根上避开跨时钟域问题。如果实在避不开必须在testbench里加相位偏移检查确保移位窗口落在数据稳定的区间内而不是赌它刚好采样正确。5.4 生成的PD逻辑正确但面积超标现象Tessent跑完Insertion后报告时序、功能都对但BIST逻辑面积比预期大得多。这个问题往往来自PD decode逻辑太“宽”。如果每个memory都单独写一段完整decode地址位宽又定义得偏大综合后case语句会变成很大的优先级比较树。比较省面积的做法是先把地址分段高位段先粗选低位段再细分类似二分查找的思路综合出来面积能小不少。但这块优化不用急着第一版就做先把功能跑通后续如果有面积压力再迭代。我的经验是功能优先面积优化放到Insertion结果后端报告出来之后。6. 一点个人经验与后续扩展方向这套自动化流程跑通之后最明显的收益不是省了多少时间而是整个人的心态变了。以前改库文件每次都要小心翼翼改完还要反复看。现在只要保证CSV输入是对的生成、验证、出报告全自动我只需要在出现FAIL的时候介入排查思路也清晰很多。如果后续项目里memory类型更多、结构更复杂我会优先扩展两件事一是接入更多memory behavior model的自动比对二是把验证流程挂到持续集成环境里每次代码提交自动跑一轮SBI库回归。最后分享一个小技巧每次新项目clone这套脚本之后先不要急着跑全量。拿一个最简单、最典型的memory实例从头到尾跑通一遍确认环境没问题再放全量进去。这个习惯帮我避免了很多次“一跑全量就各种环境报错”的尴尬场面。做DFT工具链自动化稳比快重要得多。
返回列表