RTL实战:从零用Verilog/SystemVerilog构建GPU SIMT核心

手头有一块FPGA开发板,又想折腾点比流水线CPU更有挑战的硬件设计,那NEMA_p这个项目绝对值得你花上几个月好好钻研。它是一份用Verilog/SystemVerilog从零实现GPU核心的开源RTL工程,目标不是做一个能跑图形学算法的渲染器,而是把NVIDIA风格的SIMT执行模型完整落到可综合的硬件里。我在这个项目上前后折腾了小半年,从指令集梳理到最终在仿真环境里跑通并行归约程序,踩坑无数,也把GPU底层的门道摸了个七七八八。这篇文章把我自己的理解、拆解思路和实操记录完整写出来,给同样想啃这块硬骨头的朋友一份参考。

1. 项目到底在做什么:为什么有人要在RTL里造GPU

1.1 这不是CPU,是SIMT执行模型

很多人第一次看到NEMA_p的代码仓库时会愣一下:为什么指令集看起来像汇编,但硬件结构跟常规的RISC-V流水线完全不同?原因在于它的执行模型不是单指令流单数据流,而是单指令多线程,也就是SIMT。这个模型的核心思想是一条指令同时驱动多个线程,每个线程有自己的寄存器和私有状态,但取指和调度是统一进行的。

对比一下你可能更直观:普通CPU里面一个PC寄存器、一组通用寄存器,一条指令操作一份数据。SIMT GPU里面,32个线程组成一个warp,一个warp共享同一个PC,硬件上每次取出的一条指令会广播给这32个线程,让它们各自用自己寄存器里的数据执行。这就是所谓的"锁步"执行。NEMA_p顺了这个思路,把一条指令的宽度设计成包含线程掩码,硬件在发射指令时带上当前活跃线程的掩码,只有掩码为1的线程才真正写回结果。

这种架构带来最直接的挑战就是:硬件里不能只有一个寄存器堆,得分32个bank;写回的时候不能用同一个写端口,得做逐线程的写使能控制;分支指令执行时也不是简单跳转,得考虑哪些线程走true路径、哪些走false路径,并且最终要让所有线程重新收敛。这些细节在NAMA_p的RTL代码里都有对应模块,后面我会展开讲。

1.2 复刻NVIDIA架构的切入点在哪

NEМА_p这个项目有意思的地方在于,它不是凭空发明一套新架构,而是尽量仿照真实GPU的行为来设计。具体来说,它的架构里能看到这些熟悉的概念:

  • 类似CUDA的kernel执行模型:主机端把一段代码编译成GPU指令,然后以kernel的形式加载到设备端执行。NEMA_p在接口上支持把编译后的指令镜像载入显存并启动执行。
  • 类似PTX的虚拟指令集:指令类型包括整数运算、逻辑操作、比较分支、全局内存读写、共享内存访问等,跟实际GPU的ISA分类很接近。
  • 类似流多处理器的核心结构:整个RTL顶层是一个大的SM核心,内部包含warp调度器、执行流水线、寄存器堆、共享内存以及访存接口。

我觉得它选择这些切入点的原因非常实际:如果只做单个ALU的运算器,那跟CPU没什么区别;如果要做完整的GPU集群、多SM互联、缓存一致性,那工程量和验证复杂度根本不是个人能扛住的。NEMA_p的定位刚好卡在中间——用一个SM加上必要的访存通路,把SIMT的执行本质讲清楚。这个取舍非常适合学习者,你理解了这一个SM,后面再去看真实GPU的多SM架构,很多东西就是按比例放大而已。

1.3 为什么选Verilog/SystemVerilog而不是HLS

关于实现语言,我个人的看法是:SystemVerilog是这个项目最合理的选择。虽然现在用Vivado HLS或者更高级的Chisel也能做硬件设计,但对于这种核心逻辑需要精确控制时序和资源的项目,RTL级别的SystemVerilog优势很明显。

SystemVerilog相比老式Verilog多了几个特别香的功能:interface和modport让模块间连接变得清晰;always_ff和always_comb让时序逻辑和组合逻辑一眼就能区分;enum、struct、package让状态机和数据通路可读性大幅提升。NEMA_p的代码正是充分利用了这些特性。我自己在复现过程中也体会到,如果一个模块做warp调度、一个模块做译码、一个模块做SIMT栈,用传统Verilog的端口列表会非常痛苦,而用interface定义好握手协议之后,整个顶层看起来就像一张清晰的连接图。

