Verilog手撕代码:从数字“1”到电路直觉的构建
1. 从“1”开始为什么Verilog手撕代码是数字IC的基石最近在带新人发现一个挺有意思的现象很多刚接触数字电路设计的朋友一上来就想搞懂复杂的CNN加速器或者DDR控制器但往往在写一个最简单的计数器时逻辑就乱了套。这让我想起自己刚入行时导师丢给我的第一个任务就是“用Verilog实现一个‘1’”。当时觉得这太简单了甚至有点被轻视的感觉但真正动手才发现这个看似简单的“1”背后藏着从组合逻辑到时序逻辑从行为描述到可综合代码的整个思维转变过程。今天我就围绕“关于‘1’的Verilog相关手撕代码”这个主题和大家深入聊聊那些面试和工作中高频出现的基础代码块。这些代码是构建一切复杂系统的砖瓦手撕它们不是为了炫技而是为了建立最扎实的电路直觉。无论你是正在准备秋招的应届生还是希望夯实基础的工程师这篇文章都会带你从“是什么”深入到“为什么”并分享我踩过的坑和总结的技巧。2. 核心逻辑拆解从行为描述到电路映射2.1 “1”的多种面孔常量、向量与参数化在Verilog里一个孤零零的“1”可以代表很多东西。最直接的就是一个单比特的逻辑高电平1‘b1。但更多时候我们需要的是一个特定宽度的向量比如一个4位的常数1写作4’d1或4‘b0001。这里第一个坑就来了直接写1在赋值给一个多位寄存器时会被解释为32’d1因为Verilog中未指定位宽的整数默认为32位这可能导致意想不到的位宽扩展和资源浪费。注意在 always 块内对 reg 型变量赋值时务必保证赋值符号右侧的位宽与左侧匹配否则综合工具会进行隐式转换可能产生你不期望的比较器或截断逻辑。更深一层“1”可以是一个参数parameter或局部参数localparam。例如定义一个状态机的IDLE状态localparam S_IDLE 1‘b0, S_WORK 1’b1;。这里“1”作为状态编码。更进一步在循环生成generate for或数组初始化中“1”可以作为索引的起始或步进值。理解“1”在不同上下文中的具体含义是写出严谨代码的第一步。我见过有人因为状态编码用了2‘b01和2’b10结果在状态判断时用了if(state 1‘b1)导致永远无法进入第二个状态调试了半天。2.2 组合逻辑中的“1”比较、选择与运算在组合逻辑中“1”最常作为比较的基准或运算的操作数。例如判断一个信号是否有效assign valid_flag (data_in ! 8‘b0);。这里不等于0本质上就是在判断数据中是否有“1”。再比如一个多路选择器MUX其选择信号sel为1时输出a为0时输出bassign out sel ? a : b;。这个三元运算符映射到硬件上就是一个二选一MUX。一个更典型的“手撕代码”题目是奇偶校验位生成。要求根据输入的8位数据计算其奇校验位使得9位数据中“1”的个数为奇数。很多新手会写一个for循环来计算但for循环在可综合代码中会被展开可能存在时序问题。更好的做法是使用缩减异或运算符^assign parity_bit ^data_in; // 偶校验位 assign odd_parity_bit ~(^data_in); // 奇校验位这行代码综合出来就是一个多输入的异或门/同或门树非常简洁高效。这里的关键是理解运算符的位宽^data_in是对所有位进行异或结果是一个单比特这就是“1”的另一种产生方式。2.3 时序逻辑中的“1”计数器、状态机与使能信号时序逻辑是Verilog的核心也是面试手撕代码的重灾区。这里的“1”常常代表一个时钟周期的脉冲或一个稳定的使能信号。最经典的莫过于计数器。实现一个从0计数到9然后清零的十进制计数器是必考题。核心代码通常如下always (posedge clk or negedge rst_n) begin if(!rst_n) begin cnt 4‘d0; end else if (cnt_en) begin // cnt_en是一个使能信号为“1”时计数 if (cnt 4’d9) begin cnt 4‘d0; end else begin cnt cnt 4’d1; // 这里的“1”是加法操作数 end end end这里有两个“1”cnt_en信号为“1”表示使能4‘d1是计数步长。我踩过的坑是曾经把cnt 4’d9写成cnt 4‘d9导致计数器在9状态只停留了一个周期不符合设计需求。另一个细节是如果使能信号cnt_en不是周期性的上述写法可能会错过使能这时需要更复杂的控制逻辑。另一个离不开“1”的是有限状态机FSM。三段式状态机是推荐写法。其中状态转移条件往往就是某个或某几个信号为“1”。例如// 状态转移逻辑第二段组合逻辑 always (*) begin next_state IDLE; case (current_state) IDLE: if (start 1‘b1) next_state WORK; WORK: if (done 1’b1) next_state IDLE; default: next_state IDLE; endcase end这里的1‘b1就是触发状态转移的“钥匙”。务必注意状态转移条件要互斥且完备否则会生成锁存器Latch这是设计大忌。我曾经因为在一个状态下的if-else没有写全缺少default分支综合后出现了难以调试的Latch导致芯片功能异常。3. 高频手撕代码场景深度剖析3.1 时钟分频与使能脉冲生成生成比系统时钟慢的时钟信号或者产生周期性的使能脉冲是基础中的基础。奇数分频如3分频、5分频比偶数分频更常考因为它需要双边沿操作或产生相位差。奇数分频以3分频为例一种经典方法是产生两个占空比非50%的时钟然后相或。reg [1:0] cnt; reg clk1, clk2; // 计数器 always (posedge clk or negedge rst_n) begin if(!rst_n) cnt 2‘d0; else cnt (cnt 2’d2) ? 2‘d0 : cnt 2’d1; end // 上升沿触发生成的时钟 always (posedge clk or negedge rst_n) begin if(!rst_n) clk1 1‘b0; else if (cnt 2’d0) clk1 ~clk1; end // 下降沿触发生成的时钟关键 always (negedge clk or negedge rst_n) begin if(!rst_n) clk2 1‘b0; else if (cnt 2’d1) clk2 ~clk2; end assign clk_div3 clk1 | clk2;这段代码的精髓在于利用了下降沿来生成第二个时钟clk2两者相位错开相或后得到占空比接近50%的3分频时钟。面试时如果只写出计数器而想不到双边沿的方法就说明对时钟的理解还不够深入。更工程化的做法是生成使能脉冲而不是直接生成时钟。因为直接生成时钟Generated Clock会带来时钟域、时钟偏斜Skew等复杂问题。推荐使用使能信号reg [1:0] cnt; reg div_en; // 分频使能信号每3个周期产生一个高脉冲 always (posedge clk or negedge rst_n) begin if(!rst_n) begin cnt 2‘d0; div_en 1’b0; end else begin if (cnt 2‘d2) begin cnt 2’d0; div_en 1‘b1; // 产生一个周期的高脉冲 end else begin cnt cnt 2’d1; div_en 1‘b0; end end end // 后续模块在 div_en 为高时进行操作 always (posedge clk or negedge rst_n) begin if(!rst_n) data_out ‘d0; else if (div_en) begin // 使用使能信号而非新时钟 data_out data_in; end end这种方法将所有逻辑都统一在原始时钟域下更安全可靠。3.2 边沿检测与脉冲同步检测一个异步信号的上升沿并将其转换为一个时钟周期宽度的同步脉冲是跨时钟域处理CDC的入门课也是高频面试题。经典的边沿检测电路reg sig_dly; wire pos_edge; always (posedge clk or negedge rst_n) begin if(!rst_n) sig_dly 1‘b0; else sig_dly async_sig; end assign pos_edge (~sig_dly) async_sig; // 组合逻辑检测上升沿这段代码将异步信号async_sig用本地时钟clk打一拍得到sig_dly然后通过组合逻辑判断当前拍采到的是‘1’而上一拍是‘0’从而检测到上升沿。但这里有个巨坑async_sig可能相对于clk是亚稳态的直接将其接入组合逻辑亚稳态可能传播导致pos_edge产生毛刺或错误脉冲。正确的做法是采用两级或多级寄存器同步后再检测边沿reg [2:0] sig_sync; always (posedge clk or negedge rst_n) begin if(!rst_n) sig_sync 3‘b000; else sig_sync {sig_sync[1:0], async_sig}; // 三级同步 end wire sig_stable sig_sync[2]; // 同步后的稳定信号 reg sig_stable_dly; wire pos_edge_pulse; always (posedge clk or negedge rst_n) begin if(!rst_n) sig_stable_dly 1’b0; else sig_stable_dly sig_stable; end assign pos_edge_pulse (~sig_stable_dly) sig_stable; // 对稳定信号检测边沿这里用了三级触发器进行同步极大降低了亚稳态传播到后续逻辑的概率。检测边沿是在完全同步后的信号sig_stable上进行的这样产生的脉冲pos_edge_pulse才是干净可靠的。记住对于任何来自异步时钟域的信号都必须先同步再使用。3.3 序列检测器与滑动窗口序列检测器是状态机和移位寄存器结合的典型应用。例如检测输入数据流中是否出现连续序列 “1011”。使用状态机实现定义状态代表已匹配到的序列前缀。这种方法逻辑清晰但状态数随序列长度指数增长最坏情况。localparam S0 3‘d0, S1 3’d1, S2 3‘d2, S3 3’d3, S4 3‘d4; reg [2:0] state, next_state; reg det_out; // 状态转移 always (*) begin next_state S0; case(state) S0: next_state (data_in 1’b1) ? S1 : S0; S1: next_state (data_in 1‘b0) ? S2 : S1; S2: next_state (data_in 1’b1) ? S3 : S0; S3: next_state (data_in 1‘b1) ? S4 : S1; // 注意匹配到“101”后再来一个“1”才成功 S4: next_state (data_in 1’b1) ? S1 : S2; // 成功后的状态转移考虑重叠序列“1011 011” default: next_state S0; endcase end // 输出逻辑当状态为S4时输出检测成功脉冲 always (posedge clk or negedge rst_n) begin if(!rst_n) det_out 1‘b0; else det_out (next_state S4); end这里的关键是状态S4的定义和成功后的转移。如果序列不允许重叠即检测到“1011”后下一个比特从新序列开始那么S4的次态应为S0。如果允许重叠即“1011011”被认为包含两个“1011”则次态应为S1因为末尾的“1”可以作为下一个序列的开头。面试官常通过这个问题考察你对序列重叠情况的理解。使用移位寄存器实现这种方法更直观将输入数据移位然后直接比较寄存器值。reg [3:0] shift_reg; always (posedge clk or negedge rst_n) begin if(!rst_n) shift_reg 4‘b0; else shift_reg {shift_reg[2:0], data_in}; end assign det_out (shift_reg 4’b1011);这种方法简单粗暴但不够灵活且当序列很长时比较器会很大。通常短序列用移位寄存器长序列或复杂规则用状态机。3.4 异步FIFO的深度计算与指针设计虽然完整手撕一个异步FIFO代码量较大但面试中经常要求你计算FIFO的最小深度并解释读写指针的同步原理。这是考察对跨时钟域和时序约束理解深度的绝佳题目。FIFO深度计算假设写时钟频率f_w 100MHz写突发长度B 120读时钟频率f_r 50MHz。问传输这120个数据FIFO需要多深写120个数据所需时间T_write B / f_w 120 / 100e6 1200 ns。在这1200 ns内读侧能读走的数据量N_read f_r * T_write 50e6 * 1200e-9 60。因此最大缓存数据量 B - N_read 120 - 60 60。考虑最坏情况FIFO深度至少需要60。但通常为了安全会取2的整数次幂如64并留有一定余量。格雷码指针与同步异步FIFO的核心是使用格雷码Gray Code作为读写指针。格雷码相邻数值间只有一位变化当指针被同步到另一个时钟域时即使发生亚稳态指针值也只会跳变到相邻值不会出现从“0111”跳变到“1000”二进制这种多位同时跳变的灾难性错误。// 二进制转格雷码 function [ADDR_WIDTH-1:0] bin2gray; input [ADDR_WIDTH-1:0] bin; begin bin2gray bin ^ (bin 1); end endfunction // 写指针逻辑示例 always (posedge wclk or negedge wrst_n) begin if(!wrst_n) begin wptr_bin ‘d0; wptr_gray ‘d0; end else if (winc !wfull) begin wptr_bin wptr_bin 1‘b1; wptr_gray bin2gray(wptr_bin 1’b1); // 注意是对加1后的二进制数转换 end end // 将写指针格雷码同步到读时钟域打两拍 always (posedge rclk or negedge rrst_n) begin if(!rrst_n) {wptr_gray_sync_r, wptr_gray_sync} ‘d0; else {wptr_gray_sync_r, wptr_gray_sync} {wptr_gray_sync, wptr_gray}; end这里的关键细节是wptr_gray必须由wptr_bin 1‘b1转换而来而不是由wptr_bin转换后再加1。因为格雷码的加法没有直接定义。空满判断是通过比较同步后的格雷码指针来进行的虽然同步导致指针“过时”但格雷码的特性保证了这不会引起误判在深度为2的幂次方时。4. 工程实践中的技巧与避坑指南4.1 代码风格与可综合约束手写代码不仅要功能对还要整洁、可维护、可综合。一些铁律时序逻辑一律用非阻塞赋值组合逻辑一律用阻塞赋值。这是避免仿真与综合结果不一致的首要原则。我见过有人在一个always (*)块里混用两种赋值导致仿真通过了综合后的网表功能完全错误。避免使用初始化语句。reg [3:0] cnt 4‘d0;这种写法在仿真中有效但大部分综合工具会忽略或者将其解释为上电复位值与实际的复位信号可能冲突。可靠的初始化必须通过复位逻辑实现。小心锁存器Latch。在组合逻辑的always块中如果if或case语句没有写全所有分支或者对某些输入变化不敏感就会综合出锁存器。锁存器对毛刺敏感静态时序分析困难应尽量避免。确保组合逻辑的always块中所有输入信号的任意变化都能导致输出被明确赋值。参数化设计。使用parameter来定义位宽、深度等提高代码复用性。例如parameter DATA_WIDTH 8;然后reg [DATA_WIDTH-1:0] data;。4.2 仿真调试与Testbench编写代码写出来能通过仿真才算成功了一半。一个良好的测试平台Testbench至关重要。时钟与复位生成这是Testbench的骨架。initial begin clk 1‘b0; forever #5 clk ~clk; // 10ns周期100MHz时钟 end initial begin rst_n 1’b0; #100 rst_n 1‘b1; // 复位100ns后释放 end激励生成避免使用绝对延时使用(posedge clk)等事件控制使激励与时钟同步。initial begin data_in 8‘d0; valid 1’b0; (negedge rst_n); // 等待复位结束 repeat(10) (posedge clk); // 空等10个周期 for(int i0; i100; i) begin (posedge clk); valid 1‘b1; data_in $urandom_range(0, 255); // 生成随机数 end (posedge clk); valid 1’b0; end自动检查不要只用肉眼看波形。在Testbench中加入断言assertion或自动比较逻辑。always (posedge clk) begin if (valid ready) begin expected_data (data_in * 2); // 假设DUT功能是乘2 if (data_out ! expected_data) begin $error(“Mismatch at time %t: in%h, out%h, exp%h”, $time, data_in, data_out, expected_data); end end end4.3 综合与静态时序分析STA意识写代码时就要想着电路想着时序。关键路径组合逻辑过长会导致建立时间Setup Time违例。例如一个大的优先级选择器if-else if 链或复杂的算术运算如乘法、除法。解决方法插入寄存器进行流水线切割。保持时间通常由时钟偏斜和短路径引起。确保信号从发送触发器到接收触发器之间的延迟不能太短。在FPGA中工具通常能较好地处理在ASIC中需要仔细约束。扇出过大一个信号驱动太多负载会导致延迟增加、斜率变差。可以通过插入缓冲器Buffer或复制寄存器Register Duplication来解决。使用generate for进行规整结构描述当需要实例化多个相同模块时generate for比手动复制粘贴更安全、更易维护。genvar i; generate for (i0; i8; ii1) begin: BIT_SLICE my_module u_my_module ( .clk(clk), .data_in(data_bus[i]), .data_out(result_bus[i]) ); end endgenerate从“1”出发我们遍历了常量定义、组合与时序逻辑、分频、边沿检测、序列检测、异步FIFO核心概念等数字逻辑设计的核心关卡。手撕代码的本质不是背诵而是理解每一个赋值、每一个运算符、每一个always块所对应的实际电路。当你看到一行代码脑海中能立刻浮现出对应的门电路、触发器和连线时你就真正入门了。最后分享一个我自己的习惯写完任何一段代码尤其是状态机我都会画一个简单的状态转移图哪怕只是草稿。这能帮你一眼看出逻辑是否完备转移条件是否互斥很多时候比仿真更快地发现设计缺陷。

