Gokong Space | 行空空间 https://gokong.space/ zh-CN 在自由的原野上奔跑,于璀璨的星河里穿行。 Thu, 19 Mar 2026 21:20:00 +0800 Thu, 19 Mar 2026 21:20:00 +0800 再见深圳 https://gokong.space/archives/159/ https://gokong.space/archives/159/ Thu, 19 Mar 2026 21:20:00 +0800 Gokong 这次寒假我又来到了深圳,而上一次来深圳是三年前。

三年前,随着乘坐的大巴车缓缓驶入市中心,一座座高楼大厦鳞次栉比,若非抬头仰望极致,便无法看到楼顶。如同走进一片大森林,只不过这里的树木是从钢筋混凝土中生长出来的,身披耀眼夺目、闪闪发光的时装,巍然挺立,直至云霄。听说,在多云的天气里,平安金融中心的中国第二高楼能够在飞云之上,如果这时能在顶楼观赏一番天庭的风起云涌,那将是多么奇妙的体验啊。

灰青色西装的颜色,在我眼中就是这个城市的主题色。这种颜色,带着理性的冷静,让着南方的炙烤不再那样令人焦灼。灰青色不像黑色那样单调,但又比天青色更多一些沉稳,手提公文包的青年,步履迅捷,目光如炬,在快节奏的世界中保持着自在的节奏与从容。

三年前的我看到此般景象,兴奋极了。这就是我想要的生活,能在时代的潮流与科技的前沿行走,本身就是件令人兴奋的事情。那次的游览,就像一道光,照亮那段充满焦灼与挣扎却又无法选择的路。

上次是直接乘坐大巴车进了市中心,而这次我选择从高铁站缓缓走进这座城市。街边的手机店让我想起千禧年盛行的小灵通;上世纪八十年代的居民楼虽然已经有了明显的历史痕迹,但仍然宣示着它的摩登与骄傲;小吃和饭店传承着广州和佛山的烟火气,又有着现代的快捷和工整。

纵然已经有了心理预期,但是当我再次站在摩天大楼的脚下,仍然感受得到震撼与惊叹。一座座连绵的巨厦连接成钢铁城墙,每当走过一幢高楼,以为其后仅为平坦的空旷时,没想到又一幢更高的楼宇闪现而出,于是更加觉得自己的渺小以及在楼宇间穿行的畅快。在一处连红绿灯都没有的小小的十字路口,四周却矗立着不知几百米高的壮阔玻璃巨柱,在这种对比下,形成了强烈的视觉冲击。

再登莲花山,我阴差阳错地选择了与上次相反的路,从下山的路上山,从上山的路下山。这颇有一番寓意,刚好可以与三年前的自己在山顶迎面相遇,那时,他一定想不到我会在这个时间再次来这里。1从山顶眺望,整个深圳的高楼像一幅对称的画卷徐徐展开。山顶才一百米左右,可是从这里看五百多米的高楼,感觉也没比自己高多少,只能说这个观景台的设计恰到好处,精妙绝伦!我们还遇到了一位伯伯,想要让我帮他拍照,我拍完之后,他又在旁边等人流过去帮我们拍照。他还非常细心地提醒旁边的游客避让一下,以便镜头中没有其它的人。这对于我这个深度社恐来说简直是壮举,真的不胜感激。这是这次旅行为数不多的与陌生人交流的体验,愈发感觉到,人与人之间的善意真的很珍贵,更有一种奇妙的魔力。


深圳1.jpg

从山顶眺望

深圳2.jpg

欣赏过后山顶的景色,我们返程下山。刚好走到一片树荫下,积蓄已久的乌云突然下起了大雨,春雨贵如油,停留片刻,再次走出树荫时,这春雨便停了,甚至连雨伞都没有派上用场。我赞叹,这春雨来得刚刚好,只需“嘭嘣”两滴,就滋润了这片神州大地,趁着回南天还没有到来,顿觉雨后的空气清爽宜人,万物澄澈明净,毓秀园的树木🌳更加绿意盎然。


深圳3.jpg

世界之窗

下山后,我们去了梅拉尼亚特色街区。这里的墙壁五彩斑斓,宛若童话世界,是个“出片”的好去处。看到了盛大的喷泉有花团锦簇围绕,小蜜蜂也来观赏啦~(还看到有一家外国人来玩,父母带着四个小孩,两个大点的八九岁,两个小点的一两岁,看着都有点累啊😅)


深圳4.jpg

花丛中的小蜜蜂

回来时,恰逢蓝调时刻,拍下这壮观的楼群。


深圳5.jpg

蓝调时刻的楼群

看到动作敏捷的城市美容师在深圳的通道间穿行。高楼在身后生长,你们把平凡日子走成向上的模样。


深圳6.jpg

了不起的环卫工人