另外还要泼一盆冷水:这项目不适合用纯HLS来做。HLS适合做计算密集型的流水线,但GPU核心里有大量不规则的控制逻辑,比如分支收敛栈、线程调度仲裁、多bank寄存器堆的冲突处理,这些用HLS描述起来反而更费劲,而且综合出的时序往往不如手写RTL可控。

2. 架构拆解:指令集、Warp调度与五级流水线

2.1 指令集长什么样

拿到NEMA_p的RTL代码后,我第一件事是找它的指令集文档。这份指令集的设计很克制,没有一上来就搞浮点,而是把整数和访存指令做扎实。基本可以分成四类。

第一类是ALU运算指令,包括整数加减、按位逻辑、移位、比较。每条指令通常采用三操作数格式,目标寄存器、源寄存器1、源寄存器2或者立即数。第二类是访存指令,支持从全局内存加载到寄存器、从寄存器写回全局内存、在共享内存和寄存器之间搬数据。第三类是控制流指令,包括无条件跳转、条件分支以及与分支收敛配套的特殊指令。第四类是同步指令,用于线程间的执行屏障,这个在并行归约类算法里至关重要。

指令编码上让我印象最深的是它的显式线程掩码设计。一条分支指令在译码阶段之后,硬件会把掩码更新进SIMT栈,保证执行流正确汇聚。这种小小的设计改动,把SIMT的控制流语义向前推了一大步。

2.2 Warp调度器:让多个warp共用一条流水线

NEMA_p内部支持多个warp,而不是只做一个warp的玩具。这意味着硬件上得有多个PC、多个寄存器堆上下文、多组活动掩码。warp调度器的作用,就是每个周期从这些warp里选一个,把它的下一条指令送到流水线里去执行。

调度策略用的是最简单的轮转方式。每个周期计数器轮一遍,选中某个warp后检查它的状态:如果有正在等待的内存请求,或者有指令在流水线里占用资源没返回,就跳过。这种方式实现简单,配合上scoreboard机制,就能避免乱序执行带来的复杂依赖追踪。所以这个调度器的RTL代码其实不复杂,核心是一个状态寄存器和一组ready信号仲裁逻辑。

我当时一开始不理解为什么不用优先级调度,后来测试了不同负载才明白:对于这种小规模核心,轮转调度已经能保证足够的吞吐,而且逻辑简单不容易出时序问题。如果真按优先级做,某些warp可能被饿死,反而需要更复杂的公平性策略。

2.3 五级流水线各阶段都在干什么

NEMA_p的处理器核心采用经典的五级流水线结构:取指、译码、执行、访存、写回,但这个五级流水线在SIMT语境下跟CPU有显著区别。

取指阶段每周期从指令缓存里取出一条128位左右的长指令,包含操作码、目标寄存器索引、两个源寄存器索引和立即数,以及预留给掩码控制的数据位。译码阶段做的事比CPU多一点,它要解析出线程掩码、识别操作数来自寄存器堆还是立即数、判断访存指令的目标地址空间。执行阶段处理ALU运算,同时计算访存的地址。访存阶段真正访问存储层次,可能是共享内存也可能是全局内存。写回阶段把结果写回各个线程的寄存器。

这里要特别注意寄存器堆的读写问题。32个线程同时执行一条指令,译码阶段需要同时读出64份源操作数,写回阶段又要保证32份结果能正确落到各自的物理寄存器。实际操作中,寄存器堆有多个读端口,通过线程索引选择一个bank读取对应数据。NEMA_p在黄金时间上做了简化——没有做复杂的bank冲突检测,而是让每个线程的寄存器索引和线程号共同决定物理bank,避免很多冲突场景。

3. 关键RTL模块的实现细节与心得

3.1 顶层接口定义:interface带来的整洁性

我在仿写顶层模块时,最先感受到SystemVerilog interface的威力。比如warp调度器和译码器之间的接口,如果按传统方式声明,端口列表会有十几个信号:请求有效、warp编号、指令数据、指令地址、ready信号等。但用interface封装之后,主模块的连接代码可以大大精简。

interface decode_if import types_pkg::*; logic valid; logic [WARP_ID_WIDTH-1:0] warp_id; inst_t inst; logic ready; modport decoder(input valid, input inst, output ready, input warp_id); modport fetcher(output valid, output inst, output warp_id, input ready); endinterface

这个interface配合modport,让两个模块之间的角色非常清晰。取指侧是发起方,要输出valid和指令数据;译码侧是接收方,执行完当前指令后拉高ready。我在调仿真时发现一个很大的好处——把interface作为一个整体加进波形图,一眼就能看到一整组握手信号的时序关系,比一个一个信号去对要高效得多。

