FPGA实现ROM图片VGA显示:Verilog时序与ROM初始化详解
简介这份资源面向FPGA初学者与数字电路课程实践者围绕“用ROM存储图片并通过VGA接口显示”这一典型项目展开帮助读者打通从图像数据生成到硬件显示的完整链路。压缩包共245个文件约1.12MB以Quartus工程文件为主包含hdb、cdb、tdf、vwf等编译与仿真中间文件以及v、qsf、qpf等Verilog源码与工程配置另有mif存储器初始化文件、sof与pof下载文件、rpt报告和readme说明覆盖综合、布局布线与下载全流程。已有1056人学习下载。资源核心价值在于提供可直接复现的工程实例读者可参考ROM模块与VGA控制器的Verilog实现理解HS、VS同步信号与RGB像素数据的时序配合并借助MATLAB生成的mif文件掌握64×64像素图片的存储与读取方法适合作为课程设计、实验报告或自学练手的参考模板。1. 从一块 FPGA 开发板说起图片怎么从 ROM 里“流”到 VGA 屏幕上手头有一块 FPGA 开发板想让它不依赖电脑、不接摄像头直接把自己预先存好的图片显示在 VGA 显示器上这件事听起来简单做起来却卡住过不少人。我最初的想法也很朴素把图片数据写进 ROM然后按 VGA 的时序把像素一个个读出来送出去不就行了真正动手才发现ROM 的地址怎么算、VGA 的行场同步怎么对齐、像素时钟从哪来、图片格式怎么转成 Verilog 能读的.mif或.coe每一步都有细节。这个项目就是围绕“ROM 存储图片 VGA 显示”这条链路展开的用 Verilog 把整个数据通路串起来适合刚接触 FPGA 图像显示、想找一个完整可跑例程的开发者。它不涉及复杂的图像处理算法核心就是存储、寻址、时序三件事但恰恰是这三件事把 FPGA 做显示的基本功全练了一遍。2. 系统架构拆解ROM、VGA 时序与时钟三块怎么咬合2.1 整体数据通路从地址计数器到 RGB 输出这个项目的结构不复杂但每一级都有明确的职责。图片数据预先存在片内 ROM 里ROM 的地址由一组行列计数器生成计数器本身又必须严格跟随 VGA 的行场同步信号走。换句话说VGA 时序不只是输出给显示器的它同时充当了 ROM 的“读地址发生器”。整个通路可以拆成四段时钟分频与像素时钟生成、行场同步计数器、ROM 地址映射、RGB 数据输出。常见做法是让 ROM 输出位宽等于一个像素的颜色深度比如 RGB565 就是 16 位RGB332 就是 8 位。地址位宽则由图片分辨率决定例如 640×480 的图片总像素数是 307200地址至少需要 19 位2^19 524288。这里有个容易忽略的点VGA 的可见区域只是行场周期的一部分消隐区同样占用时钟周期所以地址计数器不能简单地从 0 数到 307199而要在可见区域内才递增消隐区保持或复位。// 行场计数器核心逻辑简化示意 always (posedge pixel_clk or negedge rst_n) begin if (!rst_n) begin h_cnt 0; v_cnt 0; end else begin if (h_cnt H_TOTAL - 1) begin h_cnt 0; if (v_cnt V_TOTAL - 1) v_cnt 0; else v_cnt v_cnt 1; end else begin h_cnt h_cnt 1; end end end // 可见区域判断只有在这里才输出有效像素 wire visible (h_cnt H_ACTIVE) (v_cnt V_ACTIVE); wire [18:0] rom_addr visible ? (v_cnt * H_ACTIVE h_cnt) : 19d0;这段代码里H_TOTAL和V_TOTAL是包含消隐区的总周期数H_ACTIVE和V_ACTIVE是实际可见分辨率。rom_addr只在可见区域才计算真实地址消隐区给 0 或者保持都可以关键是不要让 ROM 在无效区域输出干扰数据。参数上如果你的图片是 800×600就把H_ACTIVE改成 800V_ACTIVE改成 600同时 ROM 深度要相应加大。2.2 ROM 初始化图片怎么变成 .mif 文件FPGA 的片内 ROM 通常用 IP 核生成Quartus 里叫 ROM: 1-PORTVivado 里可以用 Block Memory Generator。不管哪个平台都需要一个初始化文件常见格式是.mifMemory Initialization File或.coeCoefficient File。图片不能直接丢进去得先转成文本格式的像素数据。我一般会写一个简单的 Python 脚本做转换把 BMP 或 PNG 的每个像素提取出来按 RGB565 或 RGB332 编码成十六进制再按.mif的格式写出来。这一步看起来是“杂活”但格式错了后面全白搭。# 图片转 .mif 的简化脚本以 RGB565 为例 from PIL import Image img Image.open(test.bmp).convert(RGB) w, h img.size pixels list(img.getdata()) with open(rom_init.mif, w) as f: f.write(fWIDTH16;\nDEPTH{w*h};\n\n) f.write(ADDRESS_RADIXHEX;\nDATA_RADIXHEX;\n\nCONTENT BEGIN\n) for i, (r, g, b) in enumerate(pixels): # RGB565: R占5位G占6位B占5位 rgb565 ((r 3) 11) | ((g 2) 5) | (b 3) f.write(f{i:05X} : {rgb565:04X};\n) f.write(END;\n)这段脚本的关键在rgb565的拼接红色右移 3 位取高 5 位绿色右移 2 位取高 6 位蓝色右移 3 位取低 5 位然后按位或起来。生成的.mif文件里WIDTH16对应 ROM 输出位宽DEPTH是总像素数。如果你的图片是 640×480DEPTH就是 307200。注意地址用十六进制写数据也用十六进制Quartus 的 ROM IP 核能直接识别这个格式。2.3 VGA 时序参数行场同步到底怎么算VGA 时序是很多新手翻车的地方。行同步和场同步都有四个区间同步脉冲、后沿、可见区域、前沿。以 640×48060Hz 为例像素时钟是 25.175MHz行总周期 800 个像素时钟其中同步脉冲 96 个后沿 48 个可见区域 640 个前沿 16 个。场总周期 525 行同步脉冲 2 行后沿 33 行可见区域 480 行前沿 10 行。这些数字不是随便定的是 VGA 标准里规定的。写代码时我习惯把参数定义成parameter方便换分辨率时统一改。// 640x48060Hz 时序参数 parameter H_ACTIVE 640; parameter H_FRONT 16; parameter H_SYNC 96; parameter H_BACK 48; parameter H_TOTAL H_ACTIVE H_FRONT H_SYNC H_BACK; // 800 parameter V_ACTIVE 480; parameter V_FRONT 10; parameter V_SYNC 2; parameter V_BACK 33; parameter V_TOTAL V_ACTIVE V_FRONT V_SYNC V_BACK; // 525同步脉冲的极性也要注意VGA 通常是行同步负极性、场同步负极性也就是在同步区间输出低电平。如果你发现显示器提示“无信号”或者画面偏移先检查这两个极性再检查像素时钟是不是准确。像素时钟一般用 PLL 从板载晶振倍频或分频得到50MHz 晶振分频到 25MHz 是最常见的做法。3. 从零跑通工程搭建、引脚分配与上板验证3.1 工程目录与模块划分一个干净的工程目录能让后面改起来省心。我一般会这样组织vga_rom_display/ ├── rtl/ │ ├── top.v // 顶层模块例化所有子模块 │ ├── vga_timing.v // 行场同步与像素计数 │ ├── rom_reader.v // ROM 地址生成与数据读取 │ └── pll_25m.v // 像素时钟生成IP 核 ├── ip/ │ └── rom_1port.v // ROM IP 核 ├── sim/ │ └── tb_top.v // 仿真测试文件 ├── mif/ │ └── rom_init.mif // 图片初始化文件 └── quartus_prj/ // 工程文件顶层模块只做例化和连线不写具体逻辑。vga_timing.v负责产生hsync、vsync和当前像素坐标。rom_reader.v根据坐标算出 ROM 地址并把 ROM 输出的像素数据打一拍送到 VGA 端口。这样分的好处是换图片只改.mif换分辨率只改vga_timing.v里的参数互不影响。3.2 顶层模块连线与引脚分配顶层模块的写法很直接把 PLL、ROM、时序模块和输出端口连起来就行。module top ( input wire clk_50m, // 板载 50MHz 晶振 input wire rst_n, // 复位按键低有效 output wire vga_hsync, output wire vga_vsync, output wire [4:0] vga_r, output wire [5:0] vga_g, output wire [4:0] vga_b ); wire pixel_clk; wire [9:0] h_cnt, v_cnt; wire [18:0] rom_addr; wire [15:0] rom_data; wire visible; // PLL 生成 25MHz 像素时钟 pll_25m u_pll ( .inclk0(clk_50m), .c0(pixel_clk) ); // VGA 时序 vga_timing u_timing ( .pixel_clk(pixel_clk), .rst_n(rst_n), .h_cnt(h_cnt), .v_cnt(v_cnt), .hsync(vga_hsync), .vsync(vga_vsync), .visible(visible) ); // ROM 地址生成 rom_reader u_reader ( .h_cnt(h_cnt), .v_cnt(v_cnt), .visible(visible), .rom_addr(rom_addr) ); // ROM IP 核 rom_1port u_rom ( .address(rom_addr), .clock(pixel_clk), .q(rom_data) ); // RGB565 拆成 R/G/B 三路输出 assign vga_r visible ? rom_data[15:11] : 5d0; assign vga_g visible ? rom_data[10:5] : 6d0; assign vga_b visible ? rom_data[4:0] : 5d0; endmodule引脚分配是上板前最后一道坎。不同开发板的 VGA 接口引脚不同常见的是电阻网络 DAC 接在 GPIO 上。你需要查开发板原理图把vga_hsync、vga_vsync、vga_r[4:0]、vga_g[5:0]、vga_b[4:0]一一对应到实际引脚。Quartus 里在 Pin Planner 里填Vivado 里在 XDC 文件里写。分配错了不会烧板子但画面会花屏或者完全没显示。3.3 仿真验证与上板调试上板之前我强烈建议先跑一遍仿真。写一个简单的 testbench给 50MHz 时钟和复位观察hsync、vsync和vga_r/g/b的波形。重点看三个地方行同步周期是不是 800 个像素时钟场同步周期是不是 525 行可见区域的 RGB 输出是不是和.mif里的数据对得上。// 简化 testbench initial begin clk_50m 0; rst_n 0; #100 rst_n 1; #1000000 $stop; end always #10 clk_50m ~clk_50m; // 50MHz仿真通过后再上板。上板时如果画面偏了调H_BACK和V_BACK如果颜色不对检查 RGB565 的位拼接顺序如果画面闪烁检查像素时钟是不是稳定在 25MHz。这些排查思路在下一章会展开。4. 避坑与排查ROM 初始化、时序偏差、颜色错位怎么查4.1 现象显示器提示“无信号”但 FPGA 已经烧录成功原因通常是行场同步信号没有输出或者极性反了。先确认hsync和vsync在仿真里有没有正常翻转再检查引脚分配是不是接反了。有些开发板的 VGA 接口把行场同步标成HS和VS别和H_SYNC、V_SYNC搞混。解决方法是拿示波器或者逻辑分析仪量一下实际引脚看有没有 31.5kHz 左右的行频和 60Hz 左右的场频。4.2 现象画面显示出来了但图片是斜的或者错位这是 ROM 地址计算和 VGA 可见区域没对齐。常见错误是地址计数器在消隐区也递增导致可见区域的第一行读到了上一行末尾的数据。检查rom_addr是不是只在visible为高时才用v_cnt * H_ACTIVE h_cnt计算消隐区是不是给了固定值。另外确认H_ACTIVE和图片宽度一致图片是 640 宽就不能写成 800。4.3 现象颜色整体偏红或偏蓝RGB565 的位拼接顺序错了。rom_data[15:11]是红色[10:5]是绿色[4:0]是蓝色这个顺序不能乱。如果你在 Python 脚本里拼成了 BGR显示出来就是红蓝互换。解决方法是拿一张纯红、纯绿、纯蓝的测试图分别跑一遍看输出对不对。4.4 现象画面有轻微抖动或撕裂像素时钟和 VGA 时序不匹配。检查 PLL 输出的时钟是不是准确的 25.175MHz有些板子用 25MHz 也能显示但长时间看会有轻微抖动。另外确认pixel_clk是不是只驱动了 VGA 相关逻辑没有混入其他高频逻辑。如果 ROM 读数据有延迟记得在 RGB 输出前打一拍保证时序收敛。4.5 现象换了一张图片后 ROM 编译报错.mif文件的DEPTH和 ROM IP 核配置的深度不一致。比如图片是 640×480DEPTH应该是 307200但 IP 核里可能还留着上一次的 800×600 的 480000。改 IP 核配置重新生成或者直接改.mif里的DEPTH值两边必须一致。另外地址位宽也要够307200 需要 19 位地址IP 核里地址位宽设成 18 位就会溢出。5. 二次开发换分辨率、加多图切换与动态效果的改造思路5.1 换分辨率从 640×480 到 800×600 要改哪些地方换分辨率不是改一个参数就完事涉及四个地方联动。第一vga_timing.v里的H_ACTIVE、V_ACTIVE和对应的前后沿、同步脉冲参数要按新标准改。800×60060Hz 的像素时钟是 40MHz行总周期 1056场总周期 628。第二PLL 要重新配置输出 40MHz 而不是 25MHz。第三ROM 的深度要改成 800×600480000地址位宽至少 19 位2^19524288。第四.mif文件要重新生成图片也要缩放到 800×600。我一般会把这些参数集中到一个vga_config.vh头文件里用define定义换分辨率时只改这一个文件。这样比散落在各个模块里改要靠谱得多。5.2 多图切换用按键或状态机控制 ROM 地址偏移如果 ROM 里存了多张图片比如四张 640×480 的图总深度就是 4×3072001228800地址需要 21 位。切换图片的本质就是给 ROM 地址加一个基址偏移。// 多图切换按键选择图片基址 reg [1:0] img_sel; always (posedge pixel_clk or negedge rst_n) begin if (!rst_n) img_sel 0; else if (btn_pressed) img_sel img_sel 1; end wire [20:0] base_addr img_sel * 307200; wire [20:0] rom_addr_full visible ? (base_addr v_cnt * H_ACTIVE h_cnt) : 21d0;这里btn_pressed需要做按键消抖否则按一次会跳好几张。消抖可以用计数器实现检测到按键稳定 20ms 后再产生一个时钟周期的脉冲。img_sel从 0 到 3 循环对应四张图的基址。注意 ROM 深度要够地址位宽也要相应加大。5.3 动态效果用计数器做图片滚动或淡入淡出静态图片显示跑通后可以加一点动态效果。最简单的滚动效果就是让 ROM 地址在行方向加一个偏移量偏移量随时间递增。// 水平滚动每帧偏移量加 1 reg [9:0] scroll_x; always (posedge vsync or negedge rst_n) begin if (!rst_n) scroll_x 0; else scroll_x scroll_x 1; end wire [9:0] h_shift (h_cnt scroll_x) % H_ACTIVE; wire [18:0] rom_addr visible ? (v_cnt * H_ACTIVE h_shift) : 19d0;scroll_x在每个场同步到来时加 1h_shift是加上偏移后对宽度取模保证地址不越界。这样画面就会水平滚动。淡入淡出稍微复杂一点需要把 RGB 值乘以一个衰减系数系数随时间变化。可以用乘法器或者查表实现但要注意时序乘法器会引入延迟需要在 RGB 输出前对齐。5.4 验证方法用仿真波形和上板对比每次改完我都会先跑仿真看波形。重点看三个信号rom_addr在可见区域是不是连续递增vga_r/g/b是不是和.mif里的数据一致hsync和vsync的周期是不是符合新分辨率的参数。仿真通过后再上板上板时先显示测试图确认颜色和位置都对再换正式图片。从那以后我每次改 VGA 时序参数都强制走一遍“仿真看波形 → 上板看测试图 → 换正式图”的流程不再直接烧板子碰运气。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

