<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>硬件日记</title><description>日常分享 · 硬件设计 · 经验踩坑</description><link>https://blog.tyh123.top/</link><language>zh_CN</language><item><title>[blog-MT-02] - 使用 cloudflare pages 托管博客网页的静态资源</title><link>https://blog.tyh123.top/posts/9942d232/</link><guid isPermaLink="true">https://blog.tyh123.top/posts/9942d232/</guid><pubDate>Wed, 20 May 2026 09:43:19 GMT</pubDate><content:encoded>&lt;h3&gt;前言&lt;/h3&gt;
&lt;p&gt;由于 github pages 在国内的访问速度实在是不稳定，在查询资料的时候发现 cloudflare 可以提供静态资源部署服务，并且访问速度还是不错的。所以本篇文章基于此而诞生。&lt;/p&gt;
&lt;h3&gt;部署步骤&lt;/h3&gt;
&lt;p&gt;在这之前请注册 cloudflare 账号。关于账号注册本篇不再赘述，跟着提示一步步走即可，或者直接使用 github 账号登陆，登陆后验证一下邮箱就行了。&lt;/p&gt;
&lt;h4&gt;创建页面&lt;/h4&gt;
&lt;p&gt;在注册好并成功登录之后，我们会来到如图所示的界面，然后根据图中箭头所示进入对应的页面。
&lt;img src=&quot;/img/posts/blog/02/goto_cloud_flare_pages.png&quot; alt=&quot;goto_pages&quot; /&gt;
来到这个页面之后，我们点击右上角的 Create Application
&lt;img src=&quot;/img/posts/blog/02/works_pages_create_app.png&quot; alt=&quot;&quot; /&gt;
然后就来到了这个页面，注意要选择下方的 Get started
&lt;img src=&quot;/img/posts/blog/02/pages_create_app.png&quot; alt=&quot;&quot; /&gt;
选择从 git 导入
&lt;img src=&quot;/img/posts/blog/02/get_started_page.png&quot; alt=&quot;&quot; /&gt;
选择你的 hexo &lt;strong&gt;源码仓库&lt;/strong&gt;，即包含文章 markdown 源码的仓库。 (注意：不是 github pages 仓库，你可以前往我图中的这个仓库查看一下：&lt;a href=&quot;https://github.com/ed333a/hexo_repo&quot;&gt;ed333a/hexo_repo&lt;/a&gt;)选择完毕后点击右下角的 Begining setup 蓝色按钮。
&lt;img src=&quot;/img/posts/blog/02/sel_repo.png&quot; alt=&quot;&quot; /&gt;
之后我们会来到这个页面，相关选项已在图中表示，配置好后往下翻找到 Save and Deploy 蓝色按钮，点击保存后开始自动部署。
&lt;img src=&quot;/img/posts/blog/02/pages_setup.png&quot; alt=&quot;&quot; /&gt;
之后自动部署好的静态网页将可以通过 &lt;code&gt;hexo-repo-e1a.pages.dev&lt;/code&gt; 这个链接访问。在这之前我已经创建好了一个静态资源网页，它的地址是 &lt;code&gt;hexo-repo-1qi.pages.dev&lt;/code&gt;。&lt;/p&gt;
</content:encoded></item><item><title>[blog-MT-01] - 博客建站过程</title><link>https://blog.tyh123.top/posts/f1ef2aaa/</link><guid isPermaLink="true">https://blog.tyh123.top/posts/f1ef2aaa/</guid><pubDate>Tue, 19 May 2026 22:33:31 GMT</pubDate><content:encoded>&lt;h3&gt;关于 Hexo&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Hexo&lt;/strong&gt; 是一个基于 Node.js 编写的快速、简洁且高效的&lt;strong&gt;博客框架&lt;/strong&gt;。它通过解析 Markdown 格式的源文件，结合主题模板，一键生成静态网页资源；用户可以将这些静态资源文件部署到如 Github Pages 的静态资源托管服务站点上，无需后端环境或数据库，即可拥有高性能、易于维护的个人博客站点。&lt;/p&gt;
&lt;h3&gt;配置 Node.js 环境&lt;/h3&gt;
&lt;p&gt;在安装 Hexo 之前，首先要配置 Node.js 环境，前往 &lt;a href=&quot;https://nodejs.cn/download/&quot;&gt;Node.js 中文网下载页面&lt;/a&gt; 下载适合你当前所使用的系统的 Node.js 安装包。&lt;/p&gt;
&lt;p&gt;:::warning
使用 Windows 10 或更高版本的系统时，通常建议 &lt;strong&gt;直接下载安装包 [Windows 安装包(.msi)]&lt;/strong&gt; 进行安装，以免因 PowerShell 执行策略阻止未签名脚本运行，造成 npm 脚本无法正常执行。
:::&lt;/p&gt;
&lt;h3&gt;安装 Hexo&lt;/h3&gt;
&lt;p&gt;安装好 Node.js 后，使用以下命令来全局安装 hexo&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ npm install hexo-cli -g
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装完成后，在控制台中输入以下命令后，控制台中应出现 hexo-cli 的安装版本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ hexo -v
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/img/posts/blog/01/cmd_hexo_v.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在此次安装过程中，我的控制台中提示了以下内容，这种情况一般是在安装 Node.js 时没有配置好系统变量。将 &lt;code&gt;%appdata%/npm&lt;/code&gt; 这个路径添加到系统变量中即可 (复制到文件资源管理器中的地址栏，然后获得绝对路径)。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ hexo: 无法将 “hexo” 项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写，如果包括路径，请确保路径正确，然后再试一次。
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;初始化 Hexo 目录&lt;/h3&gt;
&lt;p&gt;完成 Hexo 的安装后，在&lt;strong&gt;适当的位置&lt;/strong&gt;创建一个文件夹，用来存放 hexo 的项目文件。
&lt;img src=&quot;/img/posts/blog/01/hexo_project_folder.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;:::tip
&lt;strong&gt;这里的文件夹仅作为演示使用，无须严格按照图中所示的路径创建，你可以依照自己的文件管理习惯自己创建&lt;/strong&gt;
:::&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;进入这个文件夹&lt;/strong&gt;，然后执行以下命令&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ hexo init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;之后 Hexo 将会在文件夹下初始化一些必要文件，如图所示。
&lt;img src=&quot;/img/posts/blog/01/hexo_project_init.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;:::warning
Hexo 初始化时会从 Github 克隆一个模板仓库，有时受限于网络波动等因素，你可能无法完成初始化，此时请考虑&lt;strong&gt;使用网络代理&lt;/strong&gt;或其它方式完成初始化。
:::&lt;/p&gt;
&lt;p&gt;初始化完成后，使用以下命令安装 Node.js 模块&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ npm install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;至此，你已完成 Hexo 建站的所有前置条件，使用以下命令即可开启一个本地服务器用来预览你的 Hexo 页面&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ hexo s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在浏览器中输入控制台输出的地址 &lt;code&gt;localhost:4000&lt;/code&gt; 即可预览你的 hexo 页面
&lt;img src=&quot;/img/posts/blog/01/hexo_page_hello_world.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;:::tip
当默认端口被占用时，你可以在命令后面增加参数 &lt;code&gt;-p [端口号]&lt;/code&gt; 来指定一个未占用的端口。&lt;/p&gt;
&lt;p&gt;命令如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ hexo s -p 4001
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;:::&lt;/p&gt;
&lt;h3&gt;生成静态资源&lt;/h3&gt;
&lt;p&gt;使用以下命令可以生成部署到 Github Pages 的静态资源&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ hexo g
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生成好的静态资源位于 &lt;code&gt;public&lt;/code&gt; 文件夹下&lt;/p&gt;
</content:encoded></item><item><title>ceshi</title><link>https://blog.tyh123.top/posts/e6447b4a/</link><guid isPermaLink="true">https://blog.tyh123.top/posts/e6447b4a/</guid><pubDate>Thu, 14 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;nihao&lt;/p&gt;
</content:encoded></item><item><title>FPGA SPI 通信</title><link>https://blog.tyh123.top/posts/52a1f482/</link><guid isPermaLink="true">https://blog.tyh123.top/posts/52a1f482/</guid><pubDate>Sun, 19 Apr 2026 14:38:34 GMT</pubDate><content:encoded>&lt;h3&gt;简介&lt;/h3&gt;
&lt;p&gt;在之前讲 {% post_link &apos;FPGA 串口通信&apos; %} 的时候，有讲到过&lt;strong&gt;串口通信是异步通信&lt;/strong&gt;，而 SPI 通信是一个&lt;strong&gt;典型的同步通信&lt;/strong&gt;。它需要&lt;strong&gt;主设备驱动时钟信号线&lt;/strong&gt;，所有数据位的发送和接收都在时钟的边沿触发，收发双方不再需要各自校准波特率，也不用起始位/停止位等操作，因此相比于异步的串口通信，这种通信方式效率更高，时序更稳定。缺点则是占用了更多的管脚，在 PCB 的布局布线上相比于串口复杂一些。&lt;/p&gt;
&lt;h3&gt;标准 SPI(四线式) 接口&lt;/h3&gt;
&lt;p&gt;标准 SPI 接口采用的是四线式接线方式，有 SCLK、MOSI、MISO 以及 SS#/CS# 四根信号线。这种方式的 SPI 接口支持全双工通信，即同时接收和发送。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SCK/SCLK&lt;/strong&gt;：SPI 的串行时钟，由主设备 (Master) 驱动。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MOSI&lt;/strong&gt;：全称：Master Out Slave In，是标准 SPI 的一根数据线，由&lt;strong&gt;主机输出&lt;/strong&gt;，从机输入&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MISO&lt;/strong&gt;：全称：Master In Slave Out，是标准 SPI 的一根数据线，由&lt;strong&gt;从机输出&lt;/strong&gt;，主机输入&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SS#/CS#&lt;/strong&gt;：全称：Slave Select/Chip Select 由&lt;strong&gt;主机驱动的片选信号线&lt;/strong&gt;，用于多设备共享一个 SPI 总线时，选定指定设备有效的信号。&lt;strong&gt;该信号低电平有效&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;关于片选信号线：从机设备片选信号线为高电平时，不会接收来自 MOSI 数据线上的数据，同时设备本身也不会输出任何数据。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;下面是一张多设备共享 SPI 通信的连接方式图
&lt;img src=&quot;/img/posts/fpga_impl_interface/spi/spi_wiring.png&quot; alt=&quot;SPI 一主多从接线方式&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;半双工 SPI (三线式) 通信&lt;/h3&gt;
&lt;p&gt;该种接线方式将数据接口 (MISO、MOSI) 整合成了一个双向数据接口 (SDIO)，不是标准的 SPI 形式，这种 SPI 通信&lt;strong&gt;只支持半双工通信&lt;/strong&gt;，但这是一种&lt;strong&gt;常用和被广泛支持的变体&lt;/strong&gt;，通常称为 &quot;三线 SPI&quot; 或 &quot;半双工 SPI&quot;。三线式 SPI 通信同样支持多设备共享 SDIO 总线。&lt;/p&gt;
&lt;p&gt;用一张表格来对比四线式 SPI 和 三线式 SPI 的区别：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;对比项&lt;/th&gt;
&lt;th&gt;标准 SPI (四线式)&lt;/th&gt;
&lt;th&gt;三线 SPI (四线式)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;信号线&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4根&lt;/strong&gt;，SCLK, MOSI, &lt;strong&gt;MISO&lt;/strong&gt;, CS&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3根&lt;/strong&gt;：SCLK, &lt;strong&gt;SDIO&lt;/strong&gt;, CS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;数据线&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;两根独立&lt;/strong&gt;：发送 (MOSI) 和接收 (MISO) 分开&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;一根共用&lt;/strong&gt;：发送和接收都通过同一条 SDIO 线&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;传输模式&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;全双工&lt;/strong&gt;：可同时发送和接收&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;半双工&lt;/strong&gt;：同一时刻只能发送或接收，不能同时进行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;引脚数量&lt;/td&gt;
&lt;td&gt;较多&lt;/td&gt;
&lt;td&gt;较少，节省I/O资源&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;关于 SDIO 如何切换数据方向&lt;/strong&gt;：这个要看具体的芯片数据手册，每款芯片的数据手册对于换向的时机都是不一样的。如我调试时的某款 ADC 芯片 SDIO 通信时序如下 (&lt;strong&gt;高位先发&lt;/strong&gt;)：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/posts/fpga_impl_interface/spi/spi_sdio_timing.png&quot; alt=&quot;SPI-SDIO通信时序&quot; /&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;bit&lt;/th&gt;
&lt;th&gt;功能&lt;/th&gt;
&lt;th&gt;功能介绍&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;R/W#&lt;/td&gt;
&lt;td&gt;读写控制位，读操作时写 1，写操作时写 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[22:08]&lt;/td&gt;
&lt;td&gt;A0 to A14&lt;/td&gt;
&lt;td&gt;15 位寄存器地址&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[07:00]&lt;/td&gt;
&lt;td&gt;Do to D7&lt;/td&gt;
&lt;td&gt;7 位数据，读操作时为向寄存器写入该数据，写操作时为读回指定地址的寄存器数据，&lt;strong&gt;读操作时此时的 SDIO 方向为输入&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/blockquote&gt;
&lt;h3&gt;SPI 的四种通信模式&lt;/h3&gt;
&lt;p&gt;由 &lt;strong&gt;CPOL(时钟极性)&lt;/strong&gt; 和 &lt;strong&gt;CPHA(时钟相位)&lt;/strong&gt; 控制&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CPOL(时钟极性)&lt;/strong&gt;: 控制 &lt;strong&gt;SCLK&lt;/strong&gt; 电平在空闲时的状态
&lt;ul&gt;
&lt;li&gt;若 &lt;strong&gt;CPOL=0&lt;/strong&gt; 则当 SPI 总线&lt;strong&gt;空闲时&lt;/strong&gt;, SCLK 处于&lt;strong&gt;低电平&lt;/strong&gt;状态&lt;/li&gt;
&lt;li&gt;若 &lt;strong&gt;CPOL=1&lt;/strong&gt; 则当 SPI 总线&lt;strong&gt;空闲时&lt;/strong&gt;, SCLK 处于&lt;strong&gt;高电平&lt;/strong&gt;状态&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CPHA(时钟相位)&lt;/strong&gt;: 控制数据的采样边沿
&lt;ul&gt;
&lt;li&gt;若 &lt;strong&gt;CPHA=0&lt;/strong&gt; 则在时钟信号的&lt;strong&gt;第一个跳变沿&lt;/strong&gt; (通常是&lt;strong&gt;上升沿&lt;/strong&gt;) 进行数据采样&lt;/li&gt;
&lt;li&gt;若 &lt;strong&gt;CPHA=1&lt;/strong&gt; 则在时钟信号的&lt;strong&gt;第二个跳变沿&lt;/strong&gt; (通常是&lt;strong&gt;下降沿&lt;/strong&gt;) 进行数据采样&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;模式&lt;/th&gt;
&lt;th&gt;CPOL&amp;lt;br&amp;gt;(时钟极性)&lt;/th&gt;
&lt;th&gt;CPHA&amp;lt;br&amp;gt;(时钟相位)&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;空闲时低电平, 上升沿采集&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;空闲时低电平, 下降沿采集&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;空闲时高电平, 上升沿采集&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;空闲时高电平, 下降沿采集&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;SPI 模块参数以及端口定义&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;参数&lt;/th&gt;
&lt;th&gt;功能描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DATA_WIDTH&lt;/td&gt;
&lt;td&gt;数据位宽&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPOL&lt;/td&gt;
&lt;td&gt;时钟极性&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPHA&lt;/td&gt;
&lt;td&gt;时钟相位&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DLK_DIV&lt;/td&gt;
&lt;td&gt;SCLK 的时钟分频系数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MOSI_IDLE_STATE&lt;/td&gt;
&lt;td&gt;MOSI 信号线在空闲时的状态&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CONTINUOUS_CLK&lt;/td&gt;
&lt;td&gt;是否产生连续的时钟信号，若设置为 1&apos;b0，则表示时钟仅在 SPI 活跃时产生。若设置为 1&apos;b1，则表示时钟信号始终产生&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;端口&lt;/th&gt;
&lt;th&gt;I/O&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;clk&lt;/td&gt;
&lt;td&gt;I&lt;/td&gt;
&lt;td&gt;模块的时钟输入&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rst_n&lt;/td&gt;
&lt;td&gt;I&lt;/td&gt;
&lt;td&gt;模块复位信号，低电平有效&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dat_tx[N-1:0]&lt;/td&gt;
&lt;td&gt;I&lt;/td&gt;
&lt;td&gt;要通过 SPI 总线发送的数据（N 为数据位宽）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dat_rx[N-1:0]&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;由从机接收到的数据（N 为数据位宽）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;start_op&lt;/td&gt;
&lt;td&gt;I&lt;/td&gt;
&lt;td&gt;一个时钟周期的脉冲信号，用于开始 SPI 通信&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;spi_end&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;一个时钟周期的脉冲信号，表示 SPI 发送或接收完成&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;spi_sclk&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;spi 的时钟信号线&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;spi_cs_n&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;spi 的片选信号线&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;spi_miso&lt;/td&gt;
&lt;td&gt;I&lt;/td&gt;
&lt;td&gt;SPI 的一根数据线，由&lt;strong&gt;从机输出&lt;/strong&gt;，主机输入&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;spi_mosi&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;SPI 的一根数据线，由&lt;strong&gt;主机输出&lt;/strong&gt;，从机输入&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;data_bit_cnt&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;发送的比特位计数器，用于在其顶层实现 SDIO 通信&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre&gt;&lt;code&gt;module spi_master #(
    parameter                       DATA_WIDTH          = 4&apos;d8             , // data width
    parameter                       CPOL                = 1&apos;b0              , // Clock polarity
    parameter                       CPHA                = 1&apos;b0              , // Clock phase
    parameter                       CLK_DIV             = 8&apos;d10             , // input clk division, used to generate SCLK.
    parameter                       MOSI_IDLE_STATE     = 1&apos;b0              , // when module goes to idle. the MOSI line logic level will be set to this value  
    parameter                       CONTINUOUS_CLK      = 1&apos;b0                // when set to 1&apos;b1, the SCLK line will continouous or only toggle when cs is low logic if it sets to 1&apos;b0.
)(
    input                           clk                                     , // module clock
    input                           rst_n                                   , // module reset signal, active low
    
    input       [DATA_WIDTH-1:0]    dat_tx                                  , // the data to be transmitted to slave device
    output  reg [DATA_WIDTH-1:0]    dat_rx                                  , // the data received from the slave device
    input                           start_op                                , // pulse, trigger transfer dat operation

    output  reg                     spi_end                                 , // signal is high when transferring data
    
    // PHY signals      
    output                          spi_sclk                                , // serial clock
    output  reg                     spi_cs_n                                , // chip select signal, active low
    input                           spi_miso                                , // master in slave out
    output  reg                     spi_mosi                                , // master out slave in
    output  reg [7:0           ]    data_bit_cnt                              // bit transmit counter
);
// ...

endmodule
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;串行时钟的产生&lt;/h3&gt;
&lt;p&gt;通过计数器产生 &lt;code&gt;sclk_inv&lt;/code&gt; 信号，该信号为一个时钟周期的脉冲信号，每一次脉冲信号都代表着 SCLK 信号将会在下一个时钟周期跳变一次。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// REGION_HEADER------------------------------------------------------------------------------------
reg                         spi_sclk_inv                ; // This signal is a pulse signal with a clk period, and on its rising edge, the SCLK signal will invert once.
reg                         spi_sclk_internal           ; // internal serial clock divied from module clock

// generate spi_sclk_inv signal
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        clk_cnt         &amp;lt;=  8&apos;d0                    ;
        spi_sclk_inv     &amp;lt;=  1&apos;b0                   ;
    end
    else if (clk_cnt == CLK_DIV - 1&apos;b1) begin
        clk_cnt         &amp;lt;=  8&apos;d0                    ;
        spi_sclk_inv     &amp;lt;=  1&apos;b1                   ;
    end
    else if (clk_cnt == CLK_DIV[7:1] - 1&apos;b1) begin
        clk_cnt         &amp;lt;=  clk_cnt + 1&apos;b1          ;
        spi_sclk_inv     &amp;lt;=  1&apos;b1                   ;
    end
    else begin
        clk_cnt         &amp;lt;=  clk_cnt + 1&apos;b1          ;
        spi_sclk_inv     &amp;lt;=  1&apos;b0                   ;
    end
end

// generate spi_sclk_internal signal
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        spi_sclk_internal   &amp;lt;=  1&apos;b0                ;
    end else if (spi_sclk_inv) begin
        spi_sclk_internal   &amp;lt;=  !spi_sclk_internal  ;
    end else begin
        spi_sclk_internal   &amp;lt;=   spi_sclk_internal  ;
    end
end

// assign continouous clock
generate
    if (CONTINUOUS_CLK == 1&apos;b1)
        assign spi_sclk = spi_sclk_internal ^ CPOL                                  ;
    else
        assign spi_sclk = (spi_cs_n == 1&apos;b1) ? CPOL : (spi_sclk_internal ^ CPOL)    ;
endgenerate
// REGION_FOOTER------------------------------------------------------------------------------------
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;时钟边沿的判断&lt;/h3&gt;
&lt;p&gt;根据内部的 &lt;code&gt;sclk_internal&lt;/code&gt; 信号，即可判断上升沿和下降沿。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当 &lt;code&gt;sclk_internal&lt;/code&gt; 是&lt;strong&gt;高电平&lt;/strong&gt;且 &lt;code&gt;spi_sclk_inv&lt;/code&gt; 产生了脉冲信号的时候，表示 &lt;code&gt;sclk_internal&lt;/code&gt; 将会&lt;strong&gt;在下个时钟周期跳变到低电平&lt;/strong&gt;，此时则为 &lt;code&gt;sclk_internal&lt;/code&gt; 的下降沿。&lt;/li&gt;
&lt;li&gt;当 &lt;code&gt;sclk_internal&lt;/code&gt; 是&lt;strong&gt;低电平&lt;/strong&gt;且 &lt;code&gt;spi_sclk_inv&lt;/code&gt; 产生了脉冲信号的时候，表示 &lt;code&gt;sclk_internal&lt;/code&gt; 将会&lt;strong&gt;在下个时钟周期跳变到高电平&lt;/strong&gt;，此时则为 &lt;code&gt;sclk_internal&lt;/code&gt; 的上升沿。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;update_edge&lt;/code&gt; 和 &lt;code&gt;sample_edge&lt;/code&gt; 则表示 SPI 数据的更新边沿和采样边沿，依照 CPHA 参数的设置在上升沿或下降沿更新/采样数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;wire    spi_sclk_negedge    =   spi_sclk_internal &amp;amp; spi_sclk_inv                ; // pulse signal, indicates SCLK negedge
wire    spi_sclk_posedge    =  !spi_sclk_internal &amp;amp; spi_sclk_inv                ;
wire    update_edge         = (CPHA == 0) ? spi_sclk_negedge : spi_sclk_posedge ; // Select update and sample edges based on CPHA
wire    sample_edge         = (CPHA == 0) ? spi_sclk_posedge : spi_sclk_negedge ;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;完整的 SPI 代码&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;module spi_master #(
    parameter                       DATA_WIDTH          = 5&apos;d24             , // data width, [1bit RW ctrl, 15 bits register addr, 8 bits data]
    parameter                       CPOL                = 1&apos;b0              , // Clock polarity
    parameter                       CPHA                = 1&apos;b0              , // Clock phase
    parameter                       CLK_DIV             = 8&apos;d10             , // input clk division, used to generate SCLK.
    parameter                       MOSI_IDLE_STATE     = 1&apos;b0              , // when module goes to idle. the MOSI line logic level will be set to this value   
    parameter                       CONTINUOUS_CLK      = 1&apos;b0                // when set to 1&apos;b1, the SCLK line will continouous or only toggle when cs is low logic if it sets to 1&apos;b0.
)(
    input                           clk                                     , // module clock
    input                           rst_n                                   , // module reset signal, active low

    input       [DATA_WIDTH-1:0]    dat_tx                                  , // the data to be transmitted to slave device
    output  reg [DATA_WIDTH-1:0]    dat_rx                                  , // the data received from the slave device

    input                           start_op                                , // pulse, trigger transfer dat operation
    output  reg                     spi_end                                 , // signal is high when transferring data

    // PHY signals      
    output                          spi_sclk                                , // serial clock
    output  reg                     spi_cs_n                                , // chip select signal, active low
    input                           spi_miso                                , // master in slave out
    output  reg                     spi_mosi                                , // master out slave in
    output  reg [7:0           ]    data_bit_cnt                              // bit transmit counter
);

// NOTE_HEADER--------------------------------------------------------------------------------------
// SPI Modes | CPOL             | CPHA          | Note
//           | (Clock Polarity) | (Clock Phase) | 
// ==========+==================+===============+===================================================
//         0 |                0 |             0 | Clock low  level when idle, data sampled on rising  edge
//         1 |                0 |             1 | Clock low  level when idle, data sampled on falling edge
//         2 |                1 |             0 | Clock high level when idle, data sampled on rising  edge
//         3 |                1 |             1 | Clock high level when idle, data sampled on falling edge
// NOTE_FOOTER--------------------------------------------------------------------------------------

//================================================================================
// local parameter declarations
//================================================================================

//================================================================================
// reg declarations
//================================================================================
reg [DATA_WIDTH-1:0 ]       data_tx_buffer              ; // tx data buffer, shift out to mosi
reg [DATA_WIDTH-1:0 ]       data_rx_buffer              ; // rx data buffer, shift in from miso
reg [7:0            ]       clk_cnt                     ; // a counter for frequency division

reg                         spi_sclk_inv                ; // This signal is a pulse signal with a clk period, and on its rising edge, the SCLK signal will invert once.
reg                         spi_sclk_internal           ; // internal serial clock divied from module clock

reg                         trans_end_d                 ;
reg                         trans_start                 ;
reg                         spi_start_op_d0             ;
reg                         spi_start_op_d1             ;
reg                         spi_busy                    ; // internal busy signal
//================================================================================
// wire declarations
//================================================================================
wire    spi_sclk_negedge    =   spi_sclk_internal &amp;amp; spi_sclk_inv                ; // pulse signal, indicates SCLK negedge
wire    spi_sclk_posedge    =  !spi_sclk_internal &amp;amp; spi_sclk_inv                ;
wire    update_edge         = (CPHA == 0) ? spi_sclk_negedge : spi_sclk_posedge ; // Select update and sample edges based on CPHA
wire    sample_edge         = (CPHA == 0) ? spi_sclk_posedge : spi_sclk_negedge ;

wire    spi_start_op_pulse                                          ;

//================================================================================
// assign declarations
//================================================================================
assign  spi_start_op_pulse  =   !spi_start_op_d0 &amp;amp; spi_start_op_d1  ;

//================================================================================
// MAIN CODE
//================================================================================

// sync start_op signal
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        spi_start_op_d0 &amp;lt;=  1&apos;b0            ;
        spi_start_op_d1 &amp;lt;=  1&apos;b0            ;
    end else begin
        spi_start_op_d0 &amp;lt;=  start_op        ;
        spi_start_op_d1 &amp;lt;=  spi_start_op_d0 ;
    end
end

// spi busy indicator
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        spi_busy    &amp;lt;=  1&apos;b0                ;
    end else if (spi_start_op_pulse) begin
        spi_busy    &amp;lt;=  1&apos;b1                ;
    end else if (spi_end) begin
        spi_busy    &amp;lt;=  1&apos;b0                ;
    end else begin
        spi_busy    &amp;lt;=  spi_busy            ;
    end
end

// REGION_HEADER------------------------------------------------------------------------------------
// generate spi_sclk_inv signal
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        clk_cnt         &amp;lt;=  8&apos;d0                    ;
        spi_sclk_inv     &amp;lt;=  1&apos;b0                   ;
    end
    else if (clk_cnt == CLK_DIV - 1&apos;b1) begin
        clk_cnt         &amp;lt;=  8&apos;d0                    ;
        spi_sclk_inv     &amp;lt;=  1&apos;b1                   ;
    end
    else if (clk_cnt == CLK_DIV[7:1] - 1&apos;b1) begin
        clk_cnt         &amp;lt;=  clk_cnt + 1&apos;b1          ;
        spi_sclk_inv     &amp;lt;=  1&apos;b1                   ;
    end
    else begin
        clk_cnt         &amp;lt;=  clk_cnt + 1&apos;b1          ;
        spi_sclk_inv     &amp;lt;=  1&apos;b0                   ;
    end
end