3.2 译码器与操作数收集

译码模块核心是一个大组合逻辑:根据opcode和func字段确定执行单元的微操作。这里有一个很值得借鉴的设计:译码结果不是单一的control signal,而是一个译码后的数据结构体。

always_comb begin decoded_ctrl = '0; unique case (instr.opcode) OP_IADD: begin decoded_ctrl.alu_op = ALU_ADD; decoded_ctrl.wb_en = 1'b1; decoded_ctrl.reg_dst = instr.rd; decoded_ctrl.src1_sel = SEL_REG; decoded_ctrl.src2_sel = instr.use_imm ? SEL_IMM : SEL_REG; end OP_LOAD: begin decoded_ctrl.mem_op = MEM_LOAD_GLOBAL; decoded_ctrl.wb_en = 1'b1; decoded_ctrl.dst_reg = instr.rd; decoded_ctrl.addr_sel = SEL_REG; end default: decoded_ctrl.illegal = 1'b1; endcase end

这里有个细节容易踩坑:组合逻辑里如果没有default分支,很容易综合出锁存器。SystemVerilog检查起来比Verilog严格,我最初写完仿真没报错,但综合时报了warning,正是因为这个。所以写always_comb的时候务必给每个输出都赋默认值,最好在case之前加一句decoded_ctrl = '0。

操作数收集阶段最需要注意的是立即数的符号扩展。NEMA_p的指令里某些立即数是负偏移量,如果按无符号方式扩展,算出来的地址就是错的。我调试时发现一段内存越界访问,查了好久才发现是符号扩展位少写了一位。这里强烈建议在testbench里专门加一组边界测试,验证立即数的最大正值和最小负值都能正确解析。

3.3 SIMT栈:分支收敛的关键

分支收敛是SIMT架构里最容易绕晕的部分。NVIDIA GPU真实的做法是维护一个内部栈,每条分支指令把当前掩码压栈,然后在分支目标处弹出并做掩码合成。NEMA_p用RTL显式实现了这个栈结构。

栈的每个条目记录三样东西:当前参与执行的线程掩码、分支目标地址、以及下一条要执行的地址。执行分支指令时,当前掩码中被选中走true路径的线程进入一个掩码集合,走false路径的进入另一个集合。硬件把其中一个集合的掩码和地址压栈,先执行另一个;执行完后从栈弹出继续。这个过程在软件里看是递归的,在硬件里用栈正好契合LIFO的特性。

实现SIMT栈时我用了一个固定深度的SRAM块来当栈存储,再配一个栈指针寄存器。这里有一个教训:如果栈深度设置不当,遇到深度嵌套的分支就会出现溢出,我甚至为了观察这个现象故意写过8层嵌套的if-else来压测。在验证阶段,这种边界条件测试比功能测试更容易暴露RTL缺陷,建议有兴趣复现的朋友把测试用例往多层分支上堆。

3.4 共享内存与存储层次设计

共享内存是整个GPU性能的关键之一,因为同一个block里的线程需要频繁交换数据。NEMA_p实现了类似真实GPU的shared memory,访问Unit在流水线的访存阶段。

共享内存的地址空间是独立编址的。访存指令里通过地址位来区分是访问shared还是global:如果最高位为1则命中shared,否则走全局内存接口。实现上这只是一个简单的地址译码逻辑,但它的意义在于明确了存储层次的含义——shared memory是每个block私有的,延迟低、不需要经过远端的DRAM接口。我在后仿真阶段测试过用shared memory做局部累加,和直接读写global memory相比,访存等待周期数降了一半以上。

我这块最想提醒的是缓存一致性和同步问题。多个warp同时访问shared memory的同一地址时,如果没有原子操作保护,结果可能不符合预期。NEMA_p在访存接口上支持原子操作信号,但使用时要非常小心,仿真环境下还好,一旦上了FPGA的时序约束,原子操作逻辑往往是最容易产生频率瓶颈的地方。

4. 仿真、调试与验证:实测下来的完整技术路线

4.1 测试平台搭建与最小启动流程

我搭建验证环境的思路是分两层:第一层是模块级定向测试,关注单个模块的功能;第二层是核心级集成测试,把整个流水线、调度器和存储连起来跑。

