news 2026/9/13 1:53:27

国产FPGA安路EG4S20开发板实战:从工具链搭建到流水灯设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
国产FPGA安路EG4S20开发板实战:从工具链搭建到流水灯设计

最近开始折腾安路EG4S20这块国产FPGA开发板。之前一直在用国外某家的工具链,软件体积动辄几十个GB,综合一个小工程都要等半天,笔记本风扇转得跟飞机起飞似的。想着国产FPGA这几年进步挺大,就拿这块板子当个补充学习平台,顺便把国产工具链也摸一遍。买的时候没抱太大期望,毕竟价格摆在那,结果开箱插电、跑通第一个工程的体验,比想象中顺不少。

这篇是学习记录的第一篇,目标很明确:把EG4S20能干什么、开发环境怎么搭、第一个工程怎么从头到尾跑通,完整记录一遍。不管你是零基础想入门FPGA,还是用过Xilinx/Intel平台想切国产工具的,这篇都可以当个参考,省得在一堆零散资料里翻来翻去找答案。EG4S20这块板子的上手成本比我想象的低,TD工具链没有一堆花里胡哨的选项,编译速度快,大概一顿饭的功夫就把流水灯点起来了。当然,便宜板子也有不少讲究,下面一一说清楚。

1. 板卡选型与硬件资源解读

1.1 为什么选安路EG4S20而不是其他

FPGA入门常见的路子是Xilinx的Artix-7或者Intel的Cyclone IV,教程多、资料全,这是事实。但我选安路EG4S20有我的理由,不是情怀,就是预算和需求摆在那。首先是价格,国产FPGA开发板的整体成本比国外方案低不少,作为个人学习投入,这个差价真的能感受到。EG4S20在安路Eagle系列里属于性价比比较高的型号,逻辑规模不大不小,刚好卡在入门和进阶的中间档位。

其次是资源结构。EG4S20这颗芯片内置了SDRAM控制器和SDRAM颗粒,这一点在入门级芯片里相当厚道。做过图像或者数据缓存相关设计的人都知道,FPGA内部那点Block RAM永远不够用,内置SDRAM意味着你可以直接拿它做帧缓冲、数据暂存,不用一上来就折腾外部DDR——外部DDR的布线和对时序的要求,对新手来说往往是劝退级别的存在。

第三是工具链。安路TD(Tang Dynasty)IDE是免费的,界面风格和操作逻辑跟早年Quartus那一代工具非常接近,没有Vivado那种厚重感,也没有复杂的IP授权流程,对国内学习者非常友好。综合、布局布线、下载、在线调试这些核心功能一个不少。

1.2 板卡资源实物盘点

我手上这块板子的布局,从左到右大致是:USB供电和下载接口、4个用户LED、4个独立按键、一个四位数码管、一个蜂鸣器、一个50MHz有源晶振,剩下的就是两排引出到外面的IO排针。麻雀虽小五脏俱全,最基础的FPGA外设都齐了,日常练习完全够。

这里特别说一下IO排针。FPGA开发板的排针和STM32、ESP32这些ARM开发板的排针从外观上看很像,但本质逻辑完全不同。ARM的引脚是处理器定义好的功能,复用不复用由代码和寄存器决定;FPGA的引脚是你自己定义的,同一个物理引脚想接什么信号都行,只要电平标准和电气特性匹配就行。

这个差异也解释了为什么FPGA资料里从来不建议把引脚写死在代码里。引脚分配属于约束的一部分,在实现阶段才生效。你把代码里的端口名字起好,后面分配引脚的时候想绑到哪个物理引脚就绑哪个,灵活性比单片机高了一个维度。

1.3 EG4S20芯片内部资源速览

EG4S20芯片本身,核心资源大致是:约2万级别的LUT4逻辑单元(具体小版本有差异),配套的寄存器、触发器,内置Block RAM,内置SDRAM,若干DSP乘法器模块,两个PLL锁相环,以及LVDS差分IO支持,IO电气标准支持常见的LVCMOS33/25/18、LVDS、SSTL等。

这个资源量级是什么概念呢。拿它做大型SoC那不可能,但做逻辑训练、搭一个简单的小CPU核、做视频时序转换、跑一个卷积运算模块,这些是足够的。DSP模块数量虽然不算多,但作为学习定点数运算、乘累加结构、FIR滤波器的载体完全OK。两个PLL在入门级里属于正常配置,一个负责主时钟,一个给外设生成时钟,够用。