// generate spi_sclk_internal signal
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        spi_sclk_internal   &amp;lt;=  1&apos;b0                ;
    end else if (spi_sclk_inv) begin
        spi_sclk_internal   &amp;lt;=  !spi_sclk_internal  ;
    end else begin
        spi_sclk_internal   &amp;lt;=   spi_sclk_internal  ;
    end
end

// assign continouous clock
generate
    if (CONTINUOUS_CLK == 1&apos;b1)
        assign spi_sclk = spi_sclk_internal ^ CPOL                                  ;
    else
        assign spi_sclk = (spi_cs_n == 1&apos;b1) ? CPOL : (spi_sclk_internal ^ CPOL)    ;
endgenerate
// REGION_FOOTER------------------------------------------------------------------------------------

// count data bit
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        data_bit_cnt    &amp;lt;=  8&apos;d0                            ;
    end 
    else if (
        spi_busy        &amp;amp;&amp;amp; 
        update_edge     &amp;amp;&amp;amp; 
        (data_bit_cnt  &amp;lt; DATA_WIDTH)
    ) begin
        data_bit_cnt    &amp;lt;=  data_bit_cnt + 1&apos;b1             ;
    end else if (update_edge &amp;amp;&amp;amp; data_bit_cnt == DATA_WIDTH) begin
        data_bit_cnt    &amp;lt;=  8&apos;d0                            ;
    end else begin
        data_bit_cnt    &amp;lt;=  data_bit_cnt                    ;
    end
end

// send data
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        spi_mosi        &amp;lt;=  MOSI_IDLE_STATE                 ;
        data_tx_buffer  &amp;lt;=  {DATA_WIDTH{1&apos;b0}}              ;
    end else if (spi_start_op_pulse) begin
        data_tx_buffer  &amp;lt;=  dat_tx                          ;
    end 
    else if (
        spi_busy                        &amp;amp;&amp;amp;
        update_edge                     &amp;amp;&amp;amp;
        (data_bit_cnt &amp;lt; DATA_WIDTH)
    ) begin
        spi_mosi        &amp;lt;=  data_tx_buffer[DATA_WIDTH-1]    ;   // output data, MSB First
        data_tx_buffer  &amp;lt;=  {                                   // left-shift data
            data_tx_buffer[DATA_WIDTH-2:0]  ,
            data_tx_buffer[DATA_WIDTH-1]     
        };
    end else if (
        spi_busy                        &amp;amp;&amp;amp;
        update_edge                     &amp;amp;&amp;amp;
        (data_bit_cnt &amp;gt;= DATA_WIDTH)
    ) begin
        spi_mosi        &amp;lt;=  MOSI_IDLE_STATE                 ;
        data_tx_buffer  &amp;lt;=  {DATA_WIDTH{1&apos;b0}}              ;
    end
end

// receive data
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        data_rx_buffer      &amp;lt;=  {DATA_WIDTH{1&apos;b0}}  ;
    end else if (spi_cs_n == 1&apos;b0 &amp;amp;&amp;amp; sample_edge) begin
        data_rx_buffer[0]   &amp;lt;=  spi_miso            ;   // sample data
        data_rx_buffer[DATA_WIDTH-1:1] &amp;lt;= data_rx_buffer[DATA_WIDTH-2:0];   // right-shif in sampled data to buffer
    end else begin
        data_rx_buffer      &amp;lt;=  data_rx_buffer      ;
    end
end

// move data from data_rx_buffer to data_rx output and generate spi_end signal 
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        spi_end &amp;lt;=  1&apos;b0                ;
        dat_rx  &amp;lt;=  {DATA_WIDTH{1&apos;b0}}  ;
    end else if (update_edge &amp;amp;&amp;amp; (data_bit_cnt == DATA_WIDTH)) begin
        spi_end &amp;lt;=  1&apos;b1                ;
        dat_rx  &amp;lt;=  data_rx_buffer      ;
    end else begin
        spi_end &amp;lt;=  1&apos;b0                ;
    end
end

always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        trans_end_d &amp;lt;=  1&apos;b0            ;
    end else begin
        trans_end_d &amp;lt;=  spi_end         ;
    end
end

always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        trans_start &amp;lt;=  1&apos;b0            ;
    end else if (spi_busy &amp;amp; update_edge) begin
        trans_start &amp;lt;=  1&apos;b1            ;
    end else begin
        trans_start &amp;lt;=  1&apos;b0            ;
    end
end

always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        spi_cs_n &amp;lt;=  1&apos;b1               ;
    end else if (trans_start) begin
        spi_cs_n &amp;lt;=  1&apos;b0               ;
    end else if (trans_end_d) begin // used a extended version to meet the timing requirements(clock to enable low time) of some device
        spi_cs_n &amp;lt;=  1&apos;b1               ;
    end
end

endmodule
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;实现三线式 SPI 通信&lt;/h3&gt;
&lt;p&gt;SDIO 通信完全不需要从头开始编写代码，以上方提供的 &lt;code&gt;spi_master&lt;/code&gt; 模块的代码作为基础，将其作为一个子模块封装到顶层模块，再用 IOBUF 原语实现一个双向 IO 即可。&lt;/p&gt;
&lt;p&gt;完整代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module sdio_interface (
	input   wire            clk             ,   //  模块的时钟信号
    input   wire            rst_n           ,   //  模块的复位信号，低电平有效
    
    input   wire            rh_wl           ,   //  读写控制信号, 读为高电平，写为低电平
    input   wire            sdio_start_op   ,   //  操作使能的脉冲信号
    input   wire    [15:00] register_addr   ,   //  16 位寄存器地址
    input   wire    [07:00] dat_tx          ,   //  要发送的数据
    output  wire    [07:00] dat_rx          ,   //  从 sdio 接口读取到的 8 位寄存器数据
    
    output  wire            spi_sclk        ,   //  串行时钟信号
    output  wire            spi_cs_n        ,   //  片选信号
    inout   wire            sdio            ,   //  双向 IO 端口
);

// 例化 spi_master 模块
wire            spi_mosi    ;
wire            spi_miso    ;
wire    [23:00] dat_rx_t    ;   //  将 spi_master 模块接收到的 24 位数据暂存到此处，我们只需要低八位的寄存器数据
wire    [07:00] data_bit_cnt;
wire    sdio_dir = (data_bit_cnt &amp;lt;= 16) ? 1&apos;b0 : rh_wl; // 前 16 位：读写控制位和寄存器地址，固定为输出方向，低 8 位根据读写控制来调整方向
assign  dat_rx  =   dat_rx_t[07:00] ;   //  只取出低八位数据
spi_master # (
    .DATA_WIDTH             (   24                              ),  // data width, [1bit RW ctrl, 15 bits register addr, 8 bits data]
    .CPOL                   (   1&apos;b0                            ),  // Clock polarity
    .CPHA                   (   1&apos;b0                            ),  // Clock phase
    .CLK_DIV                (   8&apos;d10                           ),  // input clk division, used to generate SCLK.
    .MOSI_IDLE_STATE        (   1&apos;b0                            ),  // when module goes to idle. the MOSI line logic level will be set to this value   
    .CONTINUOUS_CLK         (   1&apos;b0                            )   // when set to 1&apos;b1, the SCLK line will continouous or only toggle when cs is low logic if it sets to 1&apos;b0.
) spi_master (                   
    .clk                    (   clk                             ),  // [I] [      ] module clock
    .rst_n                  (   rst_n                           ),  // [I] [      ] module reset signal, active-low

    .dat_tx                 (   {rh_wl, register_addr, dat_tx}  ),  // [I] [DW-1:0] the data to be transmitted to slave device
    .dat_rx                 (   dat_rx                          ),  // [O] [DW-1:0] the data received from the slave device

    .start_op               (   sdio_start_op                   ),  // [I] [      ] pulse, trigger SPI transfer
    .spi_end                (   spi_end                         ),  // [O] [      ] busy is high level when transferring data

    .spi_sclk               (   spi_sclk                        ),  // [O] [   0:0] serial clock
    .spi_cs_n               (   spi_cs_n                        ),  // [O] [   0:0] chip select signal, active low
    .spi_mosi               (   spi_mosi                        ),  // [I] [   0:0] master in slave out
    .spi_miso               (   spi_miso                        ),  // [O] [   0:0] master out slave in
    .data_bit_cnt           (   data_bit_cnt                    )   // [O] [   7:0] bit transmit counter
);

// 例化 IOBUF 原语
IOBUF # (
    .DRIVE          (   12              ),
    .IBUF_LOW_PWR   (   &quot;TRUE&quot;          ),
    .IOSTANDARD     (   &quot;DEFAULT&quot;       ),
    .SLEW           (   &quot;SLOW&quot;          )
) ms14d2600_spi_iobuf (
    .O              (   spi_miso        ),  //  IOBUF 原语的输出端口，接到 spi_miso 中
    .IO             (   sdio            ),  //  IOBUF 原语的双向端口，输出到模块端口
    .I              (   spi_mosi        ),  //  IOBUF 原语的输入端口，接到 spi_mosi 中
    .T              (   sdio_dir        )   //  IOBUF 原语的方向控制端口，low: output, high: input
);

endmodule
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里我实现的三线式 SPI 通信适用于我上面提到的某款 ADC 芯片通信时序，对于 sdio 方向的更改时机可以通过修改代码中第 21 行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wire    sdio_dir = (data_bit_cnt &amp;lt;= 16) ? 1&apos;b0 : rh_wl; // 前 16 位：读写控制位和寄存器地址，固定为输出方向，低 8 位根据读写控制来调整方向
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将判断条件设置为你需要的值即可。&lt;/p&gt;
</content:encoded></item><item><title>FPGA 实现以太网 PHY 芯片配置接口 —— MDIO 的学习与应用</title><link>https://blog.tyh123.top/posts/3723985a/</link><guid isPermaLink="true">https://blog.tyh123.top/posts/3723985a/</guid><pubDate>Sat, 18 Apr 2026 15:18:25 GMT</pubDate><content:encoded>&lt;h3&gt;前言&lt;/h3&gt;
&lt;p&gt;MDIO 接口是配置以太网 PHY 芯片片上寄存器的一种通用接口，几乎所有的以太网 PHY 芯片使用的都是 MDIO 接口来配置寄存器。&lt;/p&gt;
&lt;h3&gt;接线方式&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ETH_MDC: 时钟信号线，频率不大于 12.5MHz&lt;/li&gt;
&lt;li&gt;ETH_MDIO: 双向数据线
主从式、半双工通信，所有通信都是由主设备发起，从设备被动响应（和 IIC 类似）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;数据帧格式&lt;/h3&gt;
&lt;h4&gt;读数据&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Preamble&lt;/th&gt;
&lt;th&gt;ST&lt;/th&gt;
&lt;th&gt;OP&lt;/th&gt;
&lt;th&gt;PHYAD&lt;/th&gt;
&lt;th&gt;REGAD&lt;/th&gt;
&lt;th&gt;TA&lt;/th&gt;
&lt;th&gt;DATA&lt;/th&gt;
&lt;th&gt;IDLE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1...1&lt;/td&gt;
&lt;td&gt;01&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AAAAA&lt;/td&gt;
&lt;td&gt;RRRRR&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Z0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;DDDDDDDDDDDDDDDD&lt;/td&gt;
&lt;td&gt;Z&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;Preamble: 前导码，由主设备发送的&lt;strong&gt;连续的 32 个逻辑 1&lt;/strong&gt;，用于与从设备建立同步&lt;/li&gt;
&lt;li&gt;ST: 数据帧的开始，固定为 2位数据 &lt;strong&gt;01&lt;/strong&gt;，代表有效数据的起始位置&lt;/li&gt;
&lt;li&gt;OP: 操作码，定义操作类型，&lt;strong&gt;01：写操作，10：读操作&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;PHYAD：&lt;strong&gt;5 位 PHY 芯片地址&lt;/strong&gt;，用于选择总线上的目标 PHY 芯片&lt;/li&gt;
&lt;li&gt;REGAD：&lt;strong&gt;片上寄存器地址&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;TA：转向域，2 位的空闲周期，用于读写方向切换；写操作：主设备持续驱动总线输出 &quot;10&quot;; 读操作：第一个时钟周期 MDIO 程高阻态 (主、从设备均释放总线),  在第二个时钟周期下，从设备将接管 MDIO 总线，开始驱动 MDIO 输出数据。&lt;/li&gt;
&lt;li&gt;DATA：16 位即将要写入或读取的数据&lt;/li&gt;
&lt;li&gt;IDLE：空闲状态位，帧结束后 MDIO 总线拉高进入高阻态
表中的 Z 高阻态，在外部上拉电阻的情况下最终信号体现为高电平&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;在读数据操作时，&lt;strong&gt;直到 TA 阶段之前，数均由主设备(FPGA)驱动&lt;/strong&gt;，TA 阶段第&lt;strong&gt;一个时钟周期&lt;/strong&gt;主设备控制 MDIO 为高阻态，释放总线，从&lt;strong&gt;第二个时钟周期&lt;/strong&gt;开始，&lt;strong&gt;从设备接管 MDIO&lt;/strong&gt;，数据为低电平，表示数据将在&lt;strong&gt;下一个时钟周期&lt;/strong&gt;开始发送。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/img/posts/fpga_impl_interface/eth_mdio/mdio_reading.png&quot; alt=&quot;MDIO 读数据时序图&quot; /&gt;
&lt;img src=&quot;/img/posts/fpga_impl_interface/eth_mdio/mdio_reading_note.png&quot; alt=&quot;MDIO 读数据时序图-note&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;写数据&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Preamble&lt;/th&gt;
&lt;th&gt;ST&lt;/th&gt;
&lt;th&gt;OP&lt;/th&gt;
&lt;th&gt;PHYAD&lt;/th&gt;
&lt;th&gt;REGAD&lt;/th&gt;
&lt;th&gt;TA&lt;/th&gt;
&lt;th&gt;DATA&lt;/th&gt;
&lt;th&gt;IDLE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1...1&lt;/td&gt;
&lt;td&gt;01&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;01&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AAAAA&lt;/td&gt;
&lt;td&gt;RRRRR&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;DDDDDDDDDDDDDDDD&lt;/td&gt;
&lt;td&gt;Z&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;在写数据时，MDIO 总线&lt;strong&gt;全程由主设备控制&lt;/strong&gt;
&lt;img src=&quot;/img/posts/fpga_impl_interface/eth_mdio/mdio_writing.png&quot; alt=&quot;MDIO 写数据时序图&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;实现代码&lt;/h3&gt;
&lt;h4&gt;双向 IO 口的驱动&lt;/h4&gt;
&lt;p&gt;在这里使用 &lt;code&gt;IOBUF&lt;/code&gt; 原语驱动双向 IO 口。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;IOBUF # (
    .DRIVE          (   12              ),
    .IBUF_LOW_PWR   (   &quot;TRUE&quot;          ),
    .IOSTANDARD     (   &quot;DEFAULT&quot;       ),
    .SLEW           (   &quot;SLOW&quot;          )
) mdio_obuf (
    .O              (   eth_mdio_in     ),
    .IO             (   eth_mdio        ),
    .I              (   eth_mdio_out    ),
    .T              (   eth_mdio_dir    )   //  1&apos;b1: input, 1&apos;b0: output
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者使用三态门进行逻辑判断。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module eth_mdio (
	//...
	inout eth_mdio; // 双向 io 口
	//...
);

wire eth_mdio_in ; // MDIO 从机输入的比特位数据
wire eth_mdio_out; // MDIO 主机输出的比特位数据
reg  eth_mdio_dir; // 控制 MDIO 的输入/输出方向, 1&apos;b1: 输入, 1&apos;b0:输出

assign eth_mdio = eth_mdio_dir ? eth_mdio_in : eth_mdio_out; // 三态门根据内部的 dir 信号控制端口的输入输出方向

//... 其它代码 ... 如时钟信号、驱动 eth_mdio_dir 的逻辑等等
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;时钟信号的产生&lt;/h4&gt;
&lt;p&gt;和之前有一篇讲 &lt;a href=&quot;./FPGA%20SPI%E9%80%9A%E4%BF%A1&quot;&gt;SPI 通信&lt;/a&gt; 文章中的代码类似，也是产生一个双边沿的脉冲信号，然后根据这个双边沿脉冲信号去翻转 MDC 信号就得到了驱动时钟。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// MDC inv Generation, inv pulse signal is active-high when MDC rising edge and falling edge
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        eth_mdc_cnt &amp;lt;=  32&apos;d0               ;
        eth_mdc_inv &amp;lt;=   1&apos;b0               ;
    end
    else if (eth_mdc_cnt == (CLK_DIV - 1&apos;d1)) begin         // MDC falling edge
        eth_mdc_cnt &amp;lt;=  8&apos;d0                ;
        eth_mdc_inv &amp;lt;=  1&apos;b1                ;
    end
    else if (eth_mdc_cnt == (CLK_DIV[7:1] - 1&apos;d1)) begin    // MDC rising  edge
        eth_mdc_cnt &amp;lt;=  eth_mdc_cnt + 1&apos;d1  ;
        eth_mdc_inv &amp;lt;=   1&apos;b1               ;
    end
    else begin
        eth_mdc_cnt &amp;lt;=  eth_mdc_cnt + 1&apos;d1  ;
        eth_mdc_inv &amp;lt;=  1&apos;b0                ;
    end
end

// generate MDC
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        eth_mdc     &amp;lt;=  1&apos;b0                ;
    end else if (eth_mdc_inv) begin
        eth_mdc     &amp;lt;=  ~eth_mdc            ;
    end else begin
        eth_mdc     &amp;lt;=   eth_mdc            ;
    end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;上升沿和下降沿的判断&lt;/h4&gt;
&lt;p&gt;判断逻辑如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当 &lt;code&gt;eth_mdc&lt;/code&gt; 为&lt;strong&gt;高电平&lt;/strong&gt;并且产生了 &lt;code&gt;eth_mdc_inv&lt;/code&gt; 的脉冲信号，表示 &lt;code&gt;eth_mdc&lt;/code&gt; 将会在下一个时钟周期&lt;strong&gt;跳变到低电平&lt;/strong&gt;，此时则为 &lt;code&gt;eth_mdc&lt;/code&gt; 的下降沿&lt;/li&gt;
&lt;li&gt;当 &lt;code&gt;eth_mdc&lt;/code&gt; 为&lt;strong&gt;低电平&lt;/strong&gt;并且产生了 &lt;code&gt;eth_mdc_inv&lt;/code&gt; 的脉冲信号，表示 &lt;code&gt;eth_mdc&lt;/code&gt; 将会在下一个时钟周期&lt;strong&gt;跳变到高电平&lt;/strong&gt;，此时则为 &lt;code&gt;eth_mdc&lt;/code&gt; 的上升沿&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;wire eth_mdc_negedge =  eth_mdc &amp;amp; eth_mdc_inv;   // indicates MDC negedge
wire eth_mdc_posedge = !eth_mdc &amp;amp; eth_mdc_inv;   // indicates MDC posedge
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;完整的 MDIO 驱动代码&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;module mdio_dri # (
    parameter   CLK_DIV = 8&apos;d10                 // for MDC generation, The max frequency of MDC is 12.500MHz
)(
    input                   clk             ,   // [ I] [    ] module clock, 100.000MHz is recommended
    input                   rst_n           ,   // [ I] [    ] reset signal, active-low
    input                   eth_start_op    ,   // [ I] [    ] start MDIO configuration pulse

    input   wire            rh_wl           ,   // [ I] [    ] high level for read, and low level for write.
    input   wire    [ 4:0]  phy_addr        ,   // [ I] [ 4:0] 5-bit phy address
    input   wire    [ 4:0]  reg_addr        ,   // [ I] [ 4:0] 5-bit register address 
    input   wire    [15:0]  dat_tx          ,   // [ I] [15:0] 16-bit tx data
    output  reg     [15:0]  dat_rx          ,   // [ O] [15:0] 16-bit rx data

    output  reg             eth_end         ,   // [ O] [    ] pulse signal when transmit done, active-high
    output  reg             eth_module_busy ,   // [ O] [    ] module busy siganl
    output  reg             eth_mdc         ,   // [ O] [    ] serial clock
    inout   wire            eth_mdio            // [IO] [    ] data port 
);
//================================================================================
// Local Parameter Declarations
//================================================================================
// 32-bit preamble  , 2-bit ST      , 2-bit OP  , 
//  5-bit PHYAD     , 5-bit REGAD   , 2-bit TA  ,
// 16-bit DATA      , Then higi-impedance IDLE state.
localparam  DAT_WIDTH       =   7&apos;d64       ;

//================================================================================
// Register Declarations
//================================================================================
reg [7:0]   eth_mdc_cnt     ;
reg         eth_mdc_inv     ;

reg         eth_start_op_d0 ;
reg         eth_start_op_d1 ;

reg [ 7:0]  data_bit_cnt    ;

reg [31:0]  dat_tx_buffer   ;
reg [15:0]  dat_rx_buffer   ;

reg         eth_mdio_out    ; 
//================================================================================
// Wire Declarations
//================================================================================
wire        eth_mdio_in                                                 ;   // mdio data in
wire        eth_mdio_dir                                                ;

wire        eth_start_op_pulse  =  !eth_start_op_d0 &amp;amp; eth_start_op_d1   ;   
wire        eth_mdc_negedge     =   eth_mdc &amp;amp; eth_mdc_inv               ;   // indicates MDC negedge
wire        eth_mdc_posedge     =  !eth_mdc &amp;amp; eth_mdc_inv               ;   // indicates MDC posedge

//================================================================================
// Assign Declarations
//================================================================================
// always output until we transmit/receive 16-bit data stage
assign  eth_mdio_dir    =   (data_bit_cnt &amp;lt;= (8&apos;d48)) ? 1&apos;b0 : rh_wl;

//================================================================================
// implements
//================================================================================
IOBUF # (
    .DRIVE          (   12              ),
    .IBUF_LOW_PWR   (   &quot;TRUE&quot;          ),
    .IOSTANDARD     (   &quot;DEFAULT&quot;       ),
    .SLEW           (   &quot;SLOW&quot;          )
) mdio_obuf (
    .O              (   eth_mdio_in     ),
    .IO             (   eth_mdio        ),
    .I              (   eth_mdio_out    ),
    .T              (   eth_mdio_dir    )   //  1&apos;b1: input, 1&apos;b0: output
);

//================================================================================
// MAIN CODE
//================================================================================
// sync eth_start_op signal
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        eth_start_op_d0 &amp;lt;=  1&apos;b0            ;
        eth_start_op_d1 &amp;lt;=  1&apos;b0            ;
    end else begin
        eth_start_op_d0 &amp;lt;=  eth_start_op    ;
        eth_start_op_d1 &amp;lt;=  eth_start_op_d0 ;
    end
end

// module busy indicator
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        eth_module_busy &amp;lt;=  1&apos;b0            ;
    end else if (eth_start_op_pulse) begin
        eth_module_busy &amp;lt;=  1&apos;b1            ;
    end else if (eth_end) begin
        eth_module_busy &amp;lt;=  1&apos;b0            ;
    end else begin
        eth_module_busy &amp;lt;=  eth_module_busy ;
    end
end

// MDC inv Generation, inv pulse signal is active-high when MDC rising edge and falling edge
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        eth_mdc_cnt &amp;lt;=  32&apos;d0               ;
        eth_mdc_inv &amp;lt;=   1&apos;b0               ;
    end
    else if (eth_mdc_cnt == (CLK_DIV - 1&apos;d1)) begin         // MDC falling edge
        eth_mdc_cnt &amp;lt;=  8&apos;d0                ;
        eth_mdc_inv &amp;lt;=  1&apos;b1                ;
    end
    else if (eth_mdc_cnt == (CLK_DIV[7:1] - 1&apos;d1)) begin    // MDC rising  edge
        eth_mdc_cnt &amp;lt;=  eth_mdc_cnt + 1&apos;d1  ;
        eth_mdc_inv &amp;lt;=   1&apos;b1               ;
    end
    else begin
        eth_mdc_cnt &amp;lt;=  eth_mdc_cnt + 1&apos;d1  ;
        eth_mdc_inv &amp;lt;=  1&apos;b0                ;
    end
end

// generate MDC
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        eth_mdc     &amp;lt;=  1&apos;b0                ;
    end else if (eth_mdc_inv) begin
        eth_mdc     &amp;lt;=  ~eth_mdc            ;
    end else begin
        eth_mdc     &amp;lt;=   eth_mdc            ;
    end
end

// count data bit
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        data_bit_cnt    &amp;lt;=  8&apos;d0            ;
    end else if (
        eth_module_busy     &amp;amp;&amp;amp;
        eth_mdc_negedge     &amp;amp;&amp;amp;
        (data_bit_cnt &amp;lt; DAT_WIDTH)
    ) begin
        data_bit_cnt &amp;lt;=  data_bit_cnt + 1&apos;b1;
    end else if (eth_mdc_negedge &amp;amp;&amp;amp; data_bit_cnt == DAT_WIDTH) begin
        data_bit_cnt    &amp;lt;=  8&apos;d0            ;
    end else begin
        data_bit_cnt    &amp;lt;=  data_bit_cnt    ;
    end
end

// send data
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        eth_mdio_out    &amp;lt;=  1&apos;b0            ;
        dat_tx_buffer   &amp;lt;=  32&apos;b0           ;
    end else if (eth_start_op_pulse) begin 
        dat_tx_buffer   &amp;lt;=  {
            2&apos;b01                       ,   // start of frame
            rh_wl                       ,   // Operation code bit 1
            !rh_wl                      ,   // Operation code bit 2
            phy_addr                    ,   // PHY address
            reg_addr                    ,   // Register address
            (rh_wl) ?  2&apos;b00  : 2&apos;b10   ,   // Turnaround
            (rh_wl) ? 16&apos;b0000: dat_tx      // 16-bit data
        };
    end else if (
        eth_module_busy     &amp;amp;&amp;amp;
        eth_mdc_negedge     &amp;amp;&amp;amp;
        (data_bit_cnt &amp;lt; 8&apos;d32)                      
    ) begin
        eth_mdio_out    &amp;lt;=  1&apos;b1                ;   // 32-bit preamble
    end else if (
        eth_module_busy     &amp;amp;&amp;amp;
        eth_mdc_negedge     &amp;amp;&amp;amp;
        (data_bit_cnt &amp;lt; DAT_WIDTH)
    ) begin
        eth_mdio_out    &amp;lt;=  dat_tx_buffer[31]   ;   // MSB First
        dat_tx_buffer   &amp;lt;=  {
            dat_tx_buffer[30:0],
            dat_tx_buffer[  31]
        };
    end else if (
        eth_module_busy     &amp;amp;&amp;amp;
        eth_mdc_negedge     &amp;amp;&amp;amp;
        (data_bit_cnt &amp;gt;= DAT_WIDTH)
    ) begin
        eth_mdio_out    &amp;lt;=  1&apos;b0                ;
        dat_tx_buffer   &amp;lt;=  {32{1&apos;b0}}          ;
    end
end

// receive data
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        dat_rx_buffer       &amp;lt;=  16&apos;b0               ;
    end else if (eth_mdio_dir &amp;amp;&amp;amp; eth_mdc_posedge) begin
        dat_rx_buffer[0]    &amp;lt;=  eth_mdio_in         ;
        dat_rx_buffer[15:1] &amp;lt;=  dat_rx_buffer[14:0] ;
    end else begin
        dat_rx_buffer       &amp;lt;=  dat_rx_buffer       ;
    end
end

// move data from dat_rx_buffer to dat_rx output and generate eth_end signal
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        eth_end     &amp;lt;=      1&apos;b0                    ;
        dat_rx      &amp;lt;=      16&apos;b0                   ;
    end else if (eth_mdc_negedge &amp;amp;&amp;amp; (data_bit_cnt == DAT_WIDTH)) begin
        eth_end     &amp;lt;=      1&apos;b1                    ;
        dat_rx      &amp;lt;=      dat_rx_buffer           ;
    end else begin
        eth_end     &amp;lt;=      1&apos;b0                    ;
    end
end