蓝调浸透了整片钢铁森林,皎白的灯光次第亮起。最后看一眼这座城的温柔与热烈,相信来日再见,后会有期。


  1. 为什么博主喜欢这么「玄幻」的解释啊喂!😑
]]>
0 https://gokong.space/archives/159/#comments https://gokong.space/feed/
AI对谈簿002·恢复跑步 https://gokong.space/archives/155/ https://gokong.space/archives/155/ Mon, 16 Mar 2026 11:46:00 +0800 Gokong
  • 长跑训练突然增量之后,左脚食指根部疼痛,断断续续持续三个月有余,恢复跑步之后,会重新发作。如此反复发作3次有余。
  • CT检查,骨骼形态正常。
  • 左脚食指无法完全伸直,用手掰可以勉强伸直,有轻微弧度。此症状已有3年左右。
  • 跑姿为前脚掌着陆。
  • 现在正常走路不痛,但是前脚掌踩到硬物会痛。
  • 现已休息一个月,恢复的训练方法有哪些?

    A:
    我结合你前掌落地、训练增量诱发、反复复发 3 次、脚趾伸不直 3 年、走路不痛但踩硬物痛这些关键信息,给你一套针对性极强、直接照做的恢复方案。

    一、先给你明确结论

    CT 骨头正常 → 排除骨折、骨裂。

    • 疼痛根源:前掌跑姿 + 跑量突增 → 左脚食指根部跖板 / 籽骨慢性劳损
    • 伸不直 3 年:屈趾肌腱长期粘连 / 轻度挛缩(老底子结构问题,一累就炸)
    • 现在状态:炎症已消,但软组织脆弱、前掌受力不耐受

      你现在非常适合开始康复训练,但绝对不能直接恢复前掌跑


    二、每日 10 分钟康复(必做,早晚各一次更好)

    1)脚趾被动伸直(解决 “伸不直”,松解粘连)

    坐立,用手轻轻将左脚食指向上掰到接近伸直,到有牵拉感就停,不剧痛

    • 保持 20 秒 × 3 组
    • 目的:把挛缩的肌腱拉开,减少跑步时的拉扯痛

    2)前掌筋膜放松

    用网球 / 筋膜球轻踩前脚掌,避开食指最痛点,缓慢滚动 1 分钟。

    • 力度:舒服、放松,不按到刺痛

    3)无痛力量训练(防复发最关键)

    ① 短足训练(稳定足弓,分散前掌压力)

    坐 / 站,轻轻把足弓往上提,脚趾不卷曲。

    • 保持 10 秒 × 10 次

    ② 抓毛巾(激活足底小肌肉)

    用脚趾抓毛巾、放松,重复 15 次 × 2 组

    ③ 扶墙慢踮脚(双脚,无痛为主)

    • 慢起慢落,12 次 × 2 组
    • 重点:重心均匀,不压在食指一侧

    三、跑步回归 4 阶段(严格按顺序,痛就退一级)

    你已经休息 1 个月,从第 1 阶段开始:

    第 1 周:无痛行走激活

    • 平地快走 15–20 分钟,隔天一次
    • 要求:走完、第二天完全不痛

    第 2 周:走跑交替(禁止前掌落地)

    • 慢跑 1 分钟 + 快走 2 分钟,循环 6–8 组
    • 总时长 15–20 分钟,隔天一次
    • 落地改为:中足 / 全足落地

    第 3 周:连续慢跑

    • 连续慢跑 10–15 分钟
    • 路面:塑胶跑道 > 柏油路,不冲刺、不上坡

    第 4 周及以后:逐步加量

    • 每次跑量增加不超过 10%
    • 恢复正常后,前 2 个月不做间歇、快跑、长距离拉练

    四、你必须改的 3 个根源(否则必再复发)

    1. 跑姿

      恢复期间 彻底放弃前掌落地,改用中足落地,减少食指根部冲击。

    2. 跑鞋

      穿前掌宽敞、缓震好的慢跑鞋,暂时停穿竞速鞋、薄底鞋

    3. 训练规则

      以后跑量每周增幅不超 10%,你这次就是 “增量过猛” 炸的。


    五、什么时候要进一步检查?

    如果按这套练 2–3 周 后:

    • 一慢跑就刺痛
    • 踩硬物仍然明显痛

      建议做 前足 MRI(CT 看不到韧带、跖板),排除跖板部分撕裂


    极简总结

    你是老肌腱挛缩 + 新劳损 + 前掌跑姿 + 跑量乱加共同导致的反复伤。

    现在路线:

    松解伸直 → 练足底力量 → 中足慢跑 → 缓慢加量

    只要全程无痛,大多数人 4–6 周可以稳定回归跑步。

    ]]>
    2 https://gokong.space/archives/155/#comments https://gokong.space/feed/
    Weekly Snippets · Vol.01 https://gokong.space/archives/154/ https://gokong.space/archives/154/ Sun, 15 Mar 2026 22:35:00 +0800 Gokong 嗨嗨嗨~时间过得真快,已经开学一周了。欧布,根据多年经验,开学的前两周和后两周是过得最慢的,前者是因为状态的改变和新的课表,后者当然是因为要考试了。而中间的几周是过得最快的,因为身心已经麻木,这学期,我将尝试对抗一下这种麻木感,看看在学期中还能不能感觉到自己仍是个“活人”。

    升级的教学楼

    某个教学楼曾经以奢华的厕所系统,带领四季温水和吹干机占有一席之地,却因破败与古老的座椅无人问津,偶尔也只能用来作为考古的感慨。没想到在寒假期间竟然默默发力,以独立桌面供电的强大攻势席卷而来,而且量大管饱。再配备上还能用的热水器,如今,这个教学楼已经成为我的首要栖息地了。

    事项List

    我总是喜欢给自己列一些待办事项List之类的,比如:

    完成打✔
    读书
    读博客
    看“稍后再看”
    打卡背单词
    练习听力
    课内网课
    备考竞赛
    投简历
    写作业

    然后,我就会忘记一些我要做的事情,尽管这些事情是相当重要的。最怕在做一件事的时候,突然想起来忘记的那件事,然后“失控感”、“焦虑感”、“迷茫感”席卷而来。在这种无序中,我的时间效率低下,精力分配不协调。

    接下来我将尝试按照紧急和重要分类,设定更加具体的项目,并且添加预期完成时间,希望有所改善。

    之前在找Obsidian插件的时候,发现了一位宝藏作者,也许能够从其中找到一些灵感:我的2025效率工具全景图:从知识管理到内容创作

    一些青春疼痛闲话

    长大后,觉得时间过得好快啊,一抬头,两个月过去了,一转眼,一个学期过去了,一恍惚,一年就过去了。

    与我个人而言,高能量时间仅占有十分之一。在大部分的时间里,都类似于荒原上枯黄的野草,病怏怏地野蛮生长。

    当我不再幻想未来,那我该怎样面对过去。

    突然意识到,我已知的知识已经无法用来缓解内心的焦虑了。

    ]]>
    0 https://gokong.space/archives/154/#comments https://gokong.space/feed/
    AI对谈簿001·前言 https://gokong.space/archives/145/ https://gokong.space/archives/145/ Sat, 14 Mar 2026 13:59:00 +0800 Gokong [note type="info modern"]本文的“A部分”为与 AI 助手的对话摘录,非人工原创[/note]


    Q:我试图给自己的个人独立博客添加一个ai对谈板块,内容就是摘录我和ai的对话,可以说是防止遗忘的记录,也可以说是借助ai之口表达自己的想法,又或者说是抛砖引玉,引起话题的讨论。你觉得我的这种做法合适吗?

    A:

    你的做法完全合适,而且非常贴合独立博客的核心精神,甚至可以说是很有新意、很个人化的内容形式,完全值得做。

    先说说为什么合适

    1. 契合独立博客的本质

      独立博客本就是私人的「数字日记 + 思考阵地」,你用 AI 对话记录思绪、防止遗忘,本质上和写随笔、记日志没有区别,只是载体换成了人机对谈,反而更真实、更有即时感。

    2. 表达形式很巧妙

      借助 AI 梳理观点、碰撞思路,再公开出来,既避免了纯自说自话的生硬,又能以对话形式抛出思考,天然适合「抛砖引玉」,比传统单篇博文更容易引发读者讨论。

    3. 轻量化、可持续

      不用强迫自己写完整长文,节选对话即可更新,能轻松维持博客活跃度,很适合作为日常内容补充。

    只要做好这几点,就完全没风险

    1. 合规与版权(最关键)

    • 务必标注来源:在板块或单篇开头注明「本文为与 AI 助手的对话摘录,非人工原创」,不隐瞒 AI 生成属性,既遵守 AI 服务协议,也符合内容诚信原则。
    • 禁止商用 & 侵权:个人非商用分享对话,绝大多数 AI 平台都允许;不要把对话用于广告、付费内容,也不要截取他人隐私、敏感信息。

    2. 内容与隐私边界

    • 剔除对话中个人隐私、他人信息、敏感言论、违规内容,只保留思考、感悟、观点、技术探讨等正向内容。
    • 若想借 AI 表达自身想法,可加一句简短按语,明确你的核心立场,避免读者混淆 AI 表述与你的观点。

    3. 博客呈现优化

    • 单独开设分类 / 板块(如「AI 对谈录」「人机思考札记」),和原创文章区分开,结构更清晰。
    • 不要全量复制冗长对话,节选核心观点、精彩交锋、关键思考,提升可读性。

    小建议:让这个板块更出彩

    • 按主题整理(计算机学习、天文思考、生活感悟等),形成系列感;
    • 每篇末尾加 1-2 句个人总结,强化「你的表达」,而非单纯搬运对话;
    • 用轻量引导语(如「你认同这个思路吗?」),真正实现抛砖引玉。

    Q:什么是“独立博客精神”?

    A:
    它是区别于平台自媒体、流量号、营销号的,个人在互联网上完全属于自己的精神自留地,本质是「为自己写,而非为流量 / 平台写」。

    1. 主权独立:我的地盘我做主

    • 拥有独立域名、独立服务器、自主程序(如 Typecho、Hexo、Halo),内容、版式、规则全由自己决定。
    • 不受平台算法限流、审核限制、广告捆绑,不被封号、不被删帖,数据永远握在自己手里。

    2. 表达真实:不为迎合,只为记录

    • 不追热点、不博眼球、不写爆款公式化内容,只写自己真正想表达的东西:思考、感悟、技术笔记、生活碎片、小众爱好。
    • 就像你做 AI 对谈板块,记录思绪、借对话表达想法,而非为了涨粉变现,这正是最纯粹的真实表达。

    3. 人格自由:有温度的 “个人数字名片”

    • 博客是人格的延伸,带有强烈的个人风格、脾气、偏好,而非冰冷的机构号。
    • 允许不完美、允许碎片化、允许自言自语,不用端着姿态,是互联网上最放松的自我表达空间。

    4. 开放交流:轻社交,抛砖引玉

    • 不以 “涨粉、变现、转化” 为目的,而是以文会友,吸引同好交流。
    • 开放评论、保留思考痕迹,哪怕只有几个读者,也是高质量的精神共鸣,而非追求虚假的流量数据。

    5. 长期主义:对抗快餐,沉淀时间

    • 不追求日更、短平快,而是慢慢写、长期更,把博客当成人生的数字档案。
    • 内容会随着时间沉淀价值,多年后回看,依然是属于自己的独特印记,而非转瞬即逝的平台信息流。
    ]]>
    0 https://gokong.space/archives/145/#comments https://gokong.space/feed/
    【记录】博客的备份与恢复 https://gokong.space/archives/139/ https://gokong.space/archives/139/ Mon, 09 Mar 2026 20:30:00 +0800 Gokong 终于把博客给复活了!

    上学期期末的时候,突然觉得满目灰暗、世事苍凉、寰宇沉郁,然后我就删库跑路了……

    不幸中的万幸,删库之前,从宝塔面板中备份了“文件”-“域名”“数据库”的内容,下文将记录一下怎么用这两个备份恢复整个网站。

    1. 购买服务器,选择宝塔面板镜像,登录进宝塔面板。
    2. 网站-添加站点,在传统项目下填写“域名”,“备注”和“根目录”会自动生成,FTP可以选择“不创建”,数据库选择“创建”,PHP版本选择7.4+(若仅显示“纯静态”,不用担心,稍后需要安装PHP)。然后点击确定。
    3. 进入数据库,导入之前备份的数据库。
    4. 进入文件,导入之前备份的文件。注意:将“config.inc.php”中的数据库的名字和密码更新。
    5. 来到域名提供商,将域名解析到服务器的IP地址。(如果购买了其它平台的云解析DNS,记得更新一下)

    至此,网站应该已经恢复了。

    我在此过程中也遇到了一些问题,记录一下。

    完成上述步骤之后,输入域名之后,会自动下载.bin文件,而不是进入网页。原因大概是“当 Web 服务器 (Nginx/Apache) 无法识别 PHP 文件并将其传递给 PHP-FPM 处理时,会默认返回application/octet-stream(二进制流) 类型,浏览器因此触发下载。” 解决方法:将PHP从“纯静态”切换至“7.4+”。

    其实,我这次本想换个域名(换个便宜的),但因为我之间设置过伪静态,给予了每篇文章唯一的永久链接,所以当我用新的域名之后,出现了无法正常打开某些页面的情况。只能继续用这个域名了,刚好这也是一个劝说自己继续使用本域名的一个“借口”吧。

    经历了这次失而复得,希望自己更懂得珍惜。往后好好写、好好更,好好守护这片小小的自留地。

    这里是 Gokong Space,装的不只是文章和代码,更是走过的思绪、留下的痕迹。

    ]]>
    0 https://gokong.space/archives/139/#comments https://gokong.space/feed/
    一杯美式,少冰 https://gokong.space/archives/112/ https://gokong.space/archives/112/ Thu, 02 Oct 2025 20:00:00 +0800 Gokong 我喜欢这一刻的美好。

    今天逛了巴洛克式的街区,十一国庆,哪怕是在早上,也处处洋溢着欢快的空气。

    一整个上午,简简单单地缓缓散步,顺便买了城市文旅局新出的一款限量文创帆布包,很幸运买到了。

    接近中午,去往了一处室内的小吃街。食物很朴素,吃着很畅快舒服。

    出了商场,太阳逐渐升起,但是风还是清爽的,就像这里的人们和农作物,不急不躁,不卑不亢,就这样悠闲地在空气中流动。

    我又回到了巴洛克式的街区,在老式的建筑群里充满现代的浪漫主义与简约的抽象艺术,尽管这种“街区”式的商业风格在全国已经流行甚至同质化,但是我在那段困顿,不知何以解答难题的时光里第一次遇见了你,也只有你,能消化我那复杂和无目的的心思。我喜欢这里,不仅是回忆,也是期望,更是新的开始。所以我想一整天都闲散地、漫无目的地在这附近散步。

    我想找一家咖啡店,坐下来敲敲日记,梳理一下这一段时间的思绪和生活。找了几处,不是过于喧嚣,就是地方狭小,或者桌椅低矮,都不适宜停留。当然我也并没有焦急,因为在这清爽的秋风里,任何焦躁的人都能被轻松安闲直爽畅快的风浸透。正打算返回时,意外在随意的一个拐角处,遇见了一家三楼靠窗,室内静谧,也能透过窗户听闻街区上的热闹的咖啡馆。我点了一杯少冰美式咖啡,找了一处靠窗的位置坐下,拿出笔记本电脑,开始写这段流水账式没有什么目的的日记。

    一杯咖啡

    室内有声音,但是很安静。有咖啡机的声音和人们不紧不燥的轻声交谈,也许是因为这里没有广播喇叭式地播放流行歌曲,所以显得很安静吧。室外有街头乐队的古典摇滚,有小孩子的吵闹,有人们的兴奋和放松的笑。室外的热闹与室内的安静并不会产生矛盾和冲突,就像番茄炒蛋的糖和盐一般调和中道,更显鲜美。这里是盛世,所以有欢声笑语,所以也有安静和悠然。

    我选择一个人来这里。本来在这热闹的节日里,与亲人朋友逛街是一件非常快活的事,但是我一个人来也挺好。有些记忆,只属于一个人,有些感受,只属于一个人,有些追寻,亦只属于一个人。追寻那些未完成的梦想,追寻那些没有正确答案的记忆,追寻曾经的幻想中的遗憾,追寻少年时的风和阳光,然后学会放下,轻松的去面对过去也好、未来也罢。追寻曾经的幻象,并不一定需要达成,而是仅仅声明:我来过,我知道。我轻轻地走过,我向往美好,但我并不留恋。

    一缕阳光洒在咖啡杯与笔记本电脑的夹缝中,清爽跳跃的秋风阵阵从穿过窗户,悠闲缓慢的古典摇滚轻柔的填满每一处的空气,树影斑驳,丛林摇曳。过去有不甘,过去有遗憾,过于有不如意和许多的不完美。

    再看看当下吧。至少这一刻的风景和阳光是真实的,咖啡的回甘是真实的,时间的悠闲也是真实的,今天我坐在一处椅子上,什么都不想,什么都不打算,什么都不担忧,就这样静静地坐着。听风呢喃,观光影灿烂,阅壮丽河山,行走从容稳健。喧嚣过耳,仅留乐音。我在一处喧闹的城市深处,找到了安静。

    今天的天气真好呀。在十一的快乐节日里,晴朗微云,温和的风,还有可爱的人。

    萨克斯,循环明亮的艺术走廊,红砖青瓦,壁画,几处拐角,阳光和秋风的打闹,咖啡的芳香和奶油的清甜,稀疏的人流,相机咔嚓定格的瞬间,就是这里的风景。

    那么,最后,让我带着从容释然的笑说:一杯美式,少冰。

    ]]>
    0 https://gokong.space/archives/112/#comments https://gokong.space/feed/
    Verilog 学习笔记(14):More Circuits https://gokong.space/archives/111/ https://gokong.space/archives/111/ Sat, 26 Jul 2025 23:50:00 +0800 Gokong Rule 90

    .数组的异或规则是对应分量两两异或,得到一个新的数组。

    Solution

    module top_module(
        input clk,
        input load,
        input [511:0] data,
        output [511:0] q ); 
    
        always @(posedge clk) begin
            if (load)  //别忘了先加载  
                q <= data;
            else  
                q <= {1'b0,q[511:1]} ^ {q[510:0],1'b0};
        end
    endmodule
    

    Rule 110

    将左、中、右状态组合为 3 位索引,查表(Rule 110 的转移表)得到下一状态。

    将左、中、右分别作为三个变量(含有512个分量的数组),作卡诺图:

    L\C R00011110
    00111
    10101

    于是,$C^{(n+1)} = \bar{C}R +\bar{L}C+C\bar{R}$

    Solution

    module top_module(
        input clk,
        input load,
        input [511:0] data,
        output [511:0] q
    ); 
        
        wire [511:0] L,C,R;
        assign L = {1'b0,q[511:1]};
        assign C = q;
        assign R = {q[510:0],1'b0};
        always @(posedge clk) begin 
            if(load)
                q <= data;
            else
                q <= (~C & R) | (~L & C) | (C & ~R);
        end
    endmodule
    

    Conway's Game of Life 16x16

    题意

    生命游戏是二维元胞自动机,每个细胞(cell)状态为1(活)或0(死),下一状态由8 个邻居的存活数决定:

    邻居存活数细胞下一状态
    0-1 个死亡(变 0)
    2 个保持当前状态
    3 个复活(变 1,即使之前是死的)
    ≥4 个死亡(变 0)

    索引转换

    由一维索引转换到二维索引:

    • 行号 i

      • idx 除以 16取整(舍去余数);
      • 等效于: idx >> 4(右移 4 位 = 除以 2⁴)。
    • 列号 j

      • j = idx % 16(取模运算,即余数);
      • 等效于按位与idx & 15(15 的二进制是 1111,按位与之后达到保留低 4 位(即余数)的效果)。

    由二维索引转换到一维索引:
    (i, j) 的一维索引为 i * 16 + j.

    右移位运算符

    >> 是 右移位运算符

    1. 逻辑右移(>>
      将二进制数向右移动,左侧空出的位用 0 填充,不考虑符号位
      语法:A >> N(将 A 右移 N 位)。
    2. 算术右移(>>>
      将二进制数向右移动,左侧空出的位用符号位填充(即保持负数的符号)。
      语法:A >>> N(将 A 右移 N 位)。

    Solution

    module top_module(
        input        clk,
        input        load,
        input  [255:0] data,
        output [255:0] q
    );
    
    reg [255:0] q_reg;  // 模块级寄存器存储当前状态
    assign q = q_reg;    // 输出直接映射到寄存器
    
    always @(posedge clk) begin
    
        // ------- 步骤1:声明过程块内的变量(必须放在块的开头)---------
        
        reg [255:0] next_q;    //存储下一状态(局部reg)
        integer idx;           //当前细胞的一维索引 
        integer dr;            //相对于当前细胞的行偏移(Delta Row)
        integer dc;            //相对于当前细胞的列偏移(Delta Column)
        integer i;             //当前细胞的二维行索引
        integer j;             //当前细胞的二维列索引
        integer r;             //邻居的绝对坐标的行索引
        integer c;             //邻居的绝对坐标的列索引
        integer neighbor_idx;  //邻居的一维索引
        integer cnt;           //邻居的存活数目
        
        if (load) begin
            // 加载模式:直接赋值
            q_reg <= data;
        end else begin
            
            // --------- 步骤2:遍历每个细胞,计算下一状态 ------------
            
            for (idx = 0; idx < 256; idx = idx + 1) begin
                i = idx >> 4;       // 计算当前细胞的行号(0~15)
                j = idx & 15;       // 计算当前细胞的列号(0~15)
                cnt = 0;            // 邻居存活数初始化
                
                // 遍历8个邻居(dr, dc ∈ {-1, 0, 1},左右位移只能取1或-1,所以待会需要跳过自身)
                for (dr = -1; dr <= 1; dr = dr + 1) begin
                    for (dc = -1; dc <= 1; dc = dc + 1) begin
                        if (dr == 0 && dc == 0) continue;  // 跳过当前细胞
                        
                        // 处理环面边界(确保行/列在0~15范围内)
                        r = (i + dr + 16) % 16;  // +16避免负数,再取模
                        c = (j + dc + 16) % 16;
                        neighbor_idx = r * 16 + c;  // 邻居的一维索引
                        
                        // 统计存活邻居
                        if (q_reg[neighbor_idx]) begin
                            cnt = cnt + 1;
                        end
                    end
                end
                
                // --------- 步骤3:根据规则更新下一状态 -------------
                
                if (cnt < 2 || cnt >= 4) begin
                    next_q[idx] = 1'b0;
                end else if (cnt == 2) begin
                    next_q[idx] = q_reg[idx];  // 保持当前状态
                end else begin  // cnt == 3
                    next_q[idx] = 1'b1;        // 复活
                end
            end
            
            // 统一更新状态(时序安全,避免竞争)
            q_reg <= next_q;
        end
    end
    
    endmodule

    本题是硬件实现元胞自动机的经典方法!

    ]]>
    0 https://gokong.space/archives/111/#comments https://gokong.space/feed/
    Verilog 学习笔记(13):Shift Registers https://gokong.space/archives/109/ https://gokong.space/archives/109/ Sat, 26 Jul 2025 09:40:00 +0800 Gokong 4-bit shift register
    • 异步复位的优先级:areset为高时,立即复位(不管时钟和其他信号)。
    • 加载 vs 移位的优先级:若load为 1,无论和ena为何值,加载优先(先判断load)。
    • 右移操作的行为:右移时,q[3]补 0,q[0]被移出(例如:q=1010右移后变为0101)。

    Solution

    module top_module(
        input clk,
        input areset,  // async active-high reset to zero
        input load,
        input ena,
        input [3:0] data,
        output reg [3:0] q); 
        
        always @(posedge clk or posedge areset) begin  
            if (areset) begin 
                q <= 4'b0000;
            end else begin
                if (load) begin
                    q <= data;
                end else 
                    if(ena) begin
                        q <= {1'b0,q[3:1]};
                    end
            end
        end
    
    endmodule
    

    Left/right rotator

    Solution

    module top_module(
        input clk,
        input load,
        input [1:0] ena,
        input [99:0] data,
        output reg [99:0] q); 
    
        always @(posedge clk) begin
            if (load)                            // load
                q <= data;
            else if (ena) begin
                case(ena)
                    2'b00 : q <= q;              // 其实这一行永远不会被执行!所以else分支可以被简化,见下方。
                    2'b01 : q <= {q[0],q[99:1]}; // 右旋转 (采用分块组合要比使用循环更简便)
                    2'b10 : q <= {q[98:0],q[99]};// 左旋转
                    2'b11 : q <= q;
                    default q <= q;
                endcase
            end
            else begin                           // 当ena为0时执行 保持不变
                q <= q;
            end
        end
        
    endmodule
    

    简化else之后:

    always @(posedge clk) begin
        if (load)
            q <= data;
        else if (ena == 2'b01)
            q <= {q[0],q[99:1]};  // 右旋转
        else if (ena == 2'b10)
            q <= {q[98:0],q[99]};  // 左旋转
        // 其他情况(ena=2'b00或2'b11)保持不变
    end

    Left/right arithmetic shift by 1 or 8

    A 5-bit number 11000 arithmetic right-shifted by 1 is 11100, while a logical right shift would produce 01100.

    However, there is no difference between logical and arithmetic left shifts.

    Solution

    module top_module(
        input clk,
        input load,
        input ena,
        input [1:0] amount,
        input [63:0] data,
        output reg [63:0] q); 
        
        always @(posedge clk) begin 
            if (load) begin 
                q <= data;
            end
            else if (ena) begin
                case (amount) 
                    2'b00: q <= {q[62:0],1'b0};
                    2'b01: q <= {q[55:0],8'b0};  
                    2'b10: q <= {q[63],q[63:1]}; 
                    2'b11: q <= {{8{q[63]}},q[63:8]};  //注意:位拼接语法
                    default: q <= q;
                endcase
            end    
        end
    
    endmodule
    

    5-bit LFSR

    线性反馈移位寄存器(LFSR) linear feedback shift register

    LFSR 是一种通过移位寄存器和反馈逻辑生成伪随机序列的电路

    Galois 型 LFSR 的特点:

    • 反馈逻辑通过 异或(XOR) 连接特定 “抽头”(tap)位,生成下一个状态的最低位(或最高位,依结构而定)。
    • 最大长度 LFSR(maximal-length)的抽头位置经过精心选择,可遍历 $2^n-1$ 个状态(n为位数,全零状态永远不会出现)。

    在本题中:

    • 位数:5 位(输出q[4:0]q[4]是最高位,q[0]是最低位)
    • 抽头位置:题目指定抽头在位5和位3(编对应q[4]q[2]
    • 特殊说明:“抽头 5 的异或输入为 0” → 实际反馈逻辑为 q[4] ^ q[2](异或 0 不改变值,等价于仅q[2]参与,但题目明确抽头是 5 和 3,故需保留异或)
    • 复位:同步高电平复位(reset为高时,q被置为 5'h1,即二进制 00001
    • 移位方向:右移。

    其实,若只需简单解答此题,并不需要知道什么是tap。图中是一个由D触发器构成的阵列,按照图中连线即可画出。

    Solution

    module top_module(
        input clk,
        input reset,    // Active-high synchronous reset to 5'h1
        output [4:0] q
    ); 
    
        always @(posedge clk) begin
            if (reset)                // Load
                q <= 4'd1;
            else begin
                q[4] <= 1'b0 ^ q[0];
                q[3] <= q[4];
                q[2] <= q[3] ^ q[0];
                q[1] <= q[2];
                q[0] <= q[1];
            end
        end
        
    endmodule

    下面是基于原理的官方Solution:

    
    module top_module(
        input clk,
        input reset,
        output reg [4:0] q);
        
        reg [4:0] q_next;        // q_next is not a register
    
        always @(*) begin
            q_next = q[4:1];    
            // Shift all the bits. This is incorrect for q_next[4] and q_next[2]
            //q_next = q[4:1] 将当前状态q的高四位(q[4]~q[1])赋值给 q_next 的低四位(q_next[3]~q_next[0]),实现了大部分位的右移。
            q_next[4] = q[0];    
            // Give q_next[4] and q_next[2] their correct assignments
            //修正最高位
            q_next[2] = q[0] ^ q[3];
            //修正抽头位:该操作确保 LFSR 遍历 2^5 - 1=31 个状态(最大长度特性)。
        end
    
        always @(posedge clk) begin
            if (reset)
                q <= 5'h1;
            else
                q <= q_next;
        end
        
    endmodule
    

    3-bit LFSR

    二选一选择器:

    二选一选择器.png

    本题仍是看图连线。

    Solution

    module top_module (
        input [2:0] SW,      // R
        input [1:0] KEY,     // L and clk
        output [2:0] LEDR);  // Q
    
        always @(posedge KEY[0]) begin 
            LEDR[0] <= KEY[1] ? SW[0] : LEDR[2];
            LEDR[1] <= KEY[1] ? SW[1] : LEDR[0]; 
            LEDR[2] <= KEY[1] ? SW[2] : (LEDR[2] ^ LEDR[1]); 
        end
    endmodule

    32-bit LFSR

    本题是 5-bit LFSR 的扩展,若实例化32个DDF显然不是很方便。

    Solution

    module top_module(
        input clk,
        input reset,    // Active-high synchronous reset to 32'h1
        output [31:0] q
    ); 
    
        reg [31:0] q_next; 
        always @(*) begin     // 组合逻辑块(敏感列表为*)
            q_next = q[31:1]; 
            q_next[31] = q[0]; 
            q_next[21] = q[0] ^ q[22];
            q_next[1] = q[0] ^ q[2];
            q_next[0] = q[0] ^ q[1];
        end
    
        always @(posedge clk) begin
            if (reset)
                q <= 32'h1;
            else
                q <= q_next;
        end
        
    endmodule
    

    Shift register (1)

    Solution

    module top_module (
        input clk,
        input resetn,   // synchronous reset
        input in,
        output out);
    
        reg [3:0] Q;  //记录每个DDF输出的Q
        always @(posedge clk ) begin  
            if (!resetn) begin
                Q <= 0;
            end
            else begin
                Q[0] <= in;
                Q[1] <= Q[0];
                Q[2] <= Q[1];
                Q[3] <= Q[2];
            end
        end  
        
        assign out = Q[3];
              
    endmodule
    

    Official Solution

    module top_module (
        input clk,
        input resetn,
        input in,
        output out
    );
    
        reg [3:0] sr;
        
        // Create a shift register named sr. It shifts in "in".
        always @(posedge clk) begin
            if (~resetn)        // Synchronous active-low reset
                sr <= 0;
            else 
                sr <= {sr[2:0], in};
        end
        
        assign out = sr[3];        // Output the final bit (sr[3])
    
    endmodule    

    Shift register (2)

    Solution

    module top_module (
        input [3:0] SW,
        input [3:0] KEY,
        output [3:0] LEDR
    ); 
        
        MUXDFF u1 (
            .clk(KEY[0]),
            .w(KEY[3]),
            .R(SW[3]),
            .E(KEY[1]),
            .L(KEY[2]),
            .Q(LEDR[3])
        );
        
        MUXDFF u2 (
            .clk(KEY[0]),
            .w(LEDR[3]), //易疏忽:不再是KEY[3]了
            .R(SW[2]),
            .E(KEY[1]),
            .L(KEY[2]),
            .Q(LEDR[2])
        );
        
        MUXDFF u3 (
            .clk(KEY[0]),
            .w(LEDR[2]),
            .R(SW[1]),
            .E(KEY[1]),
            .L(KEY[2]),
            .Q(LEDR[1])
        );
        
        MUXDFF u4 (
            .clk(KEY[0]),
            .w(LEDR[1]),
            .R(SW[0]),
            .E(KEY[1]),
            .L(KEY[2]),
            .Q(LEDR[0])
        );
         
    endmodule
    
    //引用自 Latches and Flip-Flops 的 Mux and DFF  
    module MUXDFF (
        input clk, 
        input w, R, E, L, 
        output Q 
    );
        wire mid1 = E ? w : Q;
        wire mid2 = L ? R : mid1;
        always @(posedge clk) begin 
            Q <= mid2;
        end
    endmodule

    3-input LUT

     “移位寄存器设计” + “3 输入查找表(LUT)实现”

    1. 8 位移位寄存器:

      • 输入 S 逐位移入,MSB( Most Significant Bit) 先移入,enable 控制是否移位。
      • 8 个 D 触发器的输出标记为 Q[0] 到 Q[7](移位顺序决定了 Q[0] 是最新移入的位,Q[7] 是最早移入的位)。
      • S 连接到第一个触发器(Q[0])的输入,每个触发器的输出连接到下一个触发器的输入。
      • 当 enable=1 时,每个时钟沿 clk 到来时:

        • Q[0] 接收新输入 S
        • Q[1] 接收原来的 Q[0]
        • Q[2] 接收原来的 Q[1]
        • ……
        • Q[7] 接收原来的 Q[6]
          (效果:数据右移Q[0] 是最新位,Q[7] 是最旧位,MSB 先存入 Q[7]。)
    2. 3 输入查找表(LUT):

      • 输入 A、B、C 作为地址(3 位,共 8 种组合:000~111)。
      • 输出 Z 根据地址选择移位寄存器的对应位:ABC=000 选 Q[0]ABC=001 选 Q[1],…,ABC=111 选 Q[7]

    Solution

    module top_module (  
        input clk,       
        input enable,     // 移位使能(高有效)  
        input S,          // 移位输入(MSB先移入)  
        input A, B, C,    // 3位地址输入  
        output Z        
    );  
    
        reg [7:0] q;     
    
        // 移位寄存器逻辑:
        always @(posedge clk) begin  
            if (enable) begin  
                q <= {q[6:0], S}; 
                // 右移操作(左低位右高位,低位变到高位。但是目前索引中这样写是左高右低):S→q[0],q[0]→q[1],…,q[6]→q[7]  
            end  
        end  
    
        // 用A、B、C作为地址,直接用{A,B,C}制作索引
        assign Z = q[{A, B, C}]; 
    
    endmodule  
    ]]>
    0 https://gokong.space/archives/109/#comments https://gokong.space/feed/
    Verilog 学习笔记(12):Counters https://gokong.space/archives/108/ https://gokong.space/archives/108/ Fri, 25 Jul 2025 08:00:00 +0800 Gokong Four-bit binary counter

    Solution

    module top_module (
        input clk,
        input reset,      // Synchronous active-high reset
        output [3:0] q);
    
        always @(posedge clk) begin
            if(reset == 1) 
                q <= 4'b0000;
            else if(q == 4'b1111)
                q <= 4'b0000;
            else
                q <= q + 1;
        end
    endmodule
    

    Decade counter

    Solution

    module top_module (
        input clk,
        input reset,        // Synchronous active-high reset
        output [3:0] q);
    
        always @(posedge clk) begin
            if(reset == 1) 
                q <= 4'b0000;
            else if(q == 4'b1001) 
                q <= 4'b0000;
            else
                q <= q + 1;
        end
    endmodule
    

    Decade counter again

    Solution

    module top_module (
        input clk,
        input reset,
        output [3:0] q);
    
        always @(posedge clk) begin
            if(reset == 1) 
                q <= 4'b0001;
            else if(q == 4'b1010) 
                q <= 4'b0001;
            else
                q <= q + 1;
        end
    endmodule  

    Slow decade counter

    暂停功能:仅当 slowena 为高电平时,计数器才会在时钟上升沿递增;否则保持当前值。

    Solution

    module top_module (
        input clk,
        input slowena,
        input reset,
        output [3:0] q);
    
        always @(posedge clk) begin  
            if(reset == 1) 
                q <= 4'd0;
            else if(slowena) begin  
                if(q == 4'd9)
                    q <= 4'd0;
                else
                    q = q + 1;
            end else
                q <= q;
        end    
    endmodule
    

    Counter 1-12

    同步复位reset 高电平时,时钟上升沿将计数器同步复位到 1

    使能控制enable 高电平时,计数器运行(计数);enable 低电平时,计数器保持当前值(暂停)。

    **load 的核心作用:

    - **立即覆盖当前值**:当 `load=1` 时,无论计数器当前状态如何,都会在下一个时钟上升沿将 `d` 端口的值加载到计数器输出 `Q`。
    - **优先级高于 `enable`**:即使 `enable=0`(计数器暂停),只要 `load=1`,计数器仍会加载 `d` 的值。
    

    计数器的四种状态:

    loadenable计数器行为
    1X加载 d 的值(例如复位到 1 或回绕到 1)
    01正常计数(Q = Q + 1)
    00保持当前值(暂停)
    10仍加载 d 的值(load 优先级更高)

    已知:

    module count4(
        input clk,
        input enable,
        input load,
        input [3:0] d,
        output reg [3:0] Q
    );  

    Solution

    module top_module (
        input clk,
        input reset,
        input enable,
        output [3:0] Q,
        output c_enable,
        output c_load,
        output [3:0] c_d
    ); 
    
        assign c_enable = enable;
        assign c_load = reset | (Q == 4'd12 && enable);
        assign c_d = c_load ? 4'b1 : 4'b0;
        
        count4 the_counter (
            .clk(clk), 
            .enable(c_enable), 
            .load(c_load), 
            .d(c_d),
            .Q(Q)
        );
    
    endmodule

    Counter 1000

    题中给出了一个十进制的模块,所以需要使用三次以达到把1000Hz降到1Hz.

    已知:

    module bcdcount (
        input clk,
        input reset,
        input enable,
        output reg [3:0] Q
    );

    Solution

    module top_module (
        input clk,
        input reset,
        output OneHertz,
        output [2:0] c_enable
    );
    
        wire [3:0] one, ten, hundred;
        
        // 实例化三个 bcdcount 模块,分别表示个位、十位、百位
        bcdcount counter0 (clk, reset, c_enable[0], one);     // 个位
        bcdcount counter1 (clk, reset, c_enable[1], ten);     // 十位
        bcdcount counter2 (clk, reset, c_enable[2], hundred); // 百位
        
        // 使能信号控制逻辑
        assign c_enable[0] = 1'b1;                            // 个位始终使能(持续计数)
        assign c_enable[1] = (one == 4'd9);                   // 十位仅在个位达到9时使能
        assign c_enable[2] = (ten == 4'd9) && (one == 4'd9);  // 百位仅在个位和十位都为9时使能
    
        // 生成1Hz脉冲:当且仅当计数器达到999时,OneHertz输出高电平
        assign OneHertz = (hundred == 4'd9) && (ten == 4'd9) && (one == 4'd9);
    endmodule

    4-digit decimal counter

    题目要求:
    构建一个 4 位 BCD(二进制编码十进制)计数器

    1. 位分配:每个十进制位用 4 位二进制编码:

      • q[3:0] → 个位(0~9),
      • q[7:4] → 十位(0~9),
      • q[11:8] → 百位(0~9),
      • q[15:12] → 千位(0~9)(共 4 位,构成 0000~9999 的循环)。
    2. 使能信号:对于 十位、百位、千位(即高位的三位),需输出对应的使能信号(如 ena[1] 对应十位、ena[2] 对应百位、ena[3] 对应千位),指示该高位何时应递增(例如:个位计满 9 时,触发十位的使能信号)。
    3. 实现方式:可通过 实例化或修改 1 位十进制计数器(如 “十进制计数器模块”,实现 0~9 循环)来级联构建。
    4. 信号解读:
    5. clk:时钟信号(上升沿触发计数)。
    6. reset:同步复位信号(高电平时,计数器复位到 0)。
    7. q[3:0]:个位的 BCD 值(如波形中依次为 7→8→9→0→1…,实现 0~9 循环)。
    8. ena[1]:十位的使能信号(仅当个位计满 9 时,ena[1] 置高,触发十位递增)。
    9. q[7:4]:十位的 BCD 值(如波形中 5→6→0…,受 ena[1] 控制,计满 9 后回 0)。

    Solution

    module top_module (
        input clk,
        input reset,   // Synchronous active-high reset
        output [3:1] ena,
        output [15:0] q
    );
    
        //实例化四个1位十进制计数器,分别表示个位、十位、百位、千位
        count1bit u1 (clk,reset,enas,q[3:0]);
        count1bit u2 (clk,reset,ena[1],q[7:4]);
        count1bit u3 (clk,reset,ena[2],q[11:8]);
        count1bit u4 (clk,reset,ena[3],q[15:12]);
        
        //其中:
        wire enas;
        assign enas = 1'b1;  //个位始终使能
    
        // 生成十位的使能信号:当个位达到9时,ena[1]置高,触发十位计数
        assign ena[1] = (q[3:0] == 4'b1001) ? 1'b1 : 1'b0;
        // 生成百位的使能信号:当十位和个位都达到9时,ena[2]置高,触发百位计数
        assign ena[2] = ((q[7:4] == 4'b1001) && (q[3:0] == 4'b1001)) ? 1'b1 : 1'b0;
        assign ena[3] = ((q[11:8] == 4'b1001) && (q[7:4] == 4'b1001) && (q[3:0] == 4'b1001)) ? 1'b1 : 1'b0;
        
    endmodule
    
    //自主构建 1位十进制计数器
    module count1bit (
        input clk,
        input reset,
        input ena,       // 使能信号:若高电平,则允许该计数器计数
        output [3:0] q   //4为BCD输出
    );
    
        always @(posedge clk) begin
            if(reset)
               q <= 4'b0000;
            else if(ena) begin
                if(q == 4'b1001)
                q <= 4'b0000;
                else
                    q <= q + 1;
            end
            else
                q <= q;  //使能无效时,保持当前值
        end
                        
    endmodule

    12-hour clock

    Solution

    module top_module(
        input clk,
        input reset,
        input ena,
        output pm,
        output [7:0] hh,
        output [7:0] mm,
        output [7:0] ss); 
        
        //------------------- 秒计数器(00-59) ---------------------
        always @(posedge clk) begin 
            if (reset) 
                ss <= 8'd0;
            else if (ena) begin 
                if (ss[3:0] == 4'd9) begin //低位到9 
                    ss[3:0] <= 4'd0;       //清零低位
                    if (ss[7:4] == 4'd5)   //低位到9并且高位到5 
                        ss[7:4] <= 4'd0;   //清零高位
                    else  
                        ss[7:4] <= ss[7:4] + 4'd1; 
                end 
                else 
                    ss[3:0] <= ss[3:0] + 4'd1; 
            end
        end
        
          //------------------- 分计数器(00-59) ---------------------
        always @(posedge clk) begin 
            if (reset) 
                mm <= 8'd0;
            else if (ena && ss[7:4] == 4'd5 && ss[3:0] == 4'd9) begin //ena有效并且秒数为59时触发使能
                if (mm[3:0] == 4'd9)begin  //低位到9 
                    mm[3:0] <= 4'd0;       //清零低位
                    if (mm[7:4] == 4'd5)   //低位到9并且高位到5 
                        mm[7:4] <= 4'd0;   //清零高位
                    else  
                        mm[7:4] <= mm[7:4] + 4'd1; 
                end 
                else
                    mm[3:0] <= mm[3:0] + 4'd1;
            end
        end 
        
          //------------------- 时计数器(01-12) ---------------------
        always @(posedge clk) begin 
            if (reset) begin
                hh[7:4] <= 4'd1;    //若直接写hh <= 8'd12; 是错误的,因为这是BCD码。
                hh[3:0] <= 4'd2;
                pm <= 1'd0;   
            end
            else if (ena &&
                mm[7:4] == 4'd5 && mm[3:0] == 4'd9 &&
                ss[7:4] == 4'd5 && ss[3:0] == 4'd9) begin //ena有效,并且秒数和分钟都为59时,触发使能
                
                //低位到9并且高位为0 (09:59:59->10:00:00)
                if (hh[3:0] == 4'd9 && hh[7:4] == 4'd0) begin 
                    hh[3:0] <= 4'd0;
                    hh[7:4] <= 4'd1; 
                end
                
                //低位到2并且高位到1 (12:59:59->01:00:00) ,回绕回01
                else if (hh[3:0] == 4'd2 && hh[7:4] == 4'd1) begin 
                    hh[7:4] <= 4'd0;
                    hh[3:0] <= 4'd1;
                end
                    
                //低位到1并且高位到1 (11:59:59->12:00:00) ,pm需要转换 
                else if (hh[3:0] == 4'd1 && hh[7:4] == 4'd1) begin 
                    hh[7:4] <= 4'd1;
                    hh[3:0] <= 4'd2;
                    pm <= ~pm;
                end
    
                else
                    hh[3:0] <= hh[3:0] + 4'd1;    
            end  
        end     
    
    endmodule

    一些小问题:
    「在 Verilog 中,always块中赋值的信号必须声明为reg。你的代码中,pmhhmmss均在always块中被赋值,但声明时使用了默认的wire型(output pmoutput [7:0] hh等),这会导致编译错误(“非法在 always 块中赋值 wire 型信号”)。」但是,在本问题中,直接赋值依然可以Success,这是因为本网站对语法检查不够严格.

    ]]>
    0 https://gokong.space/archives/108/#comments https://gokong.space/feed/
    Verilog 学习笔记(11):Latches and Flip-Flops https://gokong.space/archives/102/ https://gokong.space/archives/102/ Thu, 24 Jul 2025 14:30:00 +0800 Gokong D flip-flop

    Clocked always blocks should use non-blocking assignments: <=.

    Solution

    module top_module (
        input clk,    // Clocks are used in sequential circuits
        input d,
        output reg q );//
    
        // Use a clocked always block
        //   copy d to q at every positive edge of clk
    
        always @(posedge clk) begin 
            q <= d;  
        end
    endmodule
    

    D flip-flops

    Solution

    module top_module (
        input clk,
        input [7:0] d,
        output [7:0] q
    );
    
        always @(posedge clk) begin 
            q <= d;
        end  
    endmodule

    DFF with reset

    Solution

    module top_module (
        input clk,
        input reset,            // Synchronous reset
        input [7:0] d,
        output [7:0] q
    );
    
        always @(posedge clk) begin  
            if (reset) begin  //复位  
                q <= 8'b0;  
            end else begin  
                q <= d;
            end  
        end
    endmodule
    

    DFF with reset value

    正触发: posedge
    负触发: negedge

    Solution

    module top_module (
        input clk,
        input reset,
        input [7:0] d,
        output [7:0] q
    );
        
        always @(negedge clk) begin  
            if (reset) begin 
                q <= 8'h34;
            end else begin 
                q <= d;
            end
        end 
    
    endmodule
    

    DDF with asynchronous reset

    异步复位

    The only difference in code between synchronous and asynchronous reset flip-flops is in the sensitivity list——将复位信号写入敏感列表中。

    Solution

    module top_module (
        input clk,
        input areset,   // active high asynchronous reset
        input [7:0] d,
        output [7:0] q
    );
        
        always @(posedge clk, posedge areset) begin  
            if (areset) 
                q <= 0;
            else 
                q <= d;
        end
    
    endmodule
    

    DFF with byte enable

    wire vs. reg

    特性wirereg
    存储功能无存储功能,数值由驱动源决定有存储功能,能保持当前值直至下一次赋值
    赋值方式只能使用连续赋值(如assign语句)只能在initialalways块内赋值
    使用场景用于连线、组合逻辑输出用于时序逻辑(如触发器)、组合逻辑中的中间变量
    默认初始值默认值为z(高阻态)默认值为x(未知状态)

    下面通过两段代码示例,直观展示wirereg的使用差异:

    // wire的使用示例
    module wire_example;
        reg a, b;      // 输入信号定义为reg类型
        wire out;      // 输出信号定义为wire类型
        
        // 连续赋值,out的值会随着a和b的变化而立即改变
        assign out = a & b;
        
        initial begin
            a = 1'b0; b = 1'b0;
            #10 a = 1'b1;
            #10 b = 1'b1;
        end
    endmodule
    // reg的使用示例
    module reg_example;
        reg clk, rst, in;
        reg out;       // 输出信号定义为reg类型,因为在always块中被赋值
        
        // 时序逻辑:在时钟上升沿更新out的值
        always @(posedge clk or posedge rst) begin
            if (rst) begin
                out <= 1'b0;  // 复位时将out置为0
            end else begin
                out <= in;    // 时钟上升沿时将in的值赋给out
            end
        end
        
        initial begin
            clk = 1'b0;
            forever #5 clk = ~clk;  // 生成周期为10的时钟信号
        end
    endmodule

    同步低有效复位(resetn

    • 同步性:复位仅在 时钟上升沿 生效(异步复位会在复位信号变化时立即生效,此处为同步)。
    • 低有效:resetn = 0 时触发复位,resetn = 1 时正常工作。

    字节使能(byteena[1:0]

    • 分组控制:16 位数据分为 低字节(d[7:0],由 byteena[0] 控制)和 高字节(d[15:8],由 byteena[1] 控制)。
    • 功能:仅当对应 byteena 位为 1 时,该字节才会被更新;为 0 时,该字节保持原值。

    适用场景

    “16 位寄存器 + 字节级写保护 + 同步复位”
    (如: CPU 数据寄存器、总线接口……)

    Solution

    module top_module (
        input clk,
        input resetn,
        input [1:0] byteena,
        input [15:0] d,
        output [15:0] q
    );
        
        always @(posedge clk) begin  // 上升沿触发
        if (!resetn) begin           // 同步低有效复位:resetn=0 触发复位
            q <= 16'b0;    
        end else begin               // 正常工作模式
            
            // 低字节更新:byteena[0]=1则更新d[7:0],否则保持q[7:0]
            q[7:0]  <= byteena[0] ? d[7:0]  : q[7:0];  
            
            // 高字节更新:byteena[1]=1则更新d[15:8],否则保持q[15:8]
            q[15:8] <= byteena[1] ? d[15:8] : q[15:8]; 
        end
    end
    endmodule

    D Latch

    DLatch.png

    锁存器的状态更新依赖于控制信号(如使能信号 EN)的 “电平”,而非 “边沿”(上升沿或下降沿),这是它与触发器最核心的区别。
    而触发器仅在时钟信号的特定边沿(如上升沿)才采样输入并更新输出,其他时间无论输入如何变化,输出都保持不变。

    电平敏感的敏感列表:
    由于锁存器对控制信号的 “电平” 敏感,always块的敏感列表必须包含所有可能触发输出变化的电平信号
    例如,对于 D 锁存器(输入 D、使能 EN、输出 Q),敏感列表需包含END(因为当 EN 为有效电平时,D 的变化会直接影响 Q;当 EN 的电平变化时,也可能导致 Q 锁存或更新),即:
    always @(EN or D) begin ... end

    非阻塞赋值(Non-blocking Assignment):
    尽管锁存器是电平敏感,但作为时序元件(有记忆功能),其输出的更新并非 “立即响应” 输入(存在微小的存储延迟)。因此,Verilog 中需用非阻塞赋值(<=) 描述输出更新,以模拟这种 “延迟更新” 的特性,避免竞争条件。
    例如:Q <= D;(而非阻塞赋值=,后者会立即更新,不符合时序元件的行为)。

    Solution

    module top_module (
        input d, 
        input ena,
        output q);
    
        always @(ena or d) begin  //level-sensitive sensitivity lists
            if(ena)
            q <= d;
        end
        
    endmodule

    DFF (1)

    异步DFF

    DFF.png

    Solution

    module top_module (
        input clk,
        input d, 
        input ar,   // asynchronous reset
        output q);
    
        always @(posedge clk,posedge ar) begin  
            if(ar)
                q <= 0;
            else 
                q <= d;
        end
    endmodule

    DFF (2)

    同步DFF
    DFF2.png

    Solution

    module top_module (
        input clk,
        input d, 
        input r,   // synchronous reset
        output q);
    
        always @(posedge clk) begin  
            if(r)
                q <= 0;
            else
                q <= d;
        end
    endmodule

    DFF + gate

    Solution

    module top_module (
        input clk,
        input in, 
        output out);
    
        always @(posedge clk) begin 
            out <= out ^ in;  //out可以同时存在于赋值的两侧
        end
    endmodule
    

    Mux and DFF (1)

    Solution

    module top_module (
        input clk,
        input L,
        input r_in,
        input q_in,
        output reg Q);
    
        //Mux 2-to-1
        wire mid;
        assign mid = L ? r_in : q_in;
        
        //DFF 同步
        always @(posedge clk) begin 
            Q <= mid;
        end
           
    endmodule
    

    Mux and DFF (2)

    Solution

    module top_module (
        input clk,
        input w, R, E, L,
        output Q
    );
        wire mid1 = E ? w : Q;
        wire mid2 = L ? R : mid1;
        always @(posedge clk) begin 
            Q <= mid2;
        end
        
    endmodule
    

    DDFs and gates

    Solution

    module top_module (
        input clk,
        input x,
        output z
    ); 
        wire Q1;
        wire Q2;
        wire Q3;  
        wire mid1;
        wire mid2;
        wire mid3;
        
        assign mid1 = x ^ Q1;
        assign mid2 = x & ~Q2;
        assign mid3 = x | ~Q3;
        
        always @(posedge clk) begin
            Q1 <= mid1;
            Q2 <= mid2;
            Q3 <= mid3;
        end
        
        assign z = ~(Q1 | Q2 | Q3);
    endmodule
    

    Create circuit from truth table

    Solution

    module top_module (
        input clk,
        input j,
        input k,
        output Q); 
        
        always @(posedge clk) begin 
            if ((j != k) && (j ^ k == 1)) 
                Q <= j;
            else if (j == 0)
                Q <= Q;
            else 
                Q <= ~Q;
        end
    endmodule
    

    Detect an edge

    精彩的动态过程

    检测 8 位输入信号中每个 bit 从 0 到 1 的上升沿变化,并在变化发生后的紧接着的下一个时钟周期将对应输出位置 1。

    核心思路:延迟采样 + 边沿检测

    1. 延迟采样:用寄存器 prev_in 保存前一个时钟周期的输入信号 in,这样可以对比当前周期和前一周期的数值。
    2. 边沿检测:对每个 bit,若当前周期是 1 且前一周期是 0,则说明发生了0→1的上升沿,对应输出位 pedge 置 1。

    Solution

    module top_module (
        input clk,
        input [7:0] in,
        output [7:0] pedge
    );
    
        reg [7:0] pre_in;
        always @(posedge clk) begin 
            pre_in <= in;
        end
        always @(posedge clk) begin 
            pedge <= in & ~pre_in;
            //直观理解:随着时钟的进行,下一个周期将自动(重新检测)回落状态。
        end
    
    endmodule

    Official Solution

    module top_module(
        input clk,
        input [7:0] in,
        output reg [7:0] pedge);
        
        reg [7:0] d_last;    
                
        always @(posedge clk) begin
            d_last <= in;            // Remember the state of the previous cycle
            pedge <= in & ~d_last;    // A positive edge occurred if input was 0 and is now 1.
        end
        
    endmodule

    Detect both edges

    Solution

    module top_module (
        input clk,
        input [7:0] in,
        output [7:0] anyedge
    );
    
        reg [7:0] pre_in;
        always @(posedge clk) begin 
            pre_in <= in;
            anyedge <= (in & ~pre_in) | (~in & pre_in);  
        end 
    
    endmodule
    

    Edge capture register

    边沿捕获寄存器

    题目:
    对于 32 位向量中的每个位,当输入信号在一个时钟周期内从 1 变为下一个时钟周期内的 0 时捕获。“捕获”意味着输出将保持为 1,直到寄存器被复位(同步复位)。
    每个输出位的行为类似于 SR 触发器:在发生 1 到 0 的转换后的周期内,应将输出位设置为(1)。当复位为高电平时,应在正时钟沿将输出位复位为(0)。如果上述两个事件同时发生,则复位优先。在下面的示例波形的最后 4 个周期中,“复位(reset)”事件比“设置(set)”事件早一个周期发生,因此这里没有冲突。
    在下面的示例波形中,为了清晰起见,再次分别显示了复位、in[1] 和 out[1]。

    边沿捕获寄存器.png

    Solution

    module top_module (
        input clk,
        input reset,
        input [31:0] in,
        output [31:0] out
    );
        
        reg [31:0] pre_in;
        always @(posedge clk) begin 
            pre_in <= in;
        end
            
        always @(posedge clk) begin 
            if(reset)
                out <= 32'b0;  //同步复位
            else 
                out <= (~in & pre_in) | out;
                /** 
                1. out是上一周期的输出值;  
                2. 若检测到下降沿(~in & in_d 对应位为 1),则无论 out 原值如何,结果为 1(即:置位);
                3. 若未检测到下降沿,则结果等于 out 原值(即保持不变)。
                **/
        end
    
    endmodule

    Dual-edge triggered flip-flop

    巧妙的选择

    双向触发触发器
    双向触发触发器  .png

    Solution

    module top_module (
        input clk,
        input d,
        output q
    );
    
        reg pre_d_pos, pre_d_neg;
        
        always @(posedge clk) begin 
            pre_d_pos <= d;
        end
        always @(negedge clk) begin 
            pre_d_neg <= d;
        end
        
        assign q =  clk ? pre_d_pos : pre_d_neg;  
        /**
        clk == 1 时,选择 “d的上一个上升沿的值”;
        clk == 0 时,选择 “d的上一个下升沿的值”;
        即,output 具有“跟随的效果”:clk每跳变一次(无论上跳还是下调),q都将对“d的上一状态(pre_d_pos 或 pre_d_neg)”进行一次跟随。
        **/
    endmodule
    ]]>
    0 https://gokong.space/archives/102/#comments https://gokong.space/feed/