本地大模型部署工具实测对比:Ollama、LM Studio、LocalAI

本地大模型部署工具实测对比:Ollama、LM Studio、LocalAI

如果你最近想把大模型部署到自己电脑上,八成绕不开这三款软件:Ollama、LM Studio(标题里写的LMstdio应该是笔误,官网实际叫LM Studio)和LocalAI。我在同一台机器上把这三款都装上、跑通、拉模型、压测,前后…

2026/10/19 4:45:00 阅读更多 →
CNN-GRU-Attention负荷预测实战:混合神经网络原理与工程实现

CNN-GRU-Attention负荷预测实战:混合神经网络原理与工程实现

负荷预测是个老话题,尤其到了调度和交易环节,预测误差直接关系到要不要多备一组机组、少签一笔偏差考核。传统方法折腾过 ARIMA、指数平滑之后,我慢慢把重心移到了深度学习上。最近在做的这个项目,用的就是 CNN-GRU-Attention 混合…

2026/10/19 4:45:00 阅读更多 →
美赛B题O奖论文复盘:贝叶斯更新与动态路径规划搜索模型

美赛B题O奖论文复盘:贝叶斯更新与动态路径规划搜索模型

1. 从“失联”到“寻回”:这道题在解决什么问题1.1 2024年美赛B题到底想让你做什么2024年美赛B题“Searching for Submersibles”的场景,很多参赛者看到第一反应是“这不是给搜救队做的吗?”。题目描述的是一艘载人潜水器在某海域执行作业时失…