endmodule
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>FPGA 串口通信</title><link>https://blog.tyh123.top/posts/96d23a91/</link><guid isPermaLink="true">https://blog.tyh123.top/posts/96d23a91/</guid><pubDate>Fri, 17 Apr 2026 14:08:17 GMT</pubDate><content:encoded>&lt;h2&gt;简介&lt;/h2&gt;
&lt;p&gt;串口，即 UART(全称 Universal Asynchronous Receiver/Transmitter，中文正式名称为通用异步收发器)，是采用串行通信方式的接口。串行通信将字节数据&lt;strong&gt;按比特在一条数据线上逐个传输&lt;/strong&gt;，其特点是线路简单，但传输速度较慢。&lt;/p&gt;
&lt;p&gt;对于传输速度要求不高的场合，如工业控制、嵌入式开发等领域，串口通信是常客。&lt;/p&gt;
&lt;h2&gt;串口通信方式&lt;/h2&gt;
&lt;p&gt;UART 通信需要两根信号线来实现，分别是 TXD 和 RXD。TXD 用来发送数据；RXD 用来接收数据。在发送数据时，将并行的 8 比特数据按比特在一条数据线上逐个传输。在接收数据时，将接收到的串行数据按比特解串成并行数据。&lt;strong&gt;发送时低位先发。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;需要注意的是，两个串口设备通信时，不能把各自的 TXD 和 RXD 直接对应相连（即 TXD 接 TXD、RXD 接 RXD），而应该交叉连接：一个设备的 TXD 接另一个设备的 RXD，同时这个设备的 RXD 接另一个设备的 TXD。具体连接方式如下图所示。
&lt;img src=&quot;/img/posts/fpga_impl_interface/uart/wiring.png&quot; alt=&quot;串口接线方式&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;串口通信数据格式&lt;/h2&gt;
&lt;p&gt;如下图所示，下图为&lt;strong&gt;一个字节数据 (字符帧)&lt;/strong&gt; 的传输格式
&lt;img src=&quot;/img/posts/fpga_impl_interface/uart/data_format.png&quot; alt=&quot;异步串口通信数据格式图&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;起始位&lt;/strong&gt;：标志着一帧数据的开始，该位&lt;strong&gt;固定为 0&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据位&lt;/strong&gt;：一帧数据中的&lt;strong&gt;有效数据&lt;/strong&gt;，在串口设置中可配置为 5、6、7、8 位&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;校验位&lt;/strong&gt;：分为&lt;strong&gt;奇校验&lt;/strong&gt;和&lt;strong&gt;偶校验&lt;/strong&gt;。为了使得整体 1 的个数为对应的奇数/偶数，在该位补齐对应的 0 或 1，例图如下：
&lt;ul&gt;
&lt;li&gt;&lt;img src=&quot;/img/posts/fpga_impl_interface/uart/parity_odd.png&quot; alt=&quot;串口校验位-奇校验图例&quot; /&gt;&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;/img/posts/fpga_impl_interface/uart/parity_even.png&quot; alt=&quot;串口校验位-偶校验图例&quot; /&gt;&lt;/li&gt;
&lt;li&gt;总结一下就是：奇偶校验是为了让传输的数据（包含校验位）中 1 的个数为奇数/偶数。如果传输字节中 1 的个数为 偶数/奇数，则校验位的数据为 1，否则为 0。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;停止位&lt;/strong&gt;：标志着一个数据帧的结束。该位&lt;strong&gt;固定为 1&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;波特率&lt;/h2&gt;
&lt;p&gt;串口通信中，我们用波特率表示数据传输的快慢。波特率表示每秒传输了多少个二进制位（bit）。通信双方各自使用本地时钟源，通过分频得到所需的波特率时钟信号。这种不需要额外传输时钟信号的方式，就是异步通信——其本质是双方各自依赖本地时钟，依靠起始位来完成字符同步。&lt;/p&gt;
&lt;p&gt;常见的波特率有 9600、19200 以及 115200 等。波特率是人为规定的，理论上你可以设置任何速度的波特率，只要通信双方波特率一致，就可以正常通信。但实际硬件和软件通常只支持有限个常用值，这受限于系统时钟的分频：分频只能得到某些特定的数值，例如前面提到的常见波特率。此外，常见的操作系统和串口软件通常也只在常用波特率列表中提供选择，支持手动输入任意波特率的情况并不多。&lt;/p&gt;
&lt;h2&gt;实现代码&lt;/h2&gt;
&lt;h4&gt;串口发送模块&lt;/h4&gt;
&lt;p&gt;该模块负责串口数据的发送&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// FILE_HEADER_HEADER-------------------------------------------------------------------------------
// Copyright (c) 2026, Mr. Tian. All rights reserved.
//--------------------------------------------------------------------------------------------------
// FILE NAME        : uart_tx.v
// AUTHOR           : Mr. Tian
// DESCRIPTION      : UART transmitter
//--------------------------------------------------------------------------------------------------
// REVISION HISTORY :
//  Rev: (2026-05-06) - Mr. Tian
//          Initial release.
// FILE_HEADER_FOOTER-------------------------------------------------------------------------------

module uart_tx # (
    parameter   CLOCK_FREQ  =   32&apos;d100_000_000 ,   //  输入时钟频率 (200MHz)
    parameter   BAUD_RATE   =   32&apos;d115200      ,   //  波特率
    parameter   DATA_BITS   =   8               ,   //  数据位
    parameter   PARITY      =   &quot;ODD&quot;           ,   //  校验方式，有 ODD(奇校验)、EVEN(偶校验)、SPACE(始终为 0)、MARK(始终为 1)、NONE(无校验)
    parameter   STOP_BITS   =   &quot;1&quot;                 //  停止位: 1, 1.5, 2，输入其它值则默认为 1 停止位
) (
    input                   clk     ,   //  [I][     ] 输入的模块时钟，推荐 100.000 MHz
    input                   rst_n   ,   //  [I][     ] 模块复位信号，低电平有效

    input   wire            tx_en   ,   //  [I][     ] 发送使能信号，高脉冲有效
    input   wire    [07:00] tx_data ,   //  [I][07:00] 要发送的数据
    
    output  reg             tx_busy ,   //  [O][     ] 发送机忙信号
    output  reg             txd         //  [O][     ] TXD 信号线
);
    
localparam  BAUD_CNT_MAX        =   CLOCK_FREQ / BAUD_RATE  ;   //  计数器的最大计数值
localparam  BAUD_CNT_MID        =   BAUD_CNT_MAX / 2        ;   //  计数器值的中点
localparam  HAS_PARITY          =   (PARITY != &quot;NONE&quot;)      ;   //  是否使用了校验位
localparam  STOP_BIT_INDEX      =   DATA_BITS + (HAS_PARITY ? 1 : 0) + 1 ;  // 停止位在 bit_cnt 中的位置

// 根据停止位长度计算停止位结束时刻的 baud_cnt 计数值
// 1   -&amp;gt; 在停止位中点（BAUD_CNT_MAX/2 - 1）结束
// 1.5 -&amp;gt; 在停止位中点后继续 1 个位周期（BAUD_CNT_MAX + BAUD_CNT_MAX/2 - 1）
// 2   -&amp;gt; 在停止位中点后继续 1.5 个位周期（2*BAUD_CNT_MAX - 1）
// 为了保持代码一致，减 1 的操作在 always 语句中进行
localparam  STOP_BITS1_CNT      =   BAUD_CNT_MAX/2                  ;   //  停止位为 1   时计数器的值
localparam  STOP_BITS1_5_CNT    =   BAUD_CNT_MAX + BAUD_CNT_MAX/2   ;   //  停止位为 1.5 时计数器的值
localparam  STOP_BITS2_CNT      =   (2 * BAUD_CNT_MAX)              ;   //  停止位为 2   时计数器的值

localparam  STOP_END_CNT        =   (STOP_BITS == &quot;1&quot;  ) ? (STOP_BITS1_CNT  ) :
                                    (STOP_BITS == &quot;1.5&quot;) ? (STOP_BITS1_5_CNT) :
                                    (STOP_BITS == &quot;2&quot;  ) ? (STOP_BITS2_CNT  ) :
                                     STOP_BITS1_CNT                             ;   // 停止位计数器的值，默认按停止位 1 处理

reg [31:00] baud_cnt    ;
reg [03:00] bit_cnt     ;
reg [07:00] data_reg    ;

wire        parity_bit  ;

// 校验位计算，使用的是缩位异或运算
// 缩位异或运算用来检查数据中 1 的个数是否为奇数，对其计算结果取反后可用于检查数据中 1 的个数是否为偶数
assign      parity_bit  =  (PARITY == &quot;ODD&quot;  ) ? ~(^tx_data[DATA_BITS-1:0]) :
                           (PARITY == &quot;EVEN&quot; ) ?   ^tx_data[DATA_BITS-1:0]  :
                           (PARITY == &quot;MARK&quot; ) ? 1&apos;b1 :
                           (PARITY == &quot;SPACE&quot;) ? 1&apos;b0 :
                           (PARITY == &quot;NONE&quot; ) ? 1&apos;b0 :
                           1&apos;bz;

// 判断校验位的参数是否正确
initial begin
    if (
        !((PARITY == &quot;ODD&quot;  ) ||
          (PARITY == &quot;EVEN&quot; ) ||
          (PARITY == &quot;MARK&quot; ) ||
          (PARITY == &quot;SPACE&quot;) ||
          (PARITY == &quot;NONE&quot; ))
    ) begin
        $error(&quot;Unknown PARITY value: [%s], for parameter PARITY, its value must be one of these values: ODD,EVEN,MARK,SPACE or NONE.&quot;, PARITY);
    end
end

// 打拍同步 tx_en 信号，检测上升沿
reg [2:0] tx_en_sr;
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) tx_en_sr &amp;lt;= 3&apos;b111;
    else        tx_en_sr &amp;lt;= {tx_en_sr[1:0], tx_en};
end
wire tx_en_rising_edge = (tx_en_sr[2:1] == 2&apos;b01); // 上升降沿检测

//  当 tx_en 检测到上升沿时, 寄存输入的并行数据, 并拉高 busy 信号
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        data_reg    &amp;lt;=  8&apos;b0        ;
        tx_busy     &amp;lt;=  1&apos;b0        ;
    end else if (tx_en_rising_edge) begin
        data_reg    &amp;lt;=  tx_data     ;   //  检测到上升沿, 寄存输入的并行数据
        tx_busy     &amp;lt;=  1&apos;b1        ;   //  拉高 busy 信号
    end else if (bit_cnt == STOP_BIT_INDEX &amp;amp;&amp;amp; baud_cnt == STOP_END_CNT - 1&apos;b1) begin
        data_reg    &amp;lt;=  8&apos;b0        ;   //  计数到停止位时清空发送数据寄存器
        tx_busy     &amp;lt;=  1&apos;b0        ;   //  拉低 busy 信号
    end else begin
        data_reg    &amp;lt;=  data_reg    ;
        tx_busy     &amp;lt;=  tx_busy     ;
    end
end

//  波特率计数器
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        baud_cnt    &amp;lt;=  16&apos;d0           ;
    end else if (tx_busy) begin 
        if(bit_cnt == STOP_BIT_INDEX) begin             //  停止位阶段
            if(baud_cnt &amp;lt; STOP_END_CNT)         
                baud_cnt &amp;lt;= baud_cnt + 1&apos;b1     ;       //  计数器计数到结束时刻
            else
                baud_cnt &amp;lt;= 16&apos;d0               ;       // 到达停止位结束时刻，清零（随后rx_flag变低）
        end else begin
            if (baud_cnt &amp;lt; BAUD_CNT_MAX - 1&apos;b1) begin   //  正常阶段，非停止位正常循环计数
                baud_cnt    &amp;lt;=  baud_cnt + 1&apos;b1 ;       //  接收过程时计数器循环计数
            end else begin
                baud_cnt &amp;lt;= 16&apos;d0               ;       // 完成了一个计数周期后清零
            end
        end
    end else begin
        baud_cnt    &amp;lt;=  16&apos;d0                   ;       //  空闲状态清零
    end
end

//  接收数据计数器
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        bit_cnt         &amp;lt;=  4&apos;d0            ;
    end else if (tx_en_rising_edge) begin 
        bit_cnt         &amp;lt;=  4&apos;d0            ;
    end else if (tx_busy) begin 
        if (bit_cnt &amp;lt; STOP_BIT_INDEX) begin
            if(baud_cnt == BAUD_CNT_MAX - 1&apos;b1) begin  // 每个位周期结束时自增
                bit_cnt &amp;lt;=  bit_cnt + 1&apos;b1  ;
            end else begin
                bit_cnt &amp;lt;=  bit_cnt         ;
            end
        end else begin      // 已到达停止位索引，保持不变，等待 rx_flag 拉低
            bit_cnt     &amp;lt;=  bit_cnt         ;
        end
    end else begin
        bit_cnt &amp;lt;= 4&apos;d0 ;   //  空闲状态清零
    end
end

//  根据 bit_cnt 来发送数据到 txd 端口
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) 
        txd &amp;lt;=  1&apos;b1    ;
    else if(tx_busy) begin                          // 系统发送忙状态时
        if(baud_cnt == BAUD_CNT_MID - 1&apos;b1) begin   // 判断 baud_cnt 是否计数到数据位的中间
            if (bit_cnt == 0) begin
                txd &amp;lt;=  1&apos;b0                ;   //  起始位
            end else if((bit_cnt &amp;gt;= 1) &amp;amp;&amp;amp; (bit_cnt &amp;lt;= DATA_BITS)) begin
                txd &amp;lt;=  data_reg[bit_cnt-1] ;   //  由低到高位发送数据
            end else if (HAS_PARITY == 1&apos;b1 &amp;amp;&amp;amp; bit_cnt == STOP_BIT_INDEX - 1) begin
                txd &amp;lt;=  parity_bit          ;   //  发送校验位
            end
        end else begin
            txd &amp;lt;=  txd ;
        end
    end else begin
        txd &amp;lt;=  1&apos;b1    ;   // 空闲时清零（不保留残余）
    end
end

endmodule
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;串口接收模块&lt;/h4&gt;
&lt;p&gt;该模块负责串口数据的接收&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// FILE_HEADER_HEADER-------------------------------------------------------------------------------
// Copyright (c) 2026, Mr. Tian. All rights reserved.
//--------------------------------------------------------------------------------------------------
// FILE NAME        : uart_rx.v
// AUTHOR           : Mr. Tian
// DESCRIPTION      : UART receiver
//--------------------------------------------------------------------------------------------------
// REVISION HISTORY :
//  Rev: (2026-05-06) - Mr. Tian
//          Initial release.
// FILE_HEADER_FOOTER-------------------------------------------------------------------------------
module uart_rx # (
    parameter   CLOCK_FREQ  =   32&apos;d100_000_000 ,   //  输入时钟频率 (200MHz)
    parameter   BAUD_RATE   =   32&apos;d115200      ,   //  波特率
    parameter   DATA_BITS   =   8               ,   //  数据位
    parameter   PARITY      =   &quot;ODD&quot;           ,   //  校验方式，有 ODD(奇校验)、EVEN(偶校验)、SPACE(始终为 0)、MARK(始终为 1)、NONE(无校验)
    parameter   STOP_BITS   =   &quot;1&quot;                 //  停止位: 1, 1.5, 2，输入其它值则默认为 1 停止位
) (
    input                   clk     ,   //  [I][     ] 模块时钟
    input                   rst_n   ,   //  [I][     ] 复位信号，低有效

    input   wire            rxd     ,   //  [I][     ] RXD 信号线
    
    output  reg     [07:00] rx_data ,   //  [O][07:00] 接收到的数据
    output  reg             rx_done ,   //  [O][     ] 接收完成标志（高脉冲）
    output  reg             rx_err      //  [O][     ] 错误标志（校验错误或帧错误）
);

localparam  BAUD_CNT_MAX        =   CLOCK_FREQ / BAUD_RATE  ;   //  计数器的最大计数值
localparam  BAUD_CNT_MID        =   BAUD_CNT_MAX / 2        ;   //  计数器值的中点
localparam  HAS_PARITY          =   (PARITY != &quot;NONE&quot;)      ;   //  是否使用了校验位
localparam  STOP_BIT_INDEX      =   DATA_BITS + (HAS_PARITY ? 1 : 0) + 1 ;  // 停止位在 bit_cnt 中的位置

// 根据停止位长度计算停止位结束时刻的 baud_cnt 计数值
// 1   -&amp;gt; 在停止位中点（BAUD_CNT_MAX/2 - 1）结束
// 1.5 -&amp;gt; 在停止位中点后继续 1 个位周期（BAUD_CNT_MAX + BAUD_CNT_MAX/2 - 1）
// 2   -&amp;gt; 在停止位中点后继续 1.5 个位周期（2*BAUD_CNT_MAX - 1）
// 为了保持代码一致，减 1 的操作在 always 语句中进行
localparam  STOP_BITS1_CNT      =   BAUD_CNT_MAX/2                  ;   //  停止位为 1   时计数器的值
localparam  STOP_BITS1_5_CNT    =   BAUD_CNT_MAX + BAUD_CNT_MAX/2   ;   //  停止位为 1.5 时计数器的值
localparam  STOP_BITS2_CNT      =   (2 * BAUD_CNT_MAX)              ;   //  停止位为 2   时计数器的值

localparam  STOP_END_CNT        =   (STOP_BITS == &quot;1&quot;  ) ? (STOP_BITS1_CNT  ) :
                                    (STOP_BITS == &quot;1.5&quot;) ? (STOP_BITS1_5_CNT) :
                                    (STOP_BITS == &quot;2&quot;  ) ? (STOP_BITS2_CNT  ) :
                                     STOP_BITS1_CNT                             ;   // 停止位计数器的值，默认按停止位 1 处理

reg [31:00] baud_cnt    ;
reg [03:00] bit_cnt     ;
reg [07:00] rx_reg      ;
reg         rx_parity   ;   //  接收到的校验位

// 打拍同步 rxd 信号，检测下降沿
reg [2:0] rxd_sync;
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) rxd_sync &amp;lt;= 3&apos;b111;
    else        rxd_sync &amp;lt;= {rxd_sync[1:0], rxd};
end
wire rxd_fall = (rxd_sync[2:1] == 2&apos;b10); // 下降沿检测
wire rxd_s    = rxd_sync[2];              // 同步后的电平

// 校验计算逻辑
wire    parity_check;
assign  parity_check    =  (PARITY == &quot;ODD&quot;  ) ? ~(^rx_reg[DATA_BITS-1:0]) :
                           (PARITY == &quot;EVEN&quot; ) ?   ^rx_reg[DATA_BITS-1:0]  :
                           (PARITY == &quot;MARK&quot; ) ? 1&apos;b1 :
                           (PARITY == &quot;SPACE&quot;) ? 1&apos;b0 :
                           (PARITY == &quot;NONE&quot; ) ? 1&apos;b0 :
                           1&apos;bz;
initial begin
    if (
        !((PARITY == &quot;ODD&quot;  ) ||
          (PARITY == &quot;EVEN&quot; ) ||
          (PARITY == &quot;MARK&quot; ) ||
          (PARITY == &quot;SPACE&quot;) ||
          (PARITY == &quot;NONE&quot; ))
    ) begin
        $error(&quot;Unknown PARITY value: [%s], for parameter PARITY, its value must be one of these values: ODD,EVEN,MARK,SPACE or NONE.&quot;, PARITY);
    end
end

// 接收标志指示器
reg     rx_flag;
wire    start_en = rxd_fall &amp;amp; ~rx_flag;
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        rx_flag &amp;lt;=  1&apos;b0    ;
    end else if (start_en) begin    //  检测到起始位
        rx_flag &amp;lt;=  1&apos;b1    ;       //  接收过程中，标志信号 rx_flag 拉高
        // 根据停止位长度，在指定时刻结束接收
    end else if (bit_cnt == STOP_BIT_INDEX &amp;amp;&amp;amp; baud_cnt == STOP_END_CNT - 1&apos;b1) begin
        rx_flag &amp;lt;=  1&apos;b0    ;
    end else begin
        rx_flag &amp;lt;= rx_flag  ;
    end
end

//  波特率计数器
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        baud_cnt    &amp;lt;=  16&apos;d0           ;
    end else if (rx_flag) begin 
        if(bit_cnt == STOP_BIT_INDEX) begin             //  停止位阶段
            if(baud_cnt &amp;lt; STOP_END_CNT)         
                baud_cnt &amp;lt;= baud_cnt + 1&apos;b1     ;       //  计数器计数到结束时刻
            else
                baud_cnt &amp;lt;= 16&apos;d0               ;       // 到达停止位结束时刻，清零（随后rx_flag变低）
        end else begin
            if (baud_cnt &amp;lt; BAUD_CNT_MAX - 1&apos;b1) begin   //  正常阶段，非停止位正常循环计数
                baud_cnt    &amp;lt;=  baud_cnt + 1&apos;b1 ;       //  接收过程时计数器循环计数
            end else begin
                baud_cnt &amp;lt;= 16&apos;d0               ;       // 完成了一个计数周期后清零
            end
        end
    end else begin
        baud_cnt    &amp;lt;=  16&apos;d0                   ;       //  空闲状态清零
    end
end

//  接收数据计数器
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        bit_cnt         &amp;lt;=  4&apos;d0            ;
    end else if (rx_flag) begin 
        if (bit_cnt &amp;lt; STOP_BIT_INDEX) begin
            if(baud_cnt == BAUD_CNT_MAX - 1&apos;b1) begin  // 每个位周期结束时自增
                bit_cnt &amp;lt;=  bit_cnt + 1&apos;b1  ;
            end else begin
                bit_cnt &amp;lt;=  bit_cnt         ;
            end
        end else begin      // 已到达停止位索引，保持不变，等待 rx_flag 拉低
            bit_cnt     &amp;lt;=  bit_cnt         ;
        end
    end else begin
        bit_cnt &amp;lt;= 4&apos;d0 ;   //  空闲状态清零
    end
end

//  根据 bit_cnt 来寄存 rxd 端口数据
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        rx_reg      &amp;lt;= 8&apos;b0                     ;
        rx_parity   &amp;lt;=  1&apos;b0                    ;
    end else if(rx_flag) begin                          // 系统处于接收过程时
        if(baud_cnt == BAUD_CNT_MID - 1&apos;b1) begin   // 判断 baud_cnt 是否计数到数据位的中间
            if((bit_cnt &amp;gt;= 1) &amp;amp;&amp;amp; (bit_cnt &amp;lt;= DATA_BITS)) begin
                rx_reg[bit_cnt-1]   &amp;lt;= rxd_s    ;   //  数据位采样 (按位索引写入)
            end else if (HAS_PARITY == 1&apos;b1 &amp;amp;&amp;amp; bit_cnt == STOP_BIT_INDEX - 1&apos;b1) begin
                rx_parity           &amp;lt;=  rxd_s   ;   //  采样校验位
            end
        end else begin
            rx_reg &amp;lt;= rx_reg;
        end
    end else begin
        rx_reg &amp;lt;= 8&apos;b0;   // 空闲时清零（不保留残余）
    end
end

//  校验位判断, 若校验位不通过则将 rx_err 置 1
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        rx_err  &amp;lt;=  1&apos;b0    ;
    end else if ((HAS_PARITY == 1&apos;b1) &amp;amp;&amp;amp; (rx_parity == parity_check)) begin
        rx_err  &amp;lt;=  1&apos;b0    ;
    end else begin
        rx_err  &amp;lt;=  1&apos;b1    ;
    end
end

//  赋值接收完成信号和接收到的数据
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        rx_done &amp;lt;= 1&apos;b0;
        rx_data &amp;lt;= 8&apos;b0;
    end
    // 在停止位中点采样完毕时输出数据
    else if((bit_cnt == STOP_BIT_INDEX) &amp;amp;&amp;amp; (baud_cnt == BAUD_CNT_MID - 1&apos;b1)) begin
        rx_done &amp;lt;= 1&apos;b1     ;  // 拉高接收完成信号
        rx_data &amp;lt;= rx_reg   ;  // 并对UART接收到的数据进行赋值
    end    
    else begin
        rx_done &amp;lt;= 1&apos;b0     ;
        rx_data &amp;lt;= rx_data  ;
    end
end

endmodule
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;封装后的串口顶层&lt;/h4&gt;
&lt;p&gt;为了方便使用，直接将发送模块和接收模块作为两个子模块封装到一个顶层模块中。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// FILE_HEADER_HEADER-------------------------------------------------------------------------------
// Copyright (c) 2026, Mr. Tian. All rights reserved.
//--------------------------------------------------------------------------------------------------
// FILE NAME        : uart.v
// AUTHOR           : Mr. Tian
// DESCRIPTION      : Top level UART module encapsulating TX and RX
//--------------------------------------------------------------------------------------------------
// REVISION HISTORY :
//  Rev: (2026-05-06) - Mr. Tian
//          Initial release.
// FILE_HEADER_FOOTER-------------------------------------------------------------------------------
module uart # (
    parameter               CLOCK_FREQ  =   32&apos;d100_000_000 ,   //  输入时钟频率 (200MHz)
    parameter               BAUD_RATE   =   32&apos;d115200      ,   //  波特率
    parameter               DATA_BITS   =   8               ,   //  数据位
    parameter   [8*8-1:0]   PARITY      =   &quot;ODD&quot;           ,   //  校验方式，有 ODD(奇校验)、EVEN(偶校验)、SPACE(始终为 0)、MARK(始终为 1)、NONE(无校验)
    parameter   [8*8-1:0]   STOP_BITS   =   &quot;1&quot;                 //  停止位: 1, 1.5, 2，输入其它值则默认为 1 停止位
) (
    input                   clk     ,
    input                   rst_n   ,

    // TX 接口
    input   wire            tx_en   ,
    input   wire    [07:00] tx_data ,
    output  wire            tx_busy ,
    output  wire            txd     ,

    // RX 接口
    input   wire            rxd     ,
    output  wire    [07:00] rx_data ,
    output  wire            rx_done ,
    output  wire            rx_err
);

// 实例化发送模块
uart_tx #(
    .CLOCK_FREQ (CLOCK_FREQ ),
    .BAUD_RATE  (BAUD_RATE  ),
    .DATA_BITS  (DATA_BITS  ),
    .PARITY     (PARITY     ),
    .STOP_BITS  (STOP_BITS  )
) uart_tx_inst (
    .clk        (clk        ),
    .rst_n      (rst_n      ),
    .tx_en      (tx_en      ),
    .tx_data    (tx_data    ),
    .tx_busy    (tx_busy    ),
    .txd        (txd        )
);

// 实例化接收模块
uart_rx #(
    .CLOCK_FREQ (CLOCK_FREQ ),
    .BAUD_RATE  (BAUD_RATE  ),
    .DATA_BITS  (DATA_BITS  ),
    .PARITY     (PARITY     ),
    .STOP_BITS  (STOP_BITS  )
) uart_rx_inst (
    .clk        (clk        ),
    .rst_n      (rst_n      ),
    .rxd        (rxd        ),
    .rx_data    (rx_data    ),
    .rx_done    (rx_done    ),
    .rx_err     (rx_err     )
);

endmodule
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;回环测试(工程顶层)代码&lt;/h4&gt;
&lt;p&gt;在这里我使用的开发板型号为 &lt;code&gt;XC7K325TFFG900-2&lt;/code&gt;，时钟输入为差分信号输入&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// FILE_HEADER_HEADER-------------------------------------------------------------------------------
// Copyright (c) 2026, Mr. Tian. All rights reserved.
//--------------------------------------------------------------------------------------------------
// FILE NAME        : uart_loopback_top.v
// AUTHOR           : Mr. Tian.
// DESCRIPTION      : Top module for uart loopback
//--------------------------------------------------------------------------------------------------
// REVISION HISTORY :
//  Rev: (2026-05-06) - Mr. Tian.
//          Initial release.
// FILE_HEADER_FOOTER-------------------------------------------------------------------------------
module uart_loopback_top # (
    parameter   CLOCK_FREQ  =   32&apos;d200_000_000 ,   //  输入时钟频率 (200MHz)
    parameter   BAUD_RATE   =   32&apos;d115200      ,   //  波特率
    parameter   DATA_BITS   =   8               ,   //  数据位
    parameter   PARITY      =   &quot;NONE&quot;           ,   //  校验方式，有 ODD(奇校验)、EVEN(偶校验)、SPACE(始终为 0)、MARK(始终为 1)、NONE(无校验)
    parameter   STOP_BITS   =   &quot;1&quot;                 //  停止位: 1, 1.5, 2，输入其它值则默认为 1 停止位
) (
    input                   sys_clk_p,              // 200MHz 差分时钟 P 端
    input                   sys_clk_n,              // 200MHz 差分时钟 N 端
    input                   rst_n   ,               // 复位信号（低有效）

    // 硬件引脚连接到串口芯片
    input                   uart_rxd,               // UART 接收引脚
    output                  uart_txd,               // UART 发送引脚
    
    // 连接到 LED 的状态指示
    output                  led_rx_done,
    output                  led_err
);

// 差分时钟输入转单端时钟输出
wire clk_200m_single;
IBUFDS ibufds_inst (
    .I  (   sys_clk_p       ),
    .IB (   sys_clk_n       ),
    .O  (   clk_200m_single )
);

