Verilog generate语法详解:从重复例化到条件生成的工程实践
1. 为什么generate语法值得单独拎出来讲写Verilog写久了你迟早会碰到一类场景模块里需要例化几十个甚至上百个结构完全相同的子模块或者需要根据参数配置决定到底生成哪一套逻辑。手写例化复制粘贴到怀疑人生改一个参数要改几十处漏一处就是隐蔽的连线错误。这时候generate就是把你从重复劳动里捞出来的那根绳子。generate是Verilog-2001引入的语法结构核心作用是在编译/综合阶段根据条件或循环动态地展开硬件结构。注意关键词——编译阶段。它不是运行时行为不是软件里的for循环而是让综合工具在解析代码时帮你批量生成实例、批量连线、批量赋值。理解这一点后面所有的坑你都能提前避开。这篇文章我打算把generate的两大分支generate for和generate if/case拆开揉碎讲清楚包括它们各自的适用场景、参数计算、实操写法以及我在实际项目里踩过的那些坑。适合已经能写基本Verilog模块、但对generate总是用不利索的读者也适合想搞清楚为什么综合出来和我预期不一样的老手。全文基于常见的FPGA/ASIC设计实践展开工具行为以主流综合器的一般表现为准。2. generate到底解决什么问题从需求倒推语法设计2.1 重复例化的痛点与generate的定位先看一个最朴素的场景。你要做一个8位的行波进位加法器最直接的办法是例化8个全加器每个的进位输出接下一个的进位输入。手写大概是这样full_adder u0 (.a(a[0]), .b(b[0]), .cin(1b0), .sum(sum[0]), .cout(c0)); full_adder u1 (.a(a[1]), .b(b[1]), .cin(c0), .sum(sum[1]), .cout(c1)); // ... 一直写到u78个还能忍32个呢128个呢而且位宽一旦从参数化改成固定值代码就失去了复用性。generate for就是干这个的把上面这段重复结构用一个循环表达出来让工具帮你展开。genvar i; generate for (i 0; i 8; i i 1) begin : adder_chain full_adder u ( .a(a[i]), .b(b[i]), .cin(i 0 ? 1b0 : cout[i-1]), .sum(sum[i]), .cout(cout[i]) ); end endgenerate这里有个关键点genvar是专门用于generate循环的索引变量它和普通的integer不一样。genvar在综合时是常量每个循环迭代对应一份独立的硬件而integer如果用在always块里是运行时的变量。混用这两个是新手最常见的错误之一。2.2 条件生成让一份代码适配多种配置另一个高频需求是同一份RTL不同配置下综合出不同结构。比如一个FIFO模块深度是2的幂时可以只用位截断做指针回绕深度不是2的幂时就得用比较器。这种差异如果靠ifdef宏来处理代码会变得很难维护而且宏是在预处理阶段展开的调试起来很痛苦。generate if提供了一种更干净的方式用参数parameter作为条件在综合时决定生成哪套逻辑。parameter DEPTH 16; localparam IS_POW2 (DEPTH (DEPTH - 1)) 0; generate if (IS_POW2) begin : ptr_wrap assign wr_ptr_next wr_ptr 1b1; // 位宽自然回绕 end else begin : ptr_cmp assign wr_ptr_next (wr_ptr DEPTH - 1) ? 0 : wr_ptr 1b1; end endgenerate这段代码的价值在于条件在综合时求值不满足的分支根本不会生成硬件。这跟always块里的if完全不同——always里的if会生成多路选择器两边的逻辑都会存在。这个区别是理解generate的核心我在2.4节会专门展开。2.3 generate、always、assign三者的边界很多人搞不清什么时候该用generate什么时候该用always。我的经验判断标准很简单需要例化模块、批量声明信号、根据参数选择结构——用generate。需要对信号做时序或组合逻辑运算——用always。需要简单的连续赋值——用assign。generate本身不描述逻辑行为它描述的是结构如何组织。你可以在generate里放assign、放always块、放模块例化但generate自己不做运算。把它理解成代码层面的模板引擎更准确。2.4 一个必须澄清的误区generate if不等于always if这是我在带新人时反复强调的一点。看下面两段代码// 写法Agenerate if generate if (MODE 1) begin assign y a b; end else begin assign y a | b; end endgenerate // 写法Balways if always (*) begin if (MODE 1) y a b; else y a | b; end如果MODE是parameter写法A在综合时只会生成一个与门或一个或门另一套逻辑不存在。写法B则会生成一个与门、一个或门再加一个选择器因为always块里的if是行为描述工具会保守地认为两个分支都可能被执行。面积、功耗、时序全都不同。这个差异在资源紧张的设计里可能是致命的所以条件生成结构时优先考虑generate。3. generate for的实操细节与参数计算3.1 genvar的声明位置与作用域genvar可以声明在generate块内部也可以声明在外部。两种写法有细微差别// 写法一外部声明 genvar i; generate for (i 0; i N; i i 1) begin : loop_a // ... end endgenerate // 写法二内部声明 generate for (genvar j 0; j N; j j 1) begin : loop_b // ... end endgenerate写法二更干净j的作用域被限制在循环内不会污染模块命名空间。我现在的习惯是一律用写法二除非需要在多个generate块之间共享同一个索引这种情况很少见。注意写法二在较老的工具上可能不支持如果你的综合器版本比较旧先用写法一验证。3.2 begin-end块必须命名这不是可选项generate for的循环体必须用begin : label命名这个label不是给你看着舒服的它会成为生成实例的层次名的一部分。比如generate for (genvar i 0; i 4; i i 1) begin : chan fifo u_fifo (.clk(clk), .rst(rst), .din(din[i]), .dout(dout[i])); end endgenerate综合后你会看到四个实例层次名分别是chan[0].u_fifo、chan[1].u_fifo、chan[2].u_fifo、chan[3].u_fifo。这个命名规则在仿真波形调试、时序约束、后仿网表定位时都极其重要。如果你不命名很多工具会直接报错或者生成难以理解的默认名字。提示label命名尽量用有意义的名字比如chan、stage、bank不要用loop、gen这种无信息量的词。后期调试时你会感谢自己。3.3 循环边界与位宽计算generate for的循环次数必须是编译时常量。这意味着边界表达式里只能出现parameter、localparam、常量不能出现信号。下面这种写法是非法的// 错误N是输入信号 generate for (genvar i 0; i N; i i 1) begin : bad // ... end endgenerate如果确实需要根据运行时信号决定结构那说明你的设计思路有问题——硬件结构在综合后就固定了不可能运行时改变。这种情况应该用多路选择器或使能信号来实现。位宽计算是另一个容易翻车的地方。假设你要生成一个N级的移位寄存器索引i的位宽要能表示到N-1。如果N是parameteri的位宽由工具自动推断一般没问题。但如果你在循环体里做i1、i-1这类运算要小心边界generate for (genvar i 0; i N; i i 1) begin : shift if (i 0) assign sr[i] din; else assign sr[i] sr[i-1]; end endgenerate这里i 0的判断在综合时求值第一级直接接din其余级接前一级。这种首级特殊处理的模式在generate for里非常常见写的时候要确保边界条件覆盖完整。3.4 嵌套generate for的处理多层循环也是支持的比如生成一个二维的PE阵列generate for (genvar r 0; r ROWS; r r 1) begin : row for (genvar c 0; c COLS; c c 1) begin : col pe u_pe ( .clk(clk), .in_n(in[r][c]), .in_w(in[r][c]), .out(out[r][c]) ); end end endgenerate层次名会变成row[0].col[0].u_pe这样的形式。嵌套层数不建议超过两层超过两层说明你的结构可能该拆成子模块了否则代码可读性会急剧下降。4. generate if/case的条件生成实战4.1 generate if的典型应用可选功能模块假设你设计一个数据通路其中CRC校验模块是可选的由parameter控制parameter ENABLE_CRC 1; generate if (ENABLE_CRC) begin : gen_crc crc8 u_crc ( .clk(clk), .rst(rst), .data(data_in), .crc(crc_out) ); assign data_out data_in ^ {8{crc_out[0]}}; end else begin : gen_no_crc assign data_out data_in; end endgenerate当ENABLE_CRC 0时crc8模块根本不会被例化综合工具不会为它分配任何资源。这比用ifdef宏优雅得多因为参数可以在例化上层模块时覆盖而宏需要改代码或改编译选项。4.2 generate case多配置分支的清晰表达当配置选项超过两个时generate case比嵌套if更清晰parameter MODE 2; generate case (MODE) 0: begin : mode_bypass assign result data_in; end 1: begin : mode_add assign result a b; end 2: begin : mode_mul assign result a * b; end default: begin : mode_default assign result 0; end endcase endgeneratedefault分支一定要写否则当MODE取到未定义值时工具行为不确定有的报错有的静默生成空逻辑。这个习惯能帮你省掉很多调试时间。4.3 条件生成中的信号声明generate块内部可以声明局部信号这些信号的作用域限于该generate块generate if (WIDTH 8) begin : wide_path wire [WIDTH-1:0] temp; assign temp data_a data_b; assign result temp; end else begin : narrow_path wire [7:0] temp; assign temp data_a[7:0] data_b[7:0]; assign result {8b0, temp}; end endgenerate两个分支里的temp互不干扰各自独立。这一点比用ifdef好宏展开时同名信号会冲突而generate块天然隔离。4.4 条件生成与参数传递的配合generate if经常和参数传递一起用比如根据位宽选择不同的子模块实现generate if (WIDTH 16) begin : small_impl adder_small #(.W(WIDTH)) u_add (.a(a), .b(b), .sum(sum)); end else begin : large_impl adder_large #(.W(WIDTH)) u_add (.a(a), .b(b), .sum(sum)); end endgenerate注意两个分支里例化的模块名可以相同都叫u_add因为它们在不同的generate块里层次名不同small_impl.u_add和large_impl.u_add不会冲突。这个特性在写可配置IP时非常有用。5. 常见问题与排查技巧实录5.1 综合报错generate loop variable not constant这个错误几乎每个用generate的人都遇到过。原因通常是循环边界用了非编译时常量。检查三点边界表达式里有没有信号parameter有没有在例化时正确传递localparam的计算有没有依赖信号。我遇到过一次是localparam N WIDTH / 8;而WIDTH在某个分支里被赋成了信号导致整条链都不是常量。5.2 仿真波形里找不到生成的实例如果你在波形窗口里看不到generate生成的信号八成是层次名没找对。generate生成的实例层次名包含label和索引比如chan[2].u_fifo.data。在波形工具里展开层次树时注意方括号索引。有些工具需要你在添加信号时手动输入完整路径。5.3 generate for里例化模块的端口连接错误批量例化时最容易犯的错是索引越界或连线错位。比如generate for (genvar i 0; i 8; i i 1) begin : stage sub u_sub ( .in(data[i]), // 正确 .out(data[i1]) // 当i7时越界 ); end endgeneratedata[i1]在最后一次迭代时索引为8如果data只有8位0到7就越界了。这种错误在综合时可能只报warning但生成的硬件是错的。我的习惯是在循环体里对边界索引做显式处理或者把数组多声明一位作为哨兵。5.4 generate块内的always块敏感列表在generate里放always块是允许的但敏感列表要写完整generate for (genvar i 0; i N; i i 1) begin : regs always (posedge clk or negedge rst_n) begin if (!rst_n) q[i] 1b0; else q[i] d[i]; end end endgenerate这里每个迭代生成一个独立的触发器敏感列表在每次迭代里都一样。注意不要图省事写成always (*)时序逻辑必须用边沿敏感。5.5 常见问题速查表问题现象可能原因排查方向报错loop variable not constant边界含信号或未传递的parameter检查parameter传递链综合面积远超预期误用always if代替generate if确认条件在综合时求值波形找不到信号层次名含label和索引展开层次树查找例化端口连线错位索引越界或数组位宽不匹配检查循环边界和数组声明生成实例名冲突多个generate块用了相同label确保label唯一仿真结果与预期不符generate分支选错打印parameter值确认注意不同综合器对generate的支持程度有差异尤其是嵌套generate和generate内的局部信号声明。换工具时先用小例子验证别直接上大工程。6. 几个让我印象深刻的实操案例6.1 用generate for重构一个32路选择器早期我写过一个32选1的MUX手写了32行case语句后来要改成64选1改得头大。用generate for重构后generate for (genvar i 0; i 32; i i 1) begin : mux_gen assign mux_out (sel i) ? data[i] : mux_out; end endgenerate等等这个写法有问题——mux_out被多次驱动了。正确的做法是用中间信号做链式选择或者用OR树wire [31:0] sel_onehot; generate for (genvar i 0; i 32; i i 1) begin : mux_gen assign sel_onehot[i] (sel i); end endgenerate assign mux_out |(data sel_onehot);这个案例的教训是generate for生成的是并行结构不是顺序执行。每次迭代里的assign是同时生效的不能像软件循环那样累加。理解这一点能避免很多为什么结果不对的困惑。6.2 参数化位宽的桶形移位器桶形移位器是generate for的经典应用。一个N位、支持log2(N)级移位的桶形移位器parameter WIDTH 16; localparam STAGES $clog2(WIDTH); wire [WIDTH-1:0] stage_data [0:STAGES]; assign stage_data[0] data_in; generate for (genvar s 0; s STAGES; s s 1) begin : barrel localparam SHIFT 1 s; assign stage_data[s1] shift_en[s] ? {stage_data[s][WIDTH-SHIFT-1:0], {SHIFT{1b0}}} : stage_data[s]; end endgenerate assign data_out stage_data[STAGES];这里SHIFT是localparam在每次迭代里根据s计算是编译时常量。$clog2是系统函数返回以2为底的对数向上取整也是编译时求值。这个结构综合出来是log2(N)级MUX级联面积和延迟都是最优的。6.3 用generate if做位宽自适应有个项目需要在同一份RTL里支持8位和16位两种数据通路我最初想用ifdef后来改成generate ifparameter DATA_WIDTH 16; generate if (DATA_WIDTH 8) begin : dw8 assign packed {8b0, data_in}; assign data_out packed[7:0]; end else if (DATA_WIDTH 16) begin : dw16 assign packed data_in; assign data_out packed; end else begin : dw_unsupported // 综合时如果走到这里会报错 initial $error(Unsupported DATA_WIDTH); end endgenerate$error在综合时触发能提前暴露配置错误。这个技巧在写可配置IP时很实用比让工具静默生成错误逻辑强得多。7. 写generate时我总结的几条硬规矩第一条能用generate for就别手写重复例化。手写不仅累而且改参数时容易漏。generate for的循环边界用parameter改一个数字全搞定。第二条条件生成结构时优先用generate if别用always if。前者不生成多余硬件后者会。这个区别在资源敏感的设计里是决定性的。第三条begin-end块必须命名名字要有意义。层次名是你调试时的路标别给自己挖坑。第四条循环边界和索引运算要反复检查边界条件。i1、i-1、i0这些地方是bug高发区写完先跑一遍仿真确认。第五条generate块内的信号声明注意作用域。不同分支的同名信号互不干扰这是优点但也要注意别在块外误引用块内的信号。第六条换综合工具时先验证generate的支持情况。嵌套层数、局部信号声明、genvar声明位置这些细节不同工具行为可能不同。最后分享一个我常用的调试技巧在generate块里加initial $display打印迭代信息仿真时能看到每次迭代的参数值确认分支选择是否正确。综合时这些语句会被忽略不影响硬件。这个办法帮我定位过好几次分支选错的问题比盯着代码猜快得多。generate这个语法说穿了就是把重复和条件这两件事从手写变成工具帮你做。但工具是死的它只按你写的条件展开不会帮你检查逻辑对不对。所以写完generate仿真验证这一步绝对不能省。我见过太多人觉得generate嘛工具自动生成的肯定没错结果上板子发现数据全错回头查半天是索引差了一位。硬件设计里没有应该没问题只有验证过没问题。