LVDS接口是EG4S20的一个亮点。液晶屏显示、摄像头数据传输、板间长距离互联,这些场景都绕不开差分信号。入门板自带LVDS能力的其实不多,很多同价位板子只能跑单端IO,想做高速图像传输就得换方案。这也是我选它的一个加分项。

2. 开发环境搭建:TD工具链全流程

2.1 获取与安装TD IDE

安路的开发环境叫TD IDE,官网就能直接下载。安装过程有几个值得注意的点:第一,安装路径一定不要带中文。这不是玄学,很多国产EDA工具的编译器和路径解析对中文路径支持不好,一旦路径里有中文,综合阶段就会报出各种莫名其妙的错误,排查起来极其痛苦,我见过不止一个人卡在这个上面。第二,安装时把桌面快捷方式勾上,后面打开方便。第三,首次启动需要注册账号并获取授权,流程不复杂,按提示填个邮箱就能拿到,不用等人工审核,这一点比很多国外工具省心太多。

装完之后界面很朴素,工具栏就那么几个按钮,逻辑层次一目了然。我个人的感受是,TD的目标用户很明确——就是给做FPGA设计和学习的人用,不搞那些华而不实的东西。第一次打开你可能觉得它简陋,但真用起来会发现,该有的功能全都有,而且每个功能都放在该放的位置。

2.2 下载器驱动识别

板子和电脑的连接走的是USB转JTAG。插上USB线之后,第一次识别设备,绝大多数情况下系统会装一个通用驱动,但TD不认这个通用驱动,必须手动安装厂商提供的驱动,一般能在TD安装目录的drivers文件夹里找到。

这里有一个从Quartus时代就存在的经典坑:如果你之前用过别的调试器,装过各种USB驱动,那么新板子的驱动可能会跟旧的冲突。我的处理方式是,先拔掉其他USB调试器,在设备管理器里找到带感叹号的设备,手动指定驱动路径,一步一步来。搞完之后打开TD,在下载器列表里能看到设备,就说明驱动这关过了。

2.3 新建工程与目录规划

TD创建一个新工程的流程跟老Quartus非常像:File -> New Project,选器件型号,填工程名和路径,然后一路Next。器件型号我这块板子上有丝印标注,照着选就行,第一次别选错,选错会导致后面的引脚约束对不上。

我强烈建议你在第一次建工程前就规划好目录结构,别把所有文件堆在同一个根目录下,后面工程文件多了再挪,相当于给自己找麻烦。我个人的习惯是:

  • rtl/:存放所有Verilog源文件
  • sim/:存放testbench测试激励
  • constraint/:存放引脚约束和时序约束文件
  • doc/:放自己画的结构图和笔记
  • 工程文件本身放根目录

这样做的目的,首先是源头清晰,其次是后续做版本管理的时候方便。我见过很多新手把工程和源码混在一起提交到仓库,一个工程几百兆,源码就那么几十个文件,那个混乱程度谁体会过谁知道。养成好的工程习惯,比多写几个模块重要得多。

2.4 TD工具链与主流EDA的差异

我用过的FPGA工具主要是Vivado和Quartus,切到TD之后最大的感受是"简化"。TD没有Vivado那种复杂的IP Catalog和总线互联工具,IP核的添加方式更接近Quartus的MegaWizard,选好IP填好参数,生成一个模块化文件,例化的时候跟普通模块一样。这对新手非常友好,Vivado那种以IP为中心的设计模式,新手很容易在配置和连接总线时迷失方向。

编译流程上,TD把综合、布局、布线、生成比特流打包在一起,点一下全部执行。这种做法省心但也有隐患——新手容易只看最后有没有成功,不关心中间报告,一旦出错不知道是哪一步出的问题。我的建议是至少在入门阶段,学会单独执行综合和布局布线,逐个看警告和错误。这就像开车,自动挡确实好开,但你得知道发动机和变速箱大概是怎么配合的,出了异响才能判断是哪里的问题。

3. 第一个FPGA实验:按键控制LED流水灯

3.1 需求拆解与设计思路