// 例化串口模块
wire            tx_en   ;
wire    [07:00] tx_data ;
wire    [07:00] rx_data ;
wire            tx_busy ;
wire            rx_done ;
uart # (
    .CLOCK_FREQ (CLOCK_FREQ ),
    .BAUD_RATE  (BAUD_RATE  ),
    .DATA_BITS  (DATA_BITS  ),
    .PARITY     (PARITY     ),
    .STOP_BITS  (STOP_BITS  )
) uart_inst (
    .clk        (   clk_200m_single ),
    .rst_n      (   rst_n           ),

    // TX interface
    .tx_en      (   tx_en           ),
    .tx_data    (   tx_data         ),
    .tx_busy    (   tx_busy         ),
    .txd        (   uart_txd        ),

    // RX interface
    .rxd        (   uart_rxd        ),
    .rx_data    (   rx_data         ),
    .rx_done    (   rx_done         ),
    .rx_err     (   led_err         )
);

assign tx_data      = rx_data; // 回环测试：发送的数据为接收到的数据
assign tx_en        = rx_done; // 回环测试：发送使能信号为接收完成信号
assign led_rx_done  = rx_done; // 将接收完成信号输出到板载的一颗 led 上

endmodule
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>ETH-04 ARP 通信协议简介及实现代码</title><link>https://blog.tyh123.top/posts/424a6d95/</link><guid isPermaLink="true">https://blog.tyh123.top/posts/424a6d95/</guid><pubDate>Thu, 16 Apr 2026 11:25:17 GMT</pubDate><content:encoded>&lt;h3&gt;ARP 协议简介&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;ARP（Address Resolution Protocol，地址解析协议）&lt;/strong&gt; 是一种根据 IP 地址 (逻辑地址) 获取 MAC 地址 (物理地址) 的 TCP/IP 协议。&lt;/p&gt;
&lt;p&gt;在以太网环境中，设备之间的通信&lt;strong&gt;依赖于 MAC 地址&lt;/strong&gt;，但上层应用通常&lt;strong&gt;只知道目标设备的 IP 地址&lt;/strong&gt;。ARP 协议通过 &quot;一问一答&quot; 的机制，解决了 &quot;&lt;strong&gt;已知 IP 地址，如何找到对应 MAC 地址&lt;/strong&gt;&quot; 的问题，确保数据能够正确封装并送达目标设备。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/posts/fpga_impl_interface/ethernet_impl/eth-04/arp_block_diagram.png&quot; alt=&quot;ARP 协议图&quot; /&gt;&lt;/p&gt;
&lt;p&gt;其工作过程简述如下：主机 A 发送数据时，按照以太网帧格式封装，但将目标 MAC 地址设置为&lt;strong&gt;广播地址（FF:FF:FF:FF:FF:FF）&lt;/strong&gt;。这样，局域网内所有主机都会收到该数据包。只有 IP 地址与目标匹配的主机 B 会响应，将自己的 MAC 地址发送给主机 A，从而完成地址解析。其他非对应 IP 的主机则自动忽略该广播包。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;补充说明：TCP/IP 协议簇&lt;/strong&gt;&lt;br /&gt;
TCP/IP 是互联网的核心通信协议，它并非单一的协议，而是一个协议簇 (协议集合) ，定义了设备之间如何联网和通信。其名称来源于其中最著名的两个协议：&lt;strong&gt;TCP&lt;/strong&gt; (传输控制协议) 和 &lt;strong&gt;IP&lt;/strong&gt; (互联网协议)。ARP 正是这个协议簇中的一员。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;ARP 请求&lt;/h3&gt;
&lt;p&gt;当主机需要与目标 IP 通信，但不知道目标 IP 的 MAC 地址时，会构造 ARP 请求报文，并&lt;strong&gt;以广播的方式（目标 MAC 地址为 FF:FF:FF:FF:FF:FF）发送到整个局域网&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;特点：&lt;strong&gt;广播发送&lt;/strong&gt;，局域网内所有的设备都会收到这个数据包，但&lt;strong&gt;只有目标 IP 地址匹配的设备才会处理&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;ARP 应答&lt;/h3&gt;
&lt;p&gt;当目标设备收到 ARP 请求后，发现目标 IP 与自己的 IP 匹配，会构造 ARP 应答报文，并&lt;strong&gt;以单播的方式直接回复给请求方。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;特点：&lt;strong&gt;单播发送&lt;/strong&gt;，同时目标设备也会将请求方的 IP-MAC 映射记录到自己的 ARP 缓存中。&lt;/p&gt;
&lt;h3&gt;ARP 协议数据格式&lt;/h3&gt;
&lt;h4&gt;以太网帧格式&lt;/h4&gt;
&lt;p&gt;ARP 协议的以太网帧数据格式如下图所示，ARP 数据位于以太网数据帧中的数据段部分。
&lt;img src=&quot;/img/posts/fpga_impl_interface/ethernet_impl/eth-04/arp_format_mac.png&quot; alt=&quot;ARP 协议 MAC 帧&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;ARP 数据报格式&lt;/h4&gt;
&lt;p&gt;ARP 数据报位于以太网帧中的数据段中，它的格式如下图所示。
&lt;img src=&quot;/img/posts/fpga_impl_interface/ethernet_impl/eth-04/arp_format.png&quot; alt=&quot;ARP 数据报格式&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;硬件类型&lt;/strong&gt;：指定底层网络硬件类型。对于以太网，值为 &lt;strong&gt;1 &lt;code&gt;(0x0001)&lt;/code&gt;&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;协议类型&lt;/strong&gt;：要映射的协议地址类型，&lt;strong&gt;ARP协议的上层协议为IP协议&lt;/strong&gt;，因此该协议类型为IP协议，其值为 0x0800。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;硬件地址长度&lt;/strong&gt;：硬件地址（MAC地址）的长度，以字节为单位。对于以太网上 IP 地址的 ARP 请求或者应答来说，该值为6。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;协议地址长度&lt;/strong&gt;：IP 地址的长度，以字节为单位。对于以太网上 IP 地址的 ARP 请求或者应答来说，该值为4。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;操作码 (OP)&lt;/strong&gt;：用于表示该数据包为 ARP 请求或者 ARP 应答。1 表示 ARP 请求，2 表示ARP 应答。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;源 MAC 地址&lt;/strong&gt;：&lt;strong&gt;发送端&lt;/strong&gt;的 MAC 地址，即硬件地址&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;源 IP 地址&lt;/strong&gt;： &lt;strong&gt;发送端&lt;/strong&gt;的 IP 地址，即协议地址&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;目的 MAC 地址&lt;/strong&gt;：&lt;strong&gt;接收端&lt;/strong&gt;的 MAC 地址，即硬件地址&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;目的 IP 地址&lt;/strong&gt;： &lt;strong&gt;接收端&lt;/strong&gt;的 IP 地址，即协议地址&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;实现代码&lt;/h3&gt;
&lt;p&gt;基于之前文章 &lt;a href=&quot;ETH-03%20%E4%BB%A5%E5%A4%AA%E7%BD%91%E5%B8%A7%E7%BB%93%E6%9E%84%E4%BB%8B%E7%BB%8D%E4%BB%A5%E5%8F%8A%20Verilog%20%E5%AE%9E%E7%8E%B0%E4%BB%A3%E7%A0%81.md&quot;&gt;ETH-03 以太网帧结构介绍以及 Verilog 实现代码&lt;/a&gt; 中的以太网帧封包模块，我们只需要向模块中发送数据段的 ARP 数据即可。&lt;/p&gt;
&lt;h4&gt;ARP 发送模块&lt;/h4&gt;
&lt;p&gt;只需要发送 ARP 数据段的内容给 以太网封包模块即可。&lt;/p&gt;
&lt;p&gt;模块工作过程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;上游控制模块为端口&lt;code&gt;arp_tx_en&lt;/code&gt; 产生一个高电平脉冲。之后模块将在端口 &lt;code&gt;s_axis_tdv&lt;/code&gt; 产生持续的高电平信号，给下游的以太网封包模块。&lt;/li&gt;
&lt;li&gt;模块等待下游以太网封包模块拉高 &lt;code&gt;s_axis_trdy&lt;/code&gt; 信号，之后开始发送 ARP 数据。&lt;/li&gt;
&lt;li&gt;发送到最后一个字节的 ARP 数据时，产生 &lt;code&gt;s_axis_tend&lt;/code&gt; 信号。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;完整的实现代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module arp_tx (
    input   wire            clk             ,   //  [I] [     ] module clock
    input   wire            rst_n           ,   //  [I] [     ] module reset, active-low
    input   wire            arp_tx_en       ,   //  [I] [     ] ARP TX enable signal, active-high pulse signal
    input   wire            arp_tx_type     ,   //  [I] [     ] ARP TX type, 1&apos;b0: ARP req, 1&apos;b1: ARP ack

    input   wire    [47:0]  tx_src_mac      ,   //  [I] [47:00] Source MAC address to be send
    input   wire    [47:0]  tx_dst_mac      ,   //  [I] [47:00] Destination MAC address to be send
    input   wire    [31:0]  tx_src_ip       ,   //  [I] [31:00] Source IP address to be send
    input   wire    [31:0]  tx_dst_ip       ,   //  [I] [31:00] Destination IP address to be send

    // downstream: ethernet pack encapsulation
    input   wire            encap_busy      ,   //  [I] [     ] encapsulation module busy state input, we only send arp packet when this signal is 1&apos;b0    
    output  reg     [7:0]   s_axis_td       ,   //  [O] [07:00] axis send data output to encapsulation module
    output  reg             s_axis_tdv      ,   //  [O] [     ] axis send data valid output to encapsulation module
    output  reg             s_axis_tend     ,   //  [O] [     ] axis send tend output to encapsulation module
    input   wire            s_axis_trdy         //  [I] [     ] axis send ready signal
);

//================================================================================
// Local Parameter Declarations
//================================================================================
// states define
localparam  ST_IDLE         =   2&apos;d0        ;
localparam  ST_ARP_DATA     =   2&apos;d1        ;
localparam  ST_ARP_END      =   2&apos;d2        ;

// local parameters
localparam  ETH_TYPE        =   16&apos;h0806    ;
localparam  HD_TYPE         =   16&apos;h0001    ;
localparam  PROTOCOL_TYPE   =   16&apos;h0800    ;
localparam  MAC_ADD_LEN     =   8&apos;h06       ;
localparam  IP_ADD_LEN      =   8&apos;h04       ;
localparam  OP_CODE         =   16&apos;h0001    ;

localparam  PREAMBLE_WORD   =   8&apos;h55       ;
localparam  SFD_WORD        =   8&apos;hd5       ;

//================================================================================
// Register Declarations
//================================================================================
reg [1:0]   state               ;
reg [7:0]   arp_data    [27:0]  ;
reg [4:0]   arp_cnt             ;

reg [2:0]   arp_tx_en_sr        ;
reg         arp_tx_en_latch     ;

//================================================================================
// Wire Declarations
//================================================================================
wire        arp_tx_en_pulse     ;

//================================================================================
// Assign Declarations
//================================================================================
assign  arp_tx_en_pulse = {arp_tx_en_sr[0] &amp;amp; !arp_tx_en_sr[1]};

//================================================================================
// MAIN CODE
//================================================================================

// sync arp_tx_en signal
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        arp_tx_en_sr &amp;lt;= 3&apos;b000  ;
    end else begin
        arp_tx_en_sr &amp;lt;= {arp_tx_en_sr[1:0], arp_tx_en}  ;
    end
end

always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        arp_data[00]    &amp;lt;=  HD_TYPE[15:08]      ;
        arp_data[01]    &amp;lt;=  HD_TYPE[07:00]      ;
        arp_data[02]    &amp;lt;=  PROTOCOL_TYPE[15:08];
        arp_data[03]    &amp;lt;=  PROTOCOL_TYPE[07:00];
        arp_data[04]    &amp;lt;=  MAC_ADD_LEN         ;
        arp_data[05]    &amp;lt;=  IP_ADD_LEN          ;
        arp_data[06]    &amp;lt;=  OP_CODE[15:08]      ;
        arp_data[07]    &amp;lt;=  OP_CODE[07:00]      ;
        arp_data[08]    &amp;lt;=  8&apos;b00   ;
        arp_data[09]    &amp;lt;=  8&apos;b00   ;
        arp_data[10]    &amp;lt;=  8&apos;b00   ;
        arp_data[11]    &amp;lt;=  8&apos;b00   ;
        arp_data[12]    &amp;lt;=  8&apos;b00   ;
        arp_data[13]    &amp;lt;=  8&apos;b00   ;
        arp_data[14]    &amp;lt;=  8&apos;b00   ;
        arp_data[15]    &amp;lt;=  8&apos;b00   ;
        arp_data[16]    &amp;lt;=  8&apos;b00   ;
        arp_data[17]    &amp;lt;=  8&apos;b00   ;
        arp_data[18]    &amp;lt;=  8&apos;b00   ;
        arp_data[19]    &amp;lt;=  8&apos;b00   ;
        arp_data[20]    &amp;lt;=  8&apos;b00   ;
        arp_data[21]    &amp;lt;=  8&apos;b00   ;
        arp_data[22]    &amp;lt;=  8&apos;b00   ;
        arp_data[23]    &amp;lt;=  8&apos;b00   ;
        arp_data[24]    &amp;lt;=  8&apos;b00   ;
        arp_data[25]    &amp;lt;=  8&apos;b00   ;
        arp_data[26]    &amp;lt;=  8&apos;b00   ;
        arp_data[27]    &amp;lt;=  8&apos;b00   ;

        s_axis_tdv      &amp;lt;=  1&apos;b0    ;
        arp_tx_en_latch &amp;lt;=  1&apos;b0    ;
        s_axis_tend     &amp;lt;=  1&apos;b0    ;
        s_axis_td       &amp;lt;=  8&apos;b0    ;
        arp_cnt         &amp;lt;=  5&apos;d0    ;
        state           &amp;lt;=  ST_IDLE ;
    end else begin
        if (arp_tx_en_pulse) arp_tx_en_latch  &amp;lt;=  1&apos;b1        ;

        case (state)
            ST_IDLE: begin
                s_axis_tend &amp;lt;=  1&apos;b0    ;
                s_axis_td   &amp;lt;=  8&apos;b0    ;
                arp_cnt     &amp;lt;=  5&apos;d0    ;
                s_axis_tdv  &amp;lt;=  1&apos;b0    ;
                if ((arp_tx_en_latch == 1&apos;b1) &amp;amp;&amp;amp; (encap_busy == 1&apos;b0)) begin
                    arp_tx_en_latch &amp;lt;=  1&apos;b0                ;
                    arp_data[08]    &amp;lt;=  tx_src_mac[47:40]   ;
                    arp_data[09]    &amp;lt;=  tx_src_mac[39:32]   ;
                    arp_data[10]    &amp;lt;=  tx_src_mac[31:24]   ;
                    arp_data[11]    &amp;lt;=  tx_src_mac[23:16]   ;
                    arp_data[12]    &amp;lt;=  tx_src_mac[15:08]   ;
                    arp_data[13]    &amp;lt;=  tx_src_mac[07:00]   ;
                    arp_data[14]    &amp;lt;=  tx_src_ip [31:24]   ;
                    arp_data[15]    &amp;lt;=  tx_src_ip [23:16]   ;
                    arp_data[16]    &amp;lt;=  tx_src_ip [15:08]   ;
                    arp_data[17]    &amp;lt;=  tx_src_ip [07:00]   ;
                    arp_data[18]    &amp;lt;=  tx_dst_mac[47:40]   ;
                    arp_data[19]    &amp;lt;=  tx_dst_mac[39:32]   ;
                    arp_data[20]    &amp;lt;=  tx_dst_mac[31:24]   ;
                    arp_data[21]    &amp;lt;=  tx_dst_mac[23:16]   ;
                    arp_data[22]    &amp;lt;=  tx_dst_mac[15:08]   ;
                    arp_data[23]    &amp;lt;=  tx_dst_mac[07:00]   ;
                    arp_data[24]    &amp;lt;=  tx_dst_ip [31:24]   ;
                    arp_data[25]    &amp;lt;=  tx_dst_ip [23:16]   ;
                    arp_data[26]    &amp;lt;=  tx_dst_ip [15:08]   ;
                    arp_data[27]    &amp;lt;=  tx_dst_ip [07:00]   ;

                    if (arp_tx_type == 1&apos;b0) begin
                        arp_data[7]     &amp;lt;=  8&apos;h01   ;
                        arp_data[18]    &amp;lt;=  8&apos;h00   ;
                        arp_data[19]    &amp;lt;=  8&apos;h00   ;
                        arp_data[20]    &amp;lt;=  8&apos;h00   ;
                        arp_data[21]    &amp;lt;=  8&apos;h00   ;
                        arp_data[22]    &amp;lt;=  8&apos;h00   ;
                        arp_data[23]    &amp;lt;=  8&apos;h00   ;
                    end else begin
                        arp_data[7]     &amp;lt;=  8&apos;h02   ;
                    end

                    state           &amp;lt;=  ST_ARP_DATA     ;
                    s_axis_tdv      &amp;lt;=  1&apos;b1            ;
                end
            end

            ST_ARP_DATA: begin
                if (s_axis_trdy == 1&apos;b1) begin
                    s_axis_td   &amp;lt;=  arp_data[arp_cnt]   ;
                    arp_cnt     &amp;lt;=  arp_cnt + 1&apos;b1      ;
                    if (arp_cnt == 5&apos;d27) begin
                        s_axis_tend &amp;lt;=  1&apos;b1            ;
                        state       &amp;lt;=  ST_ARP_END      ;
                    end
                end
            end

            ST_ARP_END: begin
                s_axis_tend &amp;lt;=  1&apos;b0    ;
                s_axis_td   &amp;lt;=  8&apos;d0    ;
                s_axis_tdv  &amp;lt;=  1&apos;b0    ;
                arp_cnt     &amp;lt;=  5&apos;d0    ;
                state       &amp;lt;=  ST_IDLE ;
            end
        endcase
    end
end

endmodule
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;ARP 接收模块&lt;/h4&gt;
&lt;p&gt;接收模块同样从以太网帧解包模块中接收负载数据，负载数据即为 ARP 数据内容。&lt;/p&gt;
&lt;p&gt;模块工作过程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;从上游以太网帧解包模块中等待 &lt;code&gt;s_axis_rdv&lt;/code&gt; 信号，表明负载数据有效。&lt;/li&gt;
&lt;li&gt;开始按字节计数，将接收到的数据寄存&lt;/li&gt;
&lt;li&gt;判断目的 IP 地址是否为板卡 IP 地址，如果是板卡的 IP 地址，则将寄存的源 MAC 地址输出到模块的端口，方便其它模块调用，否则将接收到的数据丢弃。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;module arp_rx (
    input   wire            clk             ,   //  [I] [     ] Module clock
    input   wire            rst_n           ,   //  [I] [     ] Module reset signal, active-low

    input   wire    [15:00] eth_type        ,   //  [I] [15:00] received ethertype
    output  reg     [47:00] src_mac         ,   //  [O] [47:00] Received source MAC address
    output  reg     [31:00] src_ip          ,   //  [O] [31:00] Received source IP address
    input   wire    [31:00] board_ip        ,   //  [I] [31:00] Input board IP address, for comparison with received destination IP address

    // upstream: ethernet pack decapsulation
    input   wire            decap_busy      ,   //  [I] [     ] decapsulation module busy state input
    input   wire    [7:0]   s_axis_rd       ,   //  [I] [07:00] axis receive data input from decapsulation module
    input   wire            s_axis_rdv      ,   //  [I] [     ] axis receive data valid input from decapsulation module
    input   wire            s_axis_rend     ,   //  [I] [     ] axis receive tend input from decapsulation module
    output  reg             s_axis_rrdy     ,   //  [O] [     ] axis receive ready signal
    
    output  reg             arp_rx_done     ,   //  [O] [     ] ARP receive done signal, active-high
    output  reg             arp_rx_type         //  [O] [     ] ARP receive type, 1&apos;b1: ack, 1&apos;b0: req
);

//================================================================================
// Local Parameter Declarations
//================================================================================
localparam  ST_IDLE         =   2&apos;b00   ;
localparam  ST_ARP_DATA     =   2&apos;b01   ;
localparam  ST_ARP_END      =   2&apos;b10   ;

localparam  ETH_TYPE_ARP    =   16&apos;h0806;

//================================================================================
// Register Declarations
//================================================================================
reg [47:00] dst_mac_t       ;
reg [31:00] dst_ip_t        ;
reg [47:00] src_mac_t       ;
reg [31:00] src_ip_t        ;
reg [15:00] op_data         ;

reg [01:00] state           ;
reg [04:00] data_bytes_cnt  ;

//================================================================================
// MAIN CODE
//================================================================================
always  @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        state           &amp;lt;=  ST_IDLE ;
        data_bytes_cnt  &amp;lt;=  5&apos;b0    ;
        s_axis_rrdy     &amp;lt;=  1&apos;b0    ;
        op_data         &amp;lt;=  16&apos;b0   ;
        dst_mac_t       &amp;lt;=  48&apos;b0   ;
        dst_ip_t        &amp;lt;=  32&apos;b0   ;
        src_mac_t       &amp;lt;=  48&apos;b0   ;
        src_ip_t        &amp;lt;=  32&apos;b0   ;
        src_mac         &amp;lt;=  48&apos;b0   ;
        src_ip          &amp;lt;=  32&apos;b0   ;
        arp_rx_done     &amp;lt;=  1&apos;b0    ;
        arp_rx_type     &amp;lt;=  1&apos;b0    ;
    end else begin 
        case(state) 
            ST_IDLE: begin
                data_bytes_cnt  &amp;lt;=  5&apos;b0        ;
                s_axis_rrdy     &amp;lt;=  1&apos;b1        ;
                arp_rx_done     &amp;lt;=  1&apos;b0        ;
                
                if (
                    eth_type    ==  ETH_TYPE_ARP &amp;amp;&amp;amp;
                    s_axis_rdv  ==  1&apos;b1        
                ) begin
                    state       &amp;lt;=  ST_ARP_DATA ;
                    data_bytes_cnt  &amp;lt;=  data_bytes_cnt + 1&apos;b1   ;
                end else begin
                    state       &amp;lt;=  ST_IDLE     ;
                end
            end

            ST_ARP_DATA: begin
                data_bytes_cnt  &amp;lt;=  data_bytes_cnt + 1&apos;b1           ;
                if (data_bytes_cnt &amp;gt;= 5&apos;d6 &amp;amp;&amp;amp; data_bytes_cnt &amp;lt; 5&apos;d8) begin
                    op_data     &amp;lt;=  {op_data[7:0], s_axis_rd}       ;                   // OP code
                end else if (data_bytes_cnt &amp;gt;= 5&apos;d8  &amp;amp;&amp;amp; data_bytes_cnt &amp;lt; 5&apos;d14) begin   
                    src_mac_t   &amp;lt;=  {src_mac_t[39:0], s_axis_rd}    ;                   // So urce MAC address
                end else if (data_bytes_cnt &amp;gt;= 5&apos;d14 &amp;amp;&amp;amp; data_bytes_cnt &amp;lt; 5&apos;d18) begin
                    src_ip_t    &amp;lt;=  {src_ip_t[23:0], s_axis_rd}     ;                   // Source IP address
                end else if (data_bytes_cnt &amp;gt;= 5&apos;d18 &amp;amp;&amp;amp; data_bytes_cnt &amp;lt; 5&apos;d24) begin
                    dst_mac_t   &amp;lt;=  {dst_mac_t[39:0], s_axis_rd}    ;                   // Destination MAC address
                end else if (data_bytes_cnt &amp;gt;= 5&apos;d24 &amp;amp;&amp;amp; data_bytes_cnt &amp;lt; 5&apos;d28) begin
                    dst_ip_t    &amp;lt;=  {dst_ip_t[23:0], s_axis_rd}     ;                   // Destination IP address
                end else if (data_bytes_cnt &amp;gt;= 5&apos;d28) begin
                    if (
                        dst_ip_t    == board_ip &amp;amp;&amp;amp; 
                        ((op_data == 16&apos;d1) || (op_data == 16&apos;d2))
                    ) begin
                        arp_rx_done &amp;lt;=  1&apos;b1        ;
                        src_mac     &amp;lt;=  src_mac_t   ;
                        src_ip      &amp;lt;=  src_ip_t    ;
                        dst_mac_t   &amp;lt;=  48&apos;b0       ;
                        dst_ip_t    &amp;lt;=  32&apos;b0       ;
                        src_mac_t   &amp;lt;=  48&apos;b0       ;
                        src_ip_t    &amp;lt;=  32&apos;b0       ;
                        if (op_data == 16&apos;d1) begin
                            arp_rx_type &amp;lt;= 1&apos;b0     ;   //  arp request
                        end else begin
                            arp_rx_type &amp;lt;= 1&apos;b1     ;   //  arp ack
                        end
                        state   &amp;lt;=  ST_ARP_END      ;
                    end else begin
                        state   &amp;lt;= ST_ARP_END       ;
                    end
                end
            end

            ST_ARP_END: begin
                s_axis_rrdy     &amp;lt;=  1&apos;b0            ;
                if (decap_busy == 1&apos;b0) begin
                    state       &amp;lt;=  ST_IDLE         ;
                end
            end
        endcase
    end
end

endmodule
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;ARP 模块&lt;/h4&gt;
&lt;p&gt;这个模块是将两个模块进行整合封装到一个顶层，方便后续的例化&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module arp (
    input                   rst_n       ,   //  [I] [     ] Module reset signal, active-low
    input                   gmii_tx_clk ,   //  [I] [     ] GMII TX clock
    input                   gmii_rx_clk ,   //  [I] [     ] GMII RX clock
    
    input   wire    [47:00] board_mac   ,   //  [I] [47:00] Input board MAC address, for comparison with received destination MAC address.
    input   wire    [31:00] board_ip    ,   //  [I] [31:00] Input board IP address, for comparison with received destination IP address.

    input   wire            decap_busy  ,   //  [I] [     ] Upstream decapsulation module busy state input
    input   wire            encap_busy  ,   //  [I] [     ] Downstream encapsulation module busy state input

    // arp rx
    input   wire    [07:00] s_axis_rd   ,   //  [I] [07:00] axis receive data input from decapsulation module
    input   wire            s_axis_rdv  ,   //  [I] [     ] axis receive data valid input from decapsulation module
    input   wire            s_axis_rend ,   //  [I] [     ] axis receive tend input from decapsulation module
    output  wire            s_axis_rrdy ,   //  [O] [     ] axis receive ready signal
    output  wire            arp_rx_done ,   //  [O] [     ] ARP receive done signal, active-high
    output  wire            arp_rx_type ,   //  [O] [     ] ARP receive type, 1&apos;b1: ack, 1&apos;b0: req
    input   wire    [15:00] eth_type    ,   //  [I] [15:00] received ethertype
    output  wire    [47:00] rx_src_mac  ,   //  [O] [47:00] Received source MAC address
    output  wire    [31:00] rx_src_ip   ,   //  [O] [31:00] Received source IP address

    // arp tx
    input   wire            arp_tx_en   ,   //  [I] [     ] ARP TX enable signal, active-high pulse signal
    input   wire            arp_tx_type ,   //  [I] [     ] ARP TX type, 1&apos;b0: ARP req, 1&apos;b1: ARP ack
    input   wire    [47:00] tx_dst_mac  ,   //  [I] [47:00] Destination MAC address to be send
    input   wire    [31:00] tx_dst_ip   ,   //  [I] [31:00] Destination IP address to be send
    output  wire    [07:00] s_axis_td   ,   //  [O] [07:00] axis send data output to encapsulation module
    output  wire            s_axis_tdv  ,   //  [O] [     ] axis send data valid output to encapsulation module
    output  wire            s_axis_tend ,   //  [O] [     ] axis send tend output to encapsulation module
    input   wire            s_axis_trdy     //  [I] [     ] axis send ready signal
);