相关新闻

C++类设计实战:从Point到Rectangle,避开组合与值语义的坑

C++类设计实战:从Point到Rectangle,避开组合与值语义的坑

1. 从坐标点到矩形:一个看似简单的类设计练习,藏着多少坑"C创建Point类表示坐标点,点构线线构矩形"——这个标题看起来像是课本上的一道课后习题,但我跟你讲,真动手写起来,能踩的坑远比想象中多。…

2026/10/15 20:06:30 阅读更多 →
Apollo 11.0:面向车规级量产的自动驾驶仿真与通信架构解析

Apollo 11.0:面向车规级量产的自动驾驶仿真与通信架构解析

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

2026/10/15 20:05:29 阅读更多 →
Spring Boot JPA动态SQL五大方案实战对比

Spring Boot JPA动态SQL五大方案实战对比

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

2026/10/15 20:05:29 阅读更多 →

最新新闻

2025年度广东省网络空间安全协会科技进步奖揭晓,itc保伦股份产学研项目获评二等奖

2025年度广东省网络空间安全协会科技进步奖揭晓,itc保伦股份产学研项目获评二等奖

近日,广东省网络空间安全协会2025年度科学技术奖评选结果正式揭晓,广东保伦电子股份有限公司(以下简称“itc保伦股份”)联合华南理工大学申报的“智能超高清视讯系统关键技术研究与应用”项目,荣获科技进步奖二等奖。该…

