<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>FelixTensor</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://felixtensor.github.io/</id>
  <link href="https://felixtensor.github.io/" rel="alternate"/>
  <link href="https://felixtensor.github.io/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, FelixTensor</rights>
  <subtitle>探索编程与科技的无限可能</subtitle>
  <title>FelixTensor 博客</title>
  <updated>2026-09-27T06:32:00.000Z</updated>
  <entry>
    <author>
      <name>FelixTensor</name>
    </author>
    <category term="技术" scheme="https://felixtensor.github.io/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="COSMIC" scheme="https://felixtensor.github.io/tags/COSMIC/"/>
    <category term="CachyOS" scheme="https://felixtensor.github.io/tags/CachyOS/"/>
    <category term="Arch Linux" scheme="https://felixtensor.github.io/tags/Arch-Linux/"/>
    <category term="gnome-keyring" scheme="https://felixtensor.github.io/tags/gnome-keyring/"/>
    <category term="PAM" scheme="https://felixtensor.github.io/tags/PAM/"/>
    <content>
      <![CDATA[<p>装好 CachyOS + COSMIC 之后，开机后第一次打开 Chrome，或者在设置里点开网络配置，都会弹框要求解锁 “Default keyring”，每次开机都得输一遍密码。</p><p>环境：CachyOS，COSMIC 1.9.0（cosmic-greeter 1:1.9.0-1.1），gnome-keyring 50.0。</p><h2 id="原因"><a href="#原因" class="headerlink" title="原因"></a>原因</h2><p>查下来是两个问题：</p><ol><li>cosmic-greeter 跑在 greetd 上，Arch 的包在 <code>/etc/greetd/cosmic-greeter.toml</code> 里配的 PAM 服务是 <code>login</code>，所以登录走的是 <code>/etc/pam.d/login</code>，而不是 <code>/etc/pam.d/greetd</code>。这个文件里没有 <code>pam_gnome_keyring.so</code>，登录密码就没有交给 gnome-keyring。</li><li>gnome-keyring 登录时只会自动解锁叫 <code>login</code> 的钥匙环（<code>~/.local/share/keyrings/login.keyring</code>），而现有的钥匙环叫 <code>Default_Keyring</code>。</li></ol><h2 id="解决"><a href="#解决" class="headerlink" title="解决"></a>解决</h2><p>顺序是先改名、再改 PAM。锁屏也走 <code>login</code> 服务，如果先改了 PAM，中途锁一次屏，就会自动生成一个空的 <code>login.keyring</code>，和要改名的文件撞车。</p><p>另外，钥匙环密码得和登录密码一样才能自动解锁，不一样的话要先用 Seahorse 改过来。</p><h3 id="1-备份并改名"><a href="#1-备份并改名" class="headerlink" title="1. 备份并改名"></a>1. 备份并改名</h3><p>改名前先关掉 Chrome、系统设置这些会用到钥匙环的程序：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cp</span> -a ~/.local/share/keyrings ~/keyrings-backup</span><br><span class="line"></span><br><span class="line">systemctl --user stop gnome-keyring-daemon.socket gnome-keyring-daemon.service</span><br><span class="line">pgrep -a gnome-keyring   <span class="comment"># 确认没有输出</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">mv</span> -n ~/.local/share/keyrings/Default_Keyring.keyring ~/.local/share/keyrings/login.keyring</span><br><span class="line"><span class="built_in">echo</span> login &gt; ~/.local/share/keyrings/default</span><br></pre></td></tr></tbody></table></figure><p>改名不会丢数据。不建议直接删掉钥匙环重来，Chrome 保存的密码和 Cookie 都要靠里面的密钥解密。</p><h3 id="2-修改-PAM"><a href="#2-修改-PAM" class="headerlink" title="2. 修改 PAM"></a>2. 修改 PAM</h3><p>在 <code>/etc/pam.d/login</code> 里加两行，改完是这样的：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">#%PAM-1.0</span><br><span class="line"></span><br><span class="line">auth       requisite    pam_nologin.so</span><br><span class="line">auth       include      system-local-login</span><br><span class="line">auth       optional     pam_gnome_keyring.so</span><br><span class="line">account    include      system-local-login</span><br><span class="line">session    include      system-local-login</span><br><span class="line">session    optional     pam_gnome_keyring.so auto_start</span><br><span class="line">password   include      system-local-login</span><br></pre></td></tr></tbody></table></figure><h3 id="3-重启验证"><a href="#3-重启验证" class="headerlink" title="3. 重启验证"></a>3. 重启验证</h3><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">busctl --user get-property org.freedesktop.secrets \</span><br><span class="line">  /org/freedesktop/secrets/collection/login \</span><br><span class="line">  org.freedesktop.Secret.Collection Locked</span><br></pre></td></tr></tbody></table></figure><p>重启后输出是 <code>b false</code>，说明登录时已经自动解锁了，再打开 Chrome 也不弹窗了。</p><h2 id="PS"><a href="#PS" class="headerlink" title="PS"></a>PS</h2><ul><li><p><strong>为什么改 <code>login</code> 而不是新建一个 PAM 文件。</strong> COSMIC 上游其实在 <a href="https://github.com/pop-os/cosmic-greeter/commit/ef1253aa2393666bb2a0b0132b722f51ec7ebbf3">ef1253a</a> 里处理过这个问题，给 greeter 和锁屏单独建了一个 <code>cosmic-greeter</code> PAM 服务，里面配好了 keyring。但 Arch 的 <a href="https://gitlab.archlinux.org/archlinux/packaging/packages/cosmic-greeter/-/blob/main/PKGBUILD">PKGBUILD</a> 目前把这个提交 revert 掉了（<code>revert pam config for now</code>），所以才走 <code>login</code>。上面加的两行就是照抄上游的。没有另建 <code>/etc/pam.d/cosmic-greeter</code>，是因为以后 Arch 恢复上游做法时，包里会带同名文件，pacman 会报文件冲突。</p></li><li><p><strong>升级会不会失效。</strong> <code>/etc/pam.d/login</code> 属于 util-linux，升级时不会被覆盖，只会生成 <code>.pacnew</code>。如果以后 Arch 取消了 revert，greeter 会改用包里自带的 <code>/etc/pam.d/cosmic-greeter</code>，上游版本本身就带 keyring 配置，<code>login</code> 里这两行留着也没影响。哪天升级后要是又开始弹窗，多半是 <code>cosmic-greeter.toml</code> 里的 <code>service</code> 变了。</p></li><li><p><strong>重启后的日志</strong>（<code>journalctl -b | grep gkr-pam</code>）：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">greetd[858]: gkr-pam: couldn't unlock the login keyring.</span><br><span class="line">greetd[988]: gkr-pam: unable to locate daemon control file</span><br><span class="line">greetd[988]: gkr-pam: stashed password to try later in open session</span><br><span class="line">greetd[988]: gkr-pam: unlocked login keyring</span><br></pre></td></tr></tbody></table></figure><p>第一行是登录界面自己的会话（<code>cosmic-greeter</code> 用户，没有密码），可以忽略。后面三行是用户登录的过程：认证时用户会话还没建立，找不到守护进程，模块先把密码存起来，等会话建好后再解锁。如果看到的是 <code>the password for the login keyring was invalid</code>，说明钥匙环密码和登录密码不一致。</p></li><li><p>以后改登录密码时，钥匙环密码也得跟着改，不然又会弹窗。备份留在 <code>~/keyrings-backup</code>，想回退的话，删掉那两行再把它拷回去就行。</p></li></ul>]]>
    </content>
    <id>https://felixtensor.github.io/2026/09/27/cosmic-de-post-install-01-keyring-pam/</id>
    <link href="https://felixtensor.github.io/2026/09/27/cosmic-de-post-install-01-keyring-pam/"/>
    <published>2026-09-27T06:32:00.000Z</published>
    <summary>
      <![CDATA[<p>装好 CachyOS + COSMIC 之后，开机后第一次打开 Chrome，或者在设置里点开网络配置，都会弹框要求解锁 “Default keyring”，每次开机都得输一遍密码。</p>
<p>环境：CachyOS，COSMIC 1.9.0（cosmic-greeter]]>
    </summary>
    <title>COSMIC DE 装机后配置（一）：登录时自动解锁 GNOME Keyring</title>
    <updated>2026-09-27T06:32:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>FelixTensor</name>
    </author>
    <category term="技术" scheme="https://felixtensor.github.io/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="Git" scheme="https://felixtensor.github.io/tags/Git/"/>
    <category term="代理" scheme="https://felixtensor.github.io/tags/%E4%BB%A3%E7%90%86/"/>
    <content>
      <![CDATA[<p>做技术的难免需要经常从github或者gitlab等等国外的代码托管平台上拉取和下载远程的仓库内容，但由于github配置的托管仓库的服务器地址在国内速度堪称龟速，不是小水龙头般的下载速度就是出现<code>unable to access '...' Couldn't resolve host '...'</code>等错误，为此，git提供了相关代理的功能：</p><h2 id="举个栗子"><a href="#举个栗子" class="headerlink" title="举个栗子"></a>举个栗子</h2><p>当使用https方式，如：</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">$ git <span class="built_in">clone</span> https://github.com/kubernetes/kubernetes.git</span><br></pre></td></tr></tbody></table></figure><p>使用ssh+git方式：</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">$ git <span class="built_in">clone</span> git@github.com:kubernetes/kubernetes.git</span><br></pre></td></tr></tbody></table></figure><p>根据自身习惯可以选择配置针对其中一种的代理方式，也可以两者皆可，但要注意，当使用config等非临时的写入配置文件的代理配置方式，这是全局的配置方式；需要更多用于控制代理与个性化的参数请自行查阅官方git文档。</p><h2 id="Git-ssh-git"><a href="#Git-ssh-git" class="headerlink" title="Git ssh+git"></a>Git ssh+git</h2><h3 id="ssh代理设置"><a href="#ssh代理设置" class="headerlink" title="ssh代理设置"></a>ssh代理设置</h3><p><strong>注意</strong>：实际上，当我们使用ssh+git方式的时候，我们完全不必在<code>~/.gitconfig</code>中配置任何代理地址或者运行<code>git config --global http.proxy ...</code>与其他相似的命令。</p><p>在<code>~/.ssh/config</code>中配置<code>ProxyCommand</code>，git在拉取的时候就会开始使用此代理。</p><h4 id="socks代理"><a href="#socks代理" class="headerlink" title="socks代理"></a>socks代理</h4><p>当代理服务端开启<code>socks</code>服务时，我们可以使用<code>nc</code>命令，使用<code>ssh</code>的<code>ProxyCommand</code>配合<code>nc</code>可以让ssh通过设置的代理访问服务器</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">$ vim ~/.ssh/config</span><br></pre></td></tr></tbody></table></figure><p>然后写入一些命令：</p><figure class="highlight text"><table><tbody><tr><td class="code"><pre><span class="line">Host *</span><br><span class="line">ProxyCommand nc -X 5 -x 127.0.0.1:1080 %h %p</span><br></pre></td></tr></tbody></table></figure><p><code>nc</code>命令上述涉及的参数：<strong>-X</strong>是指定协议参数：4即为socks4协议，5即为socks5协议</p><p><strong>-x</strong>是指定代理服务器及端口 [代理服务器:端口]</p><h4 id="非socks代理"><a href="#非socks代理" class="headerlink" title="非socks代理"></a>非socks代理</h4><p>没有<code>socks</code>代理的时候，可以使用<code>corkscrew</code>让<code>ssh</code>使用<code>HTTP</code>代理，<code>Corkscrew</code>是一个HTTP隧道代理ssh的工具</p><p>如果你的系统没有安装<code>corkscrew</code>，你可以通过以下方式安装：</p><p>对于mac用户：</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">$ brew install corkscrew</span><br></pre></td></tr></tbody></table></figure><p>对于linux用户：</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">$ sudo apt-get install corkscrew</span><br></pre></td></tr></tbody></table></figure><p>对于windows用户有更方便的管理ssh的工具，如<code>PuTTY</code>等，都可对ssh直接设置代理, 当然mac端也有<code>termius</code>这样的图形界面工具，根据自身喜好决定配置思路</p><p>使用查询路径的命令查询corkscrew的执行文件路径如：</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">$ <span class="built_in">which</span> corkscrew</span><br></pre></td></tr></tbody></table></figure><p>假设此处查到的路径为<code>%corkscrew_path%</code>，向<code>~/.ssh/config</code>文件中添加如下内容：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Host github.com</span><br><span class="line">Hostname github.com</span><br><span class="line">ServerAliveInterval 55</span><br><span class="line">ForwardAgent yes</span><br><span class="line">ProxyCommand %corkscrew_path% 127.0.0.1 7580 %h %p</span><br></pre></td></tr></tbody></table></figure><p><code>127.0.0.1:7580</code>即代理服务端地址及端口，其中可以只添加最后一句，前面的语句都是用于控制一些参数和作用域名，关于如何详细配置git的参数可参阅：<a href="https://git-scm.com/docs/git-config">git-config</a></p><h2 id="Git-http"><a href="#Git-http" class="headerlink" title="Git http"></a>Git http</h2><h3 id="HTTP代理配置"><a href="#HTTP代理配置" class="headerlink" title="HTTP代理配置"></a>HTTP代理配置</h3><p>使用<code>~/.gitconfig</code>文件全局的配置代理，使用<code>--global</code>参数切换，或者在<code>.git/config</code>文件中设置对某个仓库的局部代理</p><h4 id="设置一个全局代理"><a href="#设置一个全局代理" class="headerlink" title="设置一个全局代理"></a>设置一个全局代理</h4><p>配置一个全局代理当你想使用它访问所有的远程仓库：</p><p>配置HTTP代理：</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">$ git config --global http.proxy http://proxyUsername:proxyPassword@proxy.server.com:port</span><br></pre></td></tr></tbody></table></figure><p>配置HTTPS代理：Git并没有<code>https.proxy</code>这个配置项，上面的<code>http.proxy</code>对<code>http://</code>和<code>https://</code>开头的远程仓库都会生效，无需另外配置。</p><p>配置socks5代理：</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">$ git config --global http.proxy socks5://proxyUsername:proxyPassword@proxy.server.com:port</span><br></pre></td></tr></tbody></table></figure><p>用户名和密码为缺省状态：</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">$ git config --global http.proxy http://proxy.server.com:port</span><br><span class="line">$ git config --global http.proxy socks5://proxy.server.com:port</span><br></pre></td></tr></tbody></table></figure><p><strong>注意</strong>：<code>git config</code>配置的代理会写入配置文件（<code>--global</code>即写入<code>~/.gitconfig</code>），效果与直接修改配置文件相同，是永久生效的，关闭终端也不会失效，需要用后文的<code>--unset</code>取消；配置后对之后执行的git命令立即生效，无需重启。如果只想临时使用代理，可以用<code>git -c http.proxy=... clone ...</code>只对单条命令生效。</p><h4 id="配置针对特定URL代理"><a href="#配置针对特定URL代理" class="headerlink" title="配置针对特定URL代理"></a>配置针对特定URL代理</h4><p>当希望能够指定代理应该用于那些被<code>http.&lt;url&gt;.key</code>所指定的URL：</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">$ git config --global http.https://domain.com.proxy http://proxyUsername:proxyPassword@proxy.server.com:port</span><br></pre></td></tr></tbody></table></figure><p>其中<code>https://domain.com</code>是要匹配的远程仓库URL，<code>https://</code>开头的仓库同样使用<code>http.</code>开头的配置项，不存在<code>https.&lt;url&gt;.proxy</code>这种写法。</p><p>同理，同样的命令在<code>~/.gitconfig</code>文件中可以添加为：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[http]</span><br><span class="line">[http "https://domain.com"]</span><br><span class="line">proxy = http://proxyUsername:proxyPassword@proxy.server.com:port</span><br></pre></td></tr></tbody></table></figure><h4 id="处理后续SSL报错"><a href="#处理后续SSL报错" class="headerlink" title="处理后续SSL报错"></a>处理后续SSL报错</h4><p>如果在配置代理之后还是会遇到<code>unable to access 'https://...': Unknown SSL protocol error in connection to ...:443</code>，或许你可以使用<code>-c http.sslVerify=false</code>参数来手动关闭SSL验证，譬如：</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">$ git -c http.sslVerify=<span class="literal">false</span> <span class="built_in">clone</span> https://domain.com/path/to/git</span><br></pre></td></tr></tbody></table></figure><p><strong>注意</strong>：<code>http.sslVerify=false</code>会关闭TLS证书校验，此时无法识别中间人攻击，建议只作为临时排查手段，并尽量只对单个仓库或特定URL关闭，用完及时恢复。</p><p>可以选择在拉取下来的项目中配置<code>.git/config</code>,在该项目文件夹打开终端：</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">$ git config http.sslVerify <span class="literal">false</span></span><br></pre></td></tr></tbody></table></figure><p>当仅仅想针对特定URL使用<code>http.&lt;url&gt;.sslVerify</code>设置而其他仍然使用全局配置时：</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">$ git config --global http.https://domain.com.proxy http://proxyUsername:proxyPassword@proxy.server.com:port</span><br><span class="line">$ git config --global http.https://domain.com.sslVerify <span class="literal">false</span></span><br></pre></td></tr></tbody></table></figure><p>在<code>~/.gitconfig</code>文件根据同样的要求配置的结果：</p><figure class="highlight text"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">[http]</span><br><span class="line">[http "https://domain.com"]</span><br><span class="line">proxy = http://proxyUsername:proxyPassword@proxy.server.com:port</span><br><span class="line">sslVerify = false</span><br></pre></td></tr></tbody></table></figure><h3 id="显示当前设置"><a href="#显示当前设置" class="headerlink" title="显示当前设置"></a>显示当前设置</h3><p>显示当前所有<code>http</code>部分当前配置：</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">$ git config --global --get-regexp <span class="string">'http.*'</span></span><br></pre></td></tr></tbody></table></figure><p>Git没有<code>https</code>部分的配置，如果之前误设过<code>https.proxy</code>之类的配置项（它们不会生效），可以这样检查并删除：</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">$ git config --global --get-regexp <span class="string">'^https\.'</span></span><br><span class="line">$ git config --global --<span class="built_in">unset</span> https.proxy</span><br></pre></td></tr></tbody></table></figure><p>如果远程项目已经被拉取到本地了，进入项目目录，丢弃<code>--global</code>参数同样能查询当前所有配置</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">$ git config --get-regexp <span class="string">'http.*'</span></span><br></pre></td></tr></tbody></table></figure><h3 id="取消代理或SSL验证的配置"><a href="#取消代理或SSL验证的配置" class="headerlink" title="取消代理或SSL验证的配置"></a>取消代理或SSL验证的配置</h3><p>在终端中使用<code>git config</code>配置的代理会写入配置文件，关闭终端后依然有效，不再需要时要手动取消。</p><p>使用<code>--unset</code>参数来取消这些特定的代理配置，比如<code>http.proxy</code>或<code>http.&lt;url&gt;.proxy</code>代理移除：</p><figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ git config --global --<span class="built_in">unset</span> http.proxy</span><br><span class="line">$ git config --global --<span class="built_in">unset</span> http.https://domain.com.proxy</span><br><span class="line"></span><br><span class="line">$ git config --global --<span class="built_in">unset</span> http.sslVerify</span><br><span class="line">$ git config --global --<span class="built_in">unset</span> http.https://domain.com.sslVerify</span><br></pre></td></tr></tbody></table></figure><p>查看配置信息：</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">$ git config -l --global</span><br></pre></td></tr></tbody></table></figure><p>执行查看代理：</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">$ git config -l</span><br></pre></td></tr></tbody></table></figure>]]>
    </content>
    <id>https://felixtensor.github.io/2022/06/22/Git%20%E9%85%8D%E7%BD%AE%E4%BB%A3%E7%90%86/</id>
    <link href="https://felixtensor.github.io/2022/06/22/Git%20%E9%85%8D%E7%BD%AE%E4%BB%A3%E7%90%86/"/>
    <published>2022-06-22T04:01:00.000Z</published>
    <summary>为 Git 的 HTTPS 和 SSH 两种访问方式配置代理：SSH 通过 ProxyCommand 走 SOCKS 或 HTTP 代理，HTTPS 通过 http.proxy 做全局或按 URL 的配置，以及 SSL 报错的处理和代理配置的查看与取消。</summary>
    <title>Git 配置代理</title>
    <updated>2026-09-27T11:35:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>FelixTensor</name>
    </author>
    <category term="技术" scheme="https://felixtensor.github.io/categories/%E6%8A%80%E6%9C%AF/"/>
    <category term="HDLBits" scheme="https://felixtensor.github.io/tags/HDLBits/"/>
    <category term="Verilog" scheme="https://felixtensor.github.io/tags/Verilog/"/>
    <content>
      <![CDATA[<blockquote><p><strong>关于这篇文章：</strong> 这篇文章最初收录了我做过的全部 HDLBits 题解。182 道题放在同一个 Markdown 文件中，查找和维护都很麻烦，因此现在已经将代码单独整理到：</p><p><strong><a href="https://github.com/felixtensor/hdlbits-solutions">felixtensor/hdlbits-solutions</a></strong></p><p>仓库按 HDLBits 的题目目录组织，每道题对应一个文件。这篇文章则保留一些入门说明，以及做题时比较容易遇到的问题。</p><p><strong>建议先独立完成题目，遇到问题时根据波形定位；确实无法解决，再将仓库中的代码作为思路参考。直接复制答案可以通过测试，但对理解 Verilog 没有什么帮助。</strong></p></blockquote><p>这篇文章主要面向刚开始在 HDLBits 上练习 Verilog 的读者。如果你刚接触 Verilog，建议按顺序阅读“先建立硬件的概念”“解题流程”和“常见问题”；如果已经做到状态机或读取仿真等相关题目，可以直接跳到对应小节。</p><span id="more"></span><h2 id="HDLBits-是什么"><a href="#HDLBits-是什么" class="headerlink" title="HDLBits 是什么"></a>HDLBits 是什么</h2><p><a href="https://hdlbits.01xz.net/">HDLBits</a> 是一个用于练习 Verilog 的在线平台。题目提供固定的模块接口，提交代码后，平台会进行编译和仿真，并将输出与参考设计比较。整个过程不需要配置本地环境，打开网页即可开始。</p><p>题目从简单的导线连接开始，之后依次涉及向量、组合逻辑、触发器、计数器、有限状态机和较完整的数据通路。它不能代替数字电路教材，但很适合用来检验自己是否真正理解了某个知识点。代码能够看懂，与代码能够正确运行，通常还是两回事。</p><h2 id="先建立硬件的概念"><a href="#先建立硬件的概念" class="headerlink" title="先建立硬件的概念"></a>先建立硬件的概念</h2><p>Verilog 的语法与 C 有不少相似之处，但不能按普通程序的方式理解它。程序通常强调语句的执行顺序，而 Verilog 描述的是电路结构和信号随时间的变化。</p><p>例如，多条彼此独立的 <code>assign</code> 语句表示同时存在的组合逻辑，不是依次执行的几个步骤。它们在文件中的顺序一般不会改变电路。不过，在同一个过程块中，阻塞赋值的先后关系仍然可能影响结果；仿真时也存在事件调度。因此，“硬件是并行的”是基本认识，并不意味着所有代码顺序都可以忽略。</p><p><code>reg</code> 这个名字也容易引起误解。它只表示信号可以在 <code>always</code> 等过程块中被赋值，并不说明最终一定会得到寄存器：</p><ul><li>在完整赋值的 <code>always @(*)</code> 中，它可以表示组合逻辑；</li><li>在 <code>always @(posedge clk)</code> 中，它通常表示触发器；</li><li>在组合块中漏掉赋值路径时，它可能使综合工具推导出锁存器。</li></ul><p>遇到难以判断的语法时，可以先考虑它对应什么电路、是否需要保存旧值，以及输出应当在什么时刻变化。</p><h2 id="解题流程"><a href="#解题流程" class="headerlink" title="解题流程"></a>解题流程</h2><p>一般可以按下面的顺序处理题目：</p><figure class="highlight text"><table><tbody><tr><td class="code"><pre><span class="line">理解题意 → 画出逻辑关系、真值表或状态图 → 编写代码 → 编译 → 仿真</span><br></pre></td></tr></tbody></table></figure><p>提交后常见的结果包括：</p><ul><li><strong>Compile Error</strong>：代码存在语法、端口或类型问题；</li><li><strong>Simulation Error</strong>：代码通过编译，但仿真过程出现错误；</li><li><strong>Incorrect</strong>：仿真能够运行，但输出与参考设计不一致；</li><li><strong>Success</strong>：通过测试。</li></ul><p>比较值得花时间的是动手之前的分析。先确定组合逻辑还是时序逻辑、复位方式、信号位宽，以及输出属于当前周期还是下一个周期。状态较多时先画状态转移图，通常比直接写 <code>case</code> 更省时间。</p><h2 id="推荐学习路径"><a href="#推荐学习路径" class="headerlink" title="推荐学习路径"></a>推荐学习路径</h2><p>HDLBits 按主题组织题目，同一主题内的难度大致递增。Verilog 语言题与电路题可以交叉练习：学习一种语法后，再用电路题确认自己是否理解了它对应的硬件。下面列出与本文章节对应、比较能体现某个知识点的题目，作为参考：</p><div class="md-table-scroll"><table><thead><tr><th>知识点</th><th>推荐题目</th><th>说明</th></tr></thead><tbody><tr><td>导线与向量</td><td><a href="https://hdlbits.01xz.net/wiki/Step_one">Step one</a> / <a href="https://hdlbits.01xz.net/wiki/Vector0">Vectors</a></td><td>熟悉模块接口和位宽</td></tr><tr><td>组合逻辑与锁存器</td><td><a href="https://hdlbits.01xz.net/wiki/Always_nolatches">Always_nolatches</a></td><td>练习默认值避免锁存器</td></tr><tr><td>阻塞与非阻塞赋值</td><td><a href="https://hdlbits.01xz.net/wiki/Alwaysblock2">Alwaysblock2</a></td><td>对比连续赋值、组合块和时钟块</td></tr><tr><td>位宽与拼接</td><td><a href="https://hdlbits.01xz.net/wiki/Vector100r">Vector100r</a> / <a href="https://hdlbits.01xz.net/wiki/Adder100i">Adder100i</a></td><td>练习拼接、循环和 generate</td></tr><tr><td>计数器与分频</td><td><a href="https://hdlbits.01xz.net/wiki/Count1to10">Count1to10</a> / <a href="https://hdlbits.01xz.net/wiki/Count_clock">Count_clock</a></td><td>理解时钟使能和复位</td></tr><tr><td>有限状态机</td><td><a href="https://hdlbits.01xz.net/wiki/Exams/review2015_fsm">Exams/review2015_fsm</a></td><td>三段式拆分与 Moore/Mealy</td></tr><tr><td>读取仿真波形</td><td><a href="https://github.com/felixtensor/hdlbits-solutions/tree/main/4-verification-reading-sims">Verification: Reading Simulations</a></td><td>10 道还原电路 + 5 道找 bug</td></tr><tr><td>编写 testbench</td><td><a href="https://github.com/felixtensor/hdlbits-solutions/tree/main/5-verification-writing-testbenches">Verification: Writing Testbenches</a></td><td>5 道 testbench 题</td></tr></tbody></table></div><p>不需要每道题都做完再看下一节，但遇到对应知识点卡壳时，可以优先挑表中的题目集中练习。</p><h2 id="常见问题"><a href="#常见问题" class="headerlink" title="常见问题"></a>常见问题</h2><h3 id="1-组合逻辑与时序逻辑"><a href="#1-组合逻辑与时序逻辑" class="headerlink" title="1. 组合逻辑与时序逻辑"></a>1. 组合逻辑与时序逻辑</h3><p><code>always @(*)</code> 用于描述组合逻辑，输出由当前输入决定；<code>always @(posedge clk)</code> 用于描述时序逻辑，输出在时钟边沿更新并保存状态。二者描述的是不同的硬件。</p><p>题目中出现 <code>clk</code>，说明模块通常包含时序逻辑，但不代表所有逻辑都应写进时钟块。例如状态机的状态寄存器属于时序逻辑，次态计算一般仍然属于组合逻辑。</p><h3 id="2-阻塞赋值与非阻塞赋值"><a href="#2-阻塞赋值与非阻塞赋值" class="headerlink" title="2. 阻塞赋值与非阻塞赋值"></a>2. 阻塞赋值与非阻塞赋值</h3><p>入门阶段可以先遵循下面的规则：</p><ul><li>组合块 <code>always @(*)</code> 使用阻塞赋值 <code>=</code>；</li><li>时钟块 <code>always @(posedge clk)</code> 使用非阻塞赋值 <code>&lt;=</code>。</li></ul><p>例如：</p><figure class="highlight verilog"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">always</span> @(<span class="keyword">posedge</span> clk) <span class="keyword">begin</span></span><br><span class="line">    q1 &lt;= d;</span><br><span class="line">    q2 &lt;= q1;</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></tbody></table></figure><p>在同一个时钟沿，<code>q1</code> 和 <code>q2</code> 都根据更新前的值进行计算，因此这段代码表示两级寄存器。若在时钟块中随意混入阻塞赋值，仿真结果可能受到语句顺序影响。</p><h3 id="3-意外生成锁存器"><a href="#3-意外生成锁存器" class="headerlink" title="3. 意外生成锁存器"></a>3. 意外生成锁存器</h3><p>组合 <code>always</code> 块需要在所有执行路径上为输出赋值。如果某条路径没有赋值，输出便需要保留原值，综合工具可能据此生成锁存器。这类问题不一定会成为编译错误。</p><p>常见的处理方法是在块开头设置默认值，之后再覆盖特殊情况：</p><figure class="highlight verilog"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">always</span> @(*) <span class="keyword">begin</span></span><br><span class="line">    up    = <span class="number">1'b0</span>;</span><br><span class="line">    down  = <span class="number">1'b0</span>;</span><br><span class="line">    left  = <span class="number">1'b0</span>;</span><br><span class="line">    right = <span class="number">1'b0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">case</span> (scancode)</span><br><span class="line">        <span class="number">16'he06b</span>: left  = <span class="number">1'b1</span>;</span><br><span class="line">        <span class="number">16'he072</span>: down  = <span class="number">1'b1</span>;</span><br><span class="line">        <span class="number">16'he074</span>: right = <span class="number">1'b1</span>;</span><br><span class="line">        <span class="number">16'he075</span>: up    = <span class="number">1'b1</span>;</span><br><span class="line">    <span class="keyword">endcase</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></tbody></table></figure><p>这里即使 <code>case</code> 没有匹配，四个输出也已经有确定值。对应的 HDLBits 题目是 <a href="https://hdlbits.01xz.net/wiki/Always_nolatches"><code>Always_nolatches</code></a>。另一种方式是完整处理所有 <code>if</code> / <code>else</code> 和 <code>case</code> / <code>default</code> 分支。</p><h3 id="4-wire、reg、assign-与-always"><a href="#4-wire、reg、assign-与-always" class="headerlink" title="4. wire、reg、assign 与 always"></a>4. <code>wire</code>、<code>reg</code>、<code>assign</code> 与 <code>always</code></h3><p>在传统 Verilog 中，由 <code>assign</code> 驱动的信号使用 <code>wire</code>；在 <code>always</code> 或 <code>initial</code> 中被赋值的信号使用 <code>reg</code>。<code>reg</code> 是否对应实际寄存器，仍然取决于过程块的写法。</p><p>使用 SystemVerilog 时，许多单驱动信号可以声明为 <code>logic</code>。HDLBits 的题目模板同时可以见到传统 Verilog 和少量 SystemVerilog 写法，按题目给定的接口完成即可。</p><p>本地编写 Verilog 时，还可以在文件开头加入：</p><figure class="highlight verilog"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">`<span class="keyword">default_nettype</span> none</span></span><br></pre></td></tr></tbody></table></figure><p>这样，拼错的信号名会直接报错，而不是自动成为隐式连线。如果源码会与其他文件一起编译，可以在文件末尾使用 <code>`default_nettype wire</code> 恢复默认设置。</p><h3 id="5-位宽"><a href="#5-位宽" class="headerlink" title="5. 位宽"></a>5. 位宽</h3><p>Verilog 会根据表达式和目标信号的宽度进行截断或扩展。有些工具会给出警告，有些情况下则不会，因此不能只看代码是否通过编译。</p><p>例如，两个 100 位无符号数相加并保留进位时，可以先显式扩展操作数：</p><figure class="highlight verilog"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">wire</span> [<span class="number">100</span>:<span class="number">0</span>] sum_ext;</span><br><span class="line"><span class="keyword">assign</span> sum_ext = {<span class="number">1'b0</span>, a} + {<span class="number">1'b0</span>, b};</span><br></pre></td></tr></tbody></table></figure><p>符号扩展也可以明确写成 <code>{{24{in[7]}}, in}</code>。处理 <code>[N-1:0]</code>、无尺寸常量和有符号运算时，应当确认中间表达式的实际宽度，而不只是输出信号的宽度。</p><h2 id="常用写法"><a href="#常用写法" class="headerlink" title="常用写法"></a>常用写法</h2><p>下面几种写法在 HDLBits 中经常出现。对每种写法，先掌握它的硬件含义，再考虑是否用它来简化代码。</p><h3 id="拼接与循环移位"><a href="#拼接与循环移位" class="headerlink" title="拼接与循环移位"></a>拼接与循环移位</h3><p><code>{a, b, c}</code> 用于组合或重新排列信号。例如 100 位循环右移：</p><figure class="highlight verilog"><table><tbody><tr><td class="code"><pre><span class="line">q &lt;= {q[<span class="number">0</span>], q[<span class="number">99</span>:<span class="number">1</span>]};</span><br></pre></td></tr></tbody></table></figure><p>对应仓库中的 <a href="https://github.com/felixtensor/hdlbits-solutions/blob/main/3-circuits/sequential/shift-registers/Rotate100.v"><code>Rotate100.v</code></a>。</p><h3 id="重复"><a href="#重复" class="headerlink" title="重复"></a>重复</h3><p><code>{N{x}}</code> 将某个信号重复 N 次，常用于符号扩展和掩码：</p><figure class="highlight verilog"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">wire</span> [<span class="number">31</span>:<span class="number">0</span>] a_ext = {{<span class="number">16</span>{a[<span class="number">15</span>]}}, a};  <span class="comment">// 16 位有符号数扩展到 32 位</span></span><br><span class="line"><span class="keyword">wire</span> [<span class="number">7</span>:<span class="number">0</span>]  mask  = {<span class="number">8</span>{enable}};       <span class="comment">// enable 为 1 时全 1，否则全 0</span></span><br></pre></td></tr></tbody></table></figure><h3 id="归约运算符"><a href="#归约运算符" class="headerlink" title="归约运算符"></a>归约运算符</h3><p><code>&amp;in</code>、<code>|in</code>、<code>^in</code> 对向量中的所有位进行 AND、OR 或 XOR。<code>^in</code> 常用于奇偶校验：</p><figure class="highlight verilog"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">assign</span> all_ones = &amp;in;      <span class="comment">// 所有位为 1 时输出 1</span></span><br><span class="line"><span class="keyword">assign</span> parity   = ^in;      <span class="comment">// 偶校验位：与输入拼接后，1 的总数为偶数</span></span><br></pre></td></tr></tbody></table></figure><h3 id="三目运算符"><a href="#三目运算符" class="headerlink" title="三目运算符"></a>三目运算符</h3><p><code>cond ? a : b</code> 对应一个 2 选 1 多路选择器：</p><figure class="highlight verilog"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">assign</span> out = sel ? a : b;</span><br></pre></td></tr></tbody></table></figure><h3 id="for-循环"><a href="#for-循环" class="headerlink" title="for 循环"></a><code>for</code> 循环</h3><p>循环边界在综合时可确定时，会展开为重复硬件。<a href="https://github.com/felixtensor/hdlbits-solutions/blob/main/2-verilog-language/more-features/Popcount255.v"><code>Popcount255.v</code></a> 使用循环统计 255 位输入中 1 的数量：</p><figure class="highlight verilog"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">integer</span> i;</span><br><span class="line"><span class="keyword">reg</span> [<span class="number">7</span>:<span class="number">0</span>] count;</span><br><span class="line"></span><br><span class="line"><span class="keyword">always</span> @(*) <span class="keyword">begin</span></span><br><span class="line">    count = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">0</span>; i &lt; <span class="number">255</span>; i = i + <span class="number">1</span>)</span><br><span class="line">        count = count + in[i];</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></tbody></table></figure><h3 id="generate-genvar"><a href="#generate-genvar" class="headerlink" title="generate / genvar"></a><code>generate</code> / <code>genvar</code></h3><p>用于重复实例化模块。<a href="https://github.com/felixtensor/hdlbits-solutions/blob/main/2-verilog-language/more-features/Adder100i.v"><code>Adder100i.v</code></a> 使用多个全加器构成 100 位加法器。假设已经定义 <code>full_adder</code> 模块，可以将第 0 位单独处理，再用前一位的 <code>cout</code> 作为下一位的进位输入：</p><figure class="highlight verilog"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">assign</span> {cout[<span class="number">0</span>], sum[<span class="number">0</span>]} = a[<span class="number">0</span>] + b[<span class="number">0</span>] + cin;</span><br><span class="line"></span><br><span class="line"><span class="keyword">genvar</span> i;</span><br><span class="line"><span class="keyword">generate</span></span><br><span class="line">    <span class="keyword">for</span> (i = <span class="number">1</span>; i &lt; <span class="number">100</span>; i = i + <span class="number">1</span>) <span class="keyword">begin</span> : adder_block</span><br><span class="line">        full_adder fa (</span><br><span class="line">            <span class="variable">.a</span>(a[i]),</span><br><span class="line">            <span class="variable">.b</span>(b[i]),</span><br><span class="line">            <span class="variable">.cin</span>(cout[i-<span class="number">1</span>]),</span><br><span class="line">            <span class="variable">.sum</span>(sum[i]),</span><br><span class="line">            <span class="variable">.cout</span>(cout[i])</span><br><span class="line">        );</span><br><span class="line">    <span class="keyword">end</span></span><br><span class="line"><span class="keyword">endgenerate</span></span><br></pre></td></tr></tbody></table></figure><h3 id="索引式部分选择"><a href="#索引式部分选择" class="headerlink" title="索引式部分选择"></a>索引式部分选择</h3><p><code>[base +: width]</code> 与 <code>[base -: width]</code> 适合处理分段总线和 BCD 数字：</p><figure class="highlight verilog"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">localparam</span> <span class="keyword">integer</span> DIGIT_INDEX = <span class="number">2</span>;</span><br><span class="line"><span class="keyword">wire</span> [<span class="number">3</span>:<span class="number">0</span>] digit = bcd[<span class="number">4</span>*DIGIT_INDEX +: <span class="number">4</span>];  <span class="comment">// 等价于 bcd[11:8]</span></span><br></pre></td></tr></tbody></table></figure><p>这些写法可以减少重复代码，但没有必要为了缩短行数而把结构写得难以辨认。HDLBits 只检查功能，代码最短并不表示实现最好。</p><h2 id="有限状态机"><a href="#有限状态机" class="headerlink" title="有限状态机"></a>有限状态机</h2><p>有限状态机在 HDLBits 中占有较多题目。状态少时可以使用较紧凑的写法，状态、计数器和控制信号增多以后，将它们拆开通常更容易检查。</p><p>常见的三部分写法为：</p><ol><li><strong>次态逻辑</strong>：组合逻辑，根据当前状态和输入计算 <code>next_state</code>；</li><li><strong>状态寄存器</strong>：时序逻辑，在时钟边沿执行 <code>state &lt;= next_state</code>，复位也在这里处理；</li><li><strong>输出逻辑</strong>：Moore 型输出只取决于当前状态，Mealy 型输出同时取决于当前状态和输入。输出逻辑可以单独编写，也可以与次态逻辑放在同一组合块中。</li></ol><p>下面是一个检测 <code>101</code> 序列的 Moore 型状态机示例，包含异步复位：</p><figure class="highlight verilog"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">localparam</span> IDLE = <span class="number">2'b00</span>;</span><br><span class="line"><span class="keyword">localparam</span> S1   = <span class="number">2'b01</span>;  <span class="comment">// 已收到 1</span></span><br><span class="line"><span class="keyword">localparam</span> S10  = <span class="number">2'b10</span>;  <span class="comment">// 已收到 10</span></span><br><span class="line"><span class="keyword">localparam</span> S101 = <span class="number">2'b11</span>;  <span class="comment">// 已收到 101</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">reg</span> [<span class="number">1</span>:<span class="number">0</span>] state, next_state;</span><br><span class="line"><span class="keyword">reg</span> out;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 1. 次态逻辑</span></span><br><span class="line"><span class="keyword">always</span> @(*) <span class="keyword">begin</span></span><br><span class="line">    <span class="keyword">case</span> (state)</span><br><span class="line">        IDLE:    next_state = in ? S1   : IDLE;</span><br><span class="line">        S1:      next_state = in ? S1   : S10;</span><br><span class="line">        S10:     next_state = in ? S101 : IDLE;</span><br><span class="line">        S101:    next_state = in ? S1   : S10;</span><br><span class="line">        <span class="keyword">default</span>: next_state = IDLE;</span><br><span class="line">    <span class="keyword">endcase</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. 状态寄存器</span></span><br><span class="line"><span class="keyword">always</span> @(<span class="keyword">posedge</span> clk <span class="keyword">or</span> <span class="keyword">posedge</span> reset) <span class="keyword">begin</span></span><br><span class="line">    <span class="keyword">if</span> (reset)</span><br><span class="line">        state &lt;= IDLE;</span><br><span class="line">    <span class="keyword">else</span></span><br><span class="line">        state &lt;= next_state;</span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 3. 输出逻辑（Moore 型）</span></span><br><span class="line"><span class="keyword">always</span> @(*) <span class="keyword">begin</span></span><br><span class="line">    out = (state == S101);</span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></tbody></table></figure><p>状态转移可以概括为：从 <code>IDLE</code> 开始，收到 <code>1</code> 进入 <code>S1</code>；再收到 <code>0</code> 进入 <code>S10</code>；再收到 <code>1</code> 进入 <code>S101</code> 并输出 <code>1</code>。其它输入则回到合适的中间状态。如果输出依赖 <code>in</code>，则变成 Mealy 型，输出可能提前一个周期变化。</p><p>仓库中的 <a href="https://github.com/felixtensor/hdlbits-solutions/blob/main/3-circuits/building-larger-circuits/Exams_review2015_fsm.v"><code>Exams_review2015_fsm.v</code></a> 是一个十状态的序列检测定时器，采用了这种拆分方式。</p><p>另外需要注意：</p><ul><li><strong>同步复位与异步复位</strong>：异步复位出现在敏感列表中，例如 <code>always @(posedge clk or posedge reset)</code>；同步复位只在 <code>@(posedge clk)</code> 内判断。还要留意高有效和低有效。</li><li><strong>当前状态与下一状态</strong>：输出应当依据题目的时序要求设计。Moore 型输出通常由 <code>state</code> 决定；直接改用 <code>next_state</code> 会让输出间接依赖当前输入，可能改变状态机类型并产生组合毛刺。波形恰好相差一个时钟周期时，应先检查输入采样边沿和输出是否需要寄存，不要只为对齐波形而替换二者。</li><li><strong>独热编码</strong>：每个状态使用一位，某个次态位可以写成所有入边条件的或。对应示例见 <a href="https://github.com/felixtensor/hdlbits-solutions/blob/main/3-circuits/building-larger-circuits/Exams_review2015_fsmonehot.v"><code>Exams_review2015_fsmonehot.v</code></a>。</li></ul><h2 id="波形与-testbench"><a href="#波形与-testbench" class="headerlink" title="波形与 testbench"></a>波形与 testbench</h2><p>输出不正确时，先找到实际输出与期望输出第一次不一致的位置，再向前检查输入、状态、复位和使能。后续的大段错误经常只是第一个错误的连锁结果。</p><p>如果输出恰好提前或延后一个周期，应检查阻塞与非阻塞赋值、<code>state</code> 与 <code>next_state</code>，以及输出是否多经过或少经过一级寄存器。若出现 <code>x</code>，则检查寄存器是否初始化、组合逻辑是否遗漏赋值路径，以及信号是否存在多个驱动源。</p><p>题解仓库中有 <a href="https://github.com/felixtensor/hdlbits-solutions/tree/main/4-verification-reading-sims">15 道读取仿真的题</a>，其中 10 道根据波形还原电路，5 道根据波形寻找 bug。这部分题目不多，但对之后自己调试电路很有帮助。</p><p>另外还有 <a href="https://github.com/felixtensor/hdlbits-solutions/tree/main/5-verification-writing-testbenches">5 道 testbench 题</a>，内容包括生成时钟、施加输入和实例化待测模块。HDLBits 会提供参考设计，而在自己的工程中，需要由 testbench 判断设计是否满足预期，因此这一部分不建议跳过。</p><h2 id="延伸阅读"><a href="#延伸阅读" class="headerlink" title="延伸阅读"></a>延伸阅读</h2><ul><li><strong><a href="https://hdlbits.01xz.net/">HDLBits</a></strong>：练习平台本身，可以按目录顺序完成。</li><li><strong><a href="https://www.chipverify.com/verilog/verilog-tutorial">ChipVerify 的 Verilog 教程</a></strong>：用于查询语法和常见模块。</li><li><strong><a href="https://www.tutorialspoint.com/digital_circuits/index.htm">TutorialsPoint 的数字电路教程</a></strong>：用于补充组合电路和时序电路的基础概念。</li><li>相关主题：<a href="https://en.wikipedia.org/wiki/Linear-feedback_shift_register#Applications">LFSR</a>、<a href="https://en.wikipedia.org/wiki/Finite-state_machine">有限状态机</a>、<a href="https://en.wikipedia.org/wiki/Cellular_automaton">元胞自动机</a>、<a href="https://en.wikipedia.org/wiki/Branch_predictor">分支预测器</a>、<a href="https://en.wikipedia.org/wiki/Race_condition">竞争冒险</a>。</li></ul><h2 id="题解仓库"><a href="#题解仓库" class="headerlink" title="题解仓库"></a>题解仓库</h2><p>全部 182 道题解收录在：</p><p><strong><a href="https://github.com/felixtensor/hdlbits-solutions">GitHub：felixtensor/hdlbits-solutions</a></strong></p><p>其中包括 2 道入门题、41 道 Verilog 语言题、115 道电路题、15 道读取仿真题、5 道 testbench 题和 4 道附加题。目录结构与 HDLBits 的导航基本一致；文件名取自题目 wiki 的 slug，文件头部链接回原题，部分复杂题目附有额外说明。仓库使用 MIT License。</p><p>这些代码写于不同阶段，其中一些只是当时第一个通过测试的版本。整理仓库时补充了注释、统一了部分格式，并做过基础静态检查，但它们仍然只是参考题解，不保证在面积、时序或代码风格上最优。</p>]]>
    </content>
    <id>https://felixtensor.github.io/2022/06/16/HDLBits%20Codes%20Collation/</id>
    <link href="https://felixtensor.github.io/2022/06/16/HDLBits%20Codes%20Collation/"/>
    <published>2022-06-16T07:36:00.000Z</published>
    <summary>
      <![CDATA[<blockquote>
<p><strong>关于这篇文章：</strong> 这篇文章最初收录了我做过的全部 HDLBits 题解。182 道题放在同一个 Markdown 文件中，查找和维护都很麻烦，因此现在已经将代码单独整理到：</p>
<p><strong><a href="https://github.com/felixtensor/hdlbits-solutions">felixtensor&#x2F;hdlbits-solutions</a></strong></p>
<p>仓库按 HDLBits 的题目目录组织，每道题对应一个文件。这篇文章则保留一些入门说明，以及做题时比较容易遇到的问题。</p>
<p><strong>建议先独立完成题目，遇到问题时根据波形定位；确实无法解决，再将仓库中的代码作为思路参考。直接复制答案可以通过测试，但对理解 Verilog 没有什么帮助。</strong></p>
</blockquote>
<p>这篇文章主要面向刚开始在 HDLBits 上练习 Verilog 的读者。如果你刚接触 Verilog，建议按顺序阅读“先建立硬件的概念”“解题流程”和“常见问题”；如果已经做到状态机或读取仿真等相关题目，可以直接跳到对应小节。</p>]]>
    </summary>
    <title>Verilog 入门：一份 HDLBits 学习指南</title>
    <updated>2026-07-19T16:00:00.000Z</updated>
  </entry>
</feed>