相关新闻

薄荷:穿越千年的清凉之味

薄荷:穿越千年的清凉之味

初秋午后,指尖捻起一片薄荷叶,轻轻揉搓,那股清冽的凉意便从叶脉间迸发出来,直抵鼻腔。这味道里藏着风——不是温吞的南风,而是穿林打叶的山风;藏着时节——不是万物蛰伏的深冬,而是暑气未消却已…

2026/7/29 8:02:52 阅读更多 →
工业物联网通信:LTE Cat 1模组与MCU的稳定连接方案

工业物联网通信:LTE Cat 1模组与MCU的稳定连接方案

1. 工业级物联网通信的核心挑战与解决方案在工业自动化、远程监控和智能设备领域,稳定可靠的通信连接是系统设计的生命线。我曾在多个工业现场见证过通信中断导致的产线停摆——温度传感器数据丢失可能引发烘烤炉过热,PLC指令延迟会造成机械臂动作不同步…

2026/7/29 8:02:52 阅读更多 →
MOSFET体二极管反向恢复:双脉冲测试与仿真验证全解析

MOSFET体二极管反向恢复:双脉冲测试与仿真验证全解析

1. 项目概述:深入理解MOSFET的“暗面”搞功率电子的,尤其是做开关电源、电机驱动的朋友,对MOSFET肯定不陌生。我们平时关注点大多在它的导通电阻Rds(on)、栅极电荷Qg、开关速度这些“正面”参数上,数据手册也把这些标得清清楚楚。…