2026/10/15 20:51:51 阅读更多 →
Vugu 2.x DOM 事件处理实战:基于 Go+WebAssembly 的 `dom-events` 示例深度解析

Vugu 2.x DOM 事件处理实战:基于 Go+WebAssembly 的 `dom-events` 示例深度解析

前端Web框架 【免费下载链接】vugu Vugu: A modern UI library for GoWebAssembly (experimental) 项目地址: https://gitcode.com/gh_mirrors/vu/vugu 点击查看 免费下载 导读 本文以 Vugu 2.x 仓库中的 dom-events 示例 为骨架,系统讲解在 Vugu&…

2026/10/15 20:51:51 阅读更多 →
Guzzle 3 HTTP CachePlugin 深度实战:透明代理缓存的配置、再验证与扩展机制详解

Guzzle 3 HTTP CachePlugin 深度实战:透明代理缓存的配置、再验证与扩展机制详解

后端电商前端 【免费下载链接】CRMEB 🔥CRMEB开源商城免费开源多语言商城系统,Tp6框架商城,系统可商用;包含小程序商城、H5商城、公众号商城、PC商城、App,支持分销、拼团、砍价、秒杀、优惠券、积分、会员等级、小程序…

2026/10/15 20:51:51 阅读更多 →
长沙智能体培训机构怎么选不踩坑 四步筛选法