2026/10/19 4:44:00 阅读更多 →

最新新闻

BERT微调实战:IMDB影评情感二分类源码全流程解析与避坑指南

BERT微调实战:IMDB影评情感二分类源码全流程解析与避坑指南

简介:基于BERT模型的情感分析项目,是一套面向自然语言处理初学者与算法工程师的Python源码包,聚焦IMDB影评正面/负面情感二分类,帮助理解BERT预训练模型微调、特征抽取及分类器设计等关键环节。资源共5个文件,其中4个P…

2026/10/19 6:08:40 阅读更多 →
用Matlab完成测风塔风能资源评估:从数据清洗到发电量估算

用Matlab完成测风塔风能资源评估:从数据清洗到发电量估算

气象塔(风电行业里叫测风塔)出来的历史风力数据,看起来就是一堆时间和风速的表格,但真正能把它变成风能资源评估结论的人,并不算多。我刚接触这个领域的时候也犯过“拿数据就求平均”的错,结果被前辈按在工…

2026/10/19 6:08:40 阅读更多 →
单矢量控制下的占空比计算:有效抑制电流纹波与噪声

单矢量控制下的占空比计算:有效抑制电流纹波与噪声

做数字电机控制的人应该都经历过这种尴尬:算法层面跑得好好的,DTC或者有限集模型预测控制(FCS-MPC)每个控制周期都选了那个“最优电压矢量”,上示波器一看,电流纹波比SVPWM大一块,高频噪声还特别…