arp_rx arp_rx_inst (
    .clk            (   gmii_rx_clk ),  //  [I] [     ] Module clock
    .rst_n          (   rst_n       ),  //  [I] [     ] Module reset signal, active-low
    
    .eth_type       (   eth_type    ),  //  [I] [15:00] received ethertype
    .src_mac        (   rx_src_mac  ),  //  [O] [47:00] Received source MAC address
    .src_ip         (   rx_src_ip   ),  //  [O] [31:00] Received source IP address
    .board_ip       (   board_ip    ),  //  [I] [31:00] Input board IP address, for comparison with received destination IP address

    .decap_busy     (   decap_busy  ),  //  [I] [     ] decapsulation module busy state input
    .s_axis_rd      (   s_axis_rd   ),  //  [I] [07:00] axis receive data input from decapsulation module
    .s_axis_rdv     (   s_axis_rdv  ),  //  [I] [     ] axis receive data valid input from decapsulation module
    .s_axis_rend    (   s_axis_rend ),  //  [I] [     ] axis receive tend input from decapsulation module
    .s_axis_rrdy    (   s_axis_rrdy ),  //  [O] [     ] axis receive ready signal
    .arp_rx_done    (   arp_rx_done ),  //  [O] [     ] ARP receive done signal, active-high
    .arp_rx_type    (   arp_rx_type )   //  [O] [     ] ARP receive type, 1&apos;b1: ack, 1&apos;b0: req
);

arp_tx arp_tx_inst (
    .clk            (   gmii_tx_clk ),  //  [I] [     ] Module clock
    .rst_n          (   rst_n       ),  //  [I] [     ] Module reset signal, active-low

    .tx_src_mac     (   board_mac   ),  //  [I] [47:00] Source MAC address to be send
    .tx_src_ip      (   board_ip    ),  //  [I] [31:00] Source IP address to be send
    .tx_dst_mac     (   tx_dst_mac  ),  //  [I] [47:00] Destination MAC address to be send
    .tx_dst_ip      (   tx_dst_ip   ),  //  [I] [31:00] Destination IP address to be send
    
    .encap_busy     (   encap_busy  ),  //  [I] [     ] encapsulation module busy state input, we only send arp packet when this signal is 1&apos;b0    
    .s_axis_td      (   s_axis_td   ),  //  [O] [07:00] axis send data output to encapsulation module
    .s_axis_tdv     (   s_axis_tdv  ),  //  [O] [     ] axis send data valid output to encapsulation module
    .s_axis_tend    (   s_axis_tend ),  //  [O] [     ] axis send tend output to encapsulation module
    .s_axis_trdy    (   s_axis_trdy ),  //  [I] [     ] axis send ready signal
    .arp_tx_en      (   arp_tx_en   ),  //  [I] [     ] ARP TX enable signal, active-high pulse signal
    .arp_tx_type    (   arp_tx_type )   //  [I] [     ] ARP TX type, 1&apos;b0: ARP req, 1&apos;b1: ARP ack
);
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>ETH-03 以太网帧结构介绍以及 Verilog 实现代码</title><link>https://blog.tyh123.top/posts/6ec21651/</link><guid isPermaLink="true">https://blog.tyh123.top/posts/6ec21651/</guid><pubDate>Wed, 15 Apr 2026 10:47:26 GMT</pubDate><content:encoded>&lt;h3&gt;什么是以太网帧&lt;/h3&gt;
&lt;p&gt;以太网帧是指在网络中&lt;strong&gt;传输数据的基本单位&lt;/strong&gt;。以太网帧按照一定的格式组成了以太网数据包。简单来讲，它类似于 &quot;数据包裹(就像快递一样)&quot;，包裹中包含了 &quot;寄件人 (发送方 MAC 地址)&quot;、&quot;收件人 (接收方 MAC 地址)&quot; 和 &quot;包裹内容 (有效数据)&quot;。以太网帧指的就是这个数据包本身。&lt;/p&gt;
&lt;p&gt;理解以太网帧的组成是我们开始学习以太网 ARP、ICMP、UDP 等上层协议的基础。&lt;/p&gt;
&lt;h3&gt;以太网帧的组成 (格式)&lt;/h3&gt;
&lt;p&gt;数据格式如下图所示。
&lt;img src=&quot;/img/posts/fpga_impl_interface/ethernet_impl/eth-03/ethII_frame_format.png&quot; alt=&quot;以太网数据帧结构&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;前导码 (Preamble)&lt;/strong&gt;：固定的 &lt;strong&gt;7 字节 0x55&lt;/strong&gt;，二进制表现为 0 和 1 交替。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;帧起始界定符 (SFD)&lt;/strong&gt;：全称：Start Frame Delimiter。固定的 &lt;strong&gt;1 字节 0xD5&lt;/strong&gt;，二进制表现为 &lt;code&gt;1101_1010&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;目的 MAC 地址&lt;/strong&gt;：即接收端的物理 MAC 地址，占用 6 个字节，MAC 地址从应用上可分为以下三种类型
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;单播地址&lt;/strong&gt;：第一个字节的最低位为 0，比如 &lt;code&gt;00:00:00:11:11:11&lt;/code&gt;一般用于标志唯一的设备&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;组播地址&lt;/strong&gt;：第一个字节的最低位为 1，比如&lt;code&gt;01:00:00:11:11:11&lt;/code&gt;一般用于标志同属一组的多个设备&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;广播地址&lt;/strong&gt;：所有的 48 bit 全部为 1，即&lt;code&gt;FF:FF:FF:FF:FF:FF&lt;/code&gt;，它用于标志同一网段中的所有设备&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;源 MAC 地址&lt;/strong&gt;：即发送端的 MAC 地址，占用 6 个字节&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;长度/类型&lt;/strong&gt;：当这两个字节的值小于 1500 (对应十六进制 0x05DC) 时，代表该以太网数据包中的数据段长度；大于 1500 (对应十六进制 0x05DC) 时，代表以太网中的数据属于某个 上层协议。例如，&lt;strong&gt;0x0800 代表 IP 协议 (国际协议)&lt;/strong&gt;、&lt;strong&gt;0x0806 代表 ARP 协议 (地址解析协议)&lt;/strong&gt; 等，其余内容会在 &lt;a href=&quot;#%E4%BB%A5%E5%A4%AA%E7%BD%91%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B&quot;&gt;以太网数据类型&lt;/a&gt; 这个表中列出&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据段&lt;/strong&gt;：长度最小46个字节，最大1500个字节。
&lt;ul&gt;
&lt;li&gt;最大值 1500 称为以太网的&lt;strong&gt;最大传输单元（MTU，Maximum Transmission Unit）&lt;/strong&gt;，之所以限制最大传输单元是因为在多个计算机的数据帧排队等待传输时，如果某个数据帧太大的话，接收这一帧的数据所带来的时间开销将会变高，从而导致体验变差。另外还要考虑网络I/O控制器缓存区资源以及网络最大的承载能力等因素，因此最大传输单元是由各种综合因素决定的。&lt;/li&gt;
&lt;li&gt;为了避免增加额外的配置，通常以太网的有效数据字段都会小于1500个字节，现在电脑的配置已经足够接收巨型帧，巨型帧可以超过 1500 个字节。但是，要成功使用巨型帧，网络中的所有设备（包括交换机、路由器和主机）都必须支持巨型帧，并且 MTU 的大小必须一致。如果网络中的任何设备不支持巨型帧，或者 MTU 的大小不一致，那么数据包可能会被丢弃或者分片，从而影响网络性能。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;帧检验序列（FCS）&lt;/strong&gt;：全称：Frame Check Sequence。&lt;strong&gt;4 个字节的循环冗余校验码 (通常是CRC-32 校验)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CRC 数据校验从目的 MAC 地址开始计算，直到数据段最后一个数据结束&lt;/strong&gt;。通用的CRC标准有 CRC-8、CRC-16、CRC-32、CRC-CCIT，其中&lt;strong&gt;在网络通信系统中应用最广泛的是 CRC-32 标准&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;帧间隙（IFG）&lt;/strong&gt;：全称：Interpack Gap。&lt;strong&gt;即两帧之间的时间间隔，最小为 96bit time。&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;IFG 的最小值是 96 bit time ，即在&lt;strong&gt;媒介中发送 96 位原始数据所需要的时间&lt;/strong&gt;，在不同媒介中 IFG 的最小值是不一样的。不管 10M/100M/1000M 的以太网，两帧之间最少要有 96bit time，IFG 的最少间隔时间计算方法如下：
&lt;ul&gt;
&lt;li&gt;10 Mbps 最小时间是 &lt;code&gt;96*100ns = 9600ns&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;100 Mbps 最小时间是 &lt;code&gt;96*10ns = 960ns&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;1000 Mbps 最小时间是 &lt;code&gt;96*1ns = 96ns&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;以太网数据类型&lt;/h3&gt;
&lt;p&gt;在以太网数据包结构中，2 字节类型字段的内容如下表，其中加粗的类型为常用类型。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Ethertype   &amp;lt;br&amp;gt;( 十六进制 )&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;协议&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x0800&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;网际协议（IP）&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0x0806&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;地址解析协议（ARP ： Address Resolution Protocol）&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x0000 - 0x05DC&lt;/td&gt;
&lt;td&gt;IEEE 802.3 长度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x0101 – 0x01FF&lt;/td&gt;
&lt;td&gt;实验&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x0600&lt;/td&gt;
&lt;td&gt;XEROX NS IDP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x0660, 0x0661&lt;/td&gt;
&lt;td&gt;DLOG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x0801&lt;/td&gt;
&lt;td&gt;X.75 Internet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x0802&lt;/td&gt;
&lt;td&gt;NBS Internet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x0803&lt;/td&gt;
&lt;td&gt;ECMA Internet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x0804&lt;/td&gt;
&lt;td&gt;Chaosnet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x0805&lt;/td&gt;
&lt;td&gt;X.25 Level 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x0808&lt;/td&gt;
&lt;td&gt;帧中继 ARP （Frame Relay ARP） [RFC1701]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x6559&lt;/td&gt;
&lt;td&gt;原始帧中继（Raw Frame Relay） [RFC1701]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x8035&lt;/td&gt;
&lt;td&gt;动态 DARP （DRARP：Dynamic RARP）&amp;lt;br&amp;gt;反向地址解析协议（RARP：Reverse Address Resolution Protocol）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x8037&lt;/td&gt;
&lt;td&gt;Novell Netware IPX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x809B&lt;/td&gt;
&lt;td&gt;EtherTalk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x80D5&lt;/td&gt;
&lt;td&gt;IBM SNA Services over Ethernet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x 80F 3&lt;/td&gt;
&lt;td&gt;AppleTalk 地址解析协议（AARP：AppleTalk Address Resolution Protocol）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x8100&lt;/td&gt;
&lt;td&gt;以太网自动保护开关（EAPS：Ethernet Automatic Protection Switching）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x8137&lt;/td&gt;
&lt;td&gt;因特网包交换（IPX：Internet Packet Exchange）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x 814C&lt;/td&gt;
&lt;td&gt;简单网络管理协议（SNMP：Simple Network Management Protocol）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x86DD&lt;/td&gt;
&lt;td&gt;网际协议v6 （IPv6，Internet Protocol version 6）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x880B&lt;/td&gt;
&lt;td&gt;点对点协议（PPP：Point-to-Point Protocol）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x 880C&lt;/td&gt;
&lt;td&gt;通用交换管理协议（GSMP：General Switch Management Protocol）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x8847&lt;/td&gt;
&lt;td&gt;多协议标签交换（单播） MPLS：Multi-Protocol Label Switching [unicast]）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x8848&lt;/td&gt;
&lt;td&gt;多协议标签交换（组播）（MPLS, Multi-Protocol Label Switching [multicast]）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x8863&lt;/td&gt;
&lt;td&gt;以太网上的 PPP（发现阶段）（PPPoE：PPP Over Ethernet [Discovery Stage]）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x8864&lt;/td&gt;
&lt;td&gt;以太网上的 PPP（PPP 会话阶段） （PPPoE，PPP Over Ethernet[PPP Session Stage]）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x88BB&lt;/td&gt;
&lt;td&gt;轻量级访问点协议（LWAPP：Light Weight Access Point Protocol）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x88CC&lt;/td&gt;
&lt;td&gt;链接层发现协议（LLDP：Link Layer Discovery Protocol）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x8E88&lt;/td&gt;
&lt;td&gt;局域网上的 EAP（EAPOL：EAP over LAN）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x9000&lt;/td&gt;
&lt;td&gt;配置测试协议（Loopback）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x9100&lt;/td&gt;
&lt;td&gt;VLAN 标签协议标识符（VLAN Tag Protocol Identifier）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x9200&lt;/td&gt;
&lt;td&gt;VLAN 标签协议标识符（VLAN Tag Protocol Identifier）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0xFFFF&lt;/td&gt;
&lt;td&gt;保留&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;实现代码&lt;/h3&gt;
&lt;h4&gt;以太网帧封包模块&lt;/h4&gt;
&lt;p&gt;该模块负责将来自上层协议的数据，如 ARP、UDP 协议 (包含上层协议的数据头) 等打包成标准的以太网帧，通过 GMII 接口输出。&lt;/p&gt;
&lt;p&gt;模块工作方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;上层协议模块将端口&lt;code&gt;s_axis_tdv&lt;/code&gt; 拉高，此时封包模块开始发送前导码、SFD、目的 MAC 地址 、源 MAC 地址以及以太网帧类型。&lt;/li&gt;
&lt;li&gt;当前导内容发送完成后，&lt;code&gt;s_axis_trdy&lt;/code&gt; 端口会被拉高，此时可以通过  &lt;code&gt;s_axis_td&lt;/code&gt; 端口写入负载数据。&lt;/li&gt;
&lt;li&gt;当负载数据写入最后一个字节时，上层协议模块给端口 &lt;code&gt;s_axis_tend&lt;/code&gt; 一个时钟周期的脉冲信号，结束发送。&lt;/li&gt;
&lt;li&gt;需要注意的是，上层协议模块给端口 &lt;code&gt;s_axis_tend&lt;/code&gt; 产生脉冲后，封包模块并不会立即结束发送，而是继续发送 FCS 和等待帧间隙。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;module_busy&lt;/code&gt; 信号在发送数据时 (从前导码开始，到 IFG 结束) 始终为高电平状态。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// NOTE_HEADER--------------------------------------------------------------------------------------
// Useage of this module:
//  - Port s_axis_tdv set to 1&apos;b1
//  - Wait s_axis_rdy goes to 1&apos;b1, write data to port s_axis_td
//  - When data write done, give an pulse signal to s_axis_tend
// NOTE_FOOTER--------------------------------------------------------------------------------------

module eth_frame_encap # (
    parameter   USE_DEBUG   =   1&apos;b0        
) (
    input   wire            rst_n           ,   //  [I] [    ] reset signal, active-low

    // user input interface, (AXI-Stream)
    input   wire    [ 7:0]  s_axis_td       ,   //  [I] [ 7:0] tx data input
    input   wire            s_axis_tdv      ,   //  [I] [    ] tx data valid
    input   wire            s_axis_tend     ,   //  [I] [    ] the last data input,
    output  reg             s_axis_trdy     ,   //  [O] [    ] tx data ready signal, high logic when module can send data

    input   wire    [47:0]  src_mac         ,   //  [I] [47:0] Source MAC address to be send
    input   wire    [47:0]  dst_mac         ,   //  [I] [47:0] Destination MAC address to be send
    input   wire    [15:0]  eth_type        ,   //  [I] [15:0] Ethernet type (or data length, but we don&apos;t need this), 
                                                //             when its value is less than 1500(Decimal, 0x05DC in hex), it refers to IEEE 802.3 data length (we don&apos;t need this)
                                                //             when its value is bigger than 1500(Decimal, 0x05DC in hex), it refers to ethernet type
    output  wire            module_busy     ,   //  [O] [    ] module busy signal when sending data, active-high
    
    // GMII output interface
    input   wire            gmii_txc        ,   //  [I]
    output  reg     [7:0]   gmii_txd        ,   //  [O]
    output  reg             gmii_tx_en          //  [O]
);

//================================================================================
// Local Parameter Declarations
//================================================================================
localparam  PADDING_DATA    =   8&apos;h00       ;   //  If data segment length is less than 46, padding this data until its length reach to 46.
localparam  PREAMBLE_WORD   =   8&apos;h55       ;
localparam  SFD_WORD        =   8&apos;hD5       ;
localparam  MIN_FRAME_LEN   =   8&apos;d64       ;   //  Minimum frame length in bytes, includes 6 bytes dest_mac, 6 bytes src_mac, 2 bytes type/length, minum 46 bytes data segment and 4 bytes FCS
localparam  HEADER_LENGTH   =   8&apos;d14       ;   //  Ethernet pack header length in bytes, 6 bytes dest_mac, 6 bytes src_mac and 2 bytes type/length
localparam  CRC_LENGTH      =   8&apos;d4        ;   //  CRC data length in bytes
localparam  MIN_PAYLOAD     =   8&apos;d46       ;   //  Minimum payload length
localparam  MAX_PAYLOAD     =   16&apos;d1500    ;   //  Maximum payload length
localparam  IFG_CNT         =   4&apos;d12       ;

// states defines
localparam  ST_IDLE         =   4&apos;d0    ;   //  IDLE state
localparam  ST_PREAMBLE     =   4&apos;d1    ;   //  State of sending preamble code, 7 bytes of 0x55
localparam  ST_SFD          =   4&apos;d2    ;   //  State of sending SFD, Start Frame Delimiter, 1 byte of 0xd5
localparam  ST_DST_MAC      =   4&apos;d3    ;   //  State of sending destination MAC Address
localparam  ST_SRC_MAC      =   4&apos;d4    ;   //  State of sending source MAC Address
localparam  ST_ETH_TYPE     =   4&apos;d5    ;   //  State of sending Ethernet type
localparam  ST_PAYLOAD      =   4&apos;d6    ;   //  State of sending payload, the data content of this packet
localparam  ST_PADDING      =   4&apos;d7    ;   //  State of padding data, if data content length is less than 46, then fill data.
localparam  ST_CRC          =   4&apos;d8    ;   //  State of sending CRC result
localparam  ST_IFG          =   4&apos;d9    ;   //  State of Interpacket GAP, 10Mbps min: 9600ns, 100Mbps min: 960ns, 1000Mbps min: 96ns

//================================================================================
// Register Declarations
//================================================================================
reg     [ 3:0]  state           ;
reg     [15:0]  byte_cnt        ;   // counter
reg     [15:0]  payload_len     ;   
reg     [ 3:0]  ifg_cnt         ;   //  IFG counter
reg     [ 7:0]  crc_data_in     ;
reg             crc_en          ;
reg             crc_clr         ;

//================================================================================
// Wire Declarations
//================================================================================
wire    [31:0]  crc_inv         ;
wire    [31:0]  crc_next_inv    ;

//================================================================================
// Assign Declarations
//================================================================================
assign  module_busy =   (state == ST_IDLE) ? 1&apos;b0 : 1&apos;b1    ;

//================================================================================
// implements
//================================================================================
crc32_d8 crc32_d8_inst (
    .clk            (   gmii_txc        ),  // [I] [    ] Module clock
    .rst_n          (   rst_n           ),  // [I] [    ] Reset signal, active-low
    .data           (   crc_data_in     ),  // [I] [ 7:0] The 8-bit data to be validated
    .crc_en         (   crc_en          ),  // [I] [    ] CRC enable
    .crc_clr        (   crc_clr         ),  // [I] [    ] CRC Result Clear
    .crc_next_inv   (   crc_next_inv    ),  // [O] [31:0] CRC data
    .crc_inv        (   crc_inv         )   // [O] [31:0] Inversed crc data
);

//================================================================================
// MAIN CODE
//================================================================================
generate
    if (USE_DEBUG == 1&apos;b1) begin: ila_frame_encap
        ila_frame_encap ila_frame_encap_inst (
            .clk    (   gmii_txc    ),
            .probe0 (   state       ),  //  4b
            .probe1 (   byte_cnt    ),  //  16b
            .probe2 (   payload_len ),  //  16b
            .probe3 (   gmii_txd    ),  //  8b
            .probe4 (   gmii_tx_en  )   //  1b
        );
    end
endgenerate

always  @(posedge gmii_txc or negedge rst_n) begin
    if (!rst_n) begin
        state       &amp;lt;=  ST_IDLE ;
        crc_en      &amp;lt;=  1&apos;b0    ;
        crc_clr     &amp;lt;=  1&apos;b1    ;
        crc_data_in &amp;lt;=  8&apos;b0    ;
        s_axis_trdy &amp;lt;=  1&apos;b0    ;
        byte_cnt    &amp;lt;=  16&apos;b0   ;
        ifg_cnt     &amp;lt;=  4&apos;b0    ;
        gmii_txd    &amp;lt;=  8&apos;b0    ;
        gmii_tx_en  &amp;lt;=  1&apos;d0    ;
        payload_len &amp;lt;=  16&apos;d0   ;
    end else begin
        case(state)
            ST_IDLE: begin
                payload_len &amp;lt;=  16&apos;d0       ;
                crc_en      &amp;lt;=  1&apos;b0        ;
                crc_clr     &amp;lt;=  1&apos;b1        ;
                crc_data_in &amp;lt;=  8&apos;b0        ;
                byte_cnt    &amp;lt;=  16&apos;d0       ;
                ifg_cnt     &amp;lt;=  4&apos;d0        ;
                gmii_txd    &amp;lt;=  8&apos;b0        ;
                gmii_tx_en  &amp;lt;=  1&apos;b0        ;
                s_axis_trdy &amp;lt;=  1&apos;b0        ;   //  Stop receive upstream data
                if (s_axis_tdv) begin
                    state   &amp;lt;=  ST_PREAMBLE ;
                end
            end

            ST_PREAMBLE: begin
                s_axis_trdy     &amp;lt;=  1&apos;b0            ;
                crc_clr         &amp;lt;=  1&apos;b1            ;
                crc_en          &amp;lt;=  1&apos;b0            ;
                gmii_txd        &amp;lt;=  PREAMBLE_WORD   ;
                gmii_tx_en      &amp;lt;=  1&apos;b1            ;

                if (byte_cnt == 7-1) begin
                    state       &amp;lt;= ST_SFD           ;
                    byte_cnt    &amp;lt;=  16&apos;d0           ;
                end else begin
                    byte_cnt        &amp;lt;=  byte_cnt + 1&apos;b1 ;
                end
                // ... Preamble does not participate in CRC
            end

            ST_SFD: begin
                crc_clr     &amp;lt;=  1&apos;b1                    ;
                crc_en      &amp;lt;=  1&apos;b0                    ;

                s_axis_trdy &amp;lt;=  1&apos;b0                    ;
                state       &amp;lt;=  ST_DST_MAC              ;
                gmii_txd    &amp;lt;=  SFD_WORD                ;
                byte_cnt    &amp;lt;=  16&apos;d0                   ;
                // ... SFD does not participate in CRC
            end

            ST_DST_MAC: begin                               //  Send destination MAC address
                crc_clr     &amp;lt;=  1&apos;b0                    ;
                crc_en      &amp;lt;=  1&apos;b1                    ;   //  enable crc
                s_axis_trdy &amp;lt;=  1&apos;b0                    ;
                
                case (byte_cnt)
                    0: begin gmii_txd &amp;lt;= dst_mac[47:40] ; crc_data_in &amp;lt;=  dst_mac[47:40]; end
                    1: begin gmii_txd &amp;lt;= dst_mac[39:32] ; crc_data_in &amp;lt;=  dst_mac[39:32]; end
                    2: begin gmii_txd &amp;lt;= dst_mac[31:24] ; crc_data_in &amp;lt;=  dst_mac[31:24]; end
                    3: begin gmii_txd &amp;lt;= dst_mac[23:16] ; crc_data_in &amp;lt;=  dst_mac[23:16]; end
                    4: begin gmii_txd &amp;lt;= dst_mac[15:08] ; crc_data_in &amp;lt;=  dst_mac[15:08]; end
                    5: begin gmii_txd &amp;lt;= dst_mac[07:00] ; crc_data_in &amp;lt;=  dst_mac[07:00]; end
                endcase

                // turn to next state
                if (byte_cnt == 6 - 1) begin
                    state       &amp;lt;= ST_SRC_MAC           ;
                    byte_cnt    &amp;lt;=  16&apos;d0               ;
                end else begin
                    byte_cnt    &amp;lt;=  byte_cnt + 1&apos;b1     ;
                end
            end

            ST_SRC_MAC: begin                               //  Send destination MAC address
                crc_clr     &amp;lt;=  1&apos;b0                    ;
                crc_en      &amp;lt;=  1&apos;b1                    ;   //  enable crc
                s_axis_trdy &amp;lt;=  1&apos;b0                    ;
                
                case (byte_cnt)
                    0: begin gmii_txd &amp;lt;= src_mac[47:40] ; crc_data_in &amp;lt;=  src_mac[47:40]; end
                    1: begin gmii_txd &amp;lt;= src_mac[39:32] ; crc_data_in &amp;lt;=  src_mac[39:32]; end
                    2: begin gmii_txd &amp;lt;= src_mac[31:24] ; crc_data_in &amp;lt;=  src_mac[31:24]; end
                    3: begin gmii_txd &amp;lt;= src_mac[23:16] ; crc_data_in &amp;lt;=  src_mac[23:16]; end
                    4: begin gmii_txd &amp;lt;= src_mac[15:08] ; crc_data_in &amp;lt;=  src_mac[15:08]; end
                    5: begin gmii_txd &amp;lt;= src_mac[07:00] ; crc_data_in &amp;lt;=  src_mac[07:00]; end
                endcase

                // turn to next state
                if (byte_cnt == 6 - 1) begin
                    state       &amp;lt;= ST_ETH_TYPE          ;
                    byte_cnt    &amp;lt;=  16&apos;d0               ;
                end else begin
                    byte_cnt    &amp;lt;=  byte_cnt + 1&apos;b1     ;
                end
            end

            ST_ETH_TYPE: begin
                crc_clr     &amp;lt;=  1&apos;b0                    ;
                crc_en      &amp;lt;=  1&apos;b1                    ;   //  enable crc
                s_axis_trdy &amp;lt;=  1&apos;b0                    ;

                case (byte_cnt)
                    0: begin 
                        gmii_txd    &amp;lt;= eth_type[15:08]  ; 
                        crc_data_in &amp;lt;= eth_type[15:08]  ;
                        s_axis_trdy &amp;lt;=  1&apos;b1            ;   //  we can receive user data at next clock period
                    end
                    1: begin 
                        gmii_txd    &amp;lt;= eth_type[07:00]  ; 
                        crc_data_in &amp;lt;= eth_type[07:00]  ;
                    end
                endcase

                // turn to next state
                if (byte_cnt == 2 - 1) begin
                    s_axis_trdy &amp;lt;=  1&apos;b1                ;   //  we can receive user data at next clock period
                    state       &amp;lt;= ST_PAYLOAD           ;
                end else begin
                    byte_cnt    &amp;lt;=  byte_cnt + 1&apos;b1     ;
                end
            end

            ST_PAYLOAD: begin                               // axi-stream data in at this time.
                if (s_axis_tdv &amp;amp;&amp;amp; s_axis_trdy) begin
                    s_axis_trdy &amp;lt;=  1&apos;b1                ;
                    crc_clr     &amp;lt;=  1&apos;b0                ;
                    crc_en      &amp;lt;=  1&apos;b1                ;

                    gmii_txd    &amp;lt;=  s_axis_td           ;
                    crc_data_in &amp;lt;=  s_axis_td           ;
                    payload_len &amp;lt;=  byte_cnt + 1&apos;b1     ;
                    byte_cnt    &amp;lt;=  byte_cnt + 1&apos;b1     ;
                end

                // turn to next state
                if (    
                    s_axis_tdv      == 1&apos;b1 &amp;amp;&amp;amp; 
                    s_axis_trdy     == 1&apos;b1 &amp;amp;&amp;amp; 
                    s_axis_tend     == 1&apos;b1
                ) begin

                    if (payload_len &amp;lt; MIN_PAYLOAD) begin
                        s_axis_trdy &amp;lt;=  1&apos;b0            ;   //  Stop receive upstream data
                        state       &amp;lt;=  ST_PADDING      ;
                        byte_cnt    &amp;lt;=  16&apos;d0           ;
                    end else begin
                        s_axis_trdy &amp;lt;=  1&apos;b0            ;   //  Stop receive upstream data
                        state       &amp;lt;=  ST_CRC          ;
                        byte_cnt    &amp;lt;=  16&apos;d0           ;
                    end
                end
            end

            ST_PADDING: begin                               //  if data in is less than MIN_PAYLOAD, padding data
                crc_clr         &amp;lt;=  1&apos;b0                ;
                crc_en          &amp;lt;=  1&apos;b1                ;
                s_axis_trdy     &amp;lt;=  1&apos;b0                ;   //  Stop receive upstream data
                
                // turn to next state
                if (byte_cnt == MIN_PAYLOAD - payload_len) begin
                    state       &amp;lt;=  ST_CRC              ;
                    byte_cnt    &amp;lt;=  16&apos;d0               ;
                end else begin
                    byte_cnt    &amp;lt;=  byte_cnt + 1&apos;b1     ;
                    gmii_txd    &amp;lt;=  PADDING_DATA        ;
                    crc_data_in &amp;lt;=  PADDING_DATA        ;
                end
            end

            ST_CRC: begin
                s_axis_trdy     &amp;lt;=  1&apos;b0                ;   //  Stop receive upstream data
                crc_en          &amp;lt;=  1&apos;b0                ;
                case(byte_cnt)
                    0: gmii_txd &amp;lt;=  crc_next_inv[07:00] ;
                    1: gmii_txd &amp;lt;=  crc_inv[15:08]      ;
                    2: gmii_txd &amp;lt;=  crc_inv[23:16]      ;
                    3: gmii_txd &amp;lt;=  crc_inv[31:24]      ;
                endcase
                if (byte_cnt == 4 - 1) begin
                    state       &amp;lt;=  ST_IFG              ;
                end else begin
                    byte_cnt    &amp;lt;=  byte_cnt + 1&apos;b1     ;
                end
            end

            ST_IFG: begin
                ifg_cnt     &amp;lt;=  ifg_cnt + 1&apos;b1          ;
                crc_clr     &amp;lt;=  1&apos;b1                    ;
                gmii_tx_en  &amp;lt;=  1&apos;b0                    ;
                if (ifg_cnt &amp;gt;= (IFG_CNT - 1)) begin
                    state   &amp;lt;=  ST_IDLE                 ;
                end
            end

            default: state  &amp;lt;= ST_IDLE;
        endcase
    end
