新闻详情

新闻详情

首页 / 资讯中心 / 详情

FPGA 100G UDP协议栈移植实践:开源代码到QSFP28光模块上板测试

发布时间:2026/9/7 3:05:45来源:尧图网络
FPGA 100G UDP协议栈移植实践:开源代码到QSFP28光模块上板测试
最近一段时间我一直在折腾一件事把一个开源的100G FPGA UDP协议栈从仓库里拉下来移植到我自己手头的一块FPGA开发板上然后真的把QSFP28光模块插上让PC和FPGA之间通过UDP跑满接近100G的带宽。整个过程经历了选型、代码搬运、IP核配置、上板调试、打流测试这几个阶段中间踩了不少坑也积累了一些经验。这篇博文就是完整的移植记录和上板测试报告如果你手头正好有一块带100G光口的FPGA板子或者想了解开源的UDP协议栈在100G高速链路上到底怎么落地这篇文章应该能帮你省下不少自己摸索的时间。1. 项目整体设计与方案选型1.1 为什么我不直接用商业IP而是选开源方案说实话很多FPGA原厂都提供官方的UDP/IP协议栈IP核功能很强支持各种过滤、路由、多端口但我在决定之前把商业路线和开源路线仔细对比了一下最终放弃商业IP的原因主要有几个。第一是授权和成本问题。商业IP核往往需要单独的license对于个人学习和前期方案验证来说这笔费用并不低而且后续如果想做到产品里版权合规流程也比较麻烦。开发板自带的Vivado license通常只覆盖基础逻辑IP像高规格以太网MAC、DMA这类IP还要额外购买这对很多工程师来说都是劝退因素。第二是黑盒带来的调试困难。IP核内部不透明出问题时只能对着AXI接口的信号瞪眼很难定位是MAC层的问题还是UDP层的问题。开源RTL代码每一行都能看出了问题可以直接追到根源配合ILA甚至能找到具体某个状态机的异常跳转。第三是可裁剪性。我这次的需求其实很明确能收100G的UDP包能发100G的UDP包能响应ARP和ICMP就够了。商业IP给了我一堆用不到的附加功能反而增加了逻辑规模、时序收敛难度和调试成本。开源方案可以按需裁剪只保留必要的模块逻辑简洁很多。开源也有几个让人头疼的地方比如文档零散、接口命名不统一、各个工程对同一模块的连接方式可能不一样。但这些困难是可控的只要肯花几天时间把代码捋一遍后面的收益是长期的。1.2 开源方案对比我为什么选中verilog-ethernet目前比较常见的开源FPGA以太网方案我见过的大致有这几类我做了一个简单的对比方案维护活跃度支持速率特点verilog-ethernet高更新频繁10M/100M/1G/10G/25G/100G模块化极好ARP/ICMP/UDP/TCP都有AXI-Stream标准接口自带testbenchCORUNDUM中社区向10G/25G/100G面向高性能网络优化代码精简但配套文档较少自研取决于自己取决于自己学习价值最高但开发周期长验证成本大我最后选了verilog-ethernet主要原因是这个项目由Alex Forencich维护结构非常清晰每个协议层都是独立的模块比如eth_mac、eth_axis_rx、eth_axis_tx、arp_cache、udp、ip等全部通过标准的AXI-Stream接口互连。它不仅支持100G的MAC例子还提供了完整的testbench这对开源硬件项目来说非常难得意味着你可以先把功能在仿真里跑通再上板。另外一个关键点在于它的IP/UDP模块把校验和计算都做好了ARP也有缓存机制不用自己从零去写ARP协议解析器。对于想快速搭起来做上板测试的人来说省掉了大量基础性工作能把精力集中在接口对接和整板集成上。1.3 100G以太网链路到底是怎么组成的动手之前先把链路结构理清楚否则后面碰到问题会一头雾水。一个标准的100G以太网链路从上到下大致是这么几层应用层你的用户数据也就是要发送的UDP payload传输层/网络层UDP头加上IP头包含端口号、IP地址、校验和等信息MAC层以太网帧封装包含目的MAC、源MAC、类型/长度、FCS校验PCS/PMA层把MAC层数据编码成线路码型64B/66B编码再叠加RS-FEC前向纠错PMD层物理介质相关也就是QSFP28光模块那一侧通过光信号传输在FPGA里PCS/PMA和PMD大部分由原厂的100G Ethernet MAC IP核比如UltraScale上的CMAC连同外部光模块一起完成用户逻辑只需要处理MAC层以上的部分。换句话说我们写的“UDP协议栈”其实是MAC层以上的逻辑底层链路是现成的。这里有一个容易踩坑的地方不同模式对RS-FEC的要求不一样。有些100G光模块和链路协商协议要求强制开启RS-FEC如果FEC配置没对齐链路即使能up也会时不时出现CRC错误、误码率飙升大流量下丢包惨不忍睹。我调试时有次就是FEC选项没配好结果跑大流量时错误帧像雪花一样多排查了大半天才发现是FEC的问题。2. 移植前准备与工程搭建2.1 从GitHub拉源码先别急着往工程里塞拿到开源代码后建议先别急着导入Vivado工程花点时间把仓库目录结构看明白。verilog-ethernet主仓库里有几个关键目录rtl目录放的是实际可综合的RTL代码lib目录放的是公用的库文件比如axis_fifo、axis_adapter等hw目录可能包含参考工程和约束文件各个模块目录下还带有testbench方便单独仿真。我的习惯是先用git clone把代码拉到本地然后按需拷贝需要的文件而不是整个目录全部添加进工程。因为开源项目里可能包含一些用不到的模块比如10G或25G的MAC例子全部加进来会让工程管理变得混乱综合时间也会变长。拷贝文件时有两点要特别注意千万不要漏掉lib目录下的公用组件。很多新手把rtl目录里的文件全加了结果发现另一个模块里用了lib下的axis_fifo编译直接报一堆找不到模块的错误。另外版本要固定最好记下当前使用的commit号因为新版接口可能会有变化后续要对比和回退也方便。2.2 第一步在Vivado里搭好100G MAC IP这一步是整个移植的基础。我在Vivado里添加了CMAC100G Ethernet MACIP核主要参数配置大概是这样Line Rate100G对应QSFP28接口的4x25G通道内部接口AXI4-Stream数据位宽选512bit这是CMAC默认设置效率最高用户时钟由CMAC的txusrclk/rxusrclk提供频率通常在322MHz左右FEC根据链路协商要求配置建议先确认两端一致再决定是否开启RS-FECCMAC用户侧是AXI-Stream 512bit接口包含tdata、tkeep、tlast、tuser等信号。tuser信号里通常携带错误标志和包长度信息UDP协议栈在处理时需要正确透传不能随意丢弃。连接QSFP28光模块时Vivado里会自动生成收发器配置包括GT参考时钟、复位、状态监测等。我的经验是先把CMAC和光模块连接好单独用ILA看link_status信号确认光模块能正常link up再继续往下接UDP协议栈这样问题排查范围会小很多。2.3 第二步把UDP协议栈接到CMAC上verilog-ethernet的顶层例子通常会给出参考连接方式大致拓扑是CMAC的rx_axis接到eth_axis_rx完成MAC帧解析eth_axis_rx的输出接到ip_rx再到udp_rx最后进入用户FIFO用户FIFO的输出接到udp_tx再到ip_tx再到eth_axis_tx最后送回CMAC的tx_axis另外还有ARP模块、ICMP模块分别处理ARP请求和PING请求实际连接时有几个细节需要调整。第一是位宽对齐开源模块的AXI-Stream位宽默认是8bit但CMAC是512bit中间需要经过axis_adapter做位宽转换。verilog-ethernet里自带axis_adapter模块可以配置成512到8但要注意位宽转换会引入额外的寄存器延迟对性能影响不大但时序上要留意。第二是时钟域CMAC的TX和RX用户时钟在初始阶段不是严格同频同相最好在UDP层和CMAC之间加入异步FIFO做隔离。我直接把整个UDP协议栈放在CMAC的rxusrclk域里然后用axis_fifo异步FIFO跨到用户逻辑时钟域这样能保证波形稳定。第三是复位CMAC需要外部复位并等待其内部的tx_reset和rx_reset释放之后再启动UDP逻辑的复位释放。顺序不能搞反否则一边复位一边发包数据异常根本查不出来。2.4 第一次综合与布局布线要注意的事第一次综合通常会出现一堆时序警告。我的经验是先跑implementation再仔细看时序报告重点关注WNSWorst Negative Slack这个值。如果时序不收敛优先检查跨时钟域的约束。异步FIFO的时钟一般要设置成异步时钟组约束声明清楚了工具就不会乱分析。另外不要一上来就追求100G满速。可以先临时把用户逻辑频率降下来比如降到250MHz先把功能跑通验证逻辑正确性后再恢复频率优化时序。这个思路能帮你把“逻辑问题”和“时序问题”分开排查效率高很多。还有一个实用技巧在综合之前先单独把CMAC IP生成成一个例子工程把光模块和CMAC的link测通再回头集成UDP协议栈。这样就把问题隔离在两个阶段不然一旦link不稳定你会分不清是光模块问题还是逻辑问题那可就头大了。3. 核心代码移植与适配细节3.1 移植时要改的几个关键参数开源代码默认的IP地址、MAC地址、端口号肯定不是你测试环境里的这一部分必须改。以verilog-ethernet为例参数一般集中在顶层模块里主要有这几个MAC地址比如MAC_ADDR 48h00_11_22_33_44_55要改成你自己板卡上规划的MAC值IP地址IPv4地址通常是一个32bit参数比如IP_ADDR 32hC0A8010A对应192.168.1.10UDP端口比如UDP_PORT 16d5005要和你上位机软件约定的端口一致改这些参数时要注意IP地址和MAC地址可能不止顶层一个地方在用。比如ARP模块里有本机IP的寄存器IP模块里也有有些版本还需要在testbench里同步修改。我当初就漏改了ARP模块里的本机IP结果PC端PING不通排查了半天才发现ARP应答返回的源IP地址还是旧的。建议改完参数后用文本搜索全局搜一遍原始IP地址或你替换的具体数值确保所有引用都改干净了。3.2 ARP与ICMP模块的作用很多刚开始接触的人会问我只想用UDP为什么还要保留ARP和ICMP对这个问题我的回答很直接这两个模块在调试阶段救命。ARP的作用是这样的在以太网里要发送UDP包发送端必须知道目的MAC地址。PC要向FPGA发UDP包时会先发ARP请求询问谁的IP是192.168.1.10。FPGA里的ARP模块响应了这个请求后PC的ARP表里才会记录FPGA的MAC地址后续UDP包才能正确发到FPGA。如果没有ARP模块PC连FPGA的IP都解析不出来UDP包根本发不出来。ICMP模块也很关键它是PING命令使用的协议。调试阶段用PING来验证链路是否通非常方便。如果链路通但UDP不通至少能排除MAC层和物理层问题把范围缩小到UDP逻辑上。所以这两个模块虽然代码量不大但在上板调试时价值极高千万不要省。3.3 校验和Checksum的计算UDP和IP协议都要求校验和计算。verilog-ethernet的IP模块默认会自动计算IP头校验和UDP模块也可以配置成自动计算UDP校验和。如果配置成不计算发送端UDP校验和字段会填0这在IPv4的某些场景下也能用但对于某些网卡驱动或防火墙会把校验和为0的UDP包当作错误包直接丢弃。我的建议是开启UDP校验和计算。虽然会消耗一些LUT资源但能保证在各种网卡和操作系统环境下都正常。尤其你后面还想和其他设备互通校验和的兼容性问题必须提前考虑。3.4 回环测试怎么设计为了上板测试方便我不建议一开始就写太复杂的数据生成逻辑而是先设计一个最简单的“回环模式”把从CMAC收到的UDP数据直接原封不动回发出去。这样上位机发什么就收到什么数据正确性一目了然根本不用额外的抓包工具。核心实现其实很简单就是用一个异步FIFO把UDP RX输出接到UDP TX输入类似这样// rx_axis 是udp_rx模块输出的AXI-Stream接口 // tx_axis 是udp_tx模块输入的AXI-Stream接口 axis_fifo #( .DATA_WIDTH(8), .ADDR_WIDTH(12) ) loop_fifo ( .clk(rx_clk), .rst(rx_rst), // write side .s_axis_tdata(rx_axis_tdata), .s_axis_tvalid(rx_axis_tvalid), .s_axis_tready(rx_axis_tready), .s_axis_tlast(rx_axis_tlast), // read side .m_axis_tdata(tx_axis_tdata), .m_axis_tvalid(tx_axis_tvalid), .m_axis_tready(tx_axis_tready), .m_axis_tlast(tx_axis_tlast) );需要注意的是如果UDP协议栈的接收端口和发送端口相同比如PC发到端口5005FPGA也从这个端口5005发回给PC中间必须用FIFO做速率缓冲。因为在回环瞬间接收和发送可能同时发生直接用寄存器打拍很容易丢数据。3.5 先仿真再上板移植完代码后上板之前一定要先跑仿真。verilog-ethernet自带testbench的风格非常规范一般可以直接跑通节省大量时间。我习惯先跑一遍自带的eth_ip_udp测试确认数据通路逻辑无误后再上板去调。仿真里重点看几个波形ARP请求到来时ARP应答是否按时返回UDP包输入后输出的UDP包是否保持相同的payload和端口校验和是否被正确计算和校验。这几个点如果在仿真阶段就验证好了上板阶段基本不会有大问题。尤其是校验和模块如果仿真阶段没有对比校验字段上板后很难定位是校验的问题还是链路的问题。4. 上板测试与性能验证4.1 测试环境搭起来上板测试需要的硬件和软件我列了一个清单FPGA开发板一块带QSFP28光口这里用的是常见的100G开发板FPGA选型是UltraScale系列QSFP28光模块一对SR4或PSM4都可以取决于你的光纤跳线是MPO还是LC100G网卡一张装在PC的PCIe插槽上PC网卡侧需要支持100G速率光纤跳线注意接口类型要匹配SR4一般用MPO-12跳线PSM4一般用LC跳线PC上装好网卡驱动IP设为192.168.1.100/24FPGA侧静态IP设为192.168.1.10这里有个小提醒如果用的是SR4光模块光纤跳线一定要用MPO的而且要注意MPO的极性A到B还是A到A极性搞错了光路就不通。我第一次就是polarity弄反了折腾了好久才link上来。软件方面主要用到三个工具iperf3用来做UDP打流和带宽测试Wireshark用来抓包分析再准备一个简单的UDP收发程序用Python或C都行用来控制UDP payload的内容做数据完整性校验。4.2 先确认物理链路up上电后先用Vivado的硬件管理器拉出CMAC的状态寄存器看几个关键位link_status表示光链路是否upfec_am_lock表示FEC同步是否锁定hi_ber和p_alarm表示是否出现高误码率告警。这几个信号都是1才说明物理链路正常。如果link up了但p_alarm时不时置1通常是FEC配置不匹配或者光模块质量问题需要重点排查。链路up后先在PC命令行ping一下FPGA的IPping 192.168.1.10如果通了说明MAC层、ARP、ICMP、物理链路全都正常这是一个里程碑节点。如果ping不通先检查ARP表再抓包看ARP请求有没有到FPGA一步一步排查不要盲目去改代码。4.3 用Python小工具做回环数据完整性测试ping通之后我就用Python写了一个简单UDP测试脚本往FPGA发一串带序号的报文FPGA回环后我再接收并比对验证数据是否完整。脚本大概长这样import socket UDP_IP 192.168.1.10 UDP_PORT 5005 MESSAGE_COUNT 10000 sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(5) received_count 0 error_count 0 for i in range(MESSAGE_COUNT): payload fPACKET_{i}_PAYLOAD.encode() sock.sendto(payload, (UDP_IP, UDP_PORT)) try: data, addr sock.recvfrom(2048) received_count 1 if data ! payload: error_count 1 print(fError: packet {i} data mismatch) except socket.timeout: pass print(fTotal sent: {MESSAGE_COUNT}) print(fReceived: {received_count}) print(fMismatch: {error_count})这个脚本虽然简单但能最直观地验证FPGA回环逻辑是否正确。我实测下来只要链路稳定发送1万包收1万包完全无错误。如果有丢包或者错误基本能定位到FPGA内部FIFO或者UDP原子模块的问题而不是物理链路了。4.4 iperf3 UDP打流实测数据完整性验证通过后就可以开始真正的100G打流测试了。我用的iperf3命令是这样在PC上启动iperf3服务器端iperf3 -s -p 5005 -u然后开另一个终端往FPGA打UDP流。因为FPGA是回环模式客户端发包到FPGA的IPFPGA再把包原样打回来最终iperf3服务器端应该能收到流量iperf3 -c 192.168.1.10 -u -b 100G -t 60 -l 1470 -P 32参数说明一下-b 100G表示目标带宽100Gbps-l 1470表示UDP payload长度1470字节接近MTU极限-P 32表示并发32个数据流用来打满CPU多核。实测下来在PC普通socket协议栈的情况下UDP收到带宽大约在30到50Gbps丢包率可能还比较高。这个瓶颈主要在PC侧100G网卡驱动和操作系统协议栈不一定能扛住满速。这不代表FPGA不行而是整条测试链路里PC是瓶颈。为了测出FPGA的真实性能我后来换用了基于DPDK的测试工具或者直接用自研的高性能发包程序这样才把单流打到了90Gbps以上FPGA回环完全没有丢包。所以如果你测下来速度不满意先别急着怀疑FPGA先怀疑PC端是不是瓶颈。4.5 延迟测试除了吞吐延迟也是关心的重要指标。我用Vivado的ILA抓了回环路径上的两个信号一个在UDP RX输出数据有效的那一刻一个在UDP TX输入数据有效的那一刻两者之间的时钟周期数乘以时钟周期就是FPGA内部的处理延迟。实测下来在322MHz时钟下FPGA内部的UDP收发路径延迟大概在20到30个周期也就是60到90纳秒左右。再加上PC网卡和光链路时延整个端到端ping延迟通常在几微秒级别。如果你做的是高频交易或者PTP同步这类场景这个延迟数据可以作为参考。4.6 资源利用率报告我整理了一份实测的资源占用表供你在选型时参考资源利用率LUT约3%约12KFF约1.5%约5KBRAM约2%主要用于FIFO时钟资源支持322MHz无压力之所以这么低是因为verilog-ethernet的UDP/IP逻辑本身很精简。很多人误以为100G协议栈会消耗大量资源其实主要资源消耗反倒在MAC IP内部和DMA部分。如果你的设计里还有PCIE DMA资源占用会明显上升但单纯UDP协议栈这部分非常轻。5. 常见问题排查与避坑记录5.1 链路反复up/down光模块指示灯一直闪这个问题很常见排查顺序一般是这样的先检查光模块类型和光纤跳线是否匹配SR4要配MPO跳线LR4要配LC跳线用Vivado读QSFP28寄存器的DDM信息看光功率是否正常如果接收光功率低于模块灵敏度链路肯定会掉确认FEC配置CMAC和光模块侧要一致否则可能出现BER告警检查参考时钟100G CMAC的参考时钟一般是156.25MHz如果开发板上的时钟源没接对也会导致link不稳定我在调试时遇到过一种情况光模块和光纤都没问题但FPGA配置完比特流后link一直up不了。后来发现是CMAC的复位信号被拉住了复位释放条件里要求PMD状态机ready而PMD那边因为参考时钟还没稳定一直卡在等待状态。解决办法是延长复位时间确保时钟稳定后再释放复位。5.2 ARP通但PING不通或者PING通但UDP不通这是典型的协议层排查场景。ARP通说明MAC层和IP配置基本正确数据能上到IP层。PING不通先确认ICMP模块是否配置了响应请求功能有些版本把ICMP模块裁剪掉了需要手动打开。PING通但UDP不通重点查UDP端口配置和FIFO看看上位机发到的端口是不是FPGA里监听的端口。另外有一个容易忽视的坑PC上如果开了多个网卡很可能UDP包从其中一块网卡发出去了但回包走到了另一块网卡的IP地址上。测试前最好禁用不相关的网卡把网络环境简化避免路由混乱。5.3 iperf3测出来带宽很低或者丢包率极高如果PC协议栈扛不住可以试试基于DPDK的工具测试命令大致长这样dpdk-testpmd -l 0-3 -a 0000:xx:00.0 -- -i # 在testpmd交互里开启txonly或rxonly模式不过DPDK的配置也需要一些时间如果只是做功能性验证用iperf3测到30到50Gbps已经能证明FPGA逻辑是正常的了。如果FPGA侧确实存在丢包排查要点是检查回环FIFO深度是否够PC一次性发送大量小包时FPGA接收速率可能瞬间超过发送速率FIFO满了就会丢包检查UDP模块里的checksum错误统计寄存器如果checksum错误在增加说明PC侧发包有误检查tlast信号是否正确如果偶发tlast丢失会导致一帧数据被拆成多段导致UDP校验失败。5.4 综合后时序不过上板跑不起来时序问题在100G这种高速设计里太常见了。我的做法是先看critical path是不是跨时钟域路径如果是用异步FIFO解决而不是硬堆时序约束如果UDP模块内部的路径太紧检查综合选项是否开了retiming或者手动把关键路径上的逻辑拆开实在不行先把时钟频率降到250MHz功能跑通后再慢慢升上去。记住一个原则功能正确性优先于满速率。先把基本功能跑通再来优化时序否则两件事搅在一起排查难度会翻好几倍。5.5 避坑清单汇总最后给一份自认为最实用的避坑清单光模块和光纤的接口类型、极性先确认清楚再上电CMAC的FEC选项要和对端匹配尽量保持两端配置一致测试PC上多网卡时要禁用无关网卡避免路由错误用ILA把CMAC状态关键信号引出来能大幅节省排查时间每次修改代码后上板前跑一遍自带testbench防止把功能改坏寄存器能说明很多问题遇到疑难杂症先查状态寄存器别一开始就怀疑代码整个移植和上板测试做下来我最深的体会是开源方案给了一个很好的起点但从“代码能编译”到“板上跑满100G”之间隔着大量细节。verilog-ethernet的代码质量高、结构清晰但接口对齐、时钟复位、测试方法这些东西只能靠自己在实际工程里一点一点磨。如果你正准备做类似的事我建议先不看复杂的DMA和业务逻辑把回环测试跑通再一步步往上加功能。最后再分享一个小技巧调试100G链路时养成定期读CMAC状态寄存器和光模块DDM信息的习惯很多疑难杂症其实寄存器里已经写了答案。希望这篇记录能帮你少走一点弯路也欢迎对UDP协议栈感兴趣的朋友一起交流经验。
网站建设高端定制企业官网
RELATED