点灯是每个FPGA学习者的第一课,但单纯点灯没意思,我直接做了个稍微延伸的版本:4个LED循环流水,间隔0.3秒左右,流水方向由一个按键控制,按键按下反向,松开正向。这个小需求里涉及了FPGA设计的几个核心思想:时钟是系统所有逻辑的基准、通过计数器产生节拍信号、移位寄存器描述"流动"、以及顶层模块与底层逻辑的划分。

很多人学FPGA有一个误区,上来就翻手册背语法。实际上FPGA设计的核心不是Verilog语法,而是你脑子里有没有形成"硬件电路"的概念。写always块的时候,你要清楚综合出来是一堆触发器、一堆查找表、一条条连线,而不是一段按顺序执行的程序。这个需求里,计数器是时序逻辑,移位寄存器是时序逻辑,按键方向选择是组合逻辑,三个东西怎么配合,在动笔之前就应该在脑子里有图。

3.2 顶层模块与时钟复位设计

顶层模块只需要把输入输出端口声明清楚。总共5个信号:50MHz时钟、低有效复位、按键输入、4位LED输出。这里有一个重要概念:为什么要有复位信号。FPGA上电瞬间,寄存器的初值不确定,如果没有复位逻辑把所有状态拉到已知值,整个系统一上电就在乱跑,仿真都仿不出那种状态。所以绝大多数工程里,复位是必不可少的一部分。

复位信号选低有效还是高有效,看板子设计。开发板上的复位按键我一般按低有效接,也就是按键按下时拉低。这个细节不重要,重要的是在设计里从上到下保持一致的复位极性,混用是最让人头疼的问题。有的模块用高有效复位,有的用低有效,综合不报错,但板子跑起来行为就怪了,排查半天才发现是复位极性不统一。

3.3 核心逻辑:计数分频与移位寄存器

核心逻辑分两部分。第一部分是计数器:50MHz时钟,如果要得到0.3秒的节拍,就是50,000,000 × 0.3 = 15,000,000个时钟周期。所以计数器从0数到14,999,999,产生一个单周期的tick信号,同时计数器清零重新数。

这里有一个值得新手注意的细节:分频好的时钟不要直接拿来当主时钟跑逻辑,而是用tick信号做使能。理由有二:一是门控时钟容易在FPGA里造成时钟偏移,影响时序收敛;二是用使能信号的方式可以让所有逻辑保持在同一时钟域,布局布线更干净。这是FPGA工程师的基本素养,从第一个实验就该养成。很多人图省事直接always @(posedge clk_div),短期能用,但设计复杂之后各种时序问题接踵而来。

流水部分用移位寄存器实现。一个4位的寄存器,默认值是0001,每个tick到来左移一次,最高位移到最低位,就形成了循环点亮的效果。按键控制方向,只需要在移位时改变拼接顺序就行。

3.4 完整代码与仿真验证

下面是我在这个工程里用的完整代码,注释写得比较多,方便后来的人看:

module led_flow( input clk_50m, input rst_n, input key_dir, output reg [3:0] led ); // 0.3s 定时参数:50M*0.3 = 15_000_000 reg [23:0] counter; reg tick; always @(posedge clk_50m or negedge rst_n) begin if (!rst_n) begin counter <= 24'd0; tick <= 1'b0; end else if (counter == 24'd14_999_999) begin counter <= 24'd0; tick <= 1'b1; // 每0.3s产生一个单周期使能脉冲 end else begin counter <= counter + 1'b1; tick <= 1'b0; end end // 按键控制流水方向 always @(posedge clk_50m or negedge rst_n) begin if (!rst_n) led <= 4'b0001; else if (tick) begin if (key_dir) led <= {led[2:0], led[3]}; // 循环左移 else led <= {led[0], led[3:1]}; // 循环右移 end end endmodule

写完代码别急着上板,先在仿真环境里验证一遍。TD的仿真支持我直接用了ModelSim,通过TD菜单可以自动把工程映射成ModelSim工程,这个功能对新手比较友好。唯一的问题是ModelSim的版本必须匹配TD的要求,版本不对会启动失败,报错信息还很隐晦,第一次遇到容易懵。

仿真时的testbench,我建议把时间参数临时改小。比如说,如果直接跑0.3秒的计数器,仿真器里要跑15,000,000个时钟周期,波形图拉出来像一条直线,没意义。我的做法是临时把计数器比较值改成小数值,比如16个时钟周期产生一个tick,这样在仿真波形里能清楚看到LED翻转和移位过程。等逻辑验证通了,再改回真实参数去烧录。