2026/10/19 6:08:40 阅读更多 →
AUV滑模控制器设计与Simulink仿真:从动力学建模到参数整定全解析

AUV滑模控制器设计与Simulink仿真:从动力学建模到参数整定全解析

1. AUV运动控制的核心难点与滑模控制的切入点自主水下机器人(AUV)的控制一直是个让人又爱又恨的问题。先说结论:在水下环境里,模型不确定性大、海流干扰强、参数摄动严重,传统的PID想同时搞定“深度保持”“航向稳定”…

2026/10/19 6:08:40 阅读更多 →
macOS终端重构:iTerm2+zsh打造可编程工作台

macOS终端重构:iTerm2+zsh打造可编程工作台

简介:本资源是一套面向 macOS 用户的终端环境深度优化配置方案,专为希望提升开发效率、定制化体验的程序员与系统爱好者设计,解决原生 Terminal 功能单一、主题简陋、插件缺失等痛点。压缩包包含 4489 个文件,主体为 1050 个 zsh …

2026/10/19 6:08:40 阅读更多 →
论文查重与AI率检测全攻略:原理、工具与降重技巧

论文查重与AI率检测全攻略:原理、工具与降重技巧

每年到了毕业论文提交季,最让人焦虑的往往不是论文本身有多难写,而是“查重”那一关。更麻烦的是,近两年几乎所有高校都多了一条新指标:AI生成内容检测。也就是说,论文不仅要控制重复率,还要控制“AI率”。…

2026/10/19 6:07:40 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 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 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 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/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练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 阅读更多 →