end

endmodule
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;以太网帧解包模块&lt;/h4&gt;
&lt;p&gt;该模块负责将以太网帧进行解包，从中分离出负载数据供下游模块使用。&lt;/p&gt;
&lt;p&gt;模块工作方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;模块接收来自网络中的以太网帧，当接收到以太网帧的前导码时，模块开始工作，拉高 &lt;code&gt;module_busy&lt;/code&gt; 信号。&lt;/li&gt;
&lt;li&gt;之后就开始接收前导码、起始帧界定符、目的 MAC、源 MAC 以及以太网类型。&lt;/li&gt;
&lt;li&gt;接收完毕后比对接收到的目的 MAC 是否与板卡上的 MAC 一致，若一致则进入接收负载数据阶段。否则直接丢弃这个数据包。&lt;/li&gt;
&lt;li&gt;接收负载阶段，&lt;code&gt;s_axis_rdv&lt;/code&gt; 信号将会被拉高，直到负载阶段结束。负载阶段结束时 &lt;code&gt;s_axis_rend&lt;/code&gt; 会产生一个时钟周期的高电平脉冲信号。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：为了能够完整的接收 FCS(帧校验序列)，模块中的做法制是制作一个 4 字节的缓冲区，将来自 &lt;code&gt;gmii_rxd&lt;/code&gt; 的数据延迟 4 个字节发送到 &lt;code&gt;s_axis_rd&lt;/code&gt; 端口上，这样直到 FCS 阶段结束，缓冲区内的 4 字节数据刚好就是接收到的 FCS 数据。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;// NOTE_HEADER--------------------------------------------------------------------------------------
// HOW TO USE:
//  1. Set s_axis_rrdy to 1&apos;b1, for enable receive data from RGMII interface.
//  2. Wait s_axis_rdv signal goes to 1&apos;b1
//  3. Downstream receive data from s_axis_rd.
//  4. Wait active-high pulse signal from port s_axis_rend  
//  5. Receive finished
// NOTE_FOOTER--------------------------------------------------------------------------------------

module eth_frame_decap (
    input   wire            rst_n           ,   //  [I] [    ] reset signal, active-low

    // module output interface, (it likes AXI-Stream)
    output  reg     [7:0]   s_axis_rd       ,   //  [O] [ 7:0] rx data output
    output  reg             s_axis_rdv      ,   //  [O] [    ] rx data valid
    output  reg             s_axis_rend     ,   //  [O] [    ] the last data output
    input   wire            s_axis_rrdy     ,   //  [I] [    ] downstream ready to receive data

    input   wire    [47:0]  board_mac       ,   //  [I] [47:0] Board MAC address, for comparison with the received destination MAC address
    output  reg     [47:0]  src_mac         ,   //  [O] [47:0] Received source MAC address
    output  reg     [15:0]  eth_type        ,   //  [O] [15:0] Received ethernet type (or data length, but we don&apos;t need this), 
                                                //             when its value is less than 1500(Decimal, 0x05DC in hex), it refers to IEEE 802.3 data length (we don&apos;t need this)
                                                //             when its value is bigger than 1500(Decimal, 0x05DC in hex), it refers to ethernet type
    output  wire            module_busy     ,   //  [O] [    ] module busy signal when receiving data, active-high
    output  reg             rx_frame_valid  ,   //  [O] [    ] active-high if crc validation passed
    output  reg             rx_frame_error  ,   //  [O] [    ] active-high when some error occurred on receive data, or CRC is not passed
    output  reg     [15:0]  rx_payload_len  ,   //  [O] [15:0] Received payload length
    
    // GMII input interface
    input   wire            gmii_rxc        ,   //  [I] [    ] GMII RX Clock
    input   wire            gmii_rx_dv      ,   //  [I] [    ] GMII RX Data valid
    input   wire    [ 7:0]  gmii_rxd            //  [I] [    ] GMII RX data
);

//================================================================================
// Local Parameter Declarations
//================================================================================
localparam  MIN_FRAME_LEN   =   8&apos;d64   ;   //  Minimum frame length in bytes, includes 6 bytes dest_mac, 6 bytes src_mac, 2 bytes type/length, minum 46 bytes data segment and 4 bytes FCS
localparam  CRC_LENGTH      =   8&apos;d4    ;
localparam  HEADER_LENGTH   =   8&apos;d14   ;   //  Ethernet pack header length in bytes, 6 bytes dest_mac, 6 bytes src_mac and 2 bytes type/length
localparam  PREAMBLE_WORD   =   8&apos;h55   ;
localparam  SFD_WORD        =   8&apos;hD5   ;

// states
localparam  ST_IDLE         =   4&apos;d0    ;   //  IDLE state
localparam  ST_PREAMBLE     =   4&apos;d1    ;   //  State of receiving preamble code, 7 bytes of 0x55
localparam  ST_SFD          =   4&apos;d2    ;   //  State of receiving SFD, Start Frame Delimiter, 1 byte of 0xd5
localparam  ST_HEADER       =   4&apos;d3    ;   //  State of receiving header, includes destination MAC, src MAC, and ethertype
localparam  ST_PAYLOAD      =   4&apos;d4    ;   //  State of receiving payload, the data content of this packet
localparam  ST_CRC          =   4&apos;d5    ;   //  State of receiving CRC result
localparam  ST_ERR          =   4&apos;d6    ;   //  State of receiving error, drop frame until end
localparam  ST_DROP         =   4&apos;d7    ;   //  State of drop frame   

//================================================================================
// Register Declarations
//================================================================================
reg     [ 3:0]  state                   ;
reg     [ 7:0]  header_buf      [13:0]  ;   //  Ethernet pack header buffer, includes 6 bytes src MAC address, 6 bytes dst MAC address and 2 bytes ethtype. 
reg     [47:0]  recv_dst_mac            ;   //  Received destination MAC address
reg     [47:0]  recv_src_mac            ;   //  Received source MAC address
reg     [16:0]  recv_eth_type           ;   //  Received ethertype

// crc
reg     [ 7:0]  crc_data_in             ;
reg             crc_en                  ;
reg             crc_clr                 ;

// output ctrl (4-byte delay)
reg     [31:0]  delay_buf               ;
reg     [ 2:0]  delay_cnt               ;
reg     [15:0]  bytes_rec_cnt           ;   //  Total bytes received (Preamble and SFD is not included)
reg             payload_active          ;   //  indicator indicates that payload is received, not an error

//================================================================================
// Wire Declarations
//================================================================================
wire    [31:0]  crc_inv                 ;
wire    [31:0]  crc_next_inv            ;

//================================================================================
// Assign Declarations
//================================================================================
assign  module_busy =   (state == ST_IDLE) ? 1&apos;b0 : 1&apos;b1                    ;

//================================================================================
// implements
//================================================================================
crc32_d8 crc32_d8_inst (
    .clk            (   gmii_rxc        ),  // [I] [    ] Module clock
    .rst_n          (   rst_n           ),  // [I] [    ] Reset signal, active-low
    .data           (   crc_data_in     ),  // [I] [ 7:0] The 8-bit data to be validated
    .crc_en         (   crc_en          ),  // [I] [    ] CRC enable
    .crc_clr        (   crc_clr         ),  // [I] [    ] CRC Result Clear
    .crc_next_inv   (   crc_next_inv    ),  // [O] [31:0] CRC data
    .crc_inv        (   crc_inv         )   // [O] [31:0] Inversed crc data
);

//================================================================================
// MAIN CODE
//================================================================================
always  @(posedge gmii_rxc or negedge rst_n) begin
    if (!rst_n) begin
        state                       &amp;lt;=  ST_IDLE                 ;
        rx_payload_len              &amp;lt;=  16&apos;d0                   ;
        bytes_rec_cnt               &amp;lt;=  16&apos;d0                   ;
        crc_data_in                 &amp;lt;=  8&apos;d0                    ;
        crc_en                      &amp;lt;=  1&apos;b0                    ;
        crc_clr                     &amp;lt;=  1&apos;b1                    ;

        delay_buf                   &amp;lt;=  32&apos;d0                   ;
        delay_cnt                   &amp;lt;=  3&apos;d0                    ;

        header_buf[00]              &amp;lt;=  8&apos;h0                    ;
        header_buf[01]              &amp;lt;=  8&apos;h0                    ;
        header_buf[02]              &amp;lt;=  8&apos;h0                    ;
        header_buf[03]              &amp;lt;=  8&apos;h0                    ;
        header_buf[04]              &amp;lt;=  8&apos;h0                    ;
        header_buf[05]              &amp;lt;=  8&apos;h0                    ;
        header_buf[06]              &amp;lt;=  8&apos;h0                    ;
        header_buf[07]              &amp;lt;=  8&apos;h0                    ;
        header_buf[08]              &amp;lt;=  8&apos;h0                    ;
        header_buf[09]              &amp;lt;=  8&apos;h0                    ;
        header_buf[10]              &amp;lt;=  8&apos;h0                    ;
        header_buf[11]              &amp;lt;=  8&apos;h0                    ;
        header_buf[12]              &amp;lt;=  8&apos;h0                    ;
        header_buf[13]              &amp;lt;=  8&apos;h0                    ;

        recv_dst_mac                &amp;lt;=  48&apos;h0                   ;
        recv_src_mac                &amp;lt;=  48&apos;h0                   ;
        recv_eth_type               &amp;lt;=  16&apos;b0                   ;
        src_mac                     &amp;lt;=  48&apos;h0                   ;
        eth_type                    &amp;lt;=  16&apos;b0                   ;

        payload_active              &amp;lt;=  1&apos;b0                    ;
        s_axis_rd                   &amp;lt;=  8&apos;b0                    ;
        s_axis_rdv                  &amp;lt;=  1&apos;b0                    ;
        s_axis_rend                 &amp;lt;=  1&apos;b0                    ;
        rx_frame_valid              &amp;lt;=  1&apos;b0                    ;
        rx_frame_error               &amp;lt;=  1&apos;b0                    ;
    end else begin
        case (state)
            ST_IDLE: begin
                bytes_rec_cnt       &amp;lt;=  16&apos;d0                   ;
                crc_en              &amp;lt;=  1&apos;b0                    ;
                delay_cnt           &amp;lt;=  3&apos;d0                    ;
                delay_buf           &amp;lt;=  32&apos;d0                   ;
                rx_frame_error       &amp;lt;=  1&apos;b0                    ;
                s_axis_rdv          &amp;lt;=  1&apos;b0                    ;
                s_axis_rend         &amp;lt;=  1&apos;b0                    ;
                if (gmii_rx_dv == 1&apos;b1 &amp;amp;&amp;amp; gmii_rxd == PREAMBLE_WORD) begin
                    state           &amp;lt;=  ST_PREAMBLE             ;
                    bytes_rec_cnt   &amp;lt;=  bytes_rec_cnt + 1&apos;b1    ;
                end
            end

            // receive 7 bytes preamble (Standard Ethernet is 7 bytes 0x55)
            ST_PREAMBLE: begin
                if ((gmii_rx_dv == 1&apos;b1) &amp;amp;&amp;amp; (gmii_rxd == PREAMBLE_WORD)) begin
                    bytes_rec_cnt   &amp;lt;=  bytes_rec_cnt + 1&apos;b1    ;
                    if (bytes_rec_cnt == 8&apos;d7 - 1) begin
                        state       &amp;lt;=  ST_SFD                  ;
                    end
                end else begin
                    state           &amp;lt;= ST_ERR                   ;
                end

                // ... Preamble does not participate in CRC
            end

            // receive 1 byte SFD
            ST_SFD: begin
                if ((gmii_rx_dv == 1&apos;b1) &amp;amp;&amp;amp; (gmii_rxd == SFD_WORD)) begin
                    state                   &amp;lt;=  ST_HEADER       ;
                    bytes_rec_cnt           &amp;lt;=  16&apos;d0           ;
                    crc_data_in             &amp;lt;=  8&apos;d0            ;
                    crc_clr                 &amp;lt;=  1&apos;b1            ;
                end else begin
                    state                   &amp;lt;=  ST_ERR          ;
                end
                
                // ... SFD does not participate in CRC
            end

            ST_HEADER: begin
                if (gmii_rx_dv == 1&apos;b1) begin
                    delay_buf   &amp;lt;=  {delay_buf[23:0], gmii_rxd};

                    if (delay_cnt &amp;lt; 4) begin                        //  do 4 bytes delay
                        delay_cnt   &amp;lt;=  delay_cnt + 1   ;
                    end else begin
                        crc_en      &amp;lt;=  1&apos;b1            ;
                        crc_clr     &amp;lt;=  1&apos;b0            ;
                        crc_data_in &amp;lt;=  delay_buf[31:24];

                        // receive header
                        header_buf[bytes_rec_cnt]   &amp;lt;=  delay_buf[31:24]        ;
                        bytes_rec_cnt               &amp;lt;=  bytes_rec_cnt + 1&apos;b1    ;

                        // receive header finish
                        if (bytes_rec_cnt == HEADER_LENGTH - 1) begin
                            recv_dst_mac  &amp;lt;= {header_buf[0], header_buf[1], header_buf[2], header_buf[3], header_buf[4], header_buf[5]};
                            recv_src_mac  &amp;lt;= {header_buf[6], header_buf[7], header_buf[8], header_buf[9], header_buf[10], header_buf[11]};
                            recv_eth_type &amp;lt;= {header_buf[12], delay_buf[31:24]};

                            if(({header_buf[0], header_buf[1], header_buf[2], header_buf[3], header_buf[4], header_buf[5]} == board_mac) || 
                            ({header_buf[0], header_buf[1], header_buf[2], header_buf[3], header_buf[4], header_buf[5]} == 48&apos;hFF_FF_FF_FF_FF_FF)) begin
                                // MAC address is match
                                state           &amp;lt;=  ST_PAYLOAD      ;
                                rx_payload_len  &amp;lt;=  16&apos;d0           ;
                                bytes_rec_cnt   &amp;lt;=  16&apos;d0           ;
                                delay_cnt       &amp;lt;=  2&apos;d0            ;

                                // output src MAC and ethertype to upstream
                                src_mac         &amp;lt;=  recv_src_mac    ;
                                eth_type        &amp;lt;=  {header_buf[12], delay_buf[31:24]};
                            end else begin
                                // MAC address is not match, drop this pack directly.
                                state           &amp;lt;=  ST_DROP         ;
                            end
                        end
                    end
                end else begin
                    state                   &amp;lt;=  ST_ERR          ;
                end
            end 

            ST_PAYLOAD: begin
                if (gmii_rx_dv) begin
                    bytes_rec_cnt   &amp;lt;=  bytes_rec_cnt + 1&apos;b1    ;

                    // crc
                    crc_en          &amp;lt;=  1&apos;b1                    ;
                    crc_clr         &amp;lt;=  1&apos;b0                    ;
                    delay_buf       &amp;lt;=  {delay_buf[23:0], gmii_rxd};
                    crc_data_in     &amp;lt;=  delay_buf[31:24]        ;

                    s_axis_rdv  &amp;lt;=  1&apos;b1                        ;
                    if (s_axis_rrdy == 1&apos;b1) begin                      //  Dowmstream is ready to receive data
                        s_axis_rd       &amp;lt;=  delay_buf[31:24]        ;
                        rx_payload_len  &amp;lt;=  rx_payload_len + 1&apos;b1   ;   //  record payload length (FCS is not included)
                        payload_active  &amp;lt;=  1&apos;b1                    ;
                    end else begin
                        state       &amp;lt;=  ST_DROP             ;   //  Downstream is not ready to receive data, drop this frame
                    end
                end else begin
                    s_axis_rdv      &amp;lt;=  1&apos;b0                ;
                    s_axis_rd       &amp;lt;=  8&apos;b0                ;
                    s_axis_rend     &amp;lt;=  1&apos;b1                ; // send end pulse
                    // gmii_rx_dv goes to 1&apos;b0, frame end, mark s_axis_rend sig
                    if (payload_active == 1&apos;b1) begin
                        payload_active  &amp;lt;=  1&apos;b0                ;
                        state           &amp;lt;=  ST_CRC              ;
                    end
                end
            end

            ST_CRC: begin
                crc_en          &amp;lt;=  1&apos;b0                ;
                s_axis_rend     &amp;lt;=  1&apos;b0                ;
                if (crc_inv == delay_buf) begin
                    rx_frame_valid  &amp;lt;=  1&apos;b1            ;
                    state           &amp;lt;=  ST_IDLE         ;
                end else begin
                    rx_frame_valid  &amp;lt;=  1&apos;b0            ;
                    state           &amp;lt;=  ST_ERR          ;
                end
            end

            ST_ERR: begin      
                rx_frame_error  &amp;lt;=  1&apos;b1                ;
                state           &amp;lt;=  ST_DROP             ;
            end

            ST_DROP: begin
                if(gmii_rx_dv == 1&apos;b0) begin                //  Drop this frame until frame end.
                    state   &amp;lt;=  ST_IDLE                ;
                end
            end
        endcase
    end
end
    
endmodule
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;以太网帧模块&lt;/h4&gt;
&lt;p&gt;这个模块的作用是将两个模块进行整合封装到一个顶层，方便后续的例化&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module eth_frame # (
    parameter   USE_DEBUG   =   1&apos;b0
) (
    input   wire            rst_n           ,

    input   wire    [47:00] board_mac       ,   //  [I] [47:0] Board MAC address, for comparison with the received destination MAC address
    input   wire    [47:00] dst_mac         ,   //  [I] [47:0] Destination MAC address to be send

    // frame decap AXI-Stream output
    output  wire    [07:00] s_axis_rd       ,   //  [O] [ 7:0] rx data output
    output  wire            s_axis_rdv      ,   //  [O] [    ] rx data valid
    output  wire            s_axis_rend     ,   //  [O] [    ] the last data output
    input   wire            s_axis_rrdy     ,   //  [I] [    ] downstream ready to receive data

    output  wire    [15:00] rx_eth_type     ,   //  [O] [15:0] Received ethernet type
    output  wire            decap_busy      ,   //  [O] [    ] decapsulation busy signal when receiving data, active-high
    output  wire            rx_frame_valid  ,   //  [O] [    ] active-high if crc validation passed
    output  wire            rx_frame_error  ,   //  [O] [    ] active-high when some error occurred on receive data, or CRC is not passed
    output  wire    [15:0]  rx_payload_len  ,   //  [O] [15:0] Received payload length

    // gmii rx interface
    input   wire            gmii_rxc        ,   //  [I] [    ] GMII RX Clock
    input   wire            gmii_rx_dv      ,   //  [I] [    ] GMII RX Data valid
    input   wire    [07:00] gmii_rxd        ,   //  [I] [07:0] GMII RX data

    // frame encap AXI-Stream input
    input   wire    [07:00] s_axis_td       ,   //  [I] [ 7:0] tx data input
    input   wire            s_axis_tdv      ,   //  [I] [    ] tx data valid
    input   wire            s_axis_tend     ,   //  [I] [    ] the last data input,
    output  wire            s_axis_trdy     ,   //  [O] [    ] tx data ready signal, high logic when module can send data
    
    input   wire    [15:00] tx_eth_type     ,   //  [I] [15:0] Ethernet type
    output  wire            encap_busy      ,   //  [O] [    ] encapsulation busy signal when sending data, active-high

    // gmii TX interface
    input   wire            gmii_txc        ,   //  [I] [    ] clock input
    output  wire    [07:00] gmii_txd        ,   //  [O] [07:0]
    output  wire            gmii_tx_en          //  [O] [    ]  
); 

eth_frame_decap eth_frame_decap_inst (
    .rst_n  (rst_n),

    // AXI-Stream output
    .s_axis_rd      (   s_axis_rd       ),  //  [O] [ 7:0] rx data output
    .s_axis_rdv     (   s_axis_rdv      ),  //  [O] [    ] rx data valid
    .s_axis_rend    (   s_axis_rend     ),  //  [O] [    ] the last data output
    .s_axis_rrdy    (   s_axis_rrdy     ),  //  [I] [    ] downstream ready to receive data

    .board_mac      (   board_mac       ),  //  [I] [47:0] Board MAC address, for comparison with the received destination MAC address
    .src_mac        (                   ),  //  [O] [47:0] Received source MAC address, unused, 
    .eth_type       (   rx_eth_type     ),  //  [O] [15:0] Received ethernet type
    .module_busy    (   decap_busy      ),  //  [O] [    ] module busy signal when receiving data, active-high
    .rx_frame_valid (   rx_frame_valid  ),  //  [O] [    ] active-high if crc validation passed
    .rx_frame_error (   rx_frame_error  ),  //  [O] [    ] active-high when some error occurred on receive data, or CRC is not passed
    .rx_payload_len (   rx_payload_len  ),  //  [O] [15:0] Received payload length

    .gmii_rxc       (   gmii_rxc        ),  //  [I] [    ] GMII RX Clock
    .gmii_rx_dv     (   gmii_rx_dv      ),  //  [I] [    ] GMII RX Data valid
    .gmii_rxd       (   gmii_rxd        )   //  [I] [07:0] GMII RX data
);