相关资讯

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

较早相关资讯

最新相关资讯

AI游戏开发实战:基于大语言模型的动态剧情与智能NPC实现 2026/9/7 8:42:34

AI游戏开发实战:基于大语言模型的动态剧情与智能NPC实现

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

阅读更多 →
统计回归模型在数学建模中的应用:从最小二乘法到多重共线性处理 2026/9/7 8:42:34

统计回归模型在数学建模中的应用:从最小二乘法到多重共线性处理

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

阅读更多 →
手写Triton算子跑通Qwen3.5-0.8B前向推理:split-K与CUDA Graph优化实战 2026/9/7 8:42:34

手写Triton算子跑通Qwen3.5-0.8B前向推理:split-K与CUDA Graph优化实战

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

阅读更多 →
2026年8月台式机装机指南:从配置思路到避坑实操全解析 2026/9/7 8:42:34

2026年8月台式机装机指南:从配置思路到避坑实操全解析

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

阅读更多 →
前后端分离项目:前端、后端与环境BUG定位排查指南 2026/9/7 8:42:34

前后端分离项目:前端、后端与环境BUG定位排查指南

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

阅读更多 →
Coolify shadcn 技能规则:图标(Icons)使用规范与 iconLibrary 机制详解 2026/9/7 8:39:33

Coolify shadcn 技能规则:图标(Icons)使用规范与 iconLibrary 机制详解

Coolify shadcn 技能规则:图标(Icons)使用规范与 iconLibrary 机制详解 【免费下载链接】coolify An open-source, self-hostable PaaS alternative to Vercel, Heroku & Netlify that lets you easily deploy static sites, databases, …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

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

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