3.5 引脚约束与一键下载

功能仿真通过之后,下一步是把逻辑"锁"到具体的物理引脚上。TD里的引脚分配可以通过图形化工具完成,打开引脚分配界面,左边是设计里的信号名,右边是芯片引脚,像填表格一样把信号和引脚对应起来,保存之后TD会自动生成约束文件。开发板上LED连接的是哪个引脚,看板子丝印或者原理图标注就行。

引脚分配有几个坑,我踩过一次之后长了记性:时钟引脚必须分配在专用时钟输入引脚上,不能随便拉到普通IO,FPGA内部时钟网络跟普通IO网络是分开的,随便接可能导致时钟到达时间不一致;同一Bank的IO电平标准必须一致,别混用3.3V和2.5V,否则电气特性冲突;做高速差分信号时,LVDS引脚必须是成对的专用差分引脚,别拿普通IO硬凑。分配完引脚,回到主界面跑综合、布局布线、生成比特流,成功之后板子上电,在下载界面选择生成的比特流文件,点下载。

看到4个LED按0.3秒节奏开始流水的时候,第一个FPGA实验就算正式跑通了。整个过程从零到点灯,算上写代码和仿真,不到半天。这个上手速度在国产工具链里确实算快的。

注意:如果工程里用了多个时钟域,引脚约束时一定要优先确认时钟引脚分配到了专用时钟管脚,否则后端的时钟树综合会出问题。

4. 基础外设驱动:从点灯走向控制

4.1 按键消抖:FPGA里的时间窗口

流水灯实验里按键直接用了电平值,这在小工程里问题不大。但如果按键是给系统发指令用的,直接用电平会出问题。机械按键按下和释放的瞬间会有一系列抖动脉冲,持续时间从几毫秒到十几毫秒不等,如果不做处理,一次按下可能被识别成几十次操作。

消抖的本质是一个时间窗口。按键电平变化后,保持等待一段时间(比如10ms),如果这之后电平仍然稳定,才认为按键确实按下了。FPGA里常见的实现方式是:用计数器累计时间,每10ms采样一次按键电平,连续若干次采样结果一致才更新按键值。这个设计思路适用于几乎所有带物理按感的输入设备。理解消抖窗口的本质,比背一个消抖代码重要得多,后面做红外解码、编码器采集、触摸按键检测,都是同一套逻辑。

4.2 数码管动态扫描

开发板上的四位数码管是另一个非常经典的练习项目。四位共阴或共阳数码管需要段选和位选两组信号,把每一位都直连到IO上,8位段选加4位位选才12根线,看起来不多,但如果做成更大规模的显示阵列呢,IO资源马上就不够用了。

数码管的动态扫描就是典型的"用时间换空间"。让四位数码管轮流被点亮,每个位点亮一小段时间(比如1ms),由于人眼的视觉暂留效应,只要刷新频率够快,看起来就是四位同时在显示。这个逻辑在FPGA里实现很简单:一个扫描计数器在四个位之间循环,每个周期把对应位的显示数据送到段选。

这个实验的价值在于,它让你理解了一个FPGA设计中反复出现的"时间分片复用"思想。后面的串行接口、总线仲裁、内存刷新,本质上都是类似思路的延伸。看似简单的数码管,其实藏着很重要的设计方法论。

4.3 分频输出与驱动能力

板子上的蜂鸣器分有源和无源两类,我那块是无源的,需要给它提供一定频率的方波才能发出声音。实现方式很简单,把50MHz时钟分频到几百赫兹到几千赫兹,再送到蜂鸣器的驱动引脚,就能听到对应的音调。

这一环节有一个容易忽视的点:FPGA的IO驱动能力是有限的,直接驱动蜂鸣器这种功率稍大的负载可能拉不上去。开发板一般会预留三极管驱动电路,但如果你是自己画板子,一定要看蜂鸣器的驱动电流和FPGA的输出能力是否匹配。逻辑上没问题的设计,到了硬件上跑不出来,很多时候不是代码的锅,是驱动能力不够。推动不了负载的时候,别怀疑代码,先查电路。

5. 调试技巧与问题排查实录

5.1 下载失败问题排查