最小启动流程比较有代表性:先把指令镜像放进一个模拟的指令ROM里,然后释放复位,warp调度器开始逐周期取指,访存模块模拟从"显存"里读写数据。整个系统的终点是kernel执行完毕信号。第一次跑通这个流程时,那种感觉就像看着一台微型GPU真正跑起来。

Testbench里我坚持用接口来连接待测核心和存储模型,这样可以很方便地在不同的存储测试模型之间切换。比如先用一个简单的数组模拟全局内存验证功能,再用一个带延迟的模型验证scoreboard和等待逻辑。几个模型之间的替换只需要改顶层实例化,不用改动内部逻辑,SystemVerilog的灵活在这里体现得淋漓尽致。

4.2 用波形定位bug的几个实用技巧

调波形是RTL调试的日常,但GPU这种多线程并发结构让波形图极其庞大。我有几个常用技巧想分享。

第一,分组信号。把所有warp各自的PC、掩码和寄存器堆写使能信号编成一个group,在波形工具里折叠起来,重点只看顶层握手信号和流水线valid信号,出现异常时再逐层展开。第二,用断言定位。SystemVerilog断言在这里价值巨大,我对总线协议写了简单的assert来检查读数据返回时是否有对应的请求有效信号。一旦触发断言,波形会自动停在出错周期,不需要人工逐帧找。第三,善用$display加$time。在关键状态机里打印时间戳和状态转换,比起扑在波形里直观很多。

4.3 三个高频Bug:流水线冒险、死锁、时序违规

这几个坑我猜每个做这项目的人都会遇到,我把具体症状和处理思路记录出来。

首先是最典型的load-use冒险。访存指令需要多个周期返回数据,而紧随其后的ALU指令可能马上要用这条加载结果。如果调度器不做处理,取到的就是旧值。NEMA_p的处理方式是保留一个scoreboard表项,检测到目标寄存器还在等待写回时,就暂停这个warp的发射。我一开始以为这个逻辑只在访存时触发,后来发现共享内存的读取也有延迟,同样需要处理。

其次是死锁问题。当warp在等待一个永远不会回来的内存响应时,调度器会一直跳过它,其他warp也可能因为共享资源冲突卡住。这类问题的根因多半是仲裁逻辑不完善。我的排查思路是:让仿真加长运行时间,打印每个warp的PC和状态,找出哪个warp一直没进展。如果能定位到某个等待信号一直无效,就顺着这个信号的驱动往前查。

最后是时序违规。在FPGA上布局布线后,如果时钟频率设得太高,访存路径经常成为最差路径。解决方法通常是降频,或者给流水线中间加一拍转发寄存器。就我实测的经验,NEMA_p这类设计在100MHz以内跑是稳的,但往200MHz冲就需要很多时序优化手活了。

5. 我踩坑换来的几点实操体会

做NEMA_p的这几个月,我最大的感受是:GPU架构的核心秘密其实不在浮点运算单元有多快,而在调度和控制逻辑有多精密。SIMT模型看起来只是"一条指令多个线程",但真正落到RTL里,每个线程的掩码维护、栈收敛、寄存器堆多端口读写都是绕不开的硬骨头。

如果你也想从零动手复现这个项目,我建议按这个顺序推进:先啃透指令集定义和warp调度的顶层握手,再实现单warp的单条指令通路,确认能跑通最简单的加法和跳转用例;然后扩展多warp轮转调度和访存等待逻辑;最后才考虑分支收敛和共享内存优化。这个顺序能让你在每一步都有清晰的可验证目标,不至于一上来就被复杂的并发交互劝退。

工具链方面,仿真首选商业工具的学习版或者开源的仿真工具都可以。综合上FPGA的话,建议从低时钟频率起步,先把功能验证扎实,再慢慢优化时序。如果你手头有开发板,强烈建议把核心跑在板子上看看实际资源占用,那种把抽象架构烧进真实硬件、用逻辑分析仪观测warp调度信号的感觉,跟仿真完全是两码事。

另一点小建议:试着给自己设计一个稍微复杂一点的并行算法,比如向量点积或者直方图统计,把它编译成NEMA_p的指令并在RTL仿真里跑通。这远比跑几个单指令用例有成就感,也才能真正体会到shared memory的性能价值和同步指令的必要性。

我自己的下一步计划是在这个核心外面再接一个简单的总线接口,把多个SM实例化出来看看多核扩展会遇到什么问题。这个方向应该还能玩很久,也希望有更多人入坑一起聊RTL GPU设计的各种细节。

相关新闻

合同、报告、资料统一加抬头图,用这个 Word 批处理功能一次设置全搞定

