<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-CN"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="/EtherNotes/feed.xml" rel="self" type="application/atom+xml" /><link href="/EtherNotes/" rel="alternate" type="text/html" hreflang="zh-CN" /><updated>2026-09-14T19:47:55+08:00</updated><id>/EtherNotes/feed.xml</id><title type="html">EtherNotes</title><subtitle>个人知识笔记 — FPGA、嵌入式、以及各种杂七杂八的东西</subtitle><author><name>Ether</name></author><entry><title type="html">Verilog 代码规范</title><link href="/EtherNotes/misc/verilog/style-guide/" rel="alternate" type="text/html" title="Verilog 代码规范" /><published>2026-09-14T00:00:00+08:00</published><updated>2026-09-14T00:00:00+08:00</updated><id>/EtherNotes/misc/verilog/style-guide</id><content type="html" xml:base="/EtherNotes/misc/verilog/style-guide/"><![CDATA[<h1 id="example-with-fsm">Example with FSM</h1>

<div class="language-verilog highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">`define</span> <span class="n">RST_EN</span> <span class="mb">1'b0</span>

<span class="k">module</span> <span class="n">UpperCamelCase</span><span class="p">#(</span>
	<span class="k">parameter</span> <span class="n">snake_camel_case</span> <span class="o">=</span> <span class="mb">8'b11110000</span><span class="p">,</span>
	<span class="k">parameter</span> <span class="n">allow_any_data_type</span> <span class="o">=</span> <span class="mi">1234</span>
<span class="p">)(</span>
	<span class="kt">input</span>  <span class="kt">wire</span> <span class="n">clk_sys</span><span class="p">,</span>
	<span class="kt">input</span>  <span class="kt">wire</span> <span class="n">rst_sys_n</span><span class="p">,</span>
	
	<span class="c1">// Input</span>
	<span class="kt">input</span>  <span class="kt">wire</span>        <span class="n">inst_dval</span><span class="p">,</span><span class="c1">// Data Valid</span>
	<span class="kt">input</span>  <span class="kt">wire</span> <span class="p">[</span><span class="mi">31</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span> <span class="n">inst_din</span><span class="p">,</span> <span class="c1">// Data Input</span>
	
	<span class="c1">// Output</span>
	<span class="kt">output</span> <span class="kt">wire</span>        <span class="n">inst_dval</span><span class="p">,</span><span class="c1">// Data Valid</span>
	<span class="kt">output</span> <span class="kt">wire</span> <span class="p">[</span><span class="mi">31</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span> <span class="n">inst_dout</span> <span class="c1">// Data Ouput</span>
<span class="p">);</span>

	<span class="k">localparam</span> <span class="n">ST_IDLE</span> <span class="o">=</span> <span class="mb">3'b000</span><span class="p">,</span>
			   <span class="n">ST_WR</span>   <span class="o">=</span> <span class="mb">3'b001</span><span class="p">,</span>
			   <span class="n">ST_RD</span>   <span class="o">=</span> <span class="mb">3'b010</span><span class="p">,</span>
			   <span class="n">ST_DONE</span> <span class="o">=</span> <span class="mb">3'b100</span><span class="p">;</span>
	
	<span class="kt">reg</span> <span class="p">[</span><span class="mi">2</span><span class="o">:</span><span class="mi">0</span><span class="p">]</span> <span class="n">state_c</span><span class="p">,</span> <span class="n">state_n</span><span class="p">;</span>
	
	<span class="kt">wire</span> <span class="n">idle_wr</span><span class="p">,</span> <span class="n">idle_rd</span><span class="p">,</span> <span class="n">wr_done</span><span class="p">,</span> <span class="n">rd_done</span><span class="p">,</span> <span class="n">done_idle</span><span class="p">;</span>
	
	<span class="k">always</span> <span class="o">@</span><span class="p">(</span><span class="kt">posedge</span> <span class="n">clk_sys</span> <span class="kt">or</span> <span class="kt">negedge</span> <span class="n">rst_sys_n</span><span class="p">)</span> <span class="k">begin</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">rst_sys_n</span> <span class="o">==</span> <span class="cp">`RST_EN</span><span class="p">)</span> 
			<span class="n">state_c</span> <span class="o">&lt;=</span> <span class="n">ST_IDLE</span><span class="p">;</span>
		<span class="k">else</span>
			<span class="n">state_c</span> <span class="o">&lt;=</span> <span class="n">state_n</span><span class="p">;</span>
	<span class="k">end</span>
	
	<span class="k">always</span> <span class="o">@</span><span class="p">(</span><span class="o">*</span><span class="p">)</span> <span class="k">begin</span>
		<span class="k">case</span><span class="p">(</span><span class="n">state_c</span><span class="p">)</span> <span class="k">begin</span>
			<span class="n">ST_IDLE</span> <span class="o">:</span> <span class="n">state_n</span> <span class="o">&lt;=</span> <span class="n">idle_wr</span> <span class="o">?</span> <span class="n">ST_WR</span> <span class="o">:</span> <span class="n">idle_rd</span> <span class="o">?</span> <span class="n">ST_RD</span> <span class="o">:</span> <span class="n">ST_IDLE</span><span class="p">;</span>
			<span class="n">ST_WR</span>   <span class="o">:</span> <span class="n">state_n</span> <span class="o">&lt;=</span> <span class="n">wr_done</span> <span class="o">?</span> <span class="n">ST_DONE</span> <span class="o">:</span> <span class="n">ST_WR</span><span class="p">;</span>
			<span class="n">ST_RD</span>   <span class="o">:</span> <span class="n">state_n</span> <span class="o">&lt;=</span> <span class="n">rd_done</span> <span class="o">?</span> <span class="n">ST_DONE</span> <span class="o">:</span> <span class="n">ST_RD</span><span class="p">;</span>
			<span class="n">ST_DONE</span> <span class="o">:</span> <span class="n">state_n</span> <span class="o">&lt;=</span> <span class="n">done_idle</span> <span class="o">?</span> <span class="n">ST_IDLE</span> <span class="o">:</span> <span class="n">ST_DONE</span><span class="p">;</span>
			<span class="k">default</span> <span class="o">:</span> <span class="n">state_n</span> <span class="o">&lt;=</span> <span class="n">ST_IDLE</span><span class="p">;</span>
		<span class="k">end</span>
	<span class="k">end</span>
	
	<span class="c1">// Other Codes...</span>
	
	<span class="k">assign</span> <span class="n">idle_wr</span> <span class="o">=</span> <span class="p">...;</span>
	<span class="k">assign</span> <span class="n">idle_wr</span> <span class="o">=</span> <span class="p">...;</span>
	<span class="k">assign</span> <span class="n">idle_wr</span> <span class="o">=</span> <span class="p">...;</span>
	<span class="k">assign</span> <span class="n">idle_wr</span> <span class="o">=</span> <span class="p">...;</span>

