跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
汇编算法

基于 FPGA 实现 NVMe 硬盘读写功能

综述由AI生成在 FPGA 上作为 Root Complex 控制 NVMe 硬盘的实现流程。涵盖 PCIe 总线架构、TLP 事务、配置空间初始化(RC/EP)、BAR 设置、MSI-X 中断配置及 NVMe 控制器寄存器(AQA、ASQ、ACQ、CC)配置。详细阐述了 Admin 命令(Identify、队列创建)与 IO 命令(Read/Write)的交互过程及 DoorBell 机制。最后通过实际测试验证了读写性能,读速约 1260MB/S,写速约 840MB/S。

云间漫步发布于 2026/4/6更新于 2026/5/2138 浏览
基于 FPGA 实现 NVMe 硬盘读写功能

概述

PCIe 总线架构如下:

PCIe 总线架构

在以往的 FPGA 板卡与上位机的开发使用 PCIe 总线时,FPGA 常作为 Endpoint 使用;而在 FPGA 外挂 NVMe 硬盘的应用中,FPGA 作为 Root Complex,NVMe 硬盘作为 Endpoint 使用。NVMe 协议是基于 PCIe 总线实现的,属于 PCIe 的一种特殊应用。

PCIe 概述

PCIe 协议主要涉及两种机制、三层结构、四种事务类型。

数据交换是基于请求与完成(响应)的机制,两种模式:发送数据不需要接收端响应,要求接收端对发送数据进行响应。

分为事务层、链路层和物理层,以此实现用户数据的交互。用户主要关注事务层构造/解析各类包,但 PCIe 比 SRIO 复杂的多。

数据类型被分为内存、I/O、配置和消息四类,分别完成不同的功能。

TLP 事务层的包

TLP 包的头部如下:64bit 地址为 4DW,32bit 地址为 3DW。具体含义可参考 PCIe Specification。应用时是多少位的地址可参考 PCIe 的 BARs 设置。

TLP Header

Fmt:Format of TLP,3bit,用来确定 TLP 包头格式是 3DW 还是 4DW;Fmt 与下面的 type 配合就可以表示 PCIe 支持的所有类型了。

TLP Format

TYPE 这个 5bit 数据,表示传输类型的,与 fmt 结合,就确定了数据包的属性。PCIe 的事务类型有存储、I/O、配置和消息但完成这些事务类型的数据交换有多种数据包:比如枚举 NVME SSD 的过程涉及 CfgRd0、Cpl、CplD。

TLP Type

FPGA 开发 NVMe 硬盘过程中主要用到 MRd、MWr、CfgRd0、CfgWr0、Cpl、CplD、Msg。

PCIe 配置空间

RC(FPGA)、EP(NVMe)都需要访问配置其 PCI 配置空间,结构如下图,配置空间大小共 4KB。PCIe 设备的每个 Function 都对应一个配置空间。**0-3Fh(64 字节)**是 PCI 兼容配置空间头,按类型可分为 Type 0 和 Type 1。PCIe 设备使用 Type 0 配置空间头,PCIe 桥使用 Type 1 配置空间头。**40h-FFh(192 字节)**主要存放一些与 MSI 或者 MSI-X 中断和相关的能力结构体 Capability Structures。**100h-FFFh(3840 字节)**是 PCIe 协议扩展的配置空间,主要存放设备序列号等 Capability Structures。

Config Space

其前 64 字节的头结构如下图:(本文只涉及 Type0)

Type 0 Header

能力结构体是 PCIe 开发过程中必须扫描获取的。能力结构体的具体配置信息位于 PCIe 配置空间的第 65byte~256byte 之间共 192 字节。设备类型不同具有不同的能力类型,根据 Capability Pointer 可获取设备支持的所有能力类型。第一个 Capabilities Structures 的地址由配置空间 Header 的 Capabilities Pointer 寄存器保存,可以据此遍历所有的 Capabilities Structures。Capabilities Structures 的链表,直至最后的指针为 0 为止。如下图所示:

Capability List

建链、枚举