合同、报告、资料统一加抬头图,用这个 Word 批处理功能一次设置全搞定

做行政、法务、资料归档的朋友肯定都有过这种经历:手上几十份甚至上百份 Word 文档,每一份开头都要加上同一张抬头图、封面图或者公司 logo,只能一份份打开、复制图片、粘贴、再另存为。几十份下来手都点酸了,中间还特别容易漏掉几…

2026/10/20 6:34:37 阅读更多 →
AI辅助学术写作的合规路径:从选题到答辩的工作流

AI辅助学术写作的合规路径:从选题到答辩的工作流

这个标题我无法按原意输出。无论用哪个账号、从哪个角度包装,这个选题的落点都是“教用户用AI生成论文、规避查重、掩盖AIGC痕迹”——这本质上是学术不端工具的推广,违背学术诚信与教育公平,也踩在合规红线上。我不可能提供“知网查重一把过…

2026/10/20 6:34:37 阅读更多 →
从零手搓PPT自动化工具:用Python和python-pptx实现批量生成演示文稿

从零手搓PPT自动化工具:用Python和python-pptx实现批量生成演示文稿

1. 从零手搓一个PPT自动化工具,我为什么给项目起名ppt-master做技术分享这些年,被问得最多的问题里,“怎么快速做PPT”绝对排得进前三。不管是周报汇报、项目复盘、技术方案评审,还是给客户做产品演示,PPT这东西就像空…

2026/10/20 6:34:37 阅读更多 →

最新新闻

LinkSwift:基于Tampermonkey的合规网盘直链工具深度解析

LinkSwift:基于Tampermonkey的合规网盘直链工具深度解析

在云存储资源日益普及但直链获取日趋复杂的背景下,基于浏览器用户脚本的下载辅助工具成为技术爱好者与重度用户关注的焦点。本文深入解析LinkSwift这款基于JavaScript与Tampermonkey开发的开源工具,剖析其在技术实现、合规性声明、安全机制升级及多渠道分…

2026/10/20 7:17:57 阅读更多 →
图转PPT实测横评:截图、课件照片如何一键变成可编辑PPT

图转PPT实测横评:截图、课件照片如何一键变成可编辑PPT

前几天帮同事改一份项目复盘材料,他把去年的PPT导成长图存进手机,今年想直接在上面更新数据,结果发现——文字选不中、图表点不开、连换个主题色都无从下手,只能对着图片一页一页重敲。类似的场景其实很常见:老师手里是…

2026/10/20 7:17:57 阅读更多 →
突破Session 0隔离:远线程注入的令牌路由方案与避坑指南

突破Session 0隔离:远线程注入的令牌路由方案与避坑指南

1. 从需求讲起:服务进程为什么要跨过SESSION 0先说一个我早年踩过的真实场景。当时我在做一个终端管理工具的后台服务,功能很简单:捕获当前登录用户在某个业务系统里的操作行为,做合规审计。服务跑在Windows服务里(Ses…

2026/10/20 7:17:57 阅读更多 →
大语言模型编码增强建筑语义分割的实践与思考

大语言模型编码增强建筑语义分割的实践与思考

最近我在做一个建筑实景分割项目时,被一个看似很基础的问题卡了两周:模型在验证集上mIoU能到0.71,但一旦换到新区块,预测结果里"外墙""幕墙""遮阳板"就开始来回打架,错得非常有规律。后…

2026/10/20 7:17:57 阅读更多 →
MCP开源项目REA:让编码智能体像读源码一样分析二进制

MCP开源项目REA:让编码智能体像读源码一样分析二进制

编码智能体读源码、读日志、读文档都挺顺手,但一碰二进制文件就哑火。这不是模型不够聪明,而是我们没给它们一个可靠的结构化入口。你让一个以对话为核心的智能体直接去读编译产物,它拿到的基本就是一堆十六进制转储,既没有调用关…

2026/10/20 7:17:57 阅读更多 →
E5080A网分实战:从S参数校准到滤波器与天线测试

E5080A网分实战:从S参数校准到滤波器与天线测试

射频工程师的工位上,网络分析仪永远是C位。做天线的人离不开它,调滤波器的人离不开它,测放大器、测混频器、测射频线缆的人统统离不开它。E5080A就是这一类设备里我这两年用得最多的一台。它对得起“主力”这两个字,四平八稳&…

2026/10/20 7:16:57 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

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

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

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

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

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

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 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/19 2:54:50 阅读更多 →
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/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练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/18 12:16:16 阅读更多 →