eth_frame_encap # (
    .USE_DEBUG      (   USE_DEBUG       )
) eth_frame_encap_inst(
    .rst_n          (   rst_n           ),  //  [I] [    ] reset signal, active-low

    // user input interface, (AXI-Stream)
    .s_axis_td      (   s_axis_td       ),  //  [I] [ 7:0] tx data input
    .s_axis_tdv     (   s_axis_tdv      ),  //  [I] [    ] tx data valid
    .s_axis_tend    (   s_axis_tend     ),  //  [I] [    ] the last data input,
    .s_axis_trdy    (   s_axis_trdy     ),  //  [O] [    ] tx data ready signal, high logic when module can send data

    .src_mac        (   board_mac       ),  //  [I] [47:0] Source MAC address to be send
    .dst_mac        (   dst_mac         ),  //  [I] [47:0] Destination MAC address to be send
    .eth_type       (   tx_eth_type     ),  //  [I] [15:0] Ethernet type
    .module_busy    (   encap_busy      ),  //  [O] [    ] module busy signal when sending data, active-high
    
    // GMII interface
    .gmii_txc       (   gmii_txc        ),  //  [I] [    ] clock input
    .gmii_txd       (   gmii_txd        ),  //  [O] [07:0]
    .gmii_tx_en     (   gmii_tx_en      )   //  [O] [    ]
);
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>ETH-02 RGMII 接口和 GMII 接口的互相转换</title><link>https://blog.tyh123.top/posts/db8d0357/</link><guid isPermaLink="true">https://blog.tyh123.top/posts/db8d0357/</guid><pubDate>Tue, 14 Apr 2026 14:26:09 GMT</pubDate><content:encoded>&lt;p&gt;在 &lt;a href=&quot;ETH-01%20%E5%88%9D%E8%AF%86%E4%BB%A5%E5%A4%AA%E7%BD%91%E7%9A%84%E7%A1%AC%E4%BB%B6%E7%BB%84%E6%88%90.md&quot;&gt;ETH-01 初识以太网的硬件组成&lt;/a&gt; 中我讲到了 RGMII 接口和 GMII 的两种数据接口。本篇则围绕这两种数据接口的互相转换展开。&lt;/p&gt;
&lt;h3&gt;前言&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;在 FPGA 中，数据通常只在时钟的上升沿被采集。对于采用双边沿数据有效的 RGMII 接口而言，若仅使用上升沿进行采样，就会丢失下降沿所对应的数据&lt;/strong&gt;。同理，FPGA 在输出数据时，也只能在上升沿发生变化。为解决这一问题，Xilinx 提供了 &lt;code&gt;IDDR&lt;/code&gt; 和 &lt;code&gt;ODDR&lt;/code&gt; 两个原语，分别用于接收和发送双边沿数据。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;下方所有配图均来自 &lt;a href=&quot;https://docs.amd.com/v/u/en-US/ug471_7Series_SelectIO&quot;&gt;7Series FPGAs SelectIO Resources User Guide (UG471)&lt;/a&gt;
你可以打开 DOCNav 来搜索对应的文档名称&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;IDDR (Input Double Data Rate)&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;IDDR&lt;/code&gt; 的作用是将 RGMII 接口中的&lt;strong&gt;双边沿数据&lt;/strong&gt; (DDR) 转换为 FPGA 内部可以处理的&lt;strong&gt;单边沿数据&lt;/strong&gt;，&lt;code&gt;IDDR&lt;/code&gt; 将双边沿变化的数据通过寄存器输出到了 Q1/Q2 两个端口上，分别代表上升沿的数据和下降沿的数据。&lt;/p&gt;
&lt;p&gt;下面是 IDDR 原语的语言模板&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;IDDR #(
  .DDR_CLK_EDGE (   &quot;OPPOSITE_EDGE&quot; ), // 数据对齐参数，有 &quot;OPPOSITE_EDGE&quot;, &quot;SAME_EDGE&quot; or &quot;SAME_EDGE_PIPELINED&quot; 
  .INIT_Q1      (   1&apos;b0            ), // Q1 的初始值: 1&apos;b0 or 1&apos;b1
  .INIT_Q2      (   1&apos;b0            ), // Q2 的初始值: 1&apos;b0 or 1&apos;b1
  .SRTYPE       (   &quot;SYNC&quot;          ) // Set/Reset type: &quot;SYNC&quot; or &quot;ASYNC&quot; 
) IDDR_inst (
  .Q1   (   Q1  ),  // 1-bit 时钟上升沿输出的值
  .Q2   (   Q2  ),  // 1-bit 时钟下降沿输出的值
  .C    (   C   ),  // 1-bit 时钟输入
  .CE   (   CE  ),  // 1-bit 时钟使能信号
  .D    (   D   ),  // 1-bit DDR 信号输入
  .R    (   R   ),  // 1-bit 复位信号
  .S    (   S   )   // 1-bit 置位信号，优先级低于复位信号
);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;参数介绍&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DDR_CLK_EDGE&lt;/strong&gt;：数据对齐方式，该参数将在下文介绍。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;INIT_Q1/Q2&lt;/strong&gt;：初始化 Q1、Q2 的初始值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SRTYPE&lt;/strong&gt;：配置复位/置位信号为同步/异步。建议是使用同步复位驱动 R/S，以避免亚稳态的产生。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;端口介绍&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Q1/Q2&lt;/strong&gt;：数据的输出端口，Q1 输出时钟上升沿的数据，Q2 输出时钟下降沿的数据。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C&lt;/strong&gt;：时钟的输入端口。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CE&lt;/strong&gt;：时钟的使能信号，高电平有效。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;D&lt;/strong&gt;：DDR 信号的输入端口。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;R/S&lt;/strong&gt;：复位/置位信号，只能使用其中之一 (置1)，或全部不使用 (置 0)。复位信号使得 Q1/Q2 输出 0，置位信号使得 Q1/Q2 输出 1。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;IDDR 数据对齐方式&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OPPOSITE_EDGE&lt;/strong&gt;：数据对齐的基本模式，该模式实现了基本的 DDR 功能，&lt;strong&gt;但是输出的数据 Q1/Q2 在单个时钟周期内不能同时有效&lt;/strong&gt;，对逻辑设计不友好，已经较少使用。下图为该模式下的时序图例。&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;/img/posts/fpga_impl_interface/ethernet_impl/eth-02/IDDR_CLK_EDGE_OPPOSITE_EDGE.png&quot; alt=&quot;IDDR_CLK_EDGE_OPPOSITE_EDGE&quot; /&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SAME_EDGE&lt;/strong&gt;：数据在同一个时钟沿提供给内部逻辑，能有效避免时序冲突。缺点是&lt;strong&gt;两个数据之间有一个时钟周期的延迟差异&lt;/strong&gt;，在逻辑上需要处理 &quot;错位&quot;。下图为该模式下的时序图例。&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;/img/posts/fpga_impl_interface/ethernet_impl/eth-02/IDDR_CLK_EDGE_SAME_EDGE.png&quot; alt=&quot;IDDR_CLK_EDGE_SAME_EDGE&quot; /&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SAME_EDGE_PIPELINED&lt;/strong&gt;：完美的数据对齐，两个输出 Q1/Q2 在&lt;strong&gt;相同的钟沿同时有效&lt;/strong&gt;。代价则是引入了额外的延时，通常是 2 个时钟周期。下图为该模式下的时序图例。&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;/img/posts/fpga_impl_interface/ethernet_impl/eth-02/IDDR_CLK_EDGE_SAME_EDGE_PIPELINED.png&quot; alt=&quot;IDDR_CLK_EDGE_SAME_EDGE_PIPELINED&quot; /&gt;
通过时序图我们可以看到 &lt;code&gt;IDDR&lt;/code&gt; 原语在 &lt;code&gt;SAME_EDGE_PIPELINED&lt;/code&gt; 对齐模式下满足了我们的数据对齐要求，所以在这里我们使用 &lt;code&gt;SAME_EDGE_PIPELINED&lt;/code&gt; 对齐方式。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;ODDR (Output Double Data Rate)&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ODDR&lt;/code&gt; 的作用正好与 &lt;code&gt;IDDR&lt;/code&gt; 是相反的，它通过在时钟的上升沿更新 &lt;code&gt;Q1/Q2&lt;/code&gt; 的值，之后输出双边沿变化的 DDR 信号。&lt;/p&gt;
&lt;p&gt;下面是 ODDR 的语言模板&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ODDR #(
  .DDR_CLK_EDGE (&quot;OPPOSITE_EDGE&quot;),  // &quot;OPPOSITE_EDGE&quot; or &quot;SAME_EDGE&quot; 
  .INIT         (1&apos;b0           ),  // Initial value of Q: 1&apos;b0 or 1&apos;b1
  .SRTYPE       (&quot;SYNC&quot;         )   // Set/Reset type: &quot;SYNC&quot; or &quot;ASYNC&quot; 
) ODDR_inst (
  .Q            (   Q           ),  // 1-bit DDR output
  .C            (   C           ),  // 1-bit clock input
  .CE           (   CE          ),  // 1-bit clock enable input
  .D1           (   D1          ),  // 1-bit data input (positive edge)
  .D2           (   D2          ),  // 1-bit data input (negative edge)
  .R            (   R           ),  // 1-bit reset
  .S            (   S           )   // 1-bit set
);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;参数介绍&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DDR_CLK_EDGE&lt;/strong&gt;：数据对齐方式，该参数将在下文介绍。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;INIT&lt;/strong&gt;：初始化端口 Q 的初始值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SRTYPE&lt;/strong&gt;：配置复位/置位信号为同步/异步。建议是使用同步复位驱动 R/S，以避免亚稳态的产生。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;端口介绍&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;D1/D2&lt;/strong&gt;：数据的输入端口，D1 将会被输出到时钟的上升沿，D2 将会被输出到时钟的下降沿。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C&lt;/strong&gt;：时钟的输入端口。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CE&lt;/strong&gt;：时钟的使能信号，高电平有效。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Q&lt;/strong&gt;：DDR 信号的输出端口。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;R/S&lt;/strong&gt;：复位/置位信号，只能使用其中之一 (置1)，或全部不使用 (置 0)。复位信号使得 Q1/Q2 输出置 0，置位信号使得 Q1/Q2 输出置 1。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;ODDR 数据对齐方式&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OPPOSITE_EDGE&lt;/strong&gt;：在该模式下时钟的两个边沿都用于从 FPGA 逻辑捕获数据，从而实现两倍数据的吞吐量。这种方式要求内部逻辑&lt;strong&gt;必须提供两个数据流&lt;/strong&gt;：D1 数据流&lt;strong&gt;与上升沿对齐&lt;/strong&gt;，D2 数据流&lt;strong&gt;与下降沿对齐&lt;/strong&gt;, (或提前半个周期来保持数据的稳定)。该方式通常要求使用下降沿的触发器来驱动，容易导致时序违例。该模式的时序图例如下。&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;/img/posts/fpga_impl_interface/ethernet_impl/eth-02/ODDR_CLK_EDGE_OPPOSITE_EDGE.png&quot; alt=&quot;ODDR_CLK_EDGE_OPPOSITE_EDGE&quot; /&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SAME_EDGE&lt;/strong&gt;：在该模式下，D1/D2 都可以由上升沿触发的逻辑产生，DDR 内部会自动把 D2 对齐到下降沿进行输出，从而避免了跨边沿的约束，时序宽松，设计更加简单。该模式的图例如下。&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;/img/posts/fpga_impl_interface/ethernet_impl/eth-02/ODDR_CLK_EDGE_SAME_EDGE.png&quot; alt=&quot;ODDR_CLK_EDGE_SAME_EDGE&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;TX_CTRL 的转换&lt;/h3&gt;
&lt;p&gt;在 RGMII 接口中，&lt;code&gt;TX_CTL&lt;/code&gt;是一个集成了 GMII 接口中 &lt;code&gt;TX_EN&lt;/code&gt;(发送使能) 和 &lt;code&gt;TX_ER&lt;/code&gt;(发送错误) 功能的符合信号，通过将这两个信号编码在了同一个时钟周期内，传递了两个控制信息。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TX_CTL&lt;/code&gt; 采用的同样也是&lt;strong&gt;双边沿采样 (DDR)&lt;/strong&gt;，配合发送时钟 &lt;code&gt;TXC&lt;/code&gt; 一起传输。它的编码规则如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;上升沿&lt;/strong&gt;：传输 &lt;code&gt;TX_EN&lt;/code&gt; 信号，表示该时钟周期内的数据是否有效。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;下降沿&lt;/strong&gt;：传输 &lt;code&gt;TX_EN ^ TX_ER&lt;/code&gt;，即 &lt;code&gt;TX_EN ^ TX_ER&lt;/code&gt; 的&lt;strong&gt;异或 (XOR) 结果&lt;/strong&gt;。
这种编码方式定义了四种明确的状态：&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;组合场景&lt;/th&gt;
&lt;th&gt;GMII: TX_EN&lt;/th&gt;
&lt;th&gt;GMII_TX_ER&lt;/th&gt;
&lt;th&gt;RGMII: TX_CTL&amp;lt;br&amp;gt;(上升沿)&lt;/th&gt;
&lt;th&gt;RGMII: TX_CTL&amp;lt;br&amp;gt;(下降沿)&lt;/th&gt;
&lt;th&gt;状态描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;正常帧间隙&amp;lt;br&amp;gt;(空闲)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;空闲状态。链路无数据传输，&lt;code&gt;TX_CTL&lt;/code&gt;保持低电平&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;正常帧传输&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;正常数据传输&lt;/strong&gt;。表示当前时钟周期的 8 位数据全部有效&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;数据错误&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;发送错误&lt;/strong&gt;。指示当前传输帧中存在错误，通常由MAC层发起&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;保留/异常&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;无效的组合，实际过程中用不到&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;在标准操作中，大多数应用只需要关注&lt;code&gt;正常帧传输&lt;/code&gt;（上升沿=1，下降沿=1）和&lt;code&gt;数据错误&lt;/code&gt;（上升沿=1，下降沿=0）这两种情况。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;关于 &lt;code&gt;TX_CTL&lt;/code&gt; 信号的产生方式，代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ODDR #(
    .DDR_CLK_EDGE   (   &quot;SAME_EDGE&quot;     ),  //  &quot;OPPOSITE_EDGE&quot; or &quot;SAME_EDGE&quot; 
    .INIT           (   1&apos;b0            ),  //  Initial value of Q: 1&apos;b0 or 1&apos;b1
    .SRTYPE         (   &quot;SYNC&quot;          )   //  Set/Reset type: &quot;SYNC&quot; or &quot;ASYNC&quot; 
) rgmii_tdv_oddr_inst (
    .Q              (   rgmii_tx_ctl    ),  //  1-bit DDR output
    .C              (   gmii_txc        ),  //  1-bit clock input
    .CE             (   1&apos;b1            ),  //  1-bit clock enable input
    .D1             (   gmii_tx_en      ),  //  1-bit data input (positive edge)
    .D2             (   gmii_tx_en      ),  //  1-bit data input (negative edge) 实际上发送时 tx_ctl 信号一致都是拉高的，所以下降沿也接到了 gmii_tx_en 信号上。
    .R              (   1&apos;b0            ),  //  1-bit reset
    .S              (   1&apos;b0            )   //  1-bit set
);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;RGMII 接口与 GMII 接口互相转换的代码实现&lt;/h3&gt;
&lt;h4&gt;GMII 转 RGMII&lt;/h4&gt;
&lt;p&gt;首先来实现 GMII 转 RGMII 接口，这样 FPGA 侧的逻辑就可以通过 GMII 接口发送，再由该模块转换成 RGMII 接口的数据传递到 PHY 芯片了。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module rgmii_tx (
    // GMII
    input   wire            gmii_txc        ,   //  [I] [   ] gmii rx clock
    input   wire            gmii_tx_en      ,   //  [I] [   ] gmii rx control
    input   wire    [7:0]   gmii_txd        ,   //  [I] [7:0] gmii rx data

    // RGMII
    output  wire            rgmii_txc       ,   //  [O] [   ] rgmii rx clock
    output  wire            rgmii_tx_ctl    ,   //  [O] [   ] rgmii rx control
    output  wire    [3:0]   rgmii_txd           //  [O] [3:0] rgmii rx data
);

// RGMII 接口 tx_ctl 信号的产生
ODDR #(
    .DDR_CLK_EDGE   (   &quot;SAME_EDGE&quot;     ),  //  &quot;OPPOSITE_EDGE&quot; or &quot;SAME_EDGE&quot; 
    .INIT           (   1&apos;b0            ),  //  Initial value of Q: 1&apos;b0 or 1&apos;b1
    .SRTYPE         (   &quot;SYNC&quot;          )   //  Set/Reset type: &quot;SYNC&quot; or &quot;ASYNC&quot; 
) rgmii_tdv_oddr_inst (
    .Q              (   rgmii_tx_ctl    ),  //  1-bit DDR output
    .C              (   gmii_txc        ),  //  1-bit clock input
    .CE             (   1&apos;b1            ),  //  1-bit clock enable input
    .D1             (   gmii_tx_en      ),  //  1-bit data input (positive edge)
    .D2             (   gmii_tx_en      ),  //  1-bit data input (negative edge)
    .R              (   1&apos;b0            ),  //  1-bit reset
    .S              (   1&apos;b0            )   //  1-bit set
);

// generate 语句为每一条数据线都生成 ODDR 原语
genvar i;
generate 
    for (i=0; i&amp;lt;4; i=i+1) 
    begin: txd_bus
        ODDR #(
            .DDR_CLK_EDGE  (    &quot;SAME_EDGE&quot;     ),  //  &quot;OPPOSITE_EDGE&quot; or &quot;SAME_EDGE&quot; 
            .INIT          (    1&apos;b0            ),  //  Initial value of Q: 1&apos;b0 or 1&apos;b1
            .SRTYPE        (    &quot;SYNC&quot;          )   //  Set/Reset type: &quot;SYNC&quot; or &quot;ASYNC&quot; 
        ) rgmii_txd_oddr_inst (
            .Q             (    rgmii_txd[i]    ),  //  1-bit DDR output
            .C             (    gmii_txc        ),  //  1-bit clock input
            .CE            (    1&apos;b1            ),  //  1-bit clock enable input
            .D1            (    gmii_txd[i  ]   ),  //  1-bit data input (positive edge)
            .D2            (    gmii_txd[i+4]   ),  //  1-bit data input (negative edge) 在 RGMII 接口的要求中，时钟的上升沿发送高 4 位，下降沿发送低 4 位。
            .R             (    1&apos;b0            ),  //  1-bit reset
            .S             (    1&apos;b0            )   //  1-bit set
        );   
    end
endgenerate

// 使用 ODDR 原语生成 rgmii_txc 来匹配延迟
ODDR #(
    .DDR_CLK_EDGE   (   &quot;SAME_EDGE&quot;     ),  //  &quot;OPPOSITE_EDGE&quot; or &quot;SAME_EDGE&quot; 
    .INIT           (   1&apos;b0            ),  //  Initial value of Q: 1&apos;b0 or 1&apos;b1
    .SRTYPE         (   &quot;SYNC&quot;          )   //  Set/Reset type: &quot;SYNC&quot; or &quot;ASYNC&quot; 
) rgmii_clk_oddr_inst (
    .Q              (   rgmii_txc       ),  //  1-bit DDR output
    .C              (   gmii_txc        ),  //  1-bit clock input
    .CE             (   1&apos;b1            ),  //  1-bit clock enable input
    .D1             (   1&apos;b1            ),  //  1-bit data input (positive edge)
    .D2             (   1&apos;b0            ),  //  1-bit data input (negative edge)
    .R              (   1&apos;b0            ),  //  1-bit reset
    .S              (   1&apos;b0            )   //  1-bit set
);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;RGMII 转 GMII&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;module rgmii_rx (
    // RGMII
    input   wire            rgmii_rxc       ,   //  [I] [   ] rgmii rx clock
    input   wire            rgmii_rx_ctl    ,   //  [I] [   ] rgmii rx control
    input   wire    [3:0]   rgmii_rxd       ,   //  [I] [3:0] rgmii rx data

    // GMII
    output  wire            gmii_rxc        ,   //  [O] [   ] gmii rx clock
    output  wire            gmii_rx_dv      ,   //  [O] [   ] gmii rx data valid
    output  wire    [7:0]   gmii_rxd            //  [O] [7:0] gmii rx data
);

wire            rgmii_rxc_bufio     ;
wire            rgmii_rxc_bufg      ;
wire    [1:0]   gmii_rx_dv_t        ;
assign gmii_rxc     = rgmii_rxc_bufg                    ;
assign gmii_rx_dv   = gmii_rx_dv_t[0] &amp;amp; gmii_rx_dv_t[1] ;

BUFG BUFG_inst (
    .I              (   rgmii_rxc               ),
    .O              (   rgmii_rxc_bufg          )
);

BUFIO BUFIO_inst (
    .I              (   rgmii_rxc               ),
    .O              (   rgmii_rxc_bufio         )
);

genvar i;
generate
    for (i = 0; i &amp;lt; 4; i = i+1)
    begin: rxd_bus
        // input delay
        IDDR #(
            .DDR_CLK_EDGE   (   &quot;SAME_EDGE_PIPELINED&quot;   ),  //  &quot;OPPOSITE_EDGE&quot;, &quot;SAME_EDGE&quot; or &quot;SAME_EDGE_PIPELINED&quot; 
            .INIT_Q1        (   1&apos;b0                    ),  //  Initial value of Q1: 1&apos;b0 or 1&apos;b1
            .INIT_Q2        (   1&apos;b0                    ),  //  Initial value of Q2: 1&apos;b0 or 1&apos;b1
            .SRTYPE         (   &quot;SYNC&quot;                  )   //  Set/Reset type: &quot;SYNC&quot; or &quot;ASYNC&quot; 
        ) gmii_rxd_iddr (
            .Q1             (   gmii_rxd[i  ]           ),  // [O] 1-bit output for positive edge of clock
            .Q2             (   gmii_rxd[i+4]           ),  // [O] 1-bit output for negative edge of clock
            .C              (   rgmii_rxc_bufio         ),  // [I] 1-bit clock input
            .CE             (   1&apos;b1                    ),  // [I] 1-bit clock enable input
            .D              (   rgmii_rxd[i]            ),  // [I] 1-bit DDR data input
            .R              (   1&apos;b0                    ),  // [I] 1-bit reset
            .S              (   1&apos;b0                    )   // [I] 1-bit set
        );
    end
endgenerate
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>ETH-01 初识以太网的硬件组成</title><link>https://blog.tyh123.top/posts/4a0bb95d/</link><guid isPermaLink="true">https://blog.tyh123.top/posts/4a0bb95d/</guid><pubDate>Mon, 13 Apr 2026 15:14:06 GMT</pubDate><content:encoded>&lt;h3&gt;以太网通信的硬件组成&lt;/h3&gt;
&lt;p&gt;下面是一张硬件组成图例
&lt;img src=&quot;/img/posts/fpga_impl_interface/ethernet_impl/eth-01/ethernet_hardware_components.png&quot; alt=&quot;以太网通信的硬件组成&quot; /&gt;
主要由 &lt;strong&gt;MAC (Media Access Control，媒体访问控制器)&lt;/strong&gt; 和 &lt;strong&gt;PHY(Physical Layer，物理层接口)&lt;/strong&gt; 两大部分组成。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MAC&lt;/strong&gt;：通常由嵌入式设备（如单片机、FPGA 等）实现，负责数据的打包、寻址、CRC 校验等逻辑控制功能。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PHY&lt;/strong&gt;：一般作为一颗&lt;strong&gt;独立的板载芯片&lt;/strong&gt;，负责网络自协商、数据编解码等物理层相关工作。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通信设备之间通过 RJ45 接口进行连接。需要注意的是，&lt;strong&gt;接口本身不具备通信功能&lt;/strong&gt;，仅起到连接信号的作用。&lt;/p&gt;
&lt;h3&gt;RGMII 和 GMII 接口&lt;/h3&gt;
&lt;p&gt;RGMII 和 GMII 都是连接 MAC 层与 PHY 层的芯片间接口，主要区别在于引脚数量和传输效率。&lt;/p&gt;
&lt;h4&gt;GMII (Gigabit Media Independent Interface)&lt;/h4&gt;
&lt;p&gt;GMII 是千兆以太网接口的完整实现，也是早期 MII 接口的升级版（MII 最高支持 100Mbps）。
&lt;img src=&quot;/img/posts/fpga_impl_interface/ethernet_impl/eth-01/gmii_interface.png&quot; alt=&quot;GMII 接口&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;数据宽度&lt;/strong&gt;：8 位（发送和接收各 8 条数据线）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;时钟频率&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;1000 Mbps 模式：125 MHz&lt;/li&gt;
&lt;li&gt;100 Mbps 模式：25 MHz&lt;/li&gt;
&lt;li&gt;10 Mbps 模式：2.5 MHz&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关键信号&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TXD[7:0]&lt;/code&gt;（发送数据）、&lt;code&gt;RXD[7:0]&lt;/code&gt;（接收数据）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TX_CLK&lt;/code&gt; / &lt;code&gt;RX_CLK&lt;/code&gt;（时钟，由 PHY 提供或从数据中恢复）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TX_EN&lt;/code&gt;（发送使能）、&lt;code&gt;RX_DV&lt;/code&gt;（接收数据有效）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TX_ER&lt;/code&gt; / &lt;code&gt;RX_ER&lt;/code&gt;（错误指示）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CRS&lt;/code&gt;（载波侦听）、&lt;code&gt;COL&lt;/code&gt;（冲突检测，仅半双工用）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优点&lt;/strong&gt;：时序简单，兼容性好，支持全双工和半双工。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点&lt;/strong&gt;：引脚数量多，约 &lt;strong&gt;24 根&lt;/strong&gt;，PCB 布线复杂，成本高。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;应用场景&lt;/strong&gt;：早期千兆交换芯片、FPGA 开发板、对时序要求极高的场合。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：GMII 的发送时钟 &lt;code&gt;TX_CLK&lt;/code&gt; 可能来自 MAC，也可能来自 PHY（取决于模式），这一点与 MII 略有差异。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;RGMII (Reduced GMII)&lt;/h4&gt;
&lt;p&gt;RGMII 是为了解决 GMII 引脚过多问题而设计的&lt;strong&gt;精简版&lt;/strong&gt;接口，是目前大多数 SoC、交换芯片和 PHY 芯片的首选。它采用的是 &lt;strong&gt;DDR(Double Data Rate)&lt;/strong&gt; 模式传输的数据，即时钟的上升沿和下降沿均为有效数据。
&lt;img src=&quot;/img/posts/fpga_impl_interface/ethernet_impl/eth-01/rgmii_interface.png&quot; alt=&quot;RGMII 接口&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;数据宽度&lt;/strong&gt;：4 位（发送和接收各 4 条数据线）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;时钟频率&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;1000 Mbps 模式：125 MHz（双沿采样，等效 250 Mbps per pin）&lt;/li&gt;
&lt;li&gt;100 Mbps 模式：25 MHz（双沿采样，等效 50 Mbps per pin）&lt;/li&gt;
&lt;li&gt;10 Mbps 模式：2.5 MHz（双沿采样，等效 5 Mbps per pin）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;核心技术&lt;/strong&gt;：&lt;strong&gt;DDR(Double Data Rate)——时钟的上升沿和下降沿均传输数据。其中上升沿传递数据的高 4 位，下降沿传递数据的低 4 位&lt;/strong&gt;。所以 4 位数据线 x 双边沿 x 125MHz = 1000Mbps。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关键信号&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TXD[3:0]&lt;/code&gt;（发送数据）、&lt;code&gt;RXD[3:0]&lt;/code&gt;（接收数据）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TX_CLK&lt;/code&gt;（125MHz 时钟，由 MAC 或 PHY 提供）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RX_CLK&lt;/code&gt;（125MHz 时钟，由 PHY 恢复）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TX_CTL&lt;/code&gt;（发送控制，上升沿 = TX_EN，下降沿 = TX_ER 或其它编码）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RX_CTL&lt;/code&gt;（接收控制，上升沿 = RX_DV，下降沿 = RX_ER）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优点&lt;/strong&gt;：引脚数少，约 &lt;strong&gt;12 根&lt;/strong&gt;，仅为 GMII 的一半，PCB 布局更轻松。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;时序要求严格，因为用到了双沿和信号编码。&lt;/li&gt;
&lt;li&gt;通常需要 PCB 上做&lt;strong&gt;等长走线&lt;/strong&gt;，并且 MAC/PHY 内部要有时钟延迟调整功能（一般通过 RGMII TX/RX Delay 配置）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;应用场景&lt;/strong&gt;：绝大多数现代嵌入式处理器、以太网交换机芯片、路由器、网卡。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;实现以太网通信的模块框图 (UDP 通信)&lt;/h3&gt;
&lt;p&gt;模块以实现 UDP 回环测试为目标，整体模块框图如下图所示。
接下来的系列文章根据以下模块框图逐步推进。
&lt;img src=&quot;/img/posts/fpga_impl_interface/ethernet_impl/eth-01/udp_module_diagram.png&quot; alt=&quot;UDP 通信模块框图&quot; /&gt;
&lt;strong&gt;注意&lt;/strong&gt;：&lt;code&gt;Protocol SW&lt;/code&gt; 模块的作用是在 ARP 协议与 IP 协议之间进行切换。若将 ARP 协议和 IP 协议直接连接到 MAC 发送层，会&lt;strong&gt;导致多重驱动问题&lt;/strong&gt;，这在 FPGA 设计中是不允许的。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;简单来说，&lt;code&gt;Protocol SW&lt;/code&gt; 模块正是&lt;strong&gt;为了避免因多重驱动而引发时序违例&lt;/strong&gt;所设计的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;小结&lt;/h3&gt;
&lt;p&gt;以太网的硬件设计离不开 MAC 与 PHY 的协同工作，而 GMII 与 RGMII 作为两者之间的主流接口，分别在高性能与高集成度之间提供了不同的选择。GMII 以完整的 8 位数据线和简单的时序控制，适合对时序要求严苛的早期设备或开发平台；RGMII 则通过 DDR 技术和引脚精简，成为当前嵌入式系统中最常用的接口标准。理解这些接口的特性，有助于在实际项目中进行合理的芯片选型和 PCB 设计，从而在性能、成本与布线复杂度之间取得平衡。&lt;/p&gt;
</content:encoded></item><item><title>将字库数据烧录到 Flash 芯片内的方法</title><link>https://blog.tyh123.top/posts/29f8f7f6/</link><guid isPermaLink="true">https://blog.tyh123.top/posts/29f8f7f6/</guid><pubDate>Tue, 17 Mar 2026 09:18:06 GMT</pubDate><content:encoded>&lt;p&gt;:::note
本文旨在提供思路和方法，同时作为一篇开发笔记分享给大家。本文中所有的软件资源、代码等可以在 Gitee 仓库: &lt;a&gt;Stm32_HZK&lt;/a&gt; 中获取。&lt;/p&gt;
&lt;p&gt;本文中所使用的 Flash 芯片型号为 华邦(WINBOND) 生产的 W25Q 系列的芯片，它是一款&lt;strong&gt;非易失性存储芯片&lt;/strong&gt;，通信方式支持有 SPI, Dual SPI, QUAD SPI 这三种方式。 支持的最大时钟频率为  133MHz。
:::&lt;/p&gt;
&lt;h2&gt;GBK 汉字字符集的生成&lt;/h2&gt;
&lt;h3&gt;什么是 GBK 汉字字符集？&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;GBK 汉字字符集&lt;/strong&gt; 是一种用于表示中文字符的字符编码。它包含了所有的中文字符，每个中文字符都有一个唯一的编码值。&lt;/p&gt;
&lt;p&gt;:::note
通俗的来讲就是所有汉字的一个集合，这个集合按照先后顺序被排列组合起来，就形成了 GBK 汉字字符集。
:::&lt;/p&gt;
&lt;h3&gt;GBK 字符集的编码格式&lt;/h3&gt;
&lt;p&gt;与 &lt;a href=&quot;https://ascii.org.cn/&quot;&gt;ASCII 码&lt;/a&gt; 不同的是，GBK 编码&lt;strong&gt;是一个 16 位的编码&lt;/strong&gt;，每个汉字&lt;strong&gt;需要 2 个字节&lt;/strong&gt;来表示。GBK 编码的双字节结构，具体分为:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;区码 （高字节）&lt;/strong&gt;: 范围 0x81-0xFE&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;位码 （低字节）&lt;/strong&gt;: 范围 0x40-0xFE（0x7F 除外）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以，经过计算后可以得出&lt;strong&gt;GBK 理论编码位置有 23940 个&lt;/strong&gt;。计算过程如下:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;区码（高字节）长度&lt;/strong&gt;: &lt;code&gt;0x81-0xFE+0x01=0x7E&lt;/code&gt;，加上 1 则是算上起始位置。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;位码（低字节）长度&lt;/strong&gt;: &lt;strong&gt;又称为每个区的字码数量&lt;/strong&gt; &lt;code&gt;0x40-0xFE-0x01+0x01=0xBE&lt;/code&gt;，减去的 1 是被除掉的 0x7F，加上的 1 则是算上起始位置。&lt;/li&gt;
&lt;li&gt;所以总的可用的编码位置一共有: &lt;code&gt;0x7E*0xBE=0x5D84&lt;/code&gt; 个，十进制下为 &lt;code&gt;23940&lt;/code&gt; 个。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;举个例子说明为什么计算结果要加 1，比如有 &lt;code&gt;2 3 4 5 6&lt;/code&gt; 这几个数字，那么数字的数量就是 &lt;code&gt;(6-2)+1=5&lt;/code&gt;，小学数学问题，但是防止不懂还是提一下(doge)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;为什么要生成它？&lt;/h3&gt;
&lt;p&gt;在这之前，我尝试在网络上搜索现有的 GBK 汉字字符集，但是搜索到的都是已经收录到 GBK 字符集的字库文件，而我需要的是一个完整的 GBK 字符集文件，包含所有理论上的中文字符。所以我就打算自己生成一个 GBK 汉字字符集文件。&lt;/p&gt;
&lt;h3&gt;生成 GBK 汉字字符集&lt;/h3&gt;
&lt;p&gt;本次使用 python 生成 GBK 汉字字符集。代码如下:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 生成的索引文件会被保存到当前目录下的 GBK_INDEX.txt 中
file = open(&quot;GBK_INDEX.txt&quot;, &quot;wb&quot;)

highByte = 0x81 # 高字节起始位置
lowByte = 0x40  # 低字节起始位置