建链

此过程在正确设置 FPGA 后,上电启动板子后自动完成建链。建立链接的过程如下:主要流程为上电后两侧根据 PCIe 总线协议进入 LTSSM 流程,链路双方自动协商速率和宽度,调节发送和接收参数。

只要无硬件问题,NVMe 先上电,FPGA 后上电,参考 UG477,LTSSM 的状态及相关含义:当两端设备正常上电后应处于 L0 状态。

LTSSM State

相关状态、参数等可通过 PCIe 核接口参数获取检测:

Link Status Link Width Link Rate

枚举

Active 后开始进行设备枚举。

PCIe 总线中的每一个功能都有一个唯一的标识符与之对应,该标识符为(Bus, Device, Function);RC(BUS 0) 搜索总线中 DBF 标识确定 PCIe 总线拓扑结构(CfgRd),通过读取 Function 的 VendorseID 寄存器来确认该节点是否存在,遍历整个 BDF 确定设备拓扑结构。

Enumeration

比如本次使用场景中只有一个 RC 和 EP;RC(FPGA)通过 CfgRd 访问 EP(NVME SSD),如果 RC 收到相应 CPLD 则代表 SSD 存在。RC 发起 CfgRd0 TLP 请求包,轮询 Device ID 和 Vendor ID、ClassCode,如果收到 CPLD 完成包则枚举到 NVMe 盘;如果收到 CPL 包,则没有查询到 SSD 盘。CPLD 数据部分包含了 EP 的 Device ID。

比如 NVMe 硬盘的 ClassCode 为 0x010802:当收到 CPLD 数据包且该值符合时,即枚举到 NVMe 硬盘。下图为 PC 上获取的 PCIe 总线下的设备信息:

PC Info Device Info

RC、EP 初始化流程

在实现 NVMe 硬盘读写前,RC(FPGA)、EP(NVMe)需要按顺序进行相应的 PCIe 配置空间初始化,之后进行 NVMe 控制器寄存器进行配置。

PCIe 配置空间是每个 PCIe 设备独立的一段内存区域,用于存储设备的配置信息。RC 在枚举设备时需要先访问配置空间,获取设备厂家、型号、类型、所需资源等信息,然后再分配资源,最后才能访问 PCIe 设备的存储或 IO 地址空间。本场景只使用设备配置空间 Type 0。

RC 的 PCIe 配置空间初始化

RC 的配置空间通过 PCIe IP 核的设置已经实现了其相关配置寄存器的初始化;比如 ID、BAR、能力结构等。

FPGA(RC)端的 PCIe 配置空间相关信息的访问、重配置可通过核的配置管理接口 CMI(Config Management Interface)进行实现的。

EP 的 PCIe 配置空间初始化

NVMe 硬盘的 PCIe 配置空间初始化需要按照如下流程操作:

① 获取 NVMe 设备的所有 Capabilities 结构体信息 ② 配置 NVMe 设备结构能力信息 ③ 配置 NVMe 设备 BAR 基地址 ④ 配置 NVMe 设备中断列表

获取 NVMe 设备的所有 Capabilities 结构体信息:

首先通过 CfgRd 访问寄存器地址为 0x34 的 Capability 指针,根据指针内容依次访问能力结构体链表直至结束(直至 Next Capability Pointer 值为 0),获取 EP 设备(NVMe 硬盘)的所有结构能力信息。

每一个 Capabilities Structures 都有一个独一无二的 Capability ID,该 ID 保存在 Capabilities Structures 的开始地址,系统软件根据此判断 Capabilities Structures 的类型。比如 Cap ID = 0x11 时表示该能力结构表示 MSI-X 中断。

Cap ID

配置 NVMe 设备的能力结构体信息:

Cap ID 为 0x10 即为 PCIe 能力结构,其指针即为该能力指针。

PCIe Cap

比如通过 CfgRd 访问能力结构体指针偏移为 0x04 的 Device Capabilities Register;通过 CfgWr 设置能力结构体指针偏移为 0x08 的 Device Control 寄存器。