2026/7/29 8:02:52 阅读更多 →

最新新闻

C++内存管理全解析:从栈堆原理到智能指针与内存池实战

C++内存管理全解析:从栈堆原理到智能指针与内存池实战

1. 项目概述:为什么C程序员必须直面内存管理?干了这么多年C,我见过太多新手和老手在内存问题上栽跟头。一个看似简单的“Hello World”程序,背后可能就藏着内存泄漏的隐患。C给了我们无与伦比的性能和控制力,但代价就是…

2026/7/29 8:12:55 阅读更多 →
Source Insight 4.0 代码编辑器查看prototype和definition和caller的使用

Source Insight 4.0 代码编辑器查看prototype和definition和caller的使用

sourceinsight编辑器使用英文菜单快捷键中文翻译开发实操用途Symbol infoCtrlR点击选中符号信息查看变量,函数,结构体完整属性:数据类型,定义文件,大小,存储类型,注释详情;Jump to DefinitionCtrl跳转到定义处最常用功能:光标放在函数/变量上,一键跳转至标识符被编写定义的代码…

2026/7/29 8:12:55 阅读更多 →
黑暗之魂:重制版2026最新免费下载附带新手教程

黑暗之魂:重制版2026最新免费下载附带新手教程

下载链接 重返罗德兰:《黑暗之魂:重制版》的技术修缮与原味传承 在电子游戏发展史的长河中,《黑暗之魂》是一座难以忽视的丰碑,它几乎凭一己之力重塑了“高难度游戏”的定义,并催生了后续庞大的“类魂”品类。2018年…