下载失败是新手最常卡住的地方,我这段时间遇到的下载问题按出现频率排个序。第一,USB驱动不对,表现为TD的下载器列表里看不到设备,解决方式就是去设备管理器手动指定驱动目录。第二,JTAG接线顺序错误,如果你不是用原厂下载线而是自己飞线,务必核对TCK、TMS、TDI、TDO四根线的顺序,错一根整个链路就断了。第三,目标板供电不稳,有的开发板只通过下载线供电,下载线供电能力不强,复杂设计跑起来电流增大,电压跌落,表现为下载到一半失败,这种情况给板子外接一个稳定电源,问题立刻消失。

下载问题排查有个通用思路:先看链路,再看电源,最后怀疑芯片本身。链路就是USB、驱动、JTAG线序,电源就是电压和电流,这两个排查完基本能解决九成问题。别一上来就怀疑芯片坏了,FPGA芯片没那么脆。

5.2 时序约束的初步认识和set_input_delay

点灯实验用到的约束只有引脚约束,但FPGA设计到了一定规模,时序约束就是绕不开的坎。很多人在综合布局布线后看到时序报告里一堆红色,不知道什么意思。简单理解就是:你的逻辑在给定的时钟速度下,信号从一级寄存器传到下一级寄存器的路径延迟太长,来不及在一个时钟周期内稳定,导致时序违例。

时序约束里最基本的两个概念是set_input_delay和set_output_delay。它们描述的是数据从外部器件输入到FPGA、或者从FPGA输出到外部器件时,相对于时钟边沿的到达时间关系。举个例子,如果外部芯片在时钟上升沿后2ns才开始输出数据,那你就要用set_input_delay -max 2.0告诉工具"输入信号有2ns的延迟",工具才能正确分析内部逻辑是否来得及采样。

TD的时序约束界面做得比较简洁,但基本概念和行业标准一致。理解set_input_delay这类基础概念,对于从入门走向进阶的FPGA工程师是必经之路。我建议新手至少把建立时间和保持时间的定义搞清楚,再去看完整的时序报告,就不那么懵了。

5.3 用在线逻辑分析仪抓内部信号

FPGA调试有一个尴尬的阶段:仿真时逻辑全对,烧到板子上就不工作。原因可能是外部电路、可能是电气噪声、也可能是实际线路延迟和仿真模型不一致。这时候,在线逻辑分析仪就派上用场了——它相当于一个嵌在芯片内部的示波器,可以把想观察的内部信号实时抓下来,上传到电脑显示。

TD的开发环境里自带了这个功能,原理跟Xilinx的ILA类似。使用方法是在工程里插入一个逻辑分析仪IP,选择要观测的信号和触发条件,然后重新综合布局布线,下载之后运行在线分析,就能看到信号波形。有个注意点:观测信号数量越多、采样深度越大,占用的Block RAM越多,而Block RAM本身是设计资源。所以调试时只挑关键信号看,不要全部抓取。我调试的时候一般抓三五个信号就足够了,抓一堆信号除了让波形图密密麻麻之外没有任何好处。

5.4 常见错误速查表

整理一下这段时间遇到的报错和信息,后面应该还会更新:

现象主要原因处理方法
下载器列表为空驱动没装对设备管理器手动指定驱动目录
综合报找不到模块模块名不一致或文件没加入工程检查模块名与文件名对应关系
引脚约束冲突同一引脚被分配多个信号在引脚分配界面去除冗余绑定
布局布线后时序红色路径过长或约束过紧优化逻辑级数,或放宽约束
下载一半失败供电不足或下载线接触不良外接电源、检查JTAG线序

这个表格看着简单,但每个坑背后都对应一段折腾的时间。排错这件事,经验比天赋重要,踩过一次坑,下次就有肌肉记忆了。

6. 后续学习路线与扩展方向思考

6.1 从基础到进阶的学习路径推荐

如果你也是学FPGA的新手,我建议按这个路径推进:组合逻辑与时序逻辑基础 -> 状态机设计 -> UART通信协议 -> 按键消抖和数码管这类基础外设 -> FIFO与跨时钟域处理 -> 基于内置SDRAM的缓存设计 -> 图像采集与显示。