长沙智能体培训机构怎么选不踩坑 四步筛选法

摘要本文梳理长沙智能体培训市场的常见坑点,给出四步筛选法与逐项核验标准,覆盖资质、课程项目、师资、合同收费四个核心环节,帮助学习者系统规避无资质办学、低价引流、虚假项目、隐形消费等风险。一、认知背景:市场鱼龙混杂&…

2026/10/15 20:51:51 阅读更多 →
Firebase JS SDK 的 TotpMultiFactorAssertion:TOTP 第二因子认证断言的注册与登录实战解析

Firebase JS SDK 的 TotpMultiFactorAssertion:TOTP 第二因子认证断言的注册与登录实战解析

开发工具后端 【免费下载链接】firebase-js-sdk Firebase Javascript SDK 项目地址: https://gitcode.com/gh_mirrors/fi/firebase-js-sdk 点击查看 免费下载 本文以 Firebase JavaScript SDK 官方 API 参考文档 auth.totpmultifactorassertion.md 为骨架&#xff…

2026/10/15 20:51:51 阅读更多 →
ThinkJS 2.x 分页组件 think-pagination 使用指南:从 countSelect 到模板渲染的完整实践

ThinkJS 2.x 分页组件 think-pagination 使用指南:从 countSelect 到模板渲染的完整实践

后端Web框架 【免费下载链接】thinkjs Use full ES2015 features to develop Node.js applications, Support TypeScript. 项目地址: https://gitcode.com/gh_mirrors/thi/thinkjs 点击查看 免费下载 本指南以 packages/think-pagination/README_v2.md 为骨架&…

2026/10/15 20:50:49 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/15 19:41:51 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/15 9:49:14 阅读更多 →