2026/7/29 8:12:55 阅读更多 →
JavaScript 对象操作全指南:从基础增删改查到实战避坑

JavaScript 对象操作全指南:从基础增删改查到实战避坑

在前端开发中,对象(Object)是我们处理数据最核心的数据结构之一。无论是接收接口返回的商品信息,还是处理复杂的表单状态,熟练掌握对象的增删改查都是基本功。 今天我们就结合几个常见的商品数据场景,系统地…

2026/7/29 8:12:55 阅读更多 →
基于树莓派的猪笼草生长环境模拟器设计与教学应用

基于树莓派的猪笼草生长环境模拟器设计与教学应用

1. 项目概述:当科技遇上植物学 去年夏天,我在植物园偶然观察到一群孩子对猪笼草捕食昆虫的过程表现出极大兴趣,但受限于环境条件无法深入互动。这让我萌生了开发一款能够模拟猪笼草生长环境的交互式教育工具的想法。经过半年多的实践验证&…

2026/7/29 8:12:55 阅读更多 →
AI驱动的回归测试范围智能推荐系统设计与实践

AI驱动的回归测试范围智能推荐系统设计与实践

1. 项目概述:AI如何应对需求变更的回归测试挑战在敏捷开发环境中,需求变更是常态而非例外。最近三个月我们团队经历了17次需求迭代,每次变更后平均需要3人日进行回归测试范围确认。传统人工划定回归范围的方式存在两大痛点:一是依…

