首页/新闻资讯/正文详情

Verilog三种描述方式:门级、RTL与行为级对比与工程选择

发布时间:2026/9/25 1:04:09 来源:云帆数科 栏目:资讯中心
Verilog三种描述方式:门级、RTL与行为级对比与工程选择
刚开始接触 Verilog 的时候我一度以为硬件描述语言就是用代码把逻辑图“翻译”一遍。直到有一次项目评审我们三个人要分别描述同一个二选一多路选择器结果拿出来的代码风格完全不同一个写的是门级原语实例化一个写的是连续赋值 assign还有一个用 always 块写了 if/else。综合之后比较网表功能一致结构也大同小异。那一刻我才真正意识到Verilog 里的“描述方式”是有层次的而层与层之间的选择直接决定了后面的设计效率、综合质量和可读性。这篇文章我想把门级、RTL 级、行为级这三种描述方式放在一起从基础语法讲到工程场景再把我个人在这几年里踩过的坑和积累的习惯一并倒出来。无论你是刚入门 Verilog 的学生还是已经写了不少可综合代码、但始终没搞懂“哪些代码只能仿真、哪些能综合成电路”的工程师这篇应该都能帮你把概念串起来重新理解你每天都在写的那些 module。1. 三种描述方式同一块电路的三副眼镜1.1 先看同一个多路选择器的三种写法为了说明问题我用最简单的二选一多路选择器来举例。输入 a、b选择信号 sel当 sel 为 1 时输出 b否则输出 a。门级描述是把电路结构直接写出来module mux2_gate( input a, b, sel, output y ); wire nsel, t0, t1; not g1 (nsel, sel); and g2 (t0, a, nsel); and g3 (t1, b, sel); or g4 (y, t0, t1); endmoduleRTL 级描述写的是数据流module mux2_rtl( input a, b, sel, output y ); assign y sel ? b : a; endmodule行为级描述写的是“如果……那么……”的逻辑行为module mux2_behavior( input a, b, sel, output reg y ); always (*) begin if (sel) y b; else y a; end endmodule功能上三者等价综合工具也能把它们优化到差不多相同的门级网表。但它们描述电路的“视角”完全不同这正是理解整篇内容的关键。1.2 抽象层次不同思考方式就不同门级描述是最底层的“结构视角”你是在告诉工具哪个门连哪个门哪条线连哪条线。这相当于拿着电路图在画连线硬件结构一目了然但效率很低。RTL 级描述是“数据流视角”你只需描述数据如何从输入流向输出中间经过什么选择关系。具体要用几个与门、几个或门交给综合工具去推断。这个层次已经可以表达大多数数字逻辑了也是可综合代码的主力。行为级描述是“算法视角”你只需要描述模块在某种输入条件下表现为什么行为完全可以不关心内部电路结构。它的表达能力最强但如果不加约束综合工具往往找不到对应的硬件实现。1.3 为什么会有这三种层次这跟 EDA 工具的发展历史有直接关系。早期的数字设计确实是在图纸上画门电路然后人工整理连线关系所以硬件描述语言诞生时就内置了门级原语方便工程师把图纸编码成文本。后来逻辑综合工具越来越成熟大家发现与其手写门级不如用更高的抽象层次把设计意图表达清楚再由工具自动生成电路于是 RTL 级成了工业界的主流。再往后验证复杂度上来以后大家还需要一种更接近 C 语言的描述方式去搭 testbench、做算法建模行为级就承担了这个角色。这三种层次不是谁取代谁而是各有分工、共同存在。理解它们之间的关系比背十个语法点都重要。2. 门级描述手工连线时代的“电路翻译官”2.1 门级原语的基本语法Verilog 内建了一批基本门原语比如and、nand、or、nor、xor、xnor、not、buf还有带使能端的三态门bufif0、bufif1、notif0、notif1。日常设计中最常用的其实就是前几个。实例化语法是and 实例名(输出, 输入1, 输入2, ...); or 实例名(输出, 输入1, 输入2, ...); not 实例名(输出, 输入);注意端口顺序输出端口在第一个输入端口列在后面。这是很多初学者第一次连门级时报错的地方因为它的顺序和普通模块端口定义习惯正好相反。举个例子一个三输入与非门可以写成nand g1(y, a, b, c);工具会自动根据输入个数扩展门的实现。2.2 用门级搭一个全加器门级不光可以例化基本门也可以例化自己定义的子模块这属于结构描述。我们先用一个标准全加器感受一下门级手工连线的工作量module full_adder_gate( input a, b, cin, output sum, cout ); wire s1, c1, c2; xor g1 (s1, a, b); and g2 (c1, a, b); xor g3 (sum, s1, cin); and g4 (c2, s1, cin); or g5 (cout, c1, c2); endmodule这段代码能跑功能也完全正确。但你仔细看它几乎就是一张门级电路图转成文本。如果这个设计里再加一个进位传播结构、加一个选择逻辑代码量会迅速膨胀可读性也跟着崩塌。2.3 门级描述在今天还有什么用看到这里你可能会问既然综合工具能自动把 RTL 映射成门级网表那门级描述是不是只存在于教科书里其实不是门级描述在现代工程里依然有三个不可替代的场景。第一FPGA 原语例化。比如 Xilinx 器件里的全局时钟缓冲 BUFG你不能用一段行为级代码“描述”出一个 BUFG只能按原语模板例化BUFG u_bufg ( .I(clk_in), .O(clk_out) );这种写法本质上就是结构描述只不过例化的对象是厂商提供的门级单元。第二标准单元库建模。半导体工艺库里的 AND2、DFF、AOI 等单元本身也需要用 Verilog 门级模型来描述行为和时序供仿真工具使用。这部分你没有机会手写但需要知道它们的存在。第三阅读综合后的网表。做 ECO 或者排查时序违例时你经常要翻开工具吐出来的门级网表看懂哪些单元被换了、哪些路径上多了 buffer。如果你完全看不懂门级描述这一步会非常费劲。门级描述还有一个隐藏特点可以通过门延迟做仿真。比如and #(2,3) g1(...)表示上升延迟 2ns、下降延迟 3ns这能用来模拟真实门电路的时间特性。但要注意延迟控制只在仿真里有效综合工具不会把它变成实际延迟所以别指望用它来约束时序。3. RTL 级描述综合工具最熟悉的“中场发动机”3.1 RTL 到底是什么寄存器传输级RTL 的全称是 Register Transfer Level寄存器传输级。它的核心理念是描述数据在时钟节拍下从一个寄存器传输到另一个寄存器的过程中间穿插组合逻辑完成运算和判断。你关心的是“数据在什么时候、什么条件下被寄存、被搬运”而不是“具体用哪个触发器实现它”。这也是综合工具最喜欢处理的一层它既能看懂结构又能理解行为。你在 RTL 里写assign y sel ? b : a;工具会把它翻译成一个二选一逻辑。你在时序块里写q d;工具能识别出这是一个 D 触发器并自动选择工艺库里的 DFF 单元去实现。3.2 连续赋值RTL 的组合逻辑主力连续赋值语句assign是 RTL 里描述组合逻辑最常用的方式。它表示右侧表达式的任何变化都会立即反映到左侧 wire 信号上。这种风格非常直观而且天然适合综合。assign sum a ^ b ^ cin; assign carry (a b) | (a cin) | (b cin);再复杂一点的逻辑只要能用布尔表达式写清楚的都建议优先用assign。它没有锁存器风险也不存在阻塞、非阻塞赋值的坑仿真行为非常干净。3.3 RTL 里的时序逻辑计数器是最典型的例子RTL 并不只有assign。工程意义上的“RTL 设计代码”通常包含了可综合的时序逻辑也就是我们常见的那种always (posedge clk)写法。这里需要提醒一下在部分老教材里可综合的 always 块会被划到行为级而只有 assign 才叫 RTL。但在实际工业项目中“RTL”这个概念更多指的是“可综合的设计代码”与用来仿真的 testbench 相对。你只要记住这两套口径别在交流时被绕晕就行。计数器是理解 RTL 时序逻辑最好的入门例子module counter( input wire clk, input wire rst_n, output reg [7:0] q ); always (posedge clk or negedge rst_n) begin if (!rst_n) q 8d0; else q q 1b1; end endmodule这段代码里q虽然声明成reg但综合工具会根据always块里的时钟边沿判断条件把它推断成寄存器阵列。写 RTL 的时候一定要养成“想着电路”的习惯不要认为reg就一定对应硬件寄存器。实际上reg只是一种 Verilog 变量类型表示过程赋值语句中的存储载体它可能被综合成寄存器也可能被综合成连线具体要看你怎么用。3.4 综合工具是如何“看懂” RTL 的综合工具的工作流程大致是语法分析、逻辑推断、逻辑优化、工艺映射。你的 RTL 代码经过工具解析后先被拆成布尔表达式和有限状态机然后做逻辑化简最后映射到目标工艺库的标准单元上。这也是为什么 RTL 代码有那么多“推荐风格”因为工具在识别不同写法时有自己固定的套路。你按套路写工具识别得又快又准你不按套路写比如在时序块里给同一个信号多个地方赋值工具就会报乱或者生成意想不到的电路。现代综合工具还支持用always (*)写复杂的组合逻辑等价于行为级里可综合的子集。因此可以说RTL 与行为级之间并没有不可逾越的鸿沟关键是“可综合”这三个字。4. 行为级描述让代码像 C 语言一样思考电路4.1 行为级到底是什么行为级描述强调的是“行为”而不是“结构”。它允许你用更贴近算法的方式写出逻辑比如用变量、循环、任务、函数再配合initial、#延时、fork...join这类仿真控制语句。在数字设计里行为级有两个主要战场一是可综合的硬件逻辑二是不可以综合的仿真环境。前者我们通常会用一种“行为风格”的 RTL 子集来写比如用always (*)和case描述组合逻辑后者则更多用于 testbench。举个行为级的例子一个简单的 testbenchmodule tb_counter; reg clk, rst_n; wire [7:0] q; counter dut( .clk(clk), .rst_n(rst_n), .q(q) ); initial begin clk 0; rst_n 0; #20 rst_n 1; #300 $finish; end always #5 clk ~clk; initial begin $monitor($time, q %0d, q); end endmodule这里出现了一个关键信号initial、#20、#300、#5、$finish、$monitor这些语法在综合工具里都是不可综合的但它们对仿真验证极其有用。4.2 行为级可综合与不可综合的边界很多初学者最大的困惑就是同样一段always块为什么有的能综合有的不能关键在于你使用了哪些语句。可以综合的子集包括变量声明、always过程块if、else、case、casez、casex算术运算、位运算、关系运算可在always块内使用的阻塞赋值和非阻塞赋值任务和函数的可综合子集不可综合的语句主要包括initial它只在仿真开始时执行一次没有时钟边沿的“启动条件”#延时控制包括#10、#(posedge clk)这类写法wait、event、fork...joinforce、release$display、$monitor、$finish等仿真系统任务综合工具遇到这些语句时要么直接忽略要么报错。所以写 RTL 前一定要给自己立一个规矩把“可综合设计”和“仿真测试平台”分开写不要混在同一个模块里。4.3 行为级设计在真实项目里的价值行为级的价值绝对不止是写 testbench。我在做信号处理项目时经常先用行为级代码把滑动窗口滤波、FIR 滤波之类的算法在仿真环境里跑通用浮点或者高位定点先验证功能然后再慢慢改成可综合的 RTL。这个过程中行为级代码就是算法的参考模型RTL 就是它的硬件实现。两边对拍结果能省下大量调试时间。比如你要实现一个滑动窗口均值滤波可以先写一个行为级函数用循环累加窗口里的数再用行为级 testbench 给它喂数据确认输出符合预期。之后写 RTL 时你再思考数据路径怎么安排、寄存器怎么流水、输出延迟几个周期。没有前面的行为级模型你很难判断 RTL 算对没有。所以行为级并不是低人一等的“仿真专用垃圾代码”它是算法和硬件之间那块非常重要的打草稿区域。5. 工程中怎么选按设计流程分配描述方式5.1 不同阶段各用各的描述层级一个完整的数字设计流程大致包括规格定义、算法建模、RTL 编码、逻辑综合、布局布线和验证。在这些阶段里三种描述方式各有主场算法建模阶段用行为级写参考模型重点验证算法正确性。设计编码阶段用 RTL 写可综合代码每个 cycle 做什么都定义清楚。综合之后工具输出门级网表。此时你不再手写门级但要能读懂关键路径。验证阶段testbench 几乎都是行为级怎么方便怎么写。这张表格是我自己整理的选择参考使用场景推荐描述层级原因简单组合逻辑RTLassign表达直接无锁存器风险复杂组合逻辑行为级always (*) case结构清晰但要注意补全分支计数器、移位寄存器RTLalways (posedge clk)时序清晰综合工具易于推断寄存器状态机RTL 三段式状态转移和输出分离可读性好testbench 激励生成行为级可以自由使用延时、initial、forkFPGA 原语例化门级实例化原语只能结构例化不能用行为抽象5.2 为什么状态机也属于 RTL 设计的重头戏状态机大量出现在串口收发、按键拨号、出租车计费、I2C 读写这类控制逻辑里。它本身属于可综合 RTL 代码但写法上更依赖行为级风格。常规推荐三段式module fsm_example( input wire clk, input wire rst_n, input wire start, output reg done ); localparam IDLE 2d0, S1 2d1, S2 2d2; reg [1:0] state, next; always (posedge clk or negedge rst_n) begin if (!rst_n) state IDLE; else state next; end always (*) begin next state; case (state) IDLE : if (start) next S1; S1 : next S2; S2 : next IDLE; default : next IDLE; endcase end always (*) begin done (state S2); end endmodule三段式的好处是状态寄存器、次态组合逻辑、输出组合逻辑分开写既保持了 RTL 的可综合性又保留了行为级的可读性。你写状态机时如果只有一个 always 块也完全可以但信号多起来之后会难维护。5.3 仿真器与综合器是两个完全不同的世界关于“怎么选”还有一层很重要的理解仿真器是一个解释执行 Verilog 的软件它支持绝大多数语法。综合器则是一个“翻译成电路”的软件它只认可综合子集。所以我们在工程中写代码时脑子里必须时刻装着这个问题“这句话综合工具能看懂吗它会变成什么电路”如果你只是在仿真环境里验证行为那可以随意用行为级写法但如果目标是跑到 FPGA 上或做成芯片就要约束自己只在 testbench 里放飞。我在项目里见过不止一次有人把#5延时直接写进可综合模块里做“同步”结果仿真看起来一切正常因为仿真器和波形确实按延时跑了。一上板子延时被综合器直接当空气扔了时序完全错乱。这种坑一旦踩进去排查起来很痛苦。所以“选哪种描述方式”不只是一道练习题更是一条条安全红线。6. 由描述方式引出的代码风格问题三个最容易踩的坑6.1 组合逻辑 always 块漏分支行为级写法惹出的锁存器行为级里最经典的“坑王”是always (*)组合逻辑缺少赋值分支。举个例子always (*) begin if (sel) y a; end这段代码看起来只是“如果 sel 为 1 就输出 a”但问题是 sel 为 0 时 y 应该保持什么Verilog 规定过程块内未被赋值的变量要保持原值所以综合工具为了满足“保持原值”的行为会推断出一个锁存器。很多时候你根本不想生成锁存器它却悄悄进了网表。解决办法很粗暴写组合逻辑块时先给输出一个默认值再用 if/case 覆盖always (*) begin y 0; if (sel) y a; end这个习惯能救你很多次特别是用行为级风格写复杂组合逻辑时。6.2 时序逻辑里用了阻塞赋值仿真会骗人综合也会跟着跑偏第二个高频坑在时序逻辑。很多从 C 语言转过来的工程师写always (posedge clk)时习惯用赋值always (posedge clk) begin q d; end问题在于阻塞赋值是“立刻生效”的语句顺序会影响结果。如果这个块里有两条赋值后一条可能读到前一条刚更新的值这在硬件里根本不成立。正确的做法是用非阻塞赋值它模拟的是寄存器在时钟边沿并行更新的特性always (posedge clk) begin q d; end如果你把时钟边缘处的一串寄存器用一个 always 块实现所有右侧的值都是进入时钟边沿前那一刻的旧值左侧在下一次边沿统一更新。这才符合真实 D 触发器的行为。6.3 把 testbench 的习惯带进了可综合模块上板必定翻车第三个坑就是我在 5.3 节里提到的把行为级仿真机制混进 RTL 模块。除了#延时常见的还有在可综合模块里用initial给寄存器赋初值。在 FPGA 里有些工具允许寄存器初值但在 ASIC 设计里initial一般不可综合复位才是标准做法。我在入门时干过不少这种“仿真过得去、综合绕大弯”的事。后来给自己定了一条铁律模块里的可综合代码只用always、assign和实例化所有initial、#、$display一律出现在 testbench 里。6.4 门级网表不用手写但要会读最后补一个和门级描述相关的工程经验。综合工具输出的网表里面的结构名看起来可能很陌生比如BUFX4、DFFQ_X1、AOI22这种。你不需要自己手写这些单元但遇到关键路径时序违例时你还是要能打开网表沿着数据路径找到是哪一级门的输入负载太重或者哪一级触发器因为复位方式不同而多加了逻辑。学会读门级网表会让你在做时序收敛和 ECO 的时候比只会“把问题丢给后端工具”的工程师多一层理解。如果你刚接触 Verilog我建议你先从 RTL 级入手因为它是综合工具最认、工程使用面最广的一层但千万别觉得门级和行为级是没用的“古董”或者“仿真玩具”。它们和 RTL 是一套完整的工具链行为级帮你快速验证思路RTL 帮你落地实现门级帮你理解最后的物理结果。我个人的习惯是拿到一个算法需求先写行为级参考模型再一步步改成可综合 RTL最后再翻一翻综合网表和时序报告确认没有意外。这个过程多走几遍你对 Verilog 三种描述方式的理解就不再是语法标签而是真正变成了设计直觉。