# 文件头写入 ascii 码
# 也可以把这部分放到下一部分中，在文件尾写入 ascii 码
asciiCode = 20 # ascii 码起始字符位置
while True:
    asciiCode += 1
    if asciiCode &amp;gt; 0x7E: # 终止字符位置为 0x7E
        break
    file.write(bytes([asciiCode]))

while True:
    # 跳过 0x7F（GBK 编码中该位码无效）
    if lowByte == 0x7F:
        lowByte += 1
        continue
    
    # 写入当前高低字节
    file.write(bytes([highByte, lowByte]))

    # 低位字节自增
    lowByte += 1

    # 检查低位是否溢出，即达到最大值 0xFE
    if lowByte &amp;gt; 0xFE:
        # 重置低位并增加高位
        lowByte = 0x40
        highByte += 1

        # 检查高位是否达到终止条件
        if highByte &amp;gt; 0xFE:
            break

file.close()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;!-- ### 避免存储空间的浪费
实际上，有些编码值在经过取模软键取模之后，其中的字模数据表现为空白数据 0x00，为什么会出现这种情况？&lt;/p&gt;
&lt;h4&gt;问题分析&lt;/h4&gt;
&lt;p&gt;这 &lt;strong&gt;23940 个理论编码位置&lt;/strong&gt;中，实际收录的汉字和符号一共是 21003{% ref 1 来源于百度百科 https://baike.baidu.com/item/GBK字库 %} 个，所以还有 &lt;code&gt;23940-21003=2937&lt;/code&gt; 个编码位置是空白编码，在这些编码上是没有汉字收录的，取模软件会为这些空白编码生成空白的字模数据 0x00。白白占用 Flash 的存储空间。&lt;/p&gt;
&lt;p&gt;以 16x16 的点阵字库为例，每个汉字字模占 32 个字节，23940 个位置全部占满，那么需要的 Flash 存储空间就是 &lt;code&gt;23940*32=766080&lt;/code&gt; 字节。大约需要 766KB 的 Flash 存储空间。但实际上，除去空白编码，实际需要的 Flash 存储空间就是 &lt;code&gt;21003*32=672096&lt;/code&gt; 字节。大约需要 672KB 的 Flash 存储空间。浪费了将近 &lt;code&gt;766-672=94KB&lt;/code&gt; 的 Flash 存储空间。对于 32x32 的点阵字库，空间浪费现象会更严重。 --&amp;gt;&lt;/p&gt;
&lt;h2&gt;生成点阵字库&lt;/h2&gt;
&lt;p&gt;将我们生成好的 GBK 汉字字符集导入到 PCtoLCD2002 软件中去生成我们的点阵字库数据就可以了。图中配置的是每个中文字符的字模数据为 16x16 大小，占用 32 个字节。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;首先打开 PCtoLCD2002 软件，对应的软件资源位于 &lt;a href=&quot;https://gitee.com/Stm32_HZK/Stm32_HZK&quot;&gt;Stm32/_HZK&lt;/a&gt; 仓库中 &lt;code&gt;PCtoLCD2002 完美版&lt;/code&gt; 文件夹内。&lt;/li&gt;
&lt;li&gt;点击 &lt;code&gt;导入大量文本&lt;/code&gt; 按钮，选择 &lt;code&gt;打开文本文件&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;选择我们之前生成的 GBK_INDEX.txt 文件&lt;/li&gt;
&lt;li&gt;点击 &lt;code&gt;开始生成&lt;/code&gt; 按钮，选择好保存路径后，等待软件生成完成。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/img/posts/STM32/01/generate_index_data.gif&quot; alt=&quot;generate_index_data&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;计算汉字字模数据的偏移地址&lt;/h2&gt;
&lt;p&gt;在这之前，我们已经生成了&lt;strong&gt;连续的 &lt;code&gt;23940&lt;/code&gt; 个 GBK 编码的中文字符&lt;/strong&gt;，并且对每个字符都取模生成了对应的字模数据，每个中文字符的字模数据都是 32 个字节。所以在生成的字库数据中，每个&lt;strong&gt;汉字的字模数据都是连续存储的&lt;/strong&gt;，我们需要根据汉字的编码值来计算出它的字模数据在 Flash 中的偏移地址。&lt;/p&gt;
&lt;p&gt;以汉字 &quot;啊&quot; 为例子，它的 GBK 编码为 &lt;code&gt;0xB0A1&lt;/code&gt;，根据 GBK 编码的规则，计算过程如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;它的区码为 &lt;code&gt;0xB0&lt;/code&gt;，由于区码的起始位置为 &lt;code&gt;0x81&lt;/code&gt;，&lt;strong&gt;所以区码偏移为 &lt;code&gt;0xB0-0x81=0x2F&lt;/code&gt;&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;它的位码为 &lt;code&gt;0xA1&lt;/code&gt;，由于位码的起始位置为 &lt;code&gt;0x40&lt;/code&gt;，并且它的值大于了 &lt;code&gt;0x7F&lt;/code&gt;，要在最终的结果上减去 1，&lt;strong&gt;所以位码偏移为 &lt;code&gt;0xA1-0x40-0x01=0x60&lt;/code&gt;&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;将汉字的&lt;strong&gt;区码偏移和位码长度&lt;/strong&gt;相乘，再加上位码偏移，就得到了&lt;strong&gt;汉字偏移量&lt;/strong&gt;：&lt;code&gt;0x2F*0xBE+0x60=0x2342&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;再将&lt;strong&gt;汉字偏移量&lt;/strong&gt;乘以每个中文字符的字模数据大小 32 字节，就得到了&lt;strong&gt;汉字字模数据在 Flash 中的偏移地址&lt;/strong&gt;：&lt;code&gt;0x2342*0x20=0x046840&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面用一张流程图来展示计算过程(你可以自由的缩放、移动这个流程图)：
{% mermaid %}
flowchart TD
A[开始] --&amp;gt; B[输入任意汉字]
B --&amp;gt; C[获取该汉字的 GBK 编码&amp;lt;br&amp;gt;（双字节）]
C --&amp;gt; D[分离区码和位码&amp;lt;br&amp;gt;区码 = 高字节，位码 = 低字节]
D --&amp;gt; E[计算区码偏移&amp;lt;br&amp;gt;区码偏移 = 区码 - 0x81]
E --&amp;gt; F{位码 &amp;gt; 0x7F ?}
F -- 是 --&amp;gt; G[位码偏移 = 位码 - 0x40 - 0x01]
F -- 否 --&amp;gt; H[位码偏移 = 位码 - 0x40]
G --&amp;gt; I[计算汉字偏移量&amp;lt;br&amp;gt;偏移量 = 区码偏移 × 每区汉字数 + 位码偏移&amp;lt;br&amp;gt;其中每区汉字数 = 0xBE]
H --&amp;gt; I
I --&amp;gt; J[计算字模数据在 Flash 中的地址&amp;lt;br&amp;gt;地址 = 偏移量 × 每个字模大小&amp;lt;br&amp;gt;每个字模大小 = 32 字节]
J --&amp;gt; K[结束]
{% endmermaid %}&lt;/p&gt;
&lt;blockquote&gt;
&lt;h3&gt;小结&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;若位码小于等于 0x7F，则地址计算为:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;地址 = (区码 - 0x81) * 0xBE + (位码 - 0x40) * 32&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;若位码大于 0x7F，则地址计算为:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;地址 = (区码 - 0x81) * 0xBE + (位码 - 0x40 - 0x01) * 32&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;其中，0x81 是区码的起始位置，0xBE 是每个区的字码数量，0x40 是位码的起始位置，0x01 是为了修正位码的偏移量。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;h2&gt;验证计算结果&lt;/h2&gt;
&lt;p&gt;&amp;lt;!-- 丢 8147 --&amp;gt;
使用 &lt;a href=&quot;https://hexed.it/&quot;&gt;十六进制编辑器&lt;/a&gt; 打开我们生成的字库文件，按下 &lt;code&gt;Ctrl+G&lt;/code&gt; 输入 &lt;code&gt;0x046840&lt;/code&gt; 这个位置，之后按下回车跳转到这个位置，从这个位置开始，连续读取 32 个字节，就是汉字 &quot;啊&quot; 的字模数据了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/posts/STM32/01/goto_address1.png&quot; alt=&quot;goto_address1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;选中这些数据，把它复制下来，粘贴到 &lt;a href=&quot;https://www.23bei.com/tool/553.html&quot;&gt;字模验证工具网站&lt;/a&gt; 中 (注意选择对应的取模参数，数据排列和取模方式)，从图中可以看到我们的字模数据是正确的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/img/posts/STM32/01/font_data_validation.png&quot; alt=&quot;font_data_validation&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;刚刚验证的是汉字 &quot;啊&quot; 的字模数据，它的位码大于了 0x7F，验证的是 &lt;a href=&quot;#%E5%B0%8F%E7%BB%93&quot;&gt;小结&lt;/a&gt; 中 &quot;若位码大于 0x7F&quot; 这一部分的计算结果。下面以汉字 &quot;丢&quot; 为例子验证 &quot;位码小于 0x7F&quot; 这一部分的计算结果。
汉字 &quot;丢&quot; 的 GBK 编码为 &lt;code&gt;0x8147&lt;/code&gt;，根据 GBK 编码的规则，计算过程如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;它的区码为 &lt;code&gt;0x81&lt;/code&gt;，由于区码的起始位置为 &lt;code&gt;0x81&lt;/code&gt;，&lt;strong&gt;所以区码偏移为 &lt;code&gt;0x81-0x81=0x00&lt;/code&gt;&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;它的位码为 &lt;code&gt;0x47&lt;/code&gt;，由于位码的起始位置为 &lt;code&gt;0x40&lt;/code&gt;，并且它的值小于等于 0x7F，&lt;strong&gt;所以位码偏移为 &lt;code&gt;0x47-0x40=0x07&lt;/code&gt;&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以它的偏移地址为 &lt;code&gt;0x00*0xBE+0x07*0x20=0xE0&lt;/code&gt;。
跳转到 &lt;code&gt;0xE0&lt;/code&gt; 这个位置就可以看到汉字 &quot;丢&quot; 的字模数据了。在这里不再赘述验证过程。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;烧录字库数据到 Flash 芯片内&lt;/h2&gt;
&lt;p&gt;这部分内容你可以用任何方式包括但不限于 ST-LINK、J-LINK、SWD 调试器、以及最基本的串口烧录(配合 Flash 驱动程序)。在这里就不详细展开说明了。&lt;/p&gt;
</content:encoded></item><item><title>Markdown Extended Features</title><link>https://blog.tyh123.top/posts/d2ae6349/</link><guid isPermaLink="true">https://blog.tyh123.top/posts/d2ae6349/</guid><description>Read more about Markdown features in Fuwari</description><pubDate>Wed, 01 May 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;GitHub Repository Cards&lt;/h2&gt;
&lt;p&gt;You can add dynamic cards that link to GitHub repositories, on page load, the repository information is pulled from the GitHub API.&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;Fabrizz/MMM-OnSpotify&quot;}&lt;/p&gt;
&lt;p&gt;Create a GitHub repository card with the code &lt;code&gt;::github{repo=&quot;&amp;lt;owner&amp;gt;/&amp;lt;repo&amp;gt;&quot;}&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;::github{repo=&quot;saicaca/fuwari&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Admonitions&lt;/h2&gt;
&lt;p&gt;Following types of admonitions are supported: &lt;code&gt;note&lt;/code&gt; &lt;code&gt;tip&lt;/code&gt; &lt;code&gt;important&lt;/code&gt; &lt;code&gt;warning&lt;/code&gt; &lt;code&gt;caution&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;:::note
Highlights information that users should take into account, even when skimming.
:::&lt;/p&gt;
&lt;p&gt;:::tip
Optional information to help a user be more successful.
:::&lt;/p&gt;
&lt;p&gt;:::important
Crucial information necessary for users to succeed.
:::&lt;/p&gt;
&lt;p&gt;:::warning
Critical content demanding immediate user attention due to potential risks.
:::&lt;/p&gt;
&lt;p&gt;:::caution
Negative potential consequences of an action.
:::&lt;/p&gt;
&lt;h3&gt;Basic Syntax&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;:::note
Highlights information that users should take into account, even when skimming.
:::

:::tip
Optional information to help a user be more successful.
:::
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Custom Titles&lt;/h3&gt;
&lt;p&gt;The title of the admonition can be customized.&lt;/p&gt;
&lt;p&gt;:::note[MY CUSTOM TITLE]
This is a note with a custom title.
:::&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:::note[MY CUSTOM TITLE]
This is a note with a custom title.
:::
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;GitHub Syntax&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
&lt;a href=&quot;https://github.com/orgs/community/discussions/16925&quot;&gt;The GitHub syntax&lt;/a&gt; is also supported.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; [!NOTE]
&amp;gt; The GitHub syntax is also supported.

&amp;gt; [!TIP]
&amp;gt; The GitHub syntax is also supported.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Spoiler&lt;/h3&gt;
&lt;p&gt;You can add spoilers to your text. The text also supports &lt;strong&gt;Markdown&lt;/strong&gt; syntax.&lt;/p&gt;
&lt;p&gt;The content :spoiler[is hidden &lt;strong&gt;ayyy&lt;/strong&gt;]!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;The content :spoiler[is hidden **ayyy**]!

&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Expressive Code Example</title><link>https://blog.tyh123.top/posts/e406953d/</link><guid isPermaLink="true">https://blog.tyh123.top/posts/e406953d/</guid><description>How code blocks look in Markdown using Expressive Code.</description><pubDate>Wed, 10 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Here, we&apos;ll explore how code blocks look using &lt;a href=&quot;https://expressive-code.com/&quot;&gt;Expressive Code&lt;/a&gt;. The provided examples are based on the official documentation, which you can refer to for further details.&lt;/p&gt;
&lt;h2&gt;Expressive Code&lt;/h2&gt;
&lt;h3&gt;Syntax Highlighting&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/key-features/syntax-highlighting/&quot;&gt;Syntax Highlighting&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;Regular syntax highlighting&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;console.log(&apos;This code is syntax highlighted!&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Rendering ANSI escape sequences&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;ANSI colors:
- Regular: [31mRed[0m [32mGreen[0m [33mYellow[0m [34mBlue[0m [35mMagenta[0m [36mCyan[0m
- Bold:    [1;31mRed[0m [1;32mGreen[0m [1;33mYellow[0m [1;34mBlue[0m [1;35mMagenta[0m [1;36mCyan[0m
- Dimmed:  [2;31mRed[0m [2;32mGreen[0m [2;33mYellow[0m [2;34mBlue[0m [2;35mMagenta[0m [2;36mCyan[0m

256 colors (showing colors 160-177):
[38;5;160m160 [38;5;161m161 [38;5;162m162 [38;5;163m163 [38;5;164m164 [38;5;165m165[0m
[38;5;166m166 [38;5;167m167 [38;5;168m168 [38;5;169m169 [38;5;170m170 [38;5;171m171[0m
[38;5;172m172 [38;5;173m173 [38;5;174m174 [38;5;175m175 [38;5;176m176 [38;5;177m177[0m

Full RGB colors:
[38;2;34;139;34mForestGreen - RGB(34, 139, 34)[0m

Text formatting: [1mBold[0m [2mDimmed[0m [3mItalic[0m [4mUnderline[0m
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Editor &amp;amp; Terminal Frames&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/key-features/frames/&quot;&gt;Editor &amp;amp; Terminal Frames&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;Code editor frames&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;console.log(&apos;Title attribute example&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- src/content/index.html --&amp;gt;
&amp;lt;div&amp;gt;File name comment example&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Terminal frames&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;This terminal frame has no title&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;Write-Output &quot;This one has a title!&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Overriding frame types&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;Look ma, no frame!&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;# Without overriding, this would be a terminal frame
function Watch-Tail { Get-Content -Tail 20 -Wait $args }
New-Alias tail Watch-Tail
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Text &amp;amp; Line Markers&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/key-features/text-markers/&quot;&gt;Text &amp;amp; Line Markers&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;Marking full lines &amp;amp; line ranges&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// Line 1 - targeted by line number
// Line 2
// Line 3
// Line 4 - targeted by line number
// Line 5
// Line 6
// Line 7 - targeted by range &quot;7-8&quot;
// Line 8 - targeted by range &quot;7-8&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Selecting line marker types (mark, ins, del)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;function demo() {
  console.log(&apos;this line is marked as deleted&apos;)
  // This line and the next one are marked as inserted
  console.log(&apos;this is the second inserted line&apos;)

  return &apos;this line uses the neutral default marker type&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Adding labels to line markers&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// labeled-line-markers.jsx
&amp;lt;button
  role=&quot;button&quot;
  {...props}
  value={value}
  className={buttonClassName}
  disabled={disabled}
  active={active}
&amp;gt;
  {children &amp;amp;&amp;amp;
    !active &amp;amp;&amp;amp;
    (typeof children === &apos;string&apos; ? &amp;lt;span&amp;gt;{children}&amp;lt;/span&amp;gt; : children)}
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Adding long labels on their own lines&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// labeled-line-markers.jsx
&amp;lt;button
  role=&quot;button&quot;
  {...props}

  value={value}
  className={buttonClassName}

  disabled={disabled}
  active={active}
&amp;gt;

  {children &amp;amp;&amp;amp;
    !active &amp;amp;&amp;amp;
    (typeof children === &apos;string&apos; ? &amp;lt;span&amp;gt;{children}&amp;lt;/span&amp;gt; : children)}
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Using diff-like syntax&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;+this line will be marked as inserted
-this line will be marked as deleted
this is a regular line
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+this is an actual diff file
-all contents will remain unmodified
 no whitespace will be removed either
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Combining syntax highlighting with diff-like syntax&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;  function thisIsJavaScript() {
    // This entire block gets highlighted as JavaScript,
    // and we can still add diff markers to it!
-   console.log(&apos;Old code to be removed&apos;)
+   console.log(&apos;New and shiny code!&apos;)
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Marking individual text inside lines&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;function demo() {
  // Mark any given text inside lines
  return &apos;Multiple matches of the given text are supported&apos;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Regular expressions&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;console.log(&apos;The words yes and yep will be marked.&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Escaping forward slashes&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;Test&quot; &amp;gt; /home/test.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Selecting inline marker types (mark, ins, del)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;function demo() {
  console.log(&apos;These are inserted and deleted marker types&apos;);
  // The return statement uses the default marker type
  return true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Word Wrap&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/key-features/word-wrap/&quot;&gt;Word Wrap&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;Configuring word wrap per block&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// Example with wrap
function getLongString() {
  return &apos;This is a very long string that will most probably not fit into the available space unless the container is extremely wide&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;// Example with wrap=false
function getLongString() {
  return &apos;This is a very long string that will most probably not fit into the available space unless the container is extremely wide&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Configuring indentation of wrapped lines&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// Example with preserveIndent (enabled by default)
function getLongString() {
  return &apos;This is a very long string that will most probably not fit into the available space unless the container is extremely wide&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;// Example with preserveIndent=false
function getLongString() {
  return &apos;This is a very long string that will most probably not fit into the available space unless the container is extremely wide&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Collapsible Sections&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/plugins/collapsible-sections/&quot;&gt;Collapsible Sections&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// All this boilerplate setup code will be collapsed
import { someBoilerplateEngine } from &apos;@example/some-boilerplate&apos;
import { evenMoreBoilerplate } from &apos;@example/even-more-boilerplate&apos;

const engine = someBoilerplateEngine(evenMoreBoilerplate())

// This part of the code will be visible by default
engine.doSomething(1, 2, 3, calcFn)

function calcFn() {
  // You can have multiple collapsed sections
  const a = 1
  const b = 2
  const c = a + b

  // This will remain visible
  console.log(`Calculation result: ${a} + ${b} = ${c}`)
  return c
}

// All this code until the end of the block will be collapsed again
engine.closeConnection()
engine.freeMemory()
engine.shutdown({ reason: &apos;End of example boilerplate code&apos; })
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Line Numbers&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/plugins/line-numbers/&quot;&gt;Line Numbers&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Displaying line numbers per block&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// This code block will show line numbers
console.log(&apos;Greetings from line 2!&apos;)
console.log(&apos;I am on line 3&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;// Line numbers are disabled for this block
console.log(&apos;Hello?&apos;)
console.log(&apos;Sorry, do you know what line I am on?&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Changing the starting line number&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;console.log(&apos;Greetings from line 5!&apos;)
console.log(&apos;I am on line 6&apos;)
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Simple Guides for Fuwari</title><link>https://blog.tyh123.top/posts/b56cb502/</link><guid isPermaLink="true">https://blog.tyh123.top/posts/b56cb502/</guid><description>How to use this blog template.</description><pubDate>Mon, 01 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Cover image source: &lt;a href=&quot;https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=2048/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg&quot;&gt;Source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This blog template is built with &lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt;. For the things that are not mentioned in this guide, you may find the answers in the &lt;a href=&quot;https://docs.astro.build/&quot;&gt;Astro Docs&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Front-matter of Posts&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;---
title: My First Blog Post
published: 2023-09-09
description: This is the first post of my new Astro blog.
image: ./cover.jpg
tags: [Foo, Bar]
category: Front-end
draft: false
---
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attribute&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;title&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The title of the post.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;published&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The date the post was published.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;description&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A short description of the post. Displayed on index page.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The cover image path of the post.&amp;lt;br/&amp;gt;1. Start with &lt;code&gt;http://&lt;/code&gt; or &lt;code&gt;https://&lt;/code&gt;: Use web image&amp;lt;br/&amp;gt;2. Start with &lt;code&gt;/&lt;/code&gt;: For image in &lt;code&gt;public&lt;/code&gt; dir&amp;lt;br/&amp;gt;3. With none of the prefixes: Relative to the markdown file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tags&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The tags of the post.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;category&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The category of the post.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;draft&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;If this post is still a draft, which won&apos;t be displayed.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Where to Place the Post Files&lt;/h2&gt;
&lt;p&gt;Your post files should be placed in &lt;code&gt;src/content/posts/&lt;/code&gt; directory. You can also create sub-directories to better organize your posts and assets.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;src/content/posts/
├── post-1.md
└── post-2/
    ├── cover.png
    └── index.md
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Markdown Example</title><link>https://blog.tyh123.top/posts/2339e0f4/</link><guid isPermaLink="true">https://blog.tyh123.top/posts/2339e0f4/</guid><description>A simple example of a Markdown blog post.</description><pubDate>Sun, 01 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;An h1 header&lt;/h1&gt;
&lt;p&gt;Paragraphs are separated by a blank line.&lt;/p&gt;
&lt;p&gt;2nd paragraph. &lt;em&gt;Italic&lt;/em&gt;, &lt;strong&gt;bold&lt;/strong&gt;, and &lt;code&gt;monospace&lt;/code&gt;. Itemized lists
look like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;this one&lt;/li&gt;
&lt;li&gt;that one&lt;/li&gt;
&lt;li&gt;the other one&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that --- not considering the asterisk --- the actual text
content starts at 4-columns in.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Block quotes are
written like so.&lt;/p&gt;
&lt;p&gt;They can span multiple paragraphs,
if you like.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Use 3 dashes for an em-dash. Use 2 dashes for ranges (ex., &quot;it&apos;s all
in chapters 12--14&quot;). Three dots ... will be converted to an ellipsis.
Unicode is supported. ☺&lt;/p&gt;
&lt;h2&gt;An h2 header&lt;/h2&gt;
&lt;p&gt;Here&apos;s a numbered list:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;first item&lt;/li&gt;
&lt;li&gt;second item&lt;/li&gt;
&lt;li&gt;third item&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Note again how the actual text starts at 4 columns in (4 characters
from the left side). Here&apos;s a code sample:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Let me re-iterate ...
for i in 1 .. 10 { do-something(i) }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you probably guessed, indented 4 spaces. By the way, instead of
indenting the block, you can use delimited blocks, if you like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;define foobar() {
    print &quot;Welcome to flavor country!&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(which makes copying &amp;amp; pasting easier). You can optionally mark the
delimited block for Pandoc to syntax highlight it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import time
# Quick, count to ten!
for i in range(10):
    # (but not *too* quick)
    time.sleep(0.5)
    print i
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;An h3 header&lt;/h3&gt;
&lt;p&gt;Now a nested list:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;First, get these ingredients:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;carrots&lt;/li&gt;
&lt;li&gt;celery&lt;/li&gt;
&lt;li&gt;lentils&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Boil some water.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Dump everything in the pot and follow
this algorithm:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; find wooden spoon
 uncover pot
 stir
 cover pot
 balance wooden spoon precariously on pot handle
 wait 10 minutes
 goto first step (or shut off burner when done)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do not bump wooden spoon or it will fall.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Notice again how text always lines up on 4-space indents (including
that last line which continues item 3 above).&lt;/p&gt;
&lt;p&gt;Here&apos;s a link to &lt;a href=&quot;http://foo.bar&quot;&gt;a website&lt;/a&gt;, to a &lt;a href=&quot;local-doc.html&quot;&gt;local
doc&lt;/a&gt;, and to a &lt;a href=&quot;#an-h2-header&quot;&gt;section heading in the current
doc&lt;/a&gt;. Here&apos;s a footnote [^1].&lt;/p&gt;
&lt;p&gt;[^1]: Footnote text goes here.&lt;/p&gt;
&lt;p&gt;Tables can look like this:&lt;/p&gt;
&lt;p&gt;size material color&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;9 leather brown
10 hemp canvas natural
11 glass transparent&lt;/p&gt;
&lt;p&gt;Table: Shoes, their sizes, and what they&apos;re made of&lt;/p&gt;
&lt;p&gt;(The above is the caption for the table.) Pandoc also supports
multi-line tables:&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;keyword text&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;red Sunsets, apples, and
other red or reddish
things.&lt;/p&gt;
&lt;p&gt;green Leaves, grass, frogs
and other things it&apos;s
not easy being.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;A horizontal rule follows.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Here&apos;s a definition list:&lt;/p&gt;
&lt;p&gt;apples
: Good for making applesauce.
oranges
: Citrus!
tomatoes
: There&apos;s no &quot;e&quot; in tomatoe.&lt;/p&gt;
&lt;p&gt;Again, text is indented 4 spaces. (Put a blank line between each
term/definition pair to spread things out more.)&lt;/p&gt;
&lt;p&gt;Here&apos;s a &quot;line block&quot;:&lt;/p&gt;
&lt;p&gt;| Line one
| Line too
| Line tree&lt;/p&gt;
&lt;p&gt;and images can be specified like so:&lt;/p&gt;
&lt;p&gt;Inline math equations go in like so: $\omega = d\phi / dt$. Display
math should get its own line and be put in in double-dollarsigns:&lt;/p&gt;
&lt;p&gt;$$I = \int \rho R^{2} dV$$&lt;/p&gt;
&lt;p&gt;$$
\begin{equation*}
\pi
=3.1415926535
;8979323846;2643383279;5028841971;6939937510;5820974944
;5923078164;0628620899;8628034825;3421170679;\ldots
\end{equation*}
$$&lt;/p&gt;
&lt;p&gt;And note that you can backslash-escape any punctuation characters
which you wish to be displayed literally, ex.: `foo`, *bar*, etc.&lt;/p&gt;
</content:encoded></item><item><title>Include Video in the Posts</title><link>https://blog.tyh123.top/posts/189ec72d/</link><guid isPermaLink="true">https://blog.tyh123.top/posts/189ec72d/</guid><description>This post demonstrates how to include embedded video in a blog post.</description><pubDate>Tue, 01 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Just copy the embed code from YouTube or other platforms, and paste it in the markdown file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
title: Include Video in the Post
published: 2023-10-19
// ...
---

&amp;lt;iframe width=&quot;100%&quot; height=&quot;468&quot; src=&quot;https://www.youtube.com/embed/5gIf0_xpFPI?si=N1WTorLKL0uwLsU_&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;YouTube&lt;/h2&gt;
&lt;p&gt;&amp;lt;iframe width=&quot;100%&quot; height=&quot;468&quot; src=&quot;https://www.youtube.com/embed/5gIf0_xpFPI?si=N1WTorLKL0uwLsU_&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;h2&gt;Bilibili&lt;/h2&gt;
&lt;p&gt;&amp;lt;iframe width=&quot;100%&quot; height=&quot;468&quot; src=&quot;//player.bilibili.com/player.html?bvid=BV1fK4y1s7Qf&amp;amp;p=1&quot; scrolling=&quot;no&quot; border=&quot;0&quot; frameborder=&quot;no&quot; framespacing=&quot;0&quot; allowfullscreen=&quot;true&quot;&amp;gt; &amp;lt;/iframe&amp;gt;&lt;/p&gt;
</content:encoded></item></channel></rss>