2026/7/29 8:11:55 阅读更多 →

日新闻

【RT-DETR多模态创新改进】CVPR 2025 | 独家特征融合创新改进篇 | 引入RLAB残差线性注意力模块,有效融合并强调多尺度特征,多种改进点,适合红外与可见光融合目标检测任务,有效涨点

【RT-DETR多模态创新改进】CVPR 2025 | 独家特征融合创新改进篇 | 引入RLAB残差线性注意力模块,有效融合并强调多尺度特征,多种改进点,适合红外与可见光融合目标检测任务,有效涨点

一、本文介绍 🔥本文在RT-DETR多模态融合目标检测中引入RLAB残差线性注意力模块,可在不同模态特征交互阶段进行多次残差细化,使可见光、红外等特征在尺度、语义和空间位置上更好对齐;随后将细化特征与解码器输出拼接并生成Q、K、V,通过线性注意力自适应强化关键通道、目…

2026/7/29 0:00:23 阅读更多 →
AI编程系列02:合并知识功能,给 AI 问数和 RAG 场景打基础

AI编程系列02:合并知识功能,给 AI 问数和 RAG 场景打基础

AI编程系列02:合并知识功能,给 AI 问数和 RAG 场景打基础 在上一期「AI编程系列」中,我们学习了如何构建一个基础的 AI 问答系统,通过简单的输入输出让模型回应问题。但现实世界中的 AI 应用往往需要处理更复杂的场景:…