这个路径没有跳步。组合逻辑和时序逻辑是语法层面的基础,状态机是设计方法学的第一个台阶,UART是第一个真实通信协议,FIFO和跨时钟域是你从练习走向工程的分水岭。EG4S20内置的SDRAM刚好帮你跨过"缓存不够用"的坎,做图像缓存和数据流控制,比纯靠Block RAM舒服太多。状态机、接口时序、数据流控制这三个能力掌握好,就具备了做真实项目的基础。

6.2 EG4S20适合深挖的几个方向

资源摸透之后,这块板子有几个方向很值得深挖。

第一个是图像处理方向。EG4S20支持LVDS差分IO,配合DVP或LVDS接口的摄像头,做图像采集、灰度变换、边缘检测、直方图统计,再通过显示接口输出,是一条完整的图像处理入门链路。很多人说FPGA图像处理门槛高,其实从OV5640摄像头采集到VGA显示输出,这个链条上的每一个环节都有对应的成熟方案,一步一步啃下来,收获非常大。

第二个是TDC时间数字转换。利用FPGA内部进位链的延迟差异,实现高精度的时间间隔测量,再通过直方图统计算法做统计分析,这个方向在激光雷达、医疗成像里有很实际的应用前景。EG4S20这种20K逻辑规模的芯片做TDC是可行的,很多做TDC直方图的课题就是在这个量级的FPGA上完成的。进位链的延迟测量、温度漂移补偿、直方图累加逻辑,每一个点都值得单独开一篇去讲。

第三个是定点数运算原理。FPGA算法设计里很少直接用浮点数,因为浮点资源消耗太大,大部分信号处理都用定点数做。理解定点数的Q格式、溢出处理、舍入规则,是在FPGA里做算法的基础功课,也是从"会写Verilog"到"会做算法"的必经之路。用EG4S20的DSP模块搭一个定点数滤波器,是性价比极高的练习。

6.3 关于更高阶接口的现实认知

热搜词里出现了MIPI、DDR4、PCIe这些高级接口,但作为一个学习者,我想诚实地说一句:EG4S20这块芯片的定位就是入门到中级,MIPI和DDR4并不适合在这个量级的芯片上强求。MIPI的物理层要求高速串行收发器,DDR4需要DDR PHY硬核和精细的时序训练,PCIe更是需要硬核或大量逻辑资源去模拟,这些不是靠堆代码就能解决的。

对这块板子的合理规划是:做好基础逻辑、状态机、通信协议、缓存设计、图像采集与显示,打牢基本功。等需要高级接口时,再根据项目需求切换到支持相应硬核的高端型号。适可而止也是工程师的一种能力,知道什么项目用什么芯片,比什么都想硬塞进一颗入门芯片里高级得多。

写在最后

这段时间用下来,我对安路EG4S20的整体评价是"值"。几百块钱的板子,从工具链到内置资源,能让你把FPGA的核心基本功踏踏实实过一遍,还能顺手做点图像处理、TDC这类有意思的东西。诚然它和高端芯片还有差距,但作为学习平台,它做到了不卡脖子,学习过程中需要的外设、接口、存储资源都给全了。

最后分享一个个人小经验:学FPGA千万别只盯着教程抄代码,每做完一个实验,强迫自己改一改逻辑再下载看效果。哪怕只是改改流水灯的速度、改改按键的方向、换一种消抖的写法,这个"动手改"的过程会让你对硬件逻辑的理解深刻很多。代码看十遍不如自己改一遍,这是PCB和FPGA这类硬件学习里最朴素的道理。下一辑我打算写写基于EG4S20内置SDRAM的图像缓存实验,感兴趣的话可以持续关注。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 1:53:22

工业协议协同接入:Modbus、OPC UA、S7与EtherNet/IP统一采集方案

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

作者头像 李华
网站建设 2026/9/13 1:52:58

Rust+Tauri本地视频剪辑工具WolfCut技术解析

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

作者头像 李华
网站建设 2026/9/13 1:52:50

多模态视觉大模型开发实战:OpenCV与新生态协同指南

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

作者头像 李华
网站建设 2026/9/13 1:52:06

Zulip Widgets 架构深度解析:从 /poll 投票到 zform 交互式消息

Zulip Widgets 架构深度解析&#xff1a;从 /poll 投票到 zform 交互式消息 【免费下载链接】zulip Zulip server and web application. Open-source team chat that helps teams stay productive and focused. 项目地址: https://gitcode.com/GitHub_Trending/zu/zulip …

作者头像 李华