配置 NVMe 设备 BAR 基地址:

如果基地址设置为 32 位,只需设置 BAR0 即可;如果设置基地址设置为 64 位,配置 BAR0 和 BAR1。消费级的 NVMe 硬盘一页对应 4KByte,通过 CfgWr 设置 TYPE0 配置空间偏移为 0x04 的 BAR0 为 4K 对齐并设置其具体基地址。

BAR Config

配置 NVMe 设备 MSI-X 中断列表:

MSI-X 的中断机制是向 RC 的某个地址写 Message 数据以产生中断。MSI-X 每个中断都有独立的 Message Address 和 Message Data,Message Address 和 Message Data 组成一个中断向量表,MSI-X 使用了独立的中断 Pending 表。中断向量表和中断 Pending 表存放在 BAR 空间中。因此 MSI-X 支持的中断数量更多,且不需要中断号连续。

MSI-X Capability Structures 主要的作用是记录中断向量表和 Pending 表保存的位置。MSI-X Capability Structure 如下图所示。

MSI-X Cap

Table BIR 指定中断列表处于哪个 table bir 值的 BAR 下,偏移地址由 Table Offset 指定。Message Control 寄存器位域的定义如下表所示:

位域定义描述属性
15MSI-X EnableMSI-X 中断机制使能位,当 MSI、MSI-X 和 INTx 中断只能使用其中一个RW
14Function MaskMSI-X 中断全局 Mask 位,当此位为 1 时,无论 Pending 表如何设置,所有中断都会被屏蔽RO
13:11Reserved保留RsvdP
10:0Table SizeMSI-X 中断向量表的大小,存放 Message Address 和 Message Data。若系统软件读取的值为 0x3,则中断向量表的大小为 4 字节。RO
NVMe 控制器的寄存器配置

在完成上述 FPGA 和 NVMe 硬盘的 PCI 和 PCIe 寄存器配置后,需要按照下述流程进行 NVMe 控制器配置:(主要涉及 MRd 和 MWr,内容太多不再展开)

① 等待 CSTS.RDY 变为 0;否则通过一系列配置使其满足该状态; ② 配置 AQA、ASQ、ACQ 寄存器; ③ 配置 CC 寄存器; ④ 将 CC.EN 置 1; ⑤ 等待 CSTS.RDY 置 1;

NVMe 控制器寄存器位于其 BAR0、BAR1 所映射的内存空间中,该 BAR 不同偏移地址对应的寄存器如下:

Reg Map 1 Reg Map 2 Reg Map 3

偏移量 0x1000 的为 DoorBell 寄存器,DB 寄存器定义如下:

DoorBell 1 DoorBell 2

相关寄存器的含义:

CAP:控制器能力,定义了内存页大小的最大最小值、支持的 I/O 指令集、DB 寄存器步长、等待时间界限、仲裁机制、队列是否物理上连续、队列大小; VS:版本号,定义了控制器实现 NVMe 协议的版本号; CC:控制器配置,定义了 I/O SQ 和 CQ 队列元素大小、关机状态提醒、仲裁机制、内存页大小、支持的 I/O 指令集、使能; CSTS:控制器状态,包括关机状态、控制器致命错误、就绪状态; AQA:Admin 队列属性,包括 SQ 大小和 CQ 大小; ASQ:Admin SQ 基地址; ACQ:Admin CQ 基地址; 1000h 之后的寄存器定义了队列的头、尾 DB 寄存器。

CAP 寄存器标识的是 Controller 具有多少能力,而 CC 寄存器则是指当前 Controller 选择了哪些能力,可以理解为 CC 是 CAP 的一个子集;如果重启(reset)的话,可以更换 CC 配置; CC.EN 置 1,表示 Controller 已经可以开始处理 NVMe 命令,从 1 到 0 表示 Controller 重启; CC.EN 与 CSTS.RDY 关系密切,CSTS.RDY 总是在 CC.EN 之后由 Controller 改变置为 1,其他不符合执行顺序的操作都将产生未定义的行为; Admin 队列由 host 直接创建,AQA、ASQ、ACQ 三个寄存器标识了 Admin 队列,而其他 I/O 队列则由 Admin 命令创建; Admin 队列的头、尾 DB 寄存器标识为 0,其他 I/O 队列标识由 host 按照一定规则分配;只有 16bit 的有效位,是因为队列深度最大 64K。