2026/7/29 0:00:23 阅读更多 →
AI智能体开发实战:从工具调用到企业级部署

AI智能体开发实战:从工具调用到企业级部署

1. 从被动问答到主动执行:AI Agent的范式转变过去两年,大语言模型最显著的应用形态是聊天机器人——用户提问,AI回答。但真正的生产力革命发生在2023年下半年:当AI学会主动调用工具完成任务时,生产力工具的历史被彻底改…

2026/7/29 0:00:23 阅读更多 →

周新闻

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 数据集6000张 完整源码已标注数据集训练好的模型环境配置教程程序运行说明文档,可以直接使用!系统支持图片、视频、摄像头等多种方式检测裂缝,功能强大实用。 1数据集6000张 8各类别

2026/7/28 12:04:22 阅读更多 →
深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

pubg数据集 精选原图1.42万数据 1.49万标签 无任何重复、算法增强或冗余图像! pubg绝地求生目标检测数据集 1分类:e_body,14905个标签,txt格式 共计14244张图,99%为640*640尺寸图像 适合yolo目标检测、AI训练关键词&am…

2026/7/28 8:29:16 阅读更多 →
Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex检测数据集数据集详情检测类别: allies enemy tag图片总量:7247张训练集:5139张验证集:1425张测试集:683张标注状态:全部已标注,即拿即用数据格式:支持YOLO格式及其他格式&#…

2026/7/28 5:03:42 阅读更多 →

月新闻