新闻详情

新闻详情

首页 / 资讯中心 / 详情

UVM验证平台树形结构详解:从组件挂载到config_db路径一致性

发布时间:2026/9/8 11:36:16来源:尧图网络
UVM验证平台树形结构详解:从组件挂载到config_db路径一致性
1. 从一棵“看不懂的树”说起UVM平台的骨架到底是什么如果你刚接触UVM翻开一份验证环境代码大概率会陷入一种“类倒是认识但之间怎么串起来”的迷茫。my_test里面new了一个my_envmy_env里面又new了my_agent和my_scoreboardmy_agent里面又藏着driver、sequencer、monitor……每个类都有一堆new和build_phase看得人头晕。这不是你的问题而是UVM平台本质上就是一棵树你对着散落的类看当然看不出名堂。只有把这棵树的层次结构画出来你才能真正理解UVM验证平台是怎么运转的、信号是怎么在组件之间流动的、配置是怎么一层层传下去的。我最早搭UVM环境时也没把树形结构当回事觉得只要class写出来、run_test一跑环境能跑起来就行。结果后来在集成多个IP的验证环境时栽了大跟头某个agent的配置怎么都传不到driver里去debug了一整天最后发现是agent在树上的“挂载位置”错了导致config_db的层级路径和组件实际路径对不上。从那天起我才真正重新把UVM的Hierarchy树形结构从头梳理了一遍。这篇内容就围绕UVM验证平台的树形结构展开包括它为什么必须是一棵树、树的每个节点怎么挂上去、build_phase和connect_phase在树上怎么流动、怎么用打印工具把树画出来以及我在实际项目中踩过的坑。不管你是刚学UVM的小白还是已经搭过一两个环境的初级验证工程师这篇文章应该都能让你对UVM体系有个更清晰的全局认识。2. 为什么UVM平台必须是树形结构三个“骨架级”理由2.1 UVM组件之间必须有清晰的隶属关系UVM设计哲学里最核心的一条验证环境的组件是有生命周期的而生命周期必须由某个统一的东西来管理。谁负责创建组件谁负责在仿真结束时销毁组件谁来决定组件之间的build顺序这些如果全靠工程师手工管理十个IP十个样验证环境很快就失控了。树形结构天然解决了这个问题。树的根是uvm_top它会自动创建你指定的testtest下面挂着envenv下面挂着agent和scoreboardagent下面挂着driver、sequencer、monitor。每个组件在创建时都能明确知道自己的父亲是谁父亲知道自己的孩子有哪些整棵树的隶属关系清清楚楚。你可以类比一下现实中的公司组织架构CEO下面管着几个VPVP下面管着几个总监总监下面管着经理和员工。上级决策层层下达下级汇报逐级上传不会乱。UVM树形结构就是验证平台的“组织架构图”组件之间谁管谁、谁向谁汇报一目了然。2.2 树形结构是phase机制和配置机制的地基UVM的phase机制build_phase、connect_phase、run_phase等等之所以能按部就班地执行依赖的就是这棵树的遍历顺序。phase调度器从树根开始先执行根节点的build_phase再按深度优先的方式往下执行所有孩子的build_phase。connect_phase则是反过来的先连接叶子节点再往上一层一层连接。如果组件之间没有形成树状结构这些phase的执行顺序就无从谈起。同一个道理uvm_config_db的set和get也是基于树节点的层次路径来匹配的。你在test里set一个配置路径写的可能是uvm_test_top.env.agent.driver而这个路径能匹配上前提是树上确实存在这么一条从test到driver的路径。树形结构一旦断了config_db的匹配就会失败最常见的现象就是配置set了但接收方get到的是null。后面我会详细讲这种坑。2.3 树形结构支撑了工厂机制和组件遍历能力UVM的factory机制可以对组件进行类型覆盖type override这依赖的是组件创建时的“品种登记”。而工厂在替换类型时也要知道这个组件被创建在树的哪个位置。树形结构提供了一种全局统一的对象管理方式让工厂、config_db、phase调度这些机制都能围绕同一棵“树”做文章。另外UVM还提供了一套组件遍历API比如从某个节点出发找它的孩子、找它的父节点、打印整棵子树的信息。这些能力在调试时非常有用尤其是当环境规模变大、组件数量多达几十上百个的时候没有树形结构这种系统性的遍历和管理就完全做不到。3. 树的节点构建原理与核心细节3.1 上树的“入场券”必须继承uvm_componentUVM里有两类基础对象uvm_component和uvm_object。它们的区别很多人一开始搞不清其实关键就在“能否在树上挂节点”。uvm_object是轻量级对象比如sequence、sequence_item、寄存器模型中的某些类它们不需要独立的生命周期管理不参与phase调度也不挂在树上。你可以把uvm_object想象成“临时工”——干完活就走不需要公司给你安排工位。uvm_component则是“正式员工”它需要常驻在整个仿真过程中必须参与phase调度必须能通过树结构被搜索到。driver、sequencer、monitor、agent、env、test、scoreboard全部继承自uvm_component。只有继承自uvm_component的类才拥有树上的“户口”。判断一个类到底该继承uvm_component还是uvm_object有个很实用的标准这个对象需不需要在仿真期间一直存在需不需要被phase机制管理如果是就是component如果不是就是object。class my_driver extends uvm_component; ... endclass class my_sequence extends uvm_sequence #(my_transaction); ... endclass上面这段代码里my_driver作为常驻组件继承uvm_componentmy_sequence作为临时激励生成器继承uvm_sequence它最终也是uvm_object的子类。3.2 parent参数树的“认亲”方式uvm_component的构造函数有两个参数name和parent。name是这个组件在树上的名字parent则是它的父节点指针。靠这两个参数UVM才能把各个组件组织成一棵树。function new(string name my_driver, uvm_component parent null); super.new(name, parent); endfunction这种设计意味着挂树的动作发生在new的时候而不是build_phase的时候。很多初学者以为在build_phase里create组件才算挂树其实create只是通过工厂去调用new真正的“认亲”是在new函数执行super.new(name, parent)那一刻完成的。注意parent参数传的是父组件的this指针如果你传了null那这个组件就会变成一个没有父亲的“孤儿”。在UVM树里唯一特殊的节点是uvm_top其他组件都不允许没有父亲。后面我会讲这种“孤儿”会引发什么问题。3.3 组件工厂注册缺了它整棵树都会出问题每个组件类都要在声明时加上uvm_component_utils宏注册或者在UVM版本较新时加上uvm_component_utils(...)。这个宏不只是为了工厂机制它会给类分配一个类型ID让UVM能统一管理。如果你漏掉了注册宏构建树的过程中工厂就无法识别这个类最常见的报错是Factory did not return a component或者是类型ID为负、组件无法被创建。class my_env extends uvm_env; uvm_component_utils(my_env) ... endclassuvm_component_utils和uvm_object_utils不要搞混。uvm_component_utils会额外调用m_register把组件注册进树的管理体系里而uvm_object_utils只是纯对象工厂注册两者功能定位完全不同。3.4 名字在整棵树中必须唯一树的每个节点都有一个名字这个名字是它在树上的“门牌号”。UVM的机制要求同一个父节点下的所有直接子节点名字不能重复否则后续按路径查找时会发生歧义。实际中同一层的组件如果都用默认名字比如某个类的name参数没传默认值是类名字符串就很容易冲突。我见过一个场景env下有两个agent但两个agent的组件都用默认名字driver。在打印树形结构时两条路径分别是uvm_test_top.env.agt1.driver和uvm_test_top.env.agt2.driver路径不冲突所以还能跑通。但如果你在config_db里只写了uvm_test_top.env.driver那就只有第一个agent的driver能匹配上另一个agent的driver拿不到配置。养成给每个组件起有辨识度名字的习惯能省掉很多后期debug的麻烦。4. 实操从零搭建一棵标准的UVM树4.1 树的结构规划先画图再写代码动手写代码之前先规划好这棵树长什么样。一个典型的UVM验证环境树结构如下uvm_test_topmy_testenvmy_envagtmy_agentdrvmy_driversqrmy_sequencermonmy_monitorref_modelmy_reference_modelscbmy_scoreboardin_agentmy_agent—— 如果是双向接口可能还要再挂一个agent树上每个节点的name就是最终打印出来路径的一部分。建议一上来就按这个规范给组件起名比如顶层叫envagent叫agt或agent0/agent1driver叫drv。名字简短清晰打印出来的拓扑图也漂亮。这一点是我经历过几个项目后养成的习惯后期跨团队协作时大家看打印的树就能快速定位到组件不用互相问“你那个driver叫什么”。4.2 核心代码实现逐个挂载节点下面是一段精简但完整的UVM树搭建代码。注意每个组件的new函数里都传了parentbuild_phase里也都调了super.build_phase。// 文件: my_test.sv class my_test extends uvm_test; uvm_component_utils(my_test) my_env env; function new(string name my_test, uvm_component parent null); super.new(name, parent); endfunction function void build_phase(uvm_phase phase); super.build_phase(phase); env my_env::type_id::create(env, this); endfunction task run_phase(uvm_phase phase); my_sequence seq; phase.raise_objection(this); seq my_sequence::type_id::create(seq); seq.start(env.agt.sqr); phase.drop_objection(this); endtask endclass// 文件: my_env.sv class my_env extends uvm_env; uvm_component_utils(my_env) my_agent agt; my_scoreboard scb; function new(string name my_env, uvm_component parent null); super.new(name, parent); endfunction function void build_phase(uvm_phase phase); super.build_phase(phase); agt my_agent::type_id::create(agt, this); scb my_scoreboard::type_id::create(scb, this); endfunction function void connect_phase(uvm_phase phase); super.connect_phase(phase); agt.mon.item_port.connect(scb.analysis_export); endfunction endclass// 文件: my_agent.sv class my_agent extends uvm_agent; uvm_component_utils(my_agent) my_driver drv; my_sequencer sqr; my_monitor mon; function new(string name my_agent, uvm_component parent null); super.new(name, parent); endfunction function void build_phase(uvm_phase phase); super.build_phase(phase); drv my_driver::type_id::create(drv, this); sqr my_sequencer::type_id::create(sqr, this); mon my_monitor::type_id::create(mon, this); endfunction function void connect_phase(uvm_phase phase); super.connect_phase(phase); drv.seq_item_port.connect(sqr.seq_item_export); endfunction endclass每往下挂一层create的第二个参数就传当前层的this。这就保证了节点一层层被挂在对应父节点下面形成一棵完整的树。4.3 打印树形结构用print_topology验证你的树搭建完成后验证树是否正确的最直接方法就是调用uvm_top.print_topology()打印整棵树。通常我们在end_of_elaboration_phase里调用或者直接在test的build_phase末尾调用也行。function void end_of_elaboration_phase(uvm_phase phase); super.end_of_elaboration_phase(phase); uvm_top.print_topology(); endfunction打印出来的效果如下UVM_INFO 0: reporter [UVMTOP] UVM topology: --------------------------------------------- Name Type Size Value --------------------------------------------- uvm_test_top my_test - 335 env my_env - 341 agt my_agent - 347 drv my_driver - 353 mon my_monitor - 365 sqr my_sequencer - 359 scb my_scoreboard - 371 ---------------------------------------------看到这种缩进层次分明的输出说明树已经成功建立。每个节点前面的缩进就代表它在树上的层级env在uvm_test_top下面缩进两格agt在env下面缩进四格drv又在agt下面缩进六格。提示如果打印出来的树“缺胳膊少腿”比如某个组件完全没出现或者在某个父节点下找不到预期子节点优先检查build_phase里是否create了这个组件以及create的parent参数是否传对了this。4.4 树的构建流程build_phase的顺序细节树的构建不只靠newbuild_phase的执行顺序也很关键。UVM中build_phase是自顶向下的父组件的build_phase先执行子组件的build_phase后执行。以4.2的代码为例实际执行顺序是my_test的build_phase执行创建envmy_env的build_phase执行创建agt和scbmy_agent的build_phase执行创建drv、sqr、mon递归返回继续执行其他分支的build_phase这背后的原因在于子组件创建时如果需要父组件传入配置比如通过config_db那么父组件的配置逻辑必须先执行完子组件才能拿到。自顶向下的顺序保证了“爸爸先准备资源儿子再出生”。connect_phase则相反自底向上执行叶子节点先连接父节点后连接。这样父节点在connect时子节点之间已经完成连接可以直接拿子节点的port或export来用。4.5 树形结构的“长成”三个必要条件通过前面的代码可以总结出让树正常“长成”的三个必要条件第一所有组件类都必须继承自uvm_component不能用uvm_object。第二每个组件的构造函数里都必须调用super.new(name, parent)并且parent不能传null顶层test除外。第三在父组件的build_phase里必须调用对应的create方法创建子组件而不是直接new。如果直接new工厂机制和类型覆盖都会失效。我之前见过有人图省事在build_phase里直接写drv new(drv, this);短期跑功能仿真没问题但一旦要做factory override就完全失效想替换成带错误注入功能的driver结果怎么都不生效查了半天才发现是绕过工厂机制的问题。5. 树形结构视角下的常见问题排查5.1 “树上有孤岛”组件没被挂在任何父节点下现象print_topology打印出来的结构里某个组件孤零零出现在一个奇怪的位置或者树并没有按预期嵌套config_db的路径无法匹配。排查思路先看“孤岛”组件的构造函数里parent参数是不是传了null或者传错了对象。这种问题在组件比较多的环境下很容易发生尤其是复制粘贴代码时很容易把上一个类的parent参数原样保留下来。最典型的例子从my_driver复制改成my_monitor时构造函数里new的name和parent忘了改最后所有monitor都挂在driver的父节点下。这种bug光看代码不容易发现但打印拓扑结构后一眼就能看清。5.2 组件创建了但“没长全”build_phase里少了create现象父组件内部明明声明了子组件句柄打印树形结构时却发现这个子节点根本不存在。最常见的原因是build_phase里忘了调用create或者create被注释掉了。有些同学在刚开始写UVM代码时把组件声明直接写在类成员变量里但声明只是声明真正分配对象内存并挂到树上的是create或者说new。没有create句柄就是null树上当然没有这个节点。排查时看两点一是build_phase里有没有create二是create的类型名和变量类型是否一致。比如你声明的是my_agent agt结果create写成了my_env::type_id::create编译器大概率会报类型不匹配但如果你把两个类名搞混成同类型可能编译能过运行时树结构就是乱的。5.3 树路径和config_db路径对不上这是最隐蔽的问题之一。config_db的set和get都依赖树的层次路径只要你set时的路径和树实际路径不一致get就会失败配置静默丢失。比如在test的build_phase里写了uvm_config_db#(virtual my_interface)::set(this, env.agt.drv, vif, vif);这个路径是相对当前组件this的。this是uvm_test_top相对路径env.agt.drv解析后就是uvm_test_top.env.agt.drv。如果树上的实际路径是uvm_test_top.env0.agt0.drv比如env取了别的名字那这个set就匹配不上。解决这类问题有个好习惯用相对路径的同时在driver的build_phase里get配置后立刻判断是否获取成功最好打印出来。否则配置丢失后后续跑仿真时driver里的虚接口是null一访问就报空指针你还得从头查。5.4 仿真结束时无法退出树上的“家长”和objection树的层次结构还和仿真结束机制有关。run_phase中通常由test来raise_objection和drop_objection因为test是树的顶级节点它的运行时间覆盖了所有子组件的运行时间。如果objection在叶子节点比如某个driver里被raise但test已经先drop了仿真就会提前结束后面的sequence还没跑完。反过来如果一直raise不drop仿真会卡住不退出。项目里我见过一种场景test的run_phase里有fork分支其中一个分支在等某个事件事件永远没来drop_objection永远执行不到仿真就一直挂着。这虽然不是树形结构本身的问题但树的层级决定了objection的合理管理位置理解这一点能帮你快速判断该在哪里管理objection。5.5 最终pass/fail的醒目标志很多团队有需求仿真跑到最后不管测试通过还是失败都要在终端显示非常醒目的PASS/FAIL字样方便一键检查回归结果。这可以结合UVM的report机制和树的层级关系来实现。一个简单的做法是在test的report_phase里判断uvm_report_server的error计数function void report_phase(uvm_phase phase); uvm_report_server srv; srv uvm_report_server::get_server(); super.report_phase(phase); if (srv.get_severity_count(UVM_FATAL) 0 || srv.get_severity_count(UVM_ERROR) 0) begin $display(####################################################); $display(# #); $display(# ** TEST FAILED ** #); $display(# #); $display(####################################################); end else begin $display(####################################################); $display(# #); $display(# ** TEST PASSED ** #); $display(# #); $display(####################################################); end endfunction这个函数挂在test类里利用report_server统计整棵树跑完后的error数量然后打印醒目的特效字符。因为tree的顶层test在最后执行report_phase所以能汇总所有子组件上报的错误。6. 树的遍历与面试视角从“会用”到“懂原理”6.1 如何从树上抓取指定组件UVM提供了从当前节点出发按路径查找子组件的接口最常用的是uvm_component::get_child、uvm_top.find和uvm_component::get_next_child。这些接口可以用来动态获取树上的任意节点。uvm_component comp; comp uvm_top.find(uvm_test_top.env.agt.drv); if (comp ! null) uvm_info(TREE, $sformatf(Found component: %s, comp.get_full_name()), UVM_LOW)这个能力在写通用VIP、封装可重用组件时特别有用比如你写一个通用的协议检查器想自动拿到所有agent里的monitor就可以遍历某个父节点下的所有孩子判断每个孩子属于什么类型再统一做处理。6.2 树的遍历API能干什么常见的树遍历场景有遍历某个env下所有agent从env节点出发遍历每个child判断类型是否为agent。遍历所有monitor并打印状态调试复杂环境时快速了解每个monitor当前有没有收到transaction。在顶层汇总各个scoreboard的统计信息通过遍历加句柄转换把各个子scoreboard的计数汇总打印。UVM迭代子节点的标准写法是uvm_component child; for (int i 0; i this.get_num_children(); i) begin child this.get_child(i); // do something with child end或者用get_next_child配合get_first_child遍历二者在功能上等价看个人习惯。6.3 面试里关于树形结构的几个高频问题UVM面试中树形结构几乎是必考方向围绕它衍生的问题通常有下面几类第一类uvm_component和uvm_object的区别是什么为什么driver要继承componenttransaction要继承object这个问题的本质就是考你是否理解树的节点和普通对象的区别。第二类build_phase和connect_phase的执行顺序是怎样的为什么build自上而下、connect自下而上这背后是父节点创建子节点、然后所有节点就绪后再做连接的逻辑。第三类config_db的路径怎么写才能确保匹配这个问题直接考你对树路径和层次命名的理解。关键点就在于路径必须与树上实际存在的一条从源头到目标的路径完全一致。第四类如何在树中查找一个组件通常会说uvm_top.find(uvm_test_top.env.agt.drv)然后展开讲讲$sformatf动态拼接路径的应用场景。第五类如果想把某个agent的monitor类型从普通monitor替换成带协议检查的monitor用factory机制怎么实现这里考的就是类型覆盖而类型覆盖是否生效又依赖于组件创建是否通过factory。而factory的注册本质上就是让这个类在“树的建设调度体系”中登记在册。这些面试题单看都不难但串起来理解背后就是在考你对UVM树形结构的整体认知是否扎实。7. 树形结构与寄存器模型的挂载关系7.1 寄存器模型在树上的位置寄存器模型UVM RAL在UVM环境里不是一个普通组件但它也需要跟树上的节点建立关系。通常的做法是在env的build_phase里创建register block然后在connect_phase或更高层把register block的adapter和predictor挂到agent的sequencer和monitor上。寄存器模型本身继承自uvm_object不占树的节点。但它需要知道树节点的句柄才能完成读写操作。例如class my_env extends uvm_env; ... my_ral_block ral; function void build_phase(uvm_phase phase); super.build_phase(phase); ral my_ral_block::type_id::create(ral); ral.configure(null); ral.build(); endfunction function void connect_phase(uvm_phase phase); super.connect_phase(phase); ral.default_map.set_sequencer(agt.sqr, null); endfunction endclass这里的ral虽然是uvm_object但通过set_sequencer和agt.sqr建立了联系。理解了树的脉络你就明白为什么要从agt.sqr这个树节点去取sequencer句柄。寄存器模型的map在发起序列时靠的就是从树节点上拿到的sequencer句柄。7.2 镜像值寄存器模型与树的信息同步寄存器模型的镜像值mirrored value是UVM RAL里一个比较重要的概念。它指的是寄存器模型内部保存的寄存器值这个值是对真实硬件寄存器值的“软件镜像”。当我们通过寄存器模型执行read或write操作时模型会自动更新镜像值但如果我们直接通过总线访问了硬件寄存器模型里的镜像值并不会自动更新这时就需要调用mirror()或update()来同步。这个机制跟树的关联在于寄存器模型需要通过树节点上的sequencer发起总线transactionADC或DUT响应之后transaction再通过monitor和predictor返回给寄存器模型。整条数据通路都依赖树形结构中各组件的正确连接。如果树上某个连接断开predictor就收不到transaction镜像值就会失步后续读回来的值和真实硬件状态对不上。这个知识点是寄存器模型面试中的高频点。搞懂树形结构和寄存器模型挂载的关系你才算真正把UVM的组件体系和寄存器访问体系串起来了。8. 实战心得树形结构设计上的几个建议搭建UVM环境时树的划分其实是一个架构决策直接决定后期维护成本。我的经验是树的层级不要过多过深通常推荐“test - env - agent - driver/sqr/monitor”再加一个scoreboard就足够了。模块化越清晰后续重用的可能性越大。关于命名建议使用统一的命名规范比如顶层test统一叫uvm_test_top这是UVM默认的test名字如果在一个testbench里run多个test可以用UVM_TESTNAME来指定test类但树的顶层名字还是uvm_test_topenv下面叫envagent根据接口方向叫in_agent、out_agent。这样最后打印出来的拓扑结构非常可读团队协作时排查问题也方便。另外一个容易被忽略的点是树的层级配置要尽早固定不要在开发后期大改。因为很多config_db路径、factory覆盖路径、甚至脚本里的正则匹配路径都依赖树的层次。改动一个中间节点的名字可能牵一发而动全身所有依赖旧路径的配置全部失效。如果你在搭建环境过程中发现某个组件的连接总是出错不妨先打印一下当前拓扑对比预期结构大多数问题都是树的结构不符合预期。这个习惯能让你省下大量debug时间。我在实际项目的体会是UVM的树形结构理解得越透搭建环境时的心理负担就越小。很多初学者纠结的“这个组件该放哪”“这里该用new还是create”“为什么我get到的配置是null”归根结底都是对树的构建过程不够熟悉。把这棵树彻底弄明白之后UVM里其他机制都是围绕这棵树的扩展学习起来自然顺畅很多。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Pixelmon宝可梦服务器开荒全指南:从环境配置到ZA进化石福利领取 2026/9/8 12:21:26