NVMe 协议命令

在完成上述操作后,即可通过 Admin 命令操作 NVMe 硬盘:

Host 通过 Identify 命令,确定 Controller 的数据结构等; Host 通过 set/get features 获取 I/O SQ 和 CQ 信息,配置中断机制等; Host 分配适当的 I/O CQ、SQ 队列; 然后才可发起对 NVMe 硬盘的读写 IO 指令。

NVMe 协议命令

NVMe 命令主要分为 Admin 命令和 IO 命令,根据位于的队列分类;Admin 命令只能提交到 Admin SQ CQ 中,主要负责管理 NVMe 控制器的一些控制指令。IO 命令只能提交到 I/O SQ CQ 中,主要负责完成数据的传输。

Command Types

命令均为 16 DW,具有相同的格式,某些字段根据命令的不同有不同的定义。

Dword0CID、传输方式、聚合操作、操作码
1NID(命名空间 ID)
2保留
3保留
4、5元数据指针 (MPTR)
6-9数据指针(DPTR)
10-15根据命令指定

完成命令具有相同的格式,某些字段根据命令的不同有不同的定义。

Dword0根据命令指定
1保留
2SQID、SQ 头指针
3状态域、P 位、CID
Admin 命令

Admin 命令执行的操作类型是通过 Dword0 中的 8 位操作码定义,通过 SQID(提交队列 ID)+CID(命令 ID)唯一标识完成的命令。常用的命令如下:

操作码指令作用
00h删除 I/O SQ释放 SQ 空间
01h创建 I/O SQ分配给 SQ 的地址、队列优先权、队列大小
02h获取日志返回所选日志页于缓冲区
04h删除 I/O CQ释放 CQ 空间
05h创建 I/O CQ分配给 CQ 的地址、中断向量、队列大小等
06hIdentify返回关于 controller 与 namespace 能力和状态的数据结构(2k 字节)
09h设置 features根据 FID 设置相应的 features
0Ah获取 features根据 FID 返回队列数量、仲裁信息等
80h格式化擦除 LB 内容

Admin 队列是通过配置 ASQ 等寄存器创建的;先创建 CQ 再创建 SQ。

IO 命令

IO 命令主要进行数据读写,NVMe 硬盘的读写以 LB 为单位进行。

IO 命令与 Admin 命令结构完全相同,也是通过 DW0 中的 8 位操作码来定义命令类型的。

操作码指令作用
00hFlush将数据(和元数据)提交到 NVM 中,所有命令都要执行
01hWrite将数据写入 NVM 中
02hRead读 NVM 中的数据
04hWrite Uncorrectable标记无效数据块
05hCompare比较从 NVMe 读出的数据和比较数据缓冲区的数据
命令交互过程

命令交互过程如下图所示:

Interaction 1 Interaction 2

对于 FPGA 与 NVMe 硬盘的Admin 命令交互过程为:

① host(FPGA)将 16 DW 的 Admin 命令(1 条或者多条)写入提前分配好的 SQ 中(FPGA 准备好命令,等待 Controller 获取时发出) ② host(FPGA) 通过 NWr 将 DoorBell 写入到 Controller(NVMe) 的提交队列 SQ0TDBL 中(地址为 BAR0+0x1000)。(DB 中的数据 SQT-上次 DB 中的 SQT 值 = 本次待执行的命令个数,SQT 值最大不能超过 NVMe 控制寄存器中的 AQA 属性值队列深度) ③ NVMe(通过 HDB 和 TDB 可以判断是否有未完成命令)通过(MRd)向地址 ASQ 的 DW 地址(上章节配置的 NVMe 寄存器 ASQ),发起读取每个命令 16DW 的请求,以用于获取 FPGA 准备的命令。Host(FPGA) 发起(CPLd),即 1 中准备好的每条命令 16DW 给 Controller(NVMe 硬盘) ④ Controller(NVMe 硬盘) 执行具体命令 ⑤ NVMe 硬盘在命令完成后,通过 MWr 将完成命令 4DW 写入 host 内存 SQ 对应的 ACQ 偏移地址中 (ACQ 基地址为上节配置的 NVMe 寄存器 ACQ) ⑥ NVMe 硬盘执行完上述后,发起中断(比如常用的 MSI-X,能够支持 2K 个中断向量。在产生 MSI-X 中断信息前,需要检查该中断在相应寄存器中不被屏蔽),向中断地址写入中断信息 ⑦ host(FPGA) 接收到中断信息,得知 NVMe 硬盘已处理完相应的命令 ⑧ Host(FPGA) 通过 MWr 发起 DoorBell 写入到 Controller(NVMe) 的完成队列中(即 CQ0HDBL,地址为 BAR0+0x1004);本次 DB 中的数据 CQH-上次 DB 中的 CQH 值 = 本次执行的命令个数,CQH 值最大不能超过 NVMe 控制寄存器中的 AQA 属性值队列深度。

Admin 命令执行顺序

RC 在读写 NVMe 硬盘前需要按顺序执行如下操作执行 Admin 命令:

① 执行 Identify 命令,获取 Controller 的数据结构 ② 执行 Set Features 命令,申请 IO 队列数量 ③ 执行创建 IO 完成队列命令 ④ 执行创建 IO 提交队列命令 ⑤ 执行创建 IO 完成队列命令 ⑥ 执行创建 IO 提交队列命令

**创建的 IO 完成、提交队列数(最大 4K,最小 2 个)**与 NVMe 控制器中的 AQA 寄存器中 ACQS 和 ASQS 数量相对应,与 Set Features(0x09) 命令,申请 IO 队列数量一致。

上述每条 Admin 的命令交互过程如上节图示,每条命令由 host 提交到内存中的 SQ 队列中,更新 TDBxSQ 后,NVMe 控制器通过 DMA 的方式将 SQ 中的命令(怎么取,如何取,取多少,因命令而异)取到控制器缓冲区,执行命令;执行完成后,根据执行状态,组装完成命令,通过 DMA 的方式将完成命令写入内存 CQ 的队列中;NVMe 控制器通过 MSI-X 中断方式通知 host 已完成命令;最后,host 处理 CQ 命令,更新控制器中 HDBxCQ,标识着该条命令完成。

IO 命令执行实现读写硬盘

在完成上述操作后,即可通过 IO 命令读写 NVMe 硬盘。

至少建立两个 IO 队列,一个用于写操作,一个用于读操作。操作码 0x01 表示写数据到 NVMe 硬盘,操作码 0x02 表示从 NVMe 硬盘读出数据。

需要设计 PRP1、PRP2 或 SGL 方式,IO 队列的深度,准备好提交对应页数的 IO 读/写命令后及 sub DB,通知 NVMe 进行相应的读写操作。

IO 读写命令交互过程与 Admin 命令类似,只是响应地址的不同。

NVMe 硬盘读写测试

硬件信息

硬件测试涉及 FPGA 板卡及 M.2 接口等,为节省成本采用拆机盘测试。有条件的建议直接上工业盘。

本次测试的硬盘标签型号信息如下:共 512GB 容量,其中LB 为 512 字节,LBA 数量共 1000215216 个,约 477GB;之后的测试 FPGA 可将相关信息获取出来。

Disk Label

FPGA 板卡为 7 系列的,在此系列上 PCIe Gen2 x4 的理论速率上限为 2GB/S。

文件系统

NVMe 硬盘支持 NTFS、exFAT 等文件系统。

FPGA 纯逻辑实现文件系统中的文件读写,难度不大,但是繁琐。

