
74自制CPU2.0 DUT验证和调试板设计心路历程在上一篇RB的故事里, 我提到了一块叫作SC8Link的板子. 它最初是为了替我测试寄存器板而出现的, 后来又逐渐承担了程序下载, Flash读写, 整机HALT和CoreDump等工作.如果只看最终功能, SC8Link有点像SC8自己的STLink. 但它又不只是下载器. 对一台由许多74逻辑芯片, CPLD和独立功能板组成的CPU来说, 我首先需要解决的是怎样可靠地控制几十根真实信号线, 怎样避免两边同时驱动共享总线, 以及怎样在接错电源, 焊接短路或者逻辑竞争发生时, 不让一块板的错误烧掉连带的整套系统.所以这一篇先不深入SC8Link的软件协议. 我想从硬件说起, 讲讲我为什么需要这样一块板, 它怎样从一块仓促画出的单板测试底板STM32, 逐渐变成SC8的DUT验证平台, 下载器和调试器.我只测了几个GPR, 就不想再插杜邦线了SC8Link真正开始成形, 是在我测试RB的时候.早期验证寄存器板, 基本就是用杜邦线手工扮演CU. 我需要先把数据放到D总线上, 再配置寄存器编号和输入使能, 然后给出正确的时钟动作. 读回时又要释放刚才的驱动, 切换选择信号, 让目标寄存器输出, 最后再去看总线上的值.一两个动作看起来不难, 但R0~R7, PC, DP和SP每一个都有装载, 输出, 保持或者计数等不同状态. 我当时只测试了几个GPR的装载, 就不想继续了, 实在太麻烦了.更麻烦的是, 手工动作本身并不可靠. 我发现杜邦线插拔一下, 时钟可能会连续跳变几次. 如果寄存器里的值不对, 我甚至无法立刻判断究竟是板子设计错了, 还是刚才插线时产生了毛刺. 同一个动作也很难保证时序完全相同.我又是在吃饭路上这种碎片时间中想起了这个问题. 既然人工操作既慢又不可信, 为什么不做一块板, 让STM32替我控制这些信号?我只需要把功能板插进DUT插槽, 下载与它对应的测试固件, 后面的上电, 信号激励, 时钟动作, 结果读取和比较都由程序自动完成. 日志实时发到PC串口, 最后再生成一份报告. 插拔功能板和选择测试程序仍然由我完成, SC8Link并不知道DUT插槽里放的究竟是哪块板, 但一旦测试开始, 那些最繁琐, 最容易重复出错的动作就不再需要我用手完成了. 还非常方便接入AI Agent,自动分析报告等功能.两年前觉得枯燥的比赛现在派上了用场这个想法让我重新想起了大一时参加的一次职业技能竞赛. 赛项叫集成电路设计和测试, 我负责给测试芯片编写测试用例.当时我觉得这项工作非常无趣. 我认为自己应该去写嵌入式程序, 调试设备, 而不是在上位机里写C testbench, 学DUT, VIH, VOH, 灌电流和拉电流这些东西.两年以后, 那些当时觉得枯燥的知识几乎全都回到了SC8Link里.商业芯片测试设备可以在规定电流条件下测量VIH, VOH等模拟指标, SC8Link当然做不到这种程度. 它没有精密的源测量单元, 也不能给每个引脚施加指定电流再扫描逻辑阈值. 但那段经历至少让我知道, 数字电路测试不应该只是给个0和1看看结果. 输入高低电平有电压范围, 输出有驱动能力, 被测对象需要明确的激励, 采样和判定过程, 测试条件本身也必须可重复.后来SC8Link里的测试用例, DUT概念和报告意识, 都能看到那次比赛留下的影子. 甚至连这次联想, 也是我走在路上时突然出现的.下载器, 测试平台和调试器三位一体SC8Link的三个身份, 并不是在规划之初就想好的.下载器是SC8立项时就确定要有的. CPU最终要从Flash取指, 如果没有办法烧录程序, 那么再完整的CPU也跑不起来. 所以最早的需求很直接: 我要有一个东西能够控制Flash的地址, 数据和读写信号, 把生成的程序写进去.测试平台来自RB. 当我发现自己手测几个GPR太麻烦时, 我才真正需要一台能够把功能板当作DUT, 自动执行testbench的设备.调试器则出现得更晚. 随着CU越来越复杂, ISA里的指令越来越多, 程序没有按预期运行已经无法只靠LED和万用表解释. 我开始需要让CPU安全停下来, 查看寄存器和当前执行现场, 再判断错误究竟发生在哪里.这三种设备听起来不同, 但它们接触的硬件资源高度重合: 地址总线, 数据总线, 控制线, 时钟, 复位, HALT和目标供电. 与其分别做三套硬件, 不如让同一块板根据不同场景切换身份.我最初在立创EDA里随手新建工程时, 给它起了一个非常直白的名字: “单板测试底板STM32”. 后来它承担的工作越来越接近STLink之于STM32, 我才模仿这个名字把它叫作SC8Link, 哈哈.底板连接了整台CPU的信号第一版SC8Link画得很快, 因为RB已经焊好了, 我急着测试它.底板本身并没有放很多复杂逻辑. 它主要由电源, STM32最小系统板插座, SC8板间连接器, 按键, 屏幕和一些辅助电路组成. 真正重要的事情, 是把SC8几乎所有板间通信信号都连接到STM32.SC8有16bit地址总线, 8bit数据总线, 还有寄存器选择, ALU控制, 存储器读写, 时钟, 复位, HALT和各种状态信号. 普通小封装MCU的GPIO数量根本不够, 所以我选择了144引脚的STM32最小系统板.我没有在STM32和SC8之间再放一层74LVC245或者CPLD. 一方面是SC8Link操作这些信号的频率并不高, STM32 GPIO直接驱动已经足够; 另一方面, GPIO方向切换本来就可以通过寄存器完成,而且切换非常方便快速. 需要驱动时配置为输出, 需要观察时配置为输入, 需要让SC8自己正常运行时则全部切成输入或者高阻, 不去干扰SC8.这种直接连接让硬件很简单, 也把总线所有权变成了一条必须严格遵守的软件契约. STM32不能只知道要输出什么值, 它还必须知道自己什么时候根本不应该输出.时钟同样采用最直接的方式. SC8的CLK连接到STM32的PA8, 但没有使用定时器输出, 而是直接用GPIO bit-bang产生电平和脉冲. 对单板DUT测试来说, 这种方式反而很方便: 程序可以先准备数据和控制线, 再明确地产生一个时钟边沿, 然后读取结果.同一个插槽,既能测试单功能板,也能接管完整SC8单块功能板和完整SC8接入SC8Link以后, 使用的是同一组物理信号, 但SC8Link扮演的角色完全不同.测试单块功能板时, SC8Link要模拟其余CPU系统. 例如测试RB, STM32要向D总线写入数据, 选择GPR或者PC, DP, SP, 产生装载和计数动作, 再切换总线方向读取结果. 测试程序知道每一步的预期值, 因而可以自动给出PASS或者FAIL.这套流程设计得很直接: 板子插好, 上电就能运行. 测试固件不会自动识别DUT板卡, 我需要先看到插槽里是什么板, 再下载对应程序. 第一批测试代码由AI根据网表辅助编写, 把大量重复的GPIO动作, 遍历, 比较和日志输出快速搭起来.整机作为TARGET运行时则相反. CU才是系统控制者, SC8Link不能继续像测试单板那样随意驱动信号. 此时几乎所有GPIO都应保持输入或高阻, 直到CPU进入HALT并完成安全的所有权交接.为了让SC8Link接管地址总线, RB还特意保留了一个地址源全关闭的选择. 当这个状态生效时, PC, DP和SP都不驱动A总线, STM32才可以成为临时地址主设备. 下载和读取Flash时, SC8Link主要控制A[15:0],D[7:0],nMEM_IE,nMEM_OE和nRST等信号.SC8Link它能够真正进入SC8的数据通路, 但前提是每一个总线驱动者都在正确时刻退出.保险丝发出的橙红色光芒直接接触所有总线带来了很强的控制能力, 也意味着一个错误可能不只是逻辑上的0和1不对.如果两个推挽输出同时连接到同一根线上, 一个输出高, 另一个输出低, 它们就会直接对抗. 这时最先出现的现象可能不是程序报错, 而是电流突然增大, 芯片发热或者电源电压被拉低.我在设计SC8Link时就考虑过这种风险. 这个想法同样是在吃饭路上出现的. 我用USB电流表测过, 当时CPU的平均电流不会超过0.2A, 所以最初比较保守地选择了250mA一次性保险丝. 后来外设功能板逐渐增加, 正常电流也变大, 我才换成500mA.SC8Link给DUT提供的也不是整数3.3V, 而是从第一块功能板开始就确定的约3.403V. 我当时希望用多出来的大约0.1V争取一点74HC器件的传播延迟余量, 同时对层叠板较长的供电路径做一点源端补偿. 这项收益能够补偿效果的其实不大,但总比远端比3.3V少几十mV好.实际开发中, 我前后一共烧掉了十几个250mA保险丝. 原因各不相同: 有时是电源接反, 有时是LCD1602电源接反, 有时是焊接短路, 但最多的还是总线竞争.大多数时候, 我会看到陶瓷保险丝里突然闪过一瞬间的橙红色光芒, 随后SC8电源掉电. 第一次看到时, 我意识到可能发生了短路, 眼疾手快地拨动开关关掉电源. 后来见得多了, 我都习以为常了哈哈. 看到光芒, 系统掉电, 就开始根据具体故障现象拿万用表, 示波器并结合代码排查.这些事故没有一次损坏真正的SC8元件. 找到问题, 更换保险丝以后, 系统又能继续工作. 这说明保险丝确实派上了很大的用场, 它像一个守望者一样,发现电流不对就熔断他自己,保护后级电路. 但是这个保险丝的性能不是很好,有时候它会处于半熔断的时候,万用表测得VCC是1v多的电压.替换掉保险丝才恢复.有一次, 我更换GPIO功能板以后突然发现系统不能正常工作了. 我排查了很久, 最后才发现之前为GPIO板增加的74LVC138补丁中, 通往U65#OE的一根飞线断了, 可能是在换板时被手碰到了. 关于这个GPIO板的bug我后续在GPIO章节会详细介绍.这片74LVC138用来把原有输出使能条件和地址命中PAW_HIT组合起来. 补丁连接为A0#OE_old,A1PAW_HIT,Y2#→U65.#OE. 正常情况下, 地址没有命中GPIO时, U65这片74LVC245必须保持高阻. 飞线断开以后, GPIO板失去了正确的输出使能约束, 开始持续抢占D总线. 当系统里另一个驱动源给出相反电平时, 电流便迅速增大.重新焊好飞线以后, 系统立即恢复. 一根飞线断开会改变了整条SC8共享总线的电气状态.类似的竞争有时不会立刻熔断保险丝, 而是让VCC指示灯突然变暗一点. 我后来用示波器Roll模式观察VCC, 确认故障发生时电压会下降数百mV. SC8Link上的INA219也能测量目标电压和平均功耗, 但它的采样速度太慢, 捕捉不到这种快速瞬态. 所以INA219现在主要用于功耗检测.F103不够用了, 好在系统板可以替换第一代SC8Link使用反客科技的STM32F103ZE最小系统板. 我最开始认为F103完全够用, 因为它的工作似乎只有烧录程序和操作GPIO.我没有把F103芯片和最小系统直接画死在SC8Link底板上. SC8的IO频率不高, 多经过一组排针不会明显影响使用. 更现实的原因是, 我担心调试过程中发生总线竞争, 把STM32的IO一起烧坏. 如果主控模块可以拔下来, 即使真的损坏, 更换起来也比维修一颗144引脚芯片容易.后来的确发生过竞争把STM32烫得非常厉害, 但它一直没有烧坏. STM32还是很耐造的, 哈哈.随着功能增加, F103的资源开始不够用了. 我引入了nanopb, Zephyr和更复杂的LCD图形菜单. LCD帧缓存需要RAM, 下载完整48KB Flash镜像也需要一块足够大的缓冲区. 这时原本只负责GPIO的固件, 已经逐渐变成了一个带USB通信, 文件系统, 图形界面和目标机状态管理的小系统.我最初并不知道反客的F103ZE和F407ZG两块最小系统板能够共用同一块底板. 后来我逐个对比引脚, 才偶然发现它们的孔位兼容. 于是我把主控换成F407, 软件也转向Zephyr. 选择Zephyr除了工程需求, 还有一个很直接的原因: 我当时刚好想学一下它.得益于原来的代码分层, 换主控以后底板和绝大多数上层代码都能继续使用, 主要修改集中在BSP. 当初为了降低维修风险采用的可插拔设计, 意外给后来的主控升级留下了空间.下图的反客F103板子是鹿小班的兼容版本,因为反客没有黑色的板子.屏幕上的滚动报告, 最后交给了串口和AISC8Link板载了一块2英寸IPS屏幕, 五个主要操作按键和蜂鸣器. 五个按键分别是 上UP, 下DN, 确认ET, 返回BK和切换ALT, 这是我过去做嵌入式项目时习惯使用的一套基本人机接口.我最初想让屏幕直接显示测试报告. 上电以后, 一条条测试结果在屏幕上滚动, 看起来很像一台独立测试仪器.真正用起来以后, 我发现这种形式并不好. 测试条目一多, 屏幕上的文字一行行滚过去, 只能让人看得眼花缭乱. 想回头寻找某条失败记录也很麻烦.后来完整日志改由串口输出到PC. PC能够保存, 搜索和比较报告, 也可以直接把日志交给AI分析. 屏幕则回到更适合它的角色: 显示当前状态, 菜单和少量关键结果.现在SC8Link仍然保留了一些基本的脱机操作, 例如复位SC8, 控制上下电, 读取Flash数据, 以及从SD卡选择程序固件进行下载. 它还不是一台完全脱离PC的复杂逻辑分析仪, 但已经不只是一块没有反馈的转接板.自动测试不再让我只能期待板子功能正常SC8Link最完整地验证过RB和ALUv1.0.RB测试会覆盖GPR, PC, DP, SP和地址源切换等功能. 程序向寄存器写入不同的数据模式, 再逐项读回; 对计数器不仅检查装载, 还会产生实际计数脉冲, 检查保持, 加减和边界行为. 每项测试都有期望值和实际值, 最后汇总成报告.RB里那个著名的PC8问题, 就是在自动测试中稳定暴露的. PC装载0x1200再读回时完全正常, 但产生一个自增脉冲以后, 实际地址却变成了0x1208. 如果只做写入读回, D和Q位序成对接反会把错误互相抵消; 真正计数以后, 74LVC161内部Q0的固定最低位语义才会暴露出来.SC8Link不仅能报告失败, 还可以把已经确认的硬件问题标记为已知, 继续运行后面的测试. 这样我不必因为一个已知错误中断全部流程, 同时又能把它明确保留在报告里.ALUv1.0测试则抓到过两次焊接故障. 一次是反相逻辑的两个相邻bit被焊锡短路, 另一次是加法器路径中某个1G小门器件出现连锡. 这些故障最后都落到了具体物理连接上, 而不是停留在一句模糊的ALU硬件有问题.我后来并没有为每一块功能板都继续编写同等级的自动DUT测试. 这件事说起来也有点偷懒, 因为后面的板子很多插上就能工作, 我便跳过了完整测试.因为我逐渐打通了让AI Agent自动审查原理图网表的工作流, 在下单以前检查位序, 极性, 输出使能, 悬空输入和网络连接. 它为我节省了大量重复打板的时间和成本, 后面的很多板子几乎一版就能成功.我的流程于是变成:设计原理图和PCB → 打板与焊接 → 用万用表检查电源是否短路 → 用SC8Link执行需要的单板测试 → 最终接入完整SC8运行在SC8Link出现以前, 板子焊好以后, 我很多时候只能期待它功能正常. 现在至少有了一台可以主动施加条件, 读取结果并留下报告的设备.SC8崩溃后可以把自己的现场交给SC8Link单板测试解决的是这块功能板是否按照设计工作, CoreDump解决的则是完整SC8为什么停在这里.当前SC8Link已经完成了CoreDump和寄存器读取, 单步等更深入的调试功能还没有完成. 这篇也不展开两线协议的每一个bit,他在后续CU章节会详细讲述. 先只看它在硬件上怎样保护现场并完成所有权切换.实际连接非常简单: 把完整SC8插到SC8Link上, 再把PC接到SC8Link的调试串口.当CPU运行时, SC8Link的共享总线GPIO保持输入或高阻. CPU进入HALT后, SC8Link先不复位, 也不立即接管旧并行总线, 而是只通过DBG_CLK和DBG_DAT两根线与CU内部调试状态机通信. CU把冻结现场中的PC, DP, SP, R0~R7, PSW, 当前指令窗口和停机原因依次送出来, SC8Link再把这些内容整理成串口报告.这里最重要的顺序是先保存现场, 再复位接管. 如果CoreDump以前就拉低nRST, 那么本来想查看的PC, 寄存器和状态都会被复位破坏. 但读取完成以后, SC8Link若要继续接管地址, 数据和Flash控制线, 又必须让CU退出旧控制总线, 否则两个推挽输出仍然可能互相对抗.我用金属镊子随意拨弄一下地址总线A,这会导致地址出错,进而程序进入错误地址,Flash大部分空间都是0xFF,正好对应HALT指令Opcode. 当发生了HALT,CU会在指令边界冻结现场, SC8Link随后从真实硬件中读出CoreDump并打印到PC.到这一步, 使用者看到的动作仍然很简单: 插上SC8, 连接PC, 按下一个按键. 但串口里出现的那份报告, 已经穿过了软件HALT指令, CU状态机, 开漏调试信号, SC8Link GPIO和串口日志, 最终把一台由74逻辑芯片和CPLD组成的CPU现场带回到PC上.它没有替我消灭bug, 但让每个bug更容易留下证据SC8Link没有让SC8从此不再出错.我还是会接反电源, 会焊接短路, 会画错输出使能, 飞线也可能在换板时被碰断. 两个总线驱动源仍然可能同时输出, 软件测试也不可能替代示波器, 电气测量和完整整机运行.它真正改变的是, 我不再需要每次都从一团杜邦线和模糊记忆开始.人工插线时, 一个寄存器值不对, 我首先要怀疑自己刚才有没有多碰一次时钟. 自动测试以后, 相同动作可以按照固定顺序反复执行, 期望值和实际值会留在报告里. 板卡发生大电流时, 保险丝先切断供电; 修复故障以后, 同一套测试又能重新运行. 完整SC8停机后, CoreDump还能把当时的寄存器和指令现场保存下来.当前这套SC8Link硬件已经能够满足我的需求, 我暂时没有一定要重做一块v2的想法. 它的底板并不复杂, 甚至最初画得有些仓促, 但它能完整控制SC8的总线, 也可以连接从设计, 焊接, 测试到下载和调试之间原本断开的环节.对我来说, SC8Link最重要的意义是让它真正成为了一台可以持续开发,调试故障的目标机.当我按下按键, SC8执行软件HALT, 调试串口打印出那份真实CoreDump时, 我看到的不只是一组寄存器数值. 那意味着这台由很多74逻辑芯片, 功能板和一块CPLD组成的计算机, 已经能够在出错或暂停时, 把自己的现场交给我.而我终于不用再靠一根根杜邦线去猜它刚才发生了什么.bySightseer.