<span class="k">endmodule</span>
</code></pre></div></div>]]></content><author><name>Ether</name></author><category term="misc" /><category term="verilog" /><summary type="html"><![CDATA[Example with FSM]]></summary></entry><entry><title type="html">基于EP4CE6F17C8芯片的图像边缘采集项目</title><link href="/EtherNotes/fpga/sdram/sccb/ov5640/vga/overview/" rel="alternate" type="text/html" title="基于EP4CE6F17C8芯片的图像边缘采集项目" /><published>2026-08-24T00:00:00+08:00</published><updated>2026-08-24T00:00:00+08:00</updated><id>/EtherNotes/fpga/sdram/sccb/ov5640/vga/overview</id><content type="html" xml:base="/EtherNotes/fpga/sdram/sccb/ov5640/vga/overview/"><![CDATA[<h1 id="项目概览">项目概览</h1>

<p>本项目基于 awc_c4mb_v11 开发板，使用 OV5640 图像传感器捕获图像并通过RGB转灰度、高斯滤波、索贝尔算子等算法提取图像边缘，并通过VGA接口显示到屏幕</p>

<h2 id="硬件资源">硬件资源</h2>
<ul>
  <li>FPGA: EP4CE6F17C8</li>
  <li>SDRAM: H57V2562GTR-75C</li>
  <li>Camera: OV5640</li>
</ul>

<h2 id="项目结构">项目结构</h2>

<p><img src="../../../../assets/images/c4mb_ov5640_edge.dio.png" alt="structure" /></p>]]></content><author><name>Ether</name></author><category term="fpga" /><category term="sdram" /><category term="sccb" /><category term="ov5640" /><category term="vga" /><summary type="html"><![CDATA[项目概览]]></summary></entry><entry><title type="html">RAM分类</title><link href="/EtherNotes/fpga/misc/ram/" rel="alternate" type="text/html" title="RAM分类" /><published>2026-08-19T00:00:00+08:00</published><updated>2026-08-19T00:00:00+08:00</updated><id>/EtherNotes/fpga/misc/ram</id><content type="html" xml:base="/EtherNotes/fpga/misc/ram/"><![CDATA[<h1 id="分类">分类</h1>
<ul>
  <li>单口RAM：<strong>只有一个窗口</strong>，寄件和取件都得排同一个队，不能同时进行。</li>
  <li>真双口RAM： <strong>两个独立窗口</strong>，每个窗口都能寄件也能取件，两边可同时干活互不干扰。</li>
  <li>伪双口RAM：<strong>两个专用窗口</strong>，一个窗口<strong>只能寄件（只写）</strong>，另一个窗口<strong>只能取件（只读）</strong>，分工明确。</li>
</ul>

<h1 id="硬件结构">硬件结构</h1>
<p>|类型|端口A|端口B|读写并发能力|
|—|—|—|—|
|<strong>单口RAM</strong>|1套地址线 + 1套数据线（输入输出复用）|<strong>无</strong>|<strong>不能同时读写</strong>。必须在同一时钟下分时操作|
|<strong>真双口RAM</strong>（True Dual Port）|1套地址线 + 1套<strong>独立</strong>读数据线 + 1套<strong>独立</strong>写数据线（可同时读写）|1套地址线 + 1套<strong>独立</strong>读数据线 + 1套<strong>独立</strong>写数据线（可同时读写）|<strong>可以</strong>。A端口和B端口<strong>各自独立</strong>进行读或写（甚至同时写不同地址）|
|<strong>伪双口RAM</strong>（Simple Dual Port）|<strong>专用写端口</strong>：地址线 + 写数据线 + 写使能|<strong>专用读端口</strong>：地址线 + 读数据线 + 读使能|<strong>可以</strong>。写端口持续写入，读端口持续读出，<strong>两边的时钟和地址完全独立</strong>（但读只能读，写只能写）|</p>

<blockquote>
  <p><strong>注意</strong>：这里的“真/伪”是Xilinx（AMD）和Intel（Altera）官方IP核的常用术语。面试时提到“Simple Dual-Port RAM”即指伪双口。</p>
</blockquote>]]></content><author><name>Ether</name></author><category term="fpga" /><category term="misc" /><category term="ram" /><summary type="html"><![CDATA[分类 单口RAM：只有一个窗口，寄件和取件都得排同一个队，不能同时进行。 真双口RAM： 两个独立窗口，每个窗口都能寄件也能取件，两边可同时干活互不干扰。 伪双口RAM：两个专用窗口，一个窗口只能寄件（只写），另一个窗口只能取件（只读），分工明确。]]></summary></entry><entry><title type="html">PLL</title><link href="/EtherNotes/fpga/hardware/pll/pll/" rel="alternate" type="text/html" title="PLL" /><published>2026-08-19T00:00:00+08:00</published><updated>2026-08-19T00:00:00+08:00</updated><id>/EtherNotes/fpga/hardware/pll/pll</id><content type="html" xml:base="/EtherNotes/fpga/hardware/pll/pll/"><![CDATA[<h1 id="pll-的作用">PLL 的作用：</h1>
<ul>
  <li>分频</li>
  <li>倍频</li>
  <li>相位调整</li>
  <li>占空比调节</li>
  <li>稳定时钟抖动</li>
</ul>

<blockquote>
  <p>Xilinx的MMCM：
MMCM是一个PLL加上DCM的一部分进行精细的相移（相位动态调整功能）</p>
</blockquote>

<h1 id="pll-的原理">PLL 的原理</h1>]]></content><author><name>Ether</name></author><category term="fpga" /><category term="hardware" /><category term="pll" /><category term="fpga" /><category term="pll" /><category term="clock" /><summary type="html"><![CDATA[PLL 的作用： 分频 倍频 相位调整 占空比调节 稳定时钟抖动]]></summary></entry><entry><title type="html">I2C 概览</title><link href="/EtherNotes/fpga/protocol/i2c/i2c-overview/" rel="alternate" type="text/html" title="I2C 概览" /><published>2026-08-19T00:00:00+08:00</published><updated>2026-08-19T00:00:00+08:00</updated><id>/EtherNotes/fpga/protocol/i2c/i2c-overview</id><content type="html" xml:base="/EtherNotes/fpga/protocol/i2c/i2c-overview/"><![CDATA[<h1 id="i2c-概览">I2C 概览</h1>

<p>I2C（Inter-Integrated Circuit，集成电路间总线）由 Philips 公司于 1982 年推出，是一种<strong>两线制、同步、半双工</strong>的串行总线协议，用于连接同一块电路板上的多个低速外设。</p>

<h2 id="为什么需要-i2c">为什么需要 I2C</h2>

<ul>
  <li><strong>引脚极少</strong>：无论挂多少个设备，都只占 2 根线（SDA 数据 + SCL 时钟），对引脚紧张的 MCU/FPGA 非常友好；</li>
  <li><strong>总线共享</strong>：多个设备挂在同一条总线上，通过<strong>器件地址</strong>寻址，不需要为每个外设单独接线；</li>
  <li><strong>多主多从</strong>：支持多个主机，配合仲裁机制可安全共存。</li>
</ul>

<h2 id="基本特点">基本特点</h2>

<ol>
  <li>两线制：SDA（串行数据线）+ SCL（串行时钟线）</li>
  <li>同步通信：时钟由主机产生，数据与时钟同步</li>
  <li>半双工：数据在同一根线上双向传输，同一时刻只能一个方向</li>
  <li>寻址机制：每个设备有独立的器件地址（7 位或 10 位）</li>
  <li>开漏 + 上拉：总线电平靠上拉电阻提供，天然支持”线与”与多主机仲裁</li>
  <li>带应答（ACK）：每传输一个字节，接收方回一个 ACK 位，传输可靠性比 SPI 高</li>
</ol>

<h2 id="传输速率">传输速率</h2>

<table>
  <thead>
    <tr>
      <th>模式</th>
      <th>速率</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>标准模式（Standard）</td>
      <td>100 kbit/s</td>
      <td>最常用，兼容性最好</td>
    </tr>
    <tr>
      <td>快速模式（Fast）</td>
      <td>400 kbit/s</td>
      <td>大多数现代器件支持</td>
    </tr>
    <tr>
      <td>快速模式+（Fast Plus）</td>
      <td>1 Mbit/s</td>
      <td>较少见</td>
    </tr>
    <tr>
      <td>高速模式（High-speed）</td>
      <td>3.4 Mbit/s</td>
      <td>需额外电流源，很少用</td>
    </tr>
  </tbody>
</table>

<h2 id="总线拓扑">总线拓扑</h2>

<pre><code class="language-mermaid">graph LR
  VCC[VCC]
  M[主机 Master]
  S1[从机1 EEPROM]
  S2[从机2 温度传感器]
  S3[从机3 摄像头]
  Rp1[上拉电阻 Rp]
  Rp2[上拉电阻 Rp]

  VCC --- Rp1 --- SDA[SDA 总线]
  VCC --- Rp2 --- SCL[SCL 总线]
  M --- SDA
  M --- SCL
  S1 --- SDA
  S1 --- SCL
  S2 --- SDA
  S2 --- SCL
  S3 --- SDA
  S3 --- SCL
</code></pre>

<blockquote>
  <p>📌 待补图：I2C 总线硬件连接原理图（多从机 + 上拉电阻选值）</p>
</blockquote>

<ul>
  <li><strong>上拉电阻</strong>：所有设备空闲时输出高阻，总线靠 Rp 拉高；Rp 取值需在速率与总线电容（&lt;400pF）之间权衡，常用 4.7kΩ（100k）~ 1kΩ（400k）；</li>
  <li><strong>总线电容</strong>：挂载设备越多、走线越长，总线电容越大，上升沿越缓，最高速率越低。</li>
</ul>

<h2 id="三协议对比uart--spi--i2c">三协议对比（UART / SPI / I2C）</h2>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>UART</th>
      <th>SPI</th>
      <th>I2C</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>线数</td>
      <td>2（TX/RX）</td>
      <td>4（SCK/MOSI/MISO/CS）</td>
      <td>2（SDA/SCL）</td>
    </tr>
    <tr>
      <td>同步方式</td>
      <td>异步（约定波特率）</td>
      <td>同步</td>
      <td>同步</td>
    </tr>
    <tr>
      <td>双工</td>
      <td>全双工</td>
      <td>全双工</td>
      <td>半双工</td>
    </tr>
    <tr>
      <td>寻址</td>
      <td>无（点对点）</td>
      <td>片选 CS</td>
      <td>器件地址</td>
    </tr>
    <tr>
      <td>速率</td>
      <td>低（≤几 Mbit/s）</td>
      <td>高（几十 MHz）</td>
      <td>中（≤3.4 Mbit/s）</td>
    </tr>
    <tr>
      <td>应答机制</td>
      <td>无</td>
      <td>无</td>
      <td>有（ACK）</td>
    </tr>
    <tr>
      <td>典型用途</td>
      <td>调试口、GPS、蓝牙</td>
      <td>Flash、ADC、LCD</td>
      <td>EEPROM、传感器、RTC</td>
    </tr>
  </tbody>
</table>

<h2 id="系列导读">系列导读</h2>

<ul>
  <li>物理层：电气特性（开漏、上拉、线与）→ <a href="/EtherNotes/fpga/protocol/i2c/i2c-physical/">I2C 物理层</a></li>
  <li>协议层：起始/停止、字节传输、读写时序 → <a href="/EtherNotes/fpga/protocol/i2c/i2c-protocol/">I2C 协议层</a></li>
  <li>实现：Verilog 主机控制器 → <a href="/EtherNotes/fpga/protocol/i2c/i2c-implementation/">I2C 实现</a></li>
</ul>]]></content><author><name>Ether</name></author><category term="fpga" /><category term="protocol" /><category term="i2c" /><summary type="html"><![CDATA[I2C 概览]]></summary></entry><entry><title type="html">SPI 物理层</title><link href="/EtherNotes/fpga/protocol/spi/spi-physical/" rel="alternate" type="text/html" title="SPI 物理层" /><published>2026-08-19T00:00:00+08:00</published><updated>2026-08-19T00:00:00+08:00</updated><id>/EtherNotes/fpga/protocol/spi/spi-physical</id><content type="html" xml:base="/EtherNotes/fpga/protocol/spi/spi-physical/"><![CDATA[<h1 id="spi-物理层">SPI 物理层</h1>

<p>SPI 的物理层很简单：<strong>4 根单向信号线 + 推挽驱动</strong>。相比 I2C 的开漏结构，SPI 的电气特性决定了它能跑出远高于 I2C 的速率。</p>

<h2 id="引脚电气特性">引脚电气特性</h2>

<table>
  <thead>
    <tr>
      <th>信号</th>
      <th>方向</th>
      <th>电气特点</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>SCK</td>
      <td>主机 → 从机</td>
      <td>推挽输出</td>
      <td>时钟，由主机产生，决定传输速率</td>
    </tr>
    <tr>
      <td>MOSI</td>
      <td>主机 → 从机</td>
      <td>推挽输出</td>
      <td>主机发送的数据流</td>
    </tr>
    <tr>
      <td>MISO</td>
      <td>从机 → 主机</td>
      <td>推挽输出</td>
      <td>从机返回的数据流</td>
    </tr>
    <tr>
      <td>CS/SS</td>
      <td>主机 → 从机</td>
      <td>推挽输出</td>
      <td>片选，<strong>低电平有效</strong></td>
    </tr>
  </tbody>
</table>

<ul>
  <li><strong>推挽输出</strong>：每个引脚由一个设备独占驱动，高低电平都是强驱动，边沿陡峭 → 速率可以很高（几十 MHz）；</li>
  <li>对比 I2C 的开漏 + 上拉：不需要上拉电阻，也没有”线与”和仲裁，代价是<strong>不支持多主机</strong>；</li>
  <li>电平通常是标准 TTL/CMOS（3.3V 或 5V），驱动能力与器件有关；</li>
  <li><strong>单向线</strong>：MOSI 只发、MISO 只收，数据流向固定，所以同一时刻可以”一进一出”（全双工）。</li>
</ul>

<h2 id="片选信号-cs">片选信号 CS</h2>

<ul>
  <li>SPI 没有地址概念，主机用 <strong>CS 片选</strong>选中目标从机：拉低 CS → 该从机参与传输，拉高 → 从机忽略总线；</li>
  <li>一个从机占一根 CS 线，<strong>n 个从机共需 3 + n 根线</strong>；</li>
  <li>空闲时所有从机的 CS 都保持高电平。</li>
</ul>

<h2 id="一主多从拓扑">一主多从拓扑</h2>

<pre><code class="language-mermaid">graph TB
  M[主机 Master]
  S1[从机 1]
  S2[从机 2]
  S3[从机 3]

  M -- SCK --&gt; S1
  M -- SCK --&gt; S2
  M -- SCK --&gt; S3
  M -- MOSI --&gt; S1
  M -- MOSI --&gt; S2
  M -- MOSI --&gt; S3
  S1 -- MISO --&gt; M
  S2 -- MISO --&gt; M
  S3 -- MISO --&gt; M
  M -- "CS0" --&gt; S1
  M -- "CS1" --&gt; S2
  M -- "CS2" --&gt; S3
</code></pre>

<ul>
  <li>SCK / MOSI / MISO 三根线<strong>所有从机共享</strong>，靠 CS 区分当前与主机通信的从机；</li>
  <li><strong>未被选中的从机必须把 MISO 置为高阻</strong>，否则多个从机同时驱动 MISO 会短路——这是从机硬件必须遵守的规则；</li>
  <li>MISO 共享线没有上拉（推挽结构），空闲时电平不确定，仅在 CS 有效的传输期间有意义。</li>
</ul>

<blockquote>
  <p>📌 待补图：FPGA 与 Flash/ADC 的 SPI 硬件连接原理图</p>
</blockquote>

<h2 id="菊花链daisy-chain拓扑">菊花链（Daisy Chain）拓扑</h2>

<p>对引脚特别紧张的场景，可以让多个从机串成一链：</p>

<pre><code class="language-mermaid">graph LR
  M[主机] -- MOSI --&gt; S1[从机1] -- MOSI --&gt; S2[从机2] -- MOSI --&gt; S3[从机3]
  S3 -- MISO --&gt; M
  M -- SCK/CS --&gt; S1
  M -- SCK/CS --&gt; S2
  M -- SCK/CS --&gt; S3
</code></pre>

<ul>
  <li>所有从机共享一个 CS 和 SCK，数据像移位寄存器一样<strong>逐级穿过</strong>每个从机；</li>
  <li>缺点：任一级故障整条链失效，且传输 n 个设备需要 n 倍时间。</li>
</ul>

<blockquote>
  <p>📌 待补图：SPI 菊花链连接原理图</p>
</blockquote>

<h2 id="速率与距离">速率与距离</h2>

<ul>
  <li>SPI 没有协议规定的速率上限，实际取决于器件和布线；</li>
  <li>板内走线可达数十 MHz；<strong>距离越远速率越低</strong>，跨板连接时 SCK 边沿劣化是主要瓶颈；</li>
  <li>主从器件在 PCB 上应尽量靠近（见 <a href="/EtherNotes/fpga/protocol/spi/spi-overview/">SPI 概览</a> 的附注）。</li>
</ul>]]></content><author><name>Ether</name></author><category term="fpga" /><category term="protocol" /><category term="spi" /><summary type="html"><![CDATA[SPI 物理层]]></summary></entry><entry><title type="html">SPI 协议层</title><link href="/EtherNotes/fpga/protocol/spi/spi-protocol/" rel="alternate" type="text/html" title="SPI 协议层" /><published>2026-08-19T00:00:00+08:00</published><updated>2026-08-19T00:00:00+08:00</updated><id>/EtherNotes/fpga/protocol/spi/spi-protocol</id><content type="html" xml:base="/EtherNotes/fpga/protocol/spi/spi-protocol/"><![CDATA[<h1 id="spi-协议层详解">SPI 协议层详解</h1>

<h2 id="一次完整传输的流程">一次完整传输的流程</h2>

<ol>
  <li>主机拉低目标从机的 <strong>CS</strong>，选中该从机；</li>
  <li>主机在 <strong>SCK</strong> 上产生时钟，同时从 MOSI 移出数据、从 MISO 移入数据——<strong>每个时钟周期交换 1 bit</strong>，发送与接收同时进行（全双工）；</li>
  <li>传完约定的位数后，主机拉高 CS，结束本次传输。</li>
</ol>

<blockquote>
  <p>SPI 没有固定的帧格式：数据位数（通常 8 bit，也可 16/24/32）由器件手册约定，没有起始位/停止位/应答位——这是它与 UART、I2C 协议层最大的区别。</p>
</blockquote>

<h2 id="时钟极性与时钟相位">时钟极性与时钟相位</h2>

<ul>
  <li><strong>时钟极性（CPOL）</strong>：决定 SCK <strong>空闲时</strong>的电平。CPOL=0 空闲低，CPOL=1 空闲高；</li>
  <li><strong>时钟相位（CPHA）</strong>：决定数据在第<strong>1</strong>个跳变沿采样（CPHA=0）还是第<strong>2</strong>个跳变沿采样（CPHA=1）。</li>
</ul>

<h3 id="spi-传输模式">SPI 传输模式</h3>

<p>CPOL 与 CPHA 组合出 4 种模式：</p>

<table>
  <thead>
    <tr>
      <th>模式</th>
      <th>CPOL</th>
      <th>CPHA</th>
      <th>空闲电平</th>
      <th>采样沿</th>
      <th>数据变化沿</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>模式 0</td>
      <td>0</td>
      <td>0</td>
      <td>低</td>
      <td>上升沿</td>
      <td>下降沿</td>
    </tr>
    <tr>
      <td>模式 1</td>
      <td>0</td>
      <td>1</td>
      <td>低</td>
      <td>下降沿</td>
      <td>上升沿</td>
    </tr>
    <tr>
      <td>模式 2</td>
      <td>1</td>
      <td>0</td>
      <td>高</td>
      <td>下降沿</td>
      <td>上升沿</td>
    </tr>
    <tr>
      <td>模式 3</td>
      <td>1</td>
      <td>1</td>
      <td>高</td>
      <td>上升沿</td>
      <td>下降沿</td>
    </tr>
  </tbody>
</table>

<blockquote>
  <p>记忆规律：<strong>采样沿与数据变化沿永远相反</strong>。CPOL 决定空闲电平，CPHA=0 用第一个沿采样，CPHA=1 用第二个沿采样。</p>
</blockquote>

<h2 id="数据传输时序">数据传输时序</h2>

<h3 id="模式-0cpol0-cpha0最常用">模式 0（CPOL=0, CPHA=0，最常用）</h3>

<p>空闲 SCK 为低；CS 拉低后，<strong>SCK 上升沿采样、下降沿换数据</strong>，先发 MSB：</p>

<pre><code class="language-wavedrom">{
  signal: [
    { name: "CS",   wave: "10........1" },
    { name: "SCK",  wave: "0pppppppp." },
    { name: "MOSI", wave: "x33333333x", data: "D7 D6 D5 D4 D3 D2 D1 D0" },
    { name: "MISO", wave: "x33333333x", data: "D7 D6 D5 D4 D3 D2 D1 D0" }
  ]
}
</code></pre>

<ul>
  <li>MOSI 与 MISO <strong>同时</strong>传输：主机每发出 1 bit，也收到从机 1 bit；</li>
  <li>数据在下降沿变化、上升沿稳定，接收方在上升沿采样最稳。</li>
</ul>

<h3 id="模式-3cpol1-cpha1">模式 3（CPOL=1, CPHA=1）</h3>

<p>空闲 SCK 为高；CS 拉低后，<strong>SCK 上升沿采样、下降沿换数据</strong>（与模式 0 采样沿相同，只是空闲电平相反）：</p>

<pre><code class="language-wavedrom">{
  signal: [
    { name: "CS",   wave: "10........1" },
    { name: "SCK",  wave: "1nnnnnnnn." },
    { name: "MOSI", wave: "x33333333x", data: "D7 D6 D5 D4 D3 D2 D1 D0" },
    { name: "MISO", wave: "x33333333x", data: "D7 D6 D5 D4 D3 D2 D1 D0" }
  ]
}
</code></pre>

<h3 id="数据有效性总结">数据有效性总结</h3>

<ul>
  <li>发送方（主机/从机各自）：在<strong>数据变化沿</strong>更新 MOSI/MISO；</li>
  <li>接收方：在<strong>采样沿</strong>锁存数据；</li>
  <li>只要主机与从机约定同一模式（CPOL/CPHA 一致），两边自然对齐。主机与从机模式不一致时，数据会在错误的边沿被采样——表现为读到移位半个周期的数据或全错。</li>
</ul>

<blockquote>
  <p>📌 待补图：模式 1、模式 2 的时序图</p>
</blockquote>]]></content><author><name>Ether</name></author><category term="fpga" /><category term="protocol" /><category term="spi" /><summary type="html"><![CDATA[SPI 协议层详解]]></summary></entry><entry><title type="html">UART 物理层</title><link href="/EtherNotes/fpga/protocol/uart/uart-physical/" rel="alternate" type="text/html" title="UART 物理层" /><published>2026-08-19T00:00:00+08:00</published><updated>2026-08-19T00:00:00+08:00</updated><id>/EtherNotes/fpga/protocol/uart/uart-physical</id><content type="html" xml:base="/EtherNotes/fpga/protocol/uart/uart-physical/"><![CDATA[<h1 id="uart-物理层">UART 物理层</h1>

<p>UART 只定义了<strong>时序协议</strong>，没有规定电气标准——”UART 电平”由物理层的具体收发器决定。同样一帧数据，在不同电平标准下以不同电压表示逻辑 0/1。</p>

<h2 id="电平标准">电平标准</h2>

<table>
  <thead>
    <tr>
      <th>标准</th>
      <th>逻辑 0</th>
      <th>逻辑 1</th>
      <th>特点</th>
      <th>典型场景</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>TTL/CMOS</td>
      <td>0V</td>
      <td>3.3V / 5V</td>
      <td>板内直连，距离 &lt;1m</td>
      <td>MCU ↔ 外设模块</td>
    </tr>
    <tr>
      <td>RS-232</td>
      <td>+3~+15V</td>
      <td>-3~-15V</td>
      <td>逻辑<strong>反相</strong>，单端，抗干扰略强，点对点</td>
      <td>PC 老式串口（DB9）</td>
    </tr>
    <tr>
      <td>RS-485</td>
      <td>差分（A-B 极性）</td>
      <td>差分</td>
      <td>差分传输，抗共模干扰，可多点组网，千米级</td>
      <td>工业总线（Modbus）</td>
    </tr>
  </tbody>
</table>

<blockquote>
  <p>注意：<strong>RS-232 的逻辑电平是反的</strong>——正电压表示逻辑 0，负电压表示逻辑 1。FPGA/MCU 的 TTL 引脚不能直接连 RS-232 设备，中间必须经过 MAX3232 之类的电平转换芯片。</p>
</blockquote>

<h2 id="波特率baud-rate">波特率（Baud Rate）</h2>

<ul>
  <li><strong>定义</strong>：每秒传输的码元（bit）数，单位 bit/s；</li>
  <li>UART 是二进制调制，波特率 = 比特率；</li>
  <li>收发双方<strong>必须约定完全相同的波特率</strong>，否则采样点逐位漂移，数据错乱；</li>
  <li>常用值：9600、19200、38400、57600、<strong>115200</strong>（最常用）、230400、460800、921600；</li>
  <li>FPGA 实现时由系统时钟分频产生，波特率不是时钟频率的整数倍时会有误差（见协议层”16 倍过采样”一节）。</li>
</ul>

<h2 id="连接拓扑点对点">连接拓扑：点对点</h2>

<p>UART 是点对点协议，一对设备之间交叉连接：</p>

<pre><code class="language-mermaid">graph LR
  A["设备 A&lt;br/&gt;TX  RX"]
  B["设备 B&lt;br/&gt;TX  RX"]
  A -- "TX ──&gt; RX" --&gt; B
  B -- "TX ──&gt; RX" --&gt; A
  A --- GND[GND 共地]
  B --- GND
</code></pre>

<ul>
  <li><strong>TX 接对方 RX</strong>（交叉），<strong>GND 必须共地</strong>——没有共地就没有参考电平；</li>
  <li>没有寻址、没有总线仲裁，只能一对一。</li>
</ul>

<blockquote>
  <p>📌 待补图：USB 转串口模块（CH340/CP2102）与 FPGA 开发板连接原理图</p>
</blockquote>

<h2 id="与-fpga-的连接">与 FPGA 的连接</h2>

<p>FPGA 引脚直接输出 TTL 电平的 UART 信号：</p>

<ul>
  <li><strong>FPGA → 模块</strong>：TX 引脚直连（电平匹配时）；</li>
  <li><strong>FPGA ↔ PC</strong>：经 USB 转串口芯片（CH340、CP2102、FT232），芯片内部完成 USB ↔ TTL 转换；</li>
  <li><strong>电平不匹配时</strong>（如 5V 模块配 3.3V FPGA）：需电平转换电路，注意 FPGA 引脚耐压。</li>
</ul>

<h2 id="传输距离与速率限制">传输距离与速率限制</h2>

<ul>
  <li>TTL 电平抗干扰弱，线缆过长时边沿劣化，一般控制在 <strong>1 米以内</strong>；</li>
  <li>距离远（&gt;1m）或环境嘈杂时应改用 RS-232 / RS-485 收发器，协议帧完全不变，只换物理层。</li>
</ul>]]></content><author><name>Ether</name></author><category term="fpga" /><category term="protocol" /><category term="uart" /><summary type="html"><![CDATA[UART 物理层]]></summary></entry><entry><title type="html">UART 协议层</title><link href="/EtherNotes/fpga/protocol/uart/uart-protocol/" rel="alternate" type="text/html" title="UART 协议层" /><published>2026-08-19T00:00:00+08:00</published><updated>2026-08-19T00:00:00+08:00</updated><id>/EtherNotes/fpga/protocol/uart/uart-protocol</id><content type="html" xml:base="/EtherNotes/fpga/protocol/uart/uart-protocol/"><![CDATA[<h1 id="uart-协议层">UART 协议层</h1>

<p>UART 是<strong>异步</strong>协议：没有时钟线，收发双方各自用本地时钟按约定波特率对数据采样。协议层解决的核心问题就是：<strong>在没有时钟的情况下，接收方如何知道”数据从哪里开始、到哪里结束”。</strong></p>

<h2 id="帧格式">帧格式</h2>

<p>UART 把数据打包成<strong>帧（Frame）</strong>传输，一帧的结构：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>空闲 ── 起始位 ── 数据位(LSB→MSB) ── 校验位(可选) ── 停止位 ── 空闲
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th>组成部分</th>
      <th>位宽</th>
      <th>说明</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>起始位（Start）</td>
      <td>1 bit</td>
      <td>固定<strong>逻辑 0</strong>，把空闲态的高电平拉低，告诉接收方”帧开始了”</td>
    </tr>
    <tr>
      <td>数据位（Data）</td>
      <td>5~9 bit，常用 8</td>
      <td><strong>LSB first</strong>，先发最低位</td>
    </tr>
    <tr>
      <td>校验位（Parity）</td>
      <td>0~1 bit</td>
      <td>可选，奇校验 / 偶校验</td>
    </tr>
    <tr>
      <td>停止位（Stop）</td>
      <td>1 / 1.5 / 2 bit</td>
      <td>固定<strong>逻辑 1</strong>，帧结束，回到空闲态</td>
    </tr>
  </tbody>
</table>

<h3 id="一帧-8n1-数据0x55lsb-first">一帧 8N1 数据（0x55，LSB first）</h3>

<p>0x55 = <code class="language-plaintext highlighter-rouge">0101_0101</code>，LSB first 发送序列即 <code class="language-plaintext highlighter-rouge">1 0 1 0 1 0 1 0</code>，与起始位/停止位拼起来波形规律、便于示波器观察：</p>

<pre><code class="language-wavedrom">{
  signal: [
    { name: "TX", wave: "10333333331", data: "start d0=1 d1=0 d2=1 d3=0 d4=1 d5=0 d6=1 d7=0 stop" }
  ]
}
</code></pre>

<h2 id="空闲态与起始位检测">空闲态与起始位检测</h2>

<ul>
  <li><strong>空闲态</strong>：总线保持高电平（逻辑 1）；</li>
  <li><strong>起始位</strong>：发送方拉低总线 1 bit 时间，接收方检测到这个<strong>下降沿</strong>即认为一帧开始；</li>
  <li>接收方从起始位的下降沿起，延迟 <strong>0.5 bit 时间</strong>采样一次确认是起始位，之后每隔 1 bit 时间采样数据位——即”中点采样”，抗抖动能力最好。</li>
</ul>

<h2 id="校验位">校验位</h2>

<p>校验位是发送方对数据位的简单检错：</p>

<table>
  <thead>
    <tr>
      <th>类型</th>
      <th>规则</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>无校验（N）</td>
      <td>不发送校验位</td>
    </tr>
    <tr>
      <td>偶校验（E）</td>
      <td>校验位取值使”数据位 + 校验位”中 1 的个数为<strong>偶数</strong></td>
    </tr>
    <tr>
      <td>奇校验（O）</td>
      <td>校验位取值使”数据位 + 校验位”中 1 的个数为<strong>奇数</strong></td>
    </tr>
  </tbody>
</table>

<p>接收方按同样规则计算，若不一致说明传输出错（UART 没有重传机制，是否处理由上层决定）。</p>

<h2 id="常见帧格式写法">常见帧格式写法</h2>

<p>帧格式通常用 <code class="language-plaintext highlighter-rouge">数据位-校验-停止位</code> 简写：</p>

<ul>
  <li><strong>8N1</strong>：8 数据位、无校验、1 停止位（最常用，PC 串口默认）</li>
  <li>8E1：8 数据位、偶校验、1 停止位</li>
  <li>8O1：8 数据位、奇校验、1 停止位</li>
</ul>

<h2 id="接收方采样原理">接收方采样原理</h2>

<p>接收方本地时钟与发送方不可能完全同频，为减小累计误差，接收逻辑一般以<strong>波特率的 16 倍</strong>过采样：</p>

<ul>
  <li>检测到起始位下降沿后，计数 8 个采样周期（即 0.5 bit）到达数据位中点；</li>
  <li>此后每 16 个采样周期采一个数据位，始终落在每一位的正中；</li>
  <li>双方波特率误差通常在 ±3% 以内可稳定通信（一位的中点偏移不会跨出该位）。</li>
</ul>

<blockquote>
  <p>📌 待补图：UART 接收方 16 倍过采样示意图</p>
</blockquote>]]></content><author><name>Ether</name></author><category term="fpga" /><category term="protocol" /><category term="uart" /><summary type="html"><![CDATA[UART 协议层]]></summary></entry><entry><title type="html">跨时钟域同步</title><link href="/EtherNotes/fpga/misc/cdc/" rel="alternate" type="text/html" title="跨时钟域同步" /><published>2026-08-18T00:00:00+08:00</published><updated>2026-08-18T00:00:00+08:00</updated><id>/EtherNotes/fpga/misc/cdc</id><content type="html" xml:base="/EtherNotes/fpga/misc/cdc/"><![CDATA[<p>在FPGA设计中，跨时钟域处理是确保系统稳定性的关键。核心挑战在于<strong>亚稳态</strong>：当一个信号被与它不同步的时钟采样时，触发器的输出可能在一段时间内处于不确定状态。正确处理的核心原则是：<strong>单bit信号</strong>通常采用同步器，而<strong>多bit信号</strong>则必须采用更复杂的机制来保证所有数据位的同时性。</p>

<h3 id="单bit信号跨时钟域处理">单bit信号跨时钟域处理</h3>

<p>单bit信号处理相对简单，但需根据信号类型（电平/脉冲）和时钟频率关系选择不同方法。</p>

<ul>
  <li><strong>电平信号（如使能、复位）</strong>：最常用 <strong>“两级触发器同步器” (2-FF Synchronizer)</strong>。通过两个级联的触发器将信号同步到目标时钟域。其Verilog实现如下：
    <div class="language-verilog highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">reg</span> <span class="n">sync_ff1</span><span class="p">,</span> <span class="n">sync_ff2</span><span class="p">;</span>
<span class="k">always</span> <span class="o">@</span><span class="p">(</span><span class="kt">posedge</span> <span class="n">dst_clk</span><span class="p">)</span> <span class="k">begin</span>
    <span class="n">sync_ff1</span> <span class="o">&lt;=</span> <span class="n">async_sig</span><span class="p">;</span> <span class="c1">// 第一级，可能产生亚稳态</span>
    <span class="n">sync_ff2</span> <span class="o">&lt;=</span> <span class="n">sync_ff1</span><span class="p">;</span> <span class="c1">// 第二级，输出稳定值</span>
<span class="k">end</span>
<span class="k">assign</span> <span class="n">synced_sig</span> <span class="o">=</span> <span class="n">sync_ff2</span><span class="p">;</span>
</code></pre></div>    </div>
    <p>为确保可靠性，需注意：</p>
    <ul>
      <li><strong>时序约束</strong>：在Xilinx Vivado中，应给同步寄存器添加 <code class="language-plaintext highlighter-rouge">(* ASYNC_REG = "TRUE" *)</code> 属性，或使用 <code class="language-plaintext highlighter-rouge">xpm_cdc_single</code> 原语，强制工具将它们放入同一个SLICE，以减小布线延迟。</li>
      <li><strong>同步级数</strong>：两级足以将亚稳态MTBF（平均无故障时间）降至极低。在极端环境（如太空）中，可考虑使用三级。</li>
    </ul>
  </li>
  <li><strong>脉冲信号</strong>：处理<strong>单周期脉冲</strong>需额外注意，否则可能因脉冲宽度太窄而被目标时钟域漏采。
    <ul>
      <li><strong>快时钟域 → 慢时钟域</strong>：常用 <strong>“脉冲展宽法”</strong> 。先在快时钟域将脉冲展宽（如用计数器），使其宽度大于一个慢时钟周期，再送入两级同步器。同步后，在慢时钟域恢复边沿。</li>
      <li><strong>任意频率关系</strong>：可使用 <strong>“Toggle同步器”</strong> 。在源时钟域，每个脉冲使一个信号翻转（Toggle）；目标时钟域用两级同步器同步此翻转信号，并通过边沿检测恢复脉冲。这种方法可靠，但需注意脉冲间隔需足够长。</li>
    </ul>
  </li>
</ul>

<h3 id="多bit信号跨时钟域处理">多bit信号跨时钟域处理</h3>

<p>多bit信号（如数据总线）不能简单打拍，因为不同bit的走线延迟不同，可能导致目的时钟域采样到错误的“中间值”。以下是几种可靠方案：</p>

<ul>
  <li>
    <p><strong>异步FIFO</strong>：这是处理多bit数据流最通用、最推荐的方法。它使用双端口RAM，在写时钟域存入数据，在读时钟域读出。其核心是<strong>格雷码（Gray Code）</strong> 指针，因格雷码每次递增仅1bit变化，保证了跨时钟域同步指针时的安全性。实际工程中，强烈建议直接使用厂商提供的FIFO IP核。</p>
  </li>
  <li><strong>握手协议 (Handshake)</strong>：适用于<strong>数据量小、速率低</strong>的场景。通过“请求-应答”信号确保数据稳定传输：
    <ol>
      <li>源域将数据准备好并保持稳定，然后拉高请求信号(<code class="language-plaintext highlighter-rouge">req</code>)。</li>
      <li>目的域同步<code class="language-plaintext highlighter-rouge">req</code>后，锁存数据，并拉高应答信号(<code class="language-plaintext highlighter-rouge">ack</code>)。</li>
      <li>源域同步<code class="language-plaintext highlighter-rouge">ack</code>后，撤销<code class="language-plaintext highlighter-rouge">req</code>，准备下一个数据。</li>
      <li>目的域同步到<code class="language-plaintext highlighter-rouge">req</code>撤销后，也撤销<code class="language-plaintext highlighter-rouge">ack</code>，完成一次握手。
<code class="language-plaintext highlighter-rouge">req</code>和<code class="language-plaintext highlighter-rouge">ack</code>本身是单bit控制信号，需用两级同步器处理。</li>
    </ol>
  </li>
  <li><strong>其它方法</strong>：
    <ul>
      <li><strong>MUX同步器</strong>：当数据带有一个有效的使能信号时，可将使能信号同步到目标时钟域，并用它来控制目标端寄存器对数据的采样。</li>
      <li><strong>异步双口RAM</strong>：原理与异步FIFO类似，常用于数据缓存。</li>
      <li><strong>格雷码</strong>：仅适用于传输连续变化、有编码规律的值（如计数器）。</li>
    </ul>
  </li>
</ul>

<h3 id="总结与建议">总结与建议</h3>

<table>
  <thead>
    <tr>
      <th style="text-align: left">信号类型</th>
      <th style="text-align: left">推荐方法</th>
      <th style="text-align: left">关键点</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>单bit 电平</strong></td>
      <td style="text-align: left">两级/三级触发器同步器</td>
      <td style="text-align: left">添加 <code class="language-plaintext highlighter-rouge">ASYNC_REG</code> 约束或使用 <code class="language-plaintext highlighter-rouge">xpm_cdc_single</code></td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>单bit 脉冲</strong></td>
      <td style="text-align: left">脉冲展宽法 / Toggle同步器</td>
      <td style="text-align: left">确保脉冲宽度能被目标时钟采样到</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>多bit 数据流</strong></td>
      <td style="text-align: left"><strong>异步FIFO (IP核)</strong></td>
      <td style="text-align: left">最可靠、最通用，强烈推荐</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>多bit 控制/配置</strong></td>
      <td style="text-align: left">握手协议</td>
      <td style="text-align: left">适合小数据量、低带宽场景</td>
    </tr>
  </tbody>
</table>

<p>简而言之，处理跨时钟域问题时，根据信号类型和场景选择合适的方法，并辅以正确的代码风格和时序约束，是确保设计可靠性的关键。</p>]]></content><author><name>Ether</name></author><category term="fpga" /><category term="misc" /><summary type="html"><![CDATA[在FPGA设计中，跨时钟域处理是确保系统稳定性的关键。核心挑战在于亚稳态：当一个信号被与它不同步的时钟采样时，触发器的输出可能在一段时间内处于不确定状态。正确处理的核心原则是：单bit信号通常采用同步器，而多bit信号则必须采用更复杂的机制来保证所有数据位的同时性。]]></summary></entry></feed>