相关推荐

芯片烧录版本管理实战:从事故分析到nrf51822量产烧录流程
芯片烧录版本管理实战:从事故分析到nrf51822量产烧录流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:04:03

SpringBoot大学生兼职系统源码解析:环境搭建、核心模块与避坑指南
SpringBoot大学生兼职系统源码解析:环境搭建、核心模块与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:04:03

Fast-LIO2激光惯性SLAM源码拆解与ROS2部署实战
Fast-LIO2激光惯性SLAM源码拆解与ROS2部署实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:03:57

labelImg汉化版exe:Windows目标检测标注工具使用指南
labelImg汉化版exe:Windows目标检测标注工具使用指南

简介:labelImg-1.8.6汉化中文版是面向计算机视觉标注入门者与算法工程师的免安装工具包,专为Windows x64系统打造,解压后双击labelImg.exe即可直接使用,无需配置Python环境,也无需处理依赖冲突,适合目标检测… · 2026/9/25 1:41:07

treg 的 Google Ads 转化跟踪实战:从 gclid 捕获、耐久 Outbox 到 Data Manager 批量上传的完整链路
treg 的 Google Ads 转化跟踪实战:从 gclid 捕获、耐久 Outbox 到 Data Manager 批量上传的完整链路

后端API网关MCP 服务dsh-plugin 【免费下载链接】treg OpenRouter for agent tools. Join community here: https://discord.gg/6mQYYfFMAn 项目地址: https://gitcode.com/GitHub_Trending/treg/treg 点击查看 免费下载 本文以 docs/context/architecture/ads-con… · 2026/9/25 1:41:07