Pixelmon宝可梦服务器开荒全指南:从环境配置到ZA进化石福利领取

每当“新服开服”“开荒”这类消息出现在《我的世界》玩家群里,总能在短时间内聚集大量讨论。对很多玩家来说,宝可梦主题服务器早已不是小众玩法:Pixelmon 模组把宝可梦的捕捉、战斗、养成系统搬进方块世界,结合原版生存、建造和红…

阅读更多 →
端侧AI芯片选型:别被TOPS忽悠,有效算力才是硬道理 2026/9/8 12:21:26

端侧AI芯片选型:别被TOPS忽悠,有效算力才是硬道理

带过几届机器人竞赛队伍,也替不少创业团队做过车载和机载AI方案的选型评估,我发现一个特别常见的现象:大家一上来就看算力芯片的TOPS数字,谁大选谁,结果模型一跑起来,发热、掉帧、延迟抖动全来了&#xff0…

阅读更多 →
图像处理项目部署指南:从环境搭建到API集成实战 2026/9/8 12:21:26

图像处理项目部署指南:从环境搭建到API集成实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
AI批量采购二手书?从异常订单检测到Python风控实战 2026/9/8 12:21:26

AI批量采购二手书?从异常订单检测到Python风控实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
老平台MCU采购必读:控制节拍核对,以DF72115D160FPV为例 2026/9/8 12:21:26

老平台MCU采购必读:控制节拍核对,以DF72115D160FPV为例

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
MODBUS RTU协议详解:帧格式、CRC校验与调试实战笔记 2026/9/8 12:18:26

MODBUS RTU协议详解:帧格式、CRC校验与调试实战笔记

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