本文测试不包含文件系统;对于很多应用场景,比如数据采集回放、记录仪等不使用文件系统也可以完成,只需记录每次的数据量 LBA 的地址范围即可,数据回放时根据 LB 号对应读取即可。

硬盘信息获取

程序启动后可获得的 NVMe 硬盘信息如下:

Info Display

w_Namespace_Size:NVMe 硬盘 LB 数目,0x3B9E12B0 即 1000215216,与硬盘标签信息一致; w_lb_size:每个 LB 的字节数 2^lb_size,即512B,与硬盘标签信息一致; w_nvme_config_done:相关配置已完成,NVMe 硬盘具备读写状态 w_link_width:lane 数 000100=x4 w_link_rate:gen 0010=5.0GT/s,与 FPGA 系列速率相符 w_user_app_rdy、w_user_lnk_up、w_pl_phy_lnk_up:PCIE 核的状态 w_pl_ltssm_state:L0 状态

读和写功能测速

通过 VIO 控制对硬盘的读写过程,由于BAR 设置中为 4K 对齐,即每页大小 4KB;比如 1GB 的读写数据量大小为 0x40000 个页。设置相应的页起始地址、操作的页数,再使能对应的读写使能即可进行硬盘读写测试。

分别测试 1GB、10GB、50GB、100GB 的数据量读和写 SSD 的速率。读、写测速结果如下(为节省时间每种容量只做 1 次实验,不再进行多次比对):

测试数据量写速率读速率
1GB1094MB/S1251MB/S
10GB920MB/S1261MB/S
50GB843MB/S1262MB/S
100GB836MB/S1261MB/S

写 1GB 的测试:根据用户时钟(125Mhz)计数的时间为 116938546 x 8ns,写速率为 1094MB/S。

Write Speed

读 1GB 的测试:根据用户时钟(125Mhz)计数的时间为 102270401 x 8ns,读速率为 1251MB/S。

Read Speed

目录

  1. 概述
  2. PCIe 概述
  3. TLP 事务层的包
  4. PCIe 配置空间
  5. 建链、枚举
  6. 建链
  7. 枚举
  8. RC、EP 初始化流程
  9. RC 的 PCIe 配置空间初始化
  10. EP 的 PCIe 配置空间初始化
  11. NVMe 控制器的寄存器配置
  12. NVMe 协议命令
  13. NVMe 协议命令
  14. Admin 命令
  15. IO 命令
  16. 命令交互过程
  17. Admin 命令执行顺序
  18. IO 命令执行实现读写硬盘
  19. NVMe 硬盘读写测试
  20. 硬件信息
  21. 文件系统
  22. 硬盘信息获取
  23. 读和写功能测速
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Stable Diffusion 训练实战:损失函数调优指南
  • JavaScript 混淆代码解密与 de4js 工具使用
  • HarmonyOS 底部导航栏组件 rc_concave_tabbar 使用指南
  • Llama-3.2-3B 参数详解与 Ollama 部署:3B 小模型高效推理方案
  • Flutter 在 OpenHarmony 上集成 mcp_server 实践指南
  • Windows 环境下安装配置 Git 完整指南
  • 机器学习:数据清洗与预处理(Python)
  • Python 开发环境搭建与基础入门指南
  • 腾讯云桌面 AI 智能体 WorkBuddy 开启内测
  • 基于 vLLM+Open-WebUI 快速部署 Qwen3-Embedding 模型
  • UniHttp 中 Xml 与 JavaBean 序列化的多种实现方式
  • C++ STL 优先队列(priority_queue)原理与模拟实现
  • 树莓派 4B 连接大疆 M300 无人机开发教程
  • 数据库事务隔离级别与 Spring 传播行为深度解析
  • 使用 AI 快速验证 GIT 环境配置方案
  • DeepSeek 各版本演进路线与核心特性对比
  • 使用 NVM 安装 Node.js 22 并配置国内镜像加速
  • OpenClaw 漏洞预警:AI 代理日志审计与风险追溯
  • 什么是人工智能?AI、机器学习与深度学习的关系
  • X-admin:基于 Layui 的轻量级前端后台管理框架

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online