Smithbox Havok Editor揭秘:艾尔登法环/黑夜君临HKX物理文件编辑进阶教程
Smithbox Havok Editor揭秘:艾尔登法环/黑夜君临HKX物理文件编辑进阶教程

Smithbox Havok Editor揭秘:艾尔登法环/黑夜君临HKX物理文件编辑进阶教程 【免费下载链接】Smithbox Smithbox is a modding tool for Elden Ring, Armored Core VI, Sekiro, Dark Souls 3, Dark Souls 2, Dark Souls, Bloodborne and Demons Souls. 项目地址: ht… · 2026/9/25 1:41:07

AgentScope Java 生产环境避坑清单:12 个常见问题与最佳实践(官方 FAQ 深度精读)
AgentScope Java 生产环境避坑清单:12 个常见问题与最佳实践(官方 FAQ 深度精读)

AgentScope Java 生产环境避坑清单:12 个常见问题与最佳实践(官方 FAQ 深度精读) 【免费下载链接】agentscope-java Build distributed, production-grade, long-running agents. 项目地址: https://gitcode.com/gh_mirrors/ag/agentscope-… · 2026/9/25 1:41:01

mlcourse.ai 开源机器学习课程指南:Jupyter Book 站点结构与 10 大主题学习路线全解析
mlcourse.ai 开源机器学习课程指南:Jupyter Book 站点结构与 10 大主题学习路线全解析

机器学习教程人工智能数据分析 【免费下载链接】mlcourse.ai Open Machine Learning Course 项目地址: https://gitcode.com/gh_mirrors/ml/mlcourse.ai 点击查看 免费下载 本篇指南以 mlcourse.ai 课程首页(mlcourse_ai_jupyter_book/book/index.md&am… · 2026/9/25 1:41:01

大气层Atmosphere完全解析:任天堂Switch定制固件入门终极指南
大气层Atmosphere完全解析:任天堂Switch定制固件入门终极指南

大气层Atmosphere完全解析:任天堂Switch定制固件入门终极指南 【免费下载链接】Atmosphere-stable 大气层整合包系统稳定版 项目地址: https://gitcode.com/gh_mirrors/at/Atmosphere-stable 大气层Atmosphere是任天堂Switch上最强大的开源定制固件系统之一&… · 2026/9/25 1:40:54

数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)
数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码