ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Verilog中wire与reg的区别:从赋值方式到工程应用全解析

Verilog中wire与reg的区别:从赋值方式到工程应用全解析 1. 先搞清楚wire 和 reg 到底在描述什么很多刚接触 Verilog 的朋友一开始就被 wire 和 reg 这两个关键字搞得晕头转向。网上的教程要么讲得太浅只说wire 是线reg 是寄存器要么直接甩一堆语法规则让人硬背。结果就是写代码的时候还是不知道该怎么选编译报错了也不知道为什么。先给一个最朴素的理解wire 描述的是物理连线reg 描述的是存储单元——但这句话只对了一半。因为在实际工程里reg 综合出来不一定是寄存器它也可能是纯组合逻辑。这一点如果不搞清楚后面全是坑。我个人的经验是要把 wire 和 reg 的区别从四个维度去看语义层面wire 代表结构性的连接关系reg 代表过程性的赋值行为。赋值方式wire 用 assign 连续赋值reg 在 always/initial 块里用过程赋值。硬件映射wire 映射为导线reg 映射为寄存器或组合逻辑取决于你怎么写。端口方向input 只能是 wireoutput 可以是 wire 也可以是 reginout 只能是 wire。这篇内容不绕弯子直接把这些讲透并且结合我实际写代码踩过的坑给你一套什么时候用谁的判断方法。不管你是在学数电基础、准备秋招还是已经在写 RTL 做项目这篇都能帮你少走弯路。2. 从赋值方式看本质区别2.1 连续赋值 assign只有 wire 能接住先看这段代码很多教材第一个例子就是这么写的wire a, b, c; assign c a b;assign 是连续赋值语句它的含义是右边表达式的结果持续不断地驱动左边信号。只要 a 或 b 发生变化c 就会立刻更新。这种持续驱动的语义本质上描述的就是一根导线——一端接着与门的输出另一端接到目标端口。如果这时候你把 c 声明成 regreg c; assign c a b; // 编译报错综合工具会直接报错。因为 reg 表达的是过程赋值——必须在 always 或 initial 块内部被赋值。这是语法层面的硬性限制没有任何例外。我见过不少人在这里卡住其实道理很简单assign 语句就像一根实体的电线电线两端的类型自然应该是线网类型wire而 reg 是一个变量容器它的赋值应该发生在某个过程里而不是一直被驱动。2.2 过程赋值 always/initialreg 的专属地盘与 assign 对应的是 always 和 initial 块。块内被赋值的信号声明为 reg 类型。reg q; always (posedge clk or negedge rst_n) begin if (!rst_n) q 1b0; else q d; end这种写法描述的是一个 D 触发器时钟上升沿到来时q 采样 d 的值。q 必须声明为 reg因为它的赋值是有条件的、由时钟事件触发的而不是像 assign 那样无时无刻在被驱动。这里有一个非常关键但初学者容易忽略的点always 块里的 reg 不一定会被综合成寄存器。如果你在 always 块里写的是纯组合逻辑reg 综合出来就是一堆门电路而不是触发器。reg y; always (*) begin if (sel) y a; else y b; end上面这个 always 块里用的是阻塞赋值没有时钟边沿触发敏感列表是(*)综合出来的就是组合逻辑的多路选择器y 在硬件上只是一根经过选择器后的连线。但因为写法上用了过程赋值语法上就必须声明成 reg。这就是 Verilog 语法和硬件结构之间的一个脱节——很多初学者在这里精神分裂明明综合出来是线为什么要我写 reg记住这个结论reg 是语法层面的变量类型不是硬件层面的寄存器类型。凡是放在 always/initial 块里被赋值的信号都要用 reg凡是放在 assign 语句里被驱动的信号都要用 wire。这是语法规则先遵守再理解硬件映射。2.3 阻塞赋值与非阻塞赋值的连带影响既然说到了 always 块就不得不提阻塞赋值和非阻塞赋值。因为很多人在纠结 wire/reg 的同时也在纠结这两个符号。它们之间有强关联写组合逻辑的 always 块用阻塞赋值reg 综合成组合逻辑。写时序逻辑的 always 块用非阻塞赋值reg 综合成触发器。如果乱用仿真结果和实际电路行为会不一致。比如在时序逻辑里用阻塞赋值reg q1, q2; always (posedge clk) begin q1 d; // 阻塞赋值 q2 q1; // 两个信号在同一时刻更新 end这种写法在仿真时 q2 会直接拿到 d 的新值看起来像是一拍完成。但实际综合出来的电路里q1 和 q2 是两个串联的触发器q2 应该滞后 q1 一拍。仿真和硬件行为不一致这就是典型的仿真通过上板翻车。所以我的建议是写时序逻辑一律用非阻塞写组合逻辑一律用阻塞。这不是教条是无数前人用惨痛教训换来的经验。把它当成铁律来执行能帮你避开一大类诡异 bug。3. 驱动源与数据类型一个容易被忽视的分水岭3.1 端口方向决定类型选择写模块的时候端口声明是个高频场景。很多面试题也会在这里挖坑。module test( input clk, input rst_n, input a, output b, output reg c );input 端口对应的是外部信号驱动进来内部只能读取它不能对它赋值。所以 input 只能声明为 wire。你在模块内部如果要根据 a 的值做判断直接拿a来用就行不需要也不允许在 always 块里给它赋值。output 端口比较灵活。如果输出是纯组合逻辑驱动的比如直接把输入经过 assign 输出那 output 端口默认就是 wire 类型module test( input a, input b, output c ); assign c a b; endmodule但如果输出是在 always 块里赋值那端口必须写成 output regmodule test( input clk, input rst_n, input d, output reg q ); always (posedge clk or negedge rst_n) begin if (!rst_n) q 1b0; else q d; end endmoduleinout 端口就一个字只能用 wire。因为双向端口必须支持多驱动源切换高阻态时让外部驱动这种特性只有 wire 能表达。你不可能用 reg 实现三态输出——不对准确地说三态门的控制逻辑里会有 reg但真正的 inout 引脚本身必须是 wire。实际工程中I2C 的 sda、DS18B20 的数据线这些双向端口全部用 inout wire然后配合三态缓冲器来做方向控制。这里给一个端口选择速查表端口方向可用类型说明input只能是 wire外部驱动内部只读outputwire 或 reg看内部赋值方式inout只能是 wire多驱动源需要高阻态3.2 多驱动源问题wire 的线或能力与 reg 的排他性wire 本质上是一根导线它天然允许多个驱动源。比如两个模块的输出同时驱动同一根 wire在 Verilog 里可以通过线或wire 的拉取关系来合并。最典型的就是三态缓冲器阵列挂在同一根数据总线上——多个设备共享一根总线谁使能谁驱动不使能的输出高阻 z。而 reg 不行。reg 代表的是一个变量它只能有一个赋值来源。你不可能在多个 always 块里对同一个 reg 赋值——不是语法报错就是综合出多驱动冲突。reg q; always (posedge clk) q a; always (posedge clk) q b; // 非法多驱动这种写法在 Vivado 或 Quartus 里综合必然报错因为一个 reg 在硬件上不可能同时被两个触发器驱动。实际工程里多驱动冲突最常见的坑出现在写 RAM/寄存器的时候定义了多个写口。比如一个双端口 RAM你本意是端口 A 和端口 B 分别写不同的地址结果一不小心在写代码时让两个 always 块同时驱动了同一个 reg 信号综合工具直接给你爆一片红色错误。排查起来还挺费劲的因为那一大片报错信息里真正的根因往往藏得比较深。我的排查经验是一旦综合报告里出现 multiple drivers 之类的关键词先回到代码里搜索所有对同一个 reg 的赋值语句看是不是有多个 always 块在写。如果是要么合并成一个 always 块用 case 区分条件要么拆分信号。这一步做完问题瞬间消失。3.3 什么时候用 wire连线和组合逻辑的输出只看语法规则可能还是不够直观我用实际场景给你梳理一遍什么情况下就无脑用 wire模块间端口连接例化子模块时子模块的 input 端口在父模块里对应的连接信号如果是信号源那边输出过来的用 wire 就够了。assign 连续赋值的信号只要你的逻辑是assign c a b;这种风格c 必须声明为 wire。组合逻辑的中间信号比如一个复杂组合逻辑的中间结果你会连着写好几条 assign中间那些信号都是 wire。三态缓冲器的输出inout 引脚内部连接的三态门输出必须是 wire。多个模块共享的总线数据总线、地址总线这类多驱动线必须用 wire 配合三态门实现。这里有个补充经验模块例化时如果子模块的 output 端口没有在父模块里声明成 wire有些人会图省事不写声明直接用。但更严谨的写法是主动声明因为这样别人看代码的时候一眼就知道哪些是连线、哪些是触发器输出代码可读性完全不一样。对于团队协作项目这种细节很重要。3.4 什么时候用 reg时序逻辑和过程赋值的场景再看 reg 的使用场景同样是无脑套用的清单always 块里被赋值的所有信号不管这个 always 块描述的是时序逻辑还是组合逻辑信号都得是 reg。寄存器和触发器输出计数器、移位寄存器、状态机的状态变量、FIFO 的读写指针——这些在 always 块里被时钟边沿更新的信号全部用 reg。组合逻辑但用过程赋值描述的场景比如组合 always 块里的中间变量声明为 reg 是语法需要。initial 块里赋值的信号主要用在 testbench 仿真里比如激励信号的定义都声明为 reg。IP 核或复杂模块的输出端口只要输出是在模块内部通过 always 块产生的端口就得是 output reg。这里我要强调一个高频踩坑点变量定义位置和宽度。在声明 reg 的时候一定要把位宽写清楚。比如reg [7:0] data_r;和reg data_r;是完全不同的东西——前者是 8 位寄存器后者是 1 位。很多时候模块功能不对从头查下来发现是声明时位宽没写对低级但致命。还有一个更隐蔽的坑reg [7:0] data_r;在 always 块里被赋值为data_r data_in;如果 data_in 只有 4 位那高 4 位会被自动补 0 还是保持原来的值答案是取决于上下文。如果是非阻塞赋值高 4 位会在赋值时被 0 填充因为右侧表达式不足 8 位时会补零。如果你本意是高 4 位保持不变那就得写成data_r[3:0] data_in;。这种细节在实际工程里特别容易翻车。4. 必须掌握的一个高级主题wire 和 reg 跟仿真模型的关系4.1 仿真中的 wire 和 reg初始状态差异如果你用过 Icarus Verilog 或者 Modelsim 做仿真会发现 wire 和 reg 在仿真器里的初始值行为不一样。wire 在仿真开始时的默认值是 z高阻因为一根没有被驱动的线自然就是悬浮状态。而 reg 的默认值是 x未知因为它是一个没有被赋值的变量。这个差异在写 testbench 的时候有实际影响。比如你写一个简单的与门仿真wire c; assign c a b; initial begin a 0; b 1; end仿真刚开始那一瞬间a 和 b 需要从 x 变成 0/1c 才能从 x 变成 0。如果 initial 块还没来得及执行c 就是 x。这个初始 x在波形图里看起来像一个 bug但其实是正常现象。对于 reg 类型如果你在 initial 块里给 rst_n 赋值之前它保持 x 状态这就有可能让时序逻辑进入不确定状态。所以工程上有一个规范测试激励里的控制信号第一条 initial 语句就要把初值赋好尤其是复位信号绝对不能让它在仿真开始阶段悬空为 x。reg clk; reg rst_n; initial begin clk 0; rst_n 0; #100 rst_n 1; end这种写法就是标准的 testbench 开头模板。如果把 rst_n 赋值的顺序搞反了或者漏了初值仿真波形一出来就是一堆红叉叉x排查起来让人头大。4.2 为什么集成逻辑分析仪抓信号时只有 wire 能看到实际调试 FPGA 的时候大家都会用 Vivado 的 ILA 或者 Quartus 的 SignalTap 来抓内部信号。你会发现一个现象ILA 的探针列表里能看到 wire 信号但很多 reg 信号看不到——或者要看的话得做额外设置。原因是这样的综合工具在优化电路时会把一些纯组合逻辑的 wire 信号吸收进 LUT 内部或者把一些 reg 信号重命名/合并到其他寄存器里。特别是那些只在一个 always 块内部出现、从来没有连接到输出端口的 reg很可能在综合后就被优化没了。我记得有一次调一个状态机状态变量是 reg但在 ILA 里死活看不到它的值。后来发现是我定义状态编码时用了localparam加reg [1:0] state;综合工具把 state 信号优化到了 LUT 的寄存器里没有暴露到可观测的节点上。解决办法有几种一是用(* mark_debug true *)属性强制保留信号二是在信号声明时加上(* keep true *)防止被综合工具优化掉。这两个属性在 Vivado 里都很常用Quartus 里对应的写法也类似。如果抓不到你要看的 reg先考虑是不是信号被优化了。这个经验其实反过来印证了一个道理你在 RTL 代码里写的 reg到了真实硬件上不一定还叫这个名字甚至不一定存在。理解这一点对 wire 和 reg 的本质理解会更深入一层。5. 工程中最常见的 wire/reg 错误与排查方法5.1 典型错误一在 always 块里给 wire 赋值这个错误新手必犯错误提示也五花八门。最常见的报错是Error (10028): Cant resolve multiple constant drivers for net xxx或者 Quartus 报error: object xxx on left-hand side of assignment must have a variable data type原因很简单always 块里赋值左侧必须是 reg 类型如果是 wire语法分析直接就拒绝。解决办法也简单把 always 块里赋值的信号声明改成 reg。但要注意如果这个信号同时又被 assign 驱动了那就不能简单改类型要先想清楚——这个信号到底是组合逻辑还是时序逻辑的输出如果是组合逻辑可以考虑把它挪到 assign 里如果是时序逻辑那就不应该有 assign 驱动它。我自己在带新人的时候最常说的话就是你先告诉我这个信号是模块输出的寄存器还是模块内部的连线如果是寄存器就在 always 块里赋值声明成 reg如果是连线就放到 assign 里声明成 wire。想清楚再动手。5.2 典型错误二端口类型不匹配模块例化时端口类型不匹配的问题非常隐蔽。尤其是当子模块的 output 端口是 reg父模块里连接这个端口的信号却声明成了 reg——这不是不能连而是要看父模块里这个信号是不是被多个源驱动。更常见的是这种情况子模块的 output 是 wire 类型父模块里把它连到了一个 reg 信号上。在某些编译器里这可能会报 warning但在另一些里可能直接报 error。不同工具对端口连接类型的容忍度不一样这就很让人抓狂。我的做法是例化模块之前先把子模块端口类型看清楚然后在父模块里对应的连接信号用 wire 声明。因为端口连接本质上就是拿一根线把两个模块的引脚焊在一起线就是 wire。如果子模块的输出在父模块里需要作为触发器使用那就应该先在父模块里加一个 reg 信号用 always 块把 wire 采进来而不是直接把 reg 信号连到子模块输出上。// 正确示范 wire child_out; reg child_out_r; child u_child( .out(child_out) ); always (posedge clk or negedge rst_n) begin if (!rst_n) child_out_r 1b0; else child_out_r child_out; end这种模式的本质是一个信号如果既需要作为导线被例化模块驱动又需要被寄存器采样那就拆成两个信号——一个 wire 一个 reg——再用 always 块把它们关联起来。这是 RTL 设计里最常见的信号处理模式一定要练熟。5.3 典型错误三reg 被综合成锁存器还有一种错误不在编译阶段报错而是在综合后的资源报告里才能看到你没打算生成锁存器但综合工具给你综合出来了。这种情况通常发生在组合 always 块的 if/else 分支不完整的时候reg y; always (*) begin if (sel) y a; // 缺少 else 分支 end当 sel 为 0 时y 保持原来的值这在硬件上需要一个锁存器来保持。但如果你本意是一个二选一多路器那这就是 bug——因为多路器在所有条件下都应该有确定的输出。排查锁存器的方法有两个一是看综合报告里有没有 latch 相关提示二是写代码时自觉检查每个组合 always 块是否有完整的 if/else 或 case 默认分支。我在写代码的时候有个习惯组合逻辑的 always 块开头先把输出赋一个默认值再写条件分支。比如reg y; always (*) begin y b; // 默认值 if (sel) y a; end这样写不管 sel 是什么值y 都有确定的输出综合工具就不会生成锁存器。这个习惯帮我避免了很多次锁存器翻车。5.4 一段排查实录FIFO 读指针莫名跳变近期在调一个异步 FIFO 的读侧逻辑时遇到一个现象读指针在仿真波形里偶尔会跳变两个值——不是每次读都会跳而是很随机地偶尔跳一次。代码里读指针的更新逻辑是标准的reg [4:0] rd_ptr; wire [4:0] rd_ptr_next rd_ptr 1b1; always (posedge rd_clk or negedge rst_n) begin if (!rst_n) rd_ptr 5d0; else if (rd_en) rd_ptr rd_ptr_next; endrd_ptr 是 regrd_ptr_next 是 wire。表面看问题不大但再往下查就发现问题出在连接关系上rd_en 信号是从写侧跨时钟域同步过来的指针比较结果在某一个时钟周期出现了亚稳态导致 rd_en 在一个周期里被采到两次有效脉冲。这种情况下 rd_ptr 自然就跳了两个值。这个问题的根因不是 wire/reg 选型错误但排查过程让我又一次意识到把 wire 和 reg 的类型理解透是解决这类信号连接问题的前提。当你看到 rd_ptr_next 是一个 wire、rd_ptr 是一个 reg你就能立刻理解——rd_ptr_next 只是组合逻辑的中间结果真正的状态在 rd_ptr 里。如果 rd_ptr 跳变要么是组合逻辑有问题要么是驱动它的使能信号有问题。分清了 data type 的职责定位问题的思路就清晰了。5.5 常见错误速查表下面这个表是我这些年总结出来的高频错误每次 review 代码都会对照检查一遍错误现象根因排查方法预防措施always 块内赋值的信号声明为 wire对语法理解不透查看报错信息定位到具体信号记住规则always 内赋值用 regassign 驱动的信号声明为 reg混淆连续赋值和过程赋值编译报错或综合多驱动记住规则assign 左侧用 wire多个 always 块驱动同一 reg多驱动源冲突综合报告找 multiple drivers每个 reg 只能有一个赋值源组合块 if 没有 else意外生成锁存器查看综合报告 latch 提示组合块写默认值分支补全非阻塞赋值用在组合块仿真/综合行为不一致对比仿真波形和实际电路时序用 组合用 位宽不匹配导致数据错误声明时位宽写错仿真波形看数据位数声明后自查位宽这些坑我在不同项目里都踩过有些还踩过不止一次。尤其是多驱动源和锁存器这两个几乎每个初学者都会碰上一次。6. 实操示例一个组合逻辑状态模块的完整选型过程前面讲了这么多原则可能还是有人觉得抽象。这里用一个具体的例子从零开始走一遍选型过程。假设要写一个出租车计价器的跳表模块功能是根据里程信号每 100 米跳一次表跳过之后把金额累加。分析一下需要哪些信号时钟和复位clk、rst_n都是 input wire。里程脉冲mile_pulseinput wire来一个脉冲代表 100 米。当前金额fareoutput reg因为需要累加必定是时序逻辑。跳表信号fare_inc内部 wire表示金额要加 1由 mile_pulse 经过边沿检测产生。module taxi_fare( input clk, input rst_n, input mile_pulse, output reg [15:0] fare ); wire pulse_edge; reg pulse_dly; // 边沿检测把 mile_pulse 打一拍再通过组合逻辑检测上升沿 always (posedge clk or negedge rst_n) begin if (!rst_n) pulse_dly 1b0; else pulse_dly mile_pulse; end assign pulse_edge mile_pulse ~pulse_dly; // 金额累加每检测到一次脉冲加 1 always (posedge clk or negedge rst_n) begin if (!rst_n) fare 16d0; else if (pulse_edge) fare fare 1b1; end endmodule来复盘一下每个信号的类型选择理由clk、rst_n、mile_pulse 是模块的输入端口只能 wire。pulse_dly 是打拍寄存器在 always 块里赋值必须 reg。pulse_edge 是组合逻辑信号由 assign 驱动必须 wire。fare 是输出端口加累加寄存器在 always 块里赋值必须 output reg。如果当初把 pulse_edge 错写成 reg那就要么把脉冲检测改写成一个组合 always 块要么在那里报错。如果错写成 wire anyway 那没问题但把 fare 写成 wire那整个累加逻辑就废了——wire 不能在 always 块里被赋值。这个例子的关键就是先想清楚信号是被时钟驱动的寄存器还是由组合逻辑算出来的连线再决定它的类型。想清楚了选型就是自然而然的事情。7. 实际工程中的几个实用建议7.1 命名规范配合类型声明我写代码的时候有一个习惯信号命名里直接体现类型语义。比如_n结尾的是低有效信号rst_n_r结尾的是打拍后的寄存器版本pulse_dly_r_w结尾的是组合逻辑信号data_w_d结尾的是延迟一拍的数据data_d这样做的好处是别人看代码时从名字就能猜出大概类型减少沟通成本。虽然 Verilog 没有强制要求但团队协作时这种隐式规范非常值钱。7.2 声明位置和代码风格我个人的习惯是端口信号在模块头部集中声明内部信号在有需要的地方就近声明。这样既保持了代码整洁又不至于把信号列表拖得太长。module example( input clk, input rst_n, input [7:0] data_in, output reg [7:0] data_out ); // 内部信号 wire [7:0] data_in_d; reg [7:0] data_in_r;这种风格在做的项目里被团队沿用代码 review 速度快了很多因为信号的类型一目了然。7.3 初学阶段怎么练习如果你是刚入门我建议按这个顺序练习先把 assign 语句练熟所有组合逻辑都用 assign wire 实现。再写简单的 always 块时序逻辑计数器、分频器用 reg。然后写带组合 always 块的代码感受 reg 被综合成组合逻辑的情况。最后把 wire/reg 和阻塞/非阻塞赋值放到一起综合考虑。这个顺序的好处是每一步的规则边界都很清晰第一步只接触 wire第二步只接触 reg第三步才开始遇到reg 但其实是组合逻辑的概念第四步就是把所有规则融会贯通。我用这个顺序带过不少人进度比直接看语法书快很多。8. 关于 wire 和 reg 再深挖一层8.1 wire 和 net 类型家族wire 只是 Verilog 里线网类型net type的一种。net 类型还包括 wand、wor、tri、supply0、supply1 等。其中 tri 在实际工程里和三态逻辑强相关wand/wor 用于开漏/开集电极场景但日常 RTL 设计里最常用的还是 wire。如果你在代码里看到别人用了tri而不是wire不用惊讶两者在很多工具里行为几乎一致。tri 更强调三态驱动的语义但综合结果和 wire 没有本质区别。这里不建议初学者一开始就纠结这些变体先把 wire 用熟后面遇到了再查就行。8.2 reg 和 integer、real 的关系Verilog 里 reg 并不是唯一的变量类型。integer、real、time 也是变量类型它们也可以在 always 块里被赋值。但实际综合时integer 通常会被当作寄存器组来处理real 只用于仿真不可综合。这里给初学者一个建议RTL 设计里能用 reg 就用 reg尽量不要混用 integer 和 real。虽然语法上允许但综合工具对这些类型的支持程度不一用了之后可能引入不必要的麻烦。面试时如果你能说出 integer 在综合时会占用多少资源面试官会高看你一眼——一个 32 位的 integer综合出来就是 32 个寄存器。8.3 SystemVerilog 里的 logic 类型如果你已经进阶到 SystemVerilog会发现多了一个logic类型。logic 可以同时兼容 wire 和 reg 的功能——既能被 assign 驱动也能在 always 块里赋值。看起来很方便对吧但其实它也有坑logic 不允许有多个驱动源。它本质上是一个变量类型而不是线网类型所以多驱动场景下比如三态总线仍然必须用 wire。所以即使你用 SystemVerilog明确知道 wire 和 reg 的区别依然是基本功。logic 只是一个类型推断的便利工具不能替代对底层硬件语义的理解。面试官问 wire 和 reg 的区别可不是为了确认你背过语法书而是要看你有没有建立代码描述硬件的心智模型。9. 最后聊几句心得体会关于 wire 和 reg我见过太多人死记硬背规则结果一写代码还是错。我想说的其实就一句话选 wire 还是 reg本质上是在回答一个问题——你写的这段代码描述的是线还是变量线就是物理连接只有被驱动的结果没有记忆能力变量是过程赋值的容器它存在于某个 always 块的作用域里。顺着这个思路你再看那些规则就都能推出来连续 assign 驱动的是线 → wirealways 块过程赋值的是变量 → reg端口 input 只能是线 → wireoutput 取决于内部怎么驱动它inout 必须是线 → wire我刚开始写 RTL 的时候也犯过把 reg 当寄存器的经典错误后来写了一个计数器模块发现 reg 也能综合成组合逻辑才真正理解了两者的语法和硬件映射关系。从那以后我写代码的顺序就变成了先想清楚信号在硬件里是什么再动手敲代码。这一步想通了wire 和 reg 就不再是问题。最后再给一个小技巧。如果你在写代码的时候不确定某个信号该用 wire 还是 reg可以这样问自己这个信号有没有可能被多个 always 块同时赋值如果有它必须被拆分成 wire 来综合——因为在 Verilog 的语法框架里只有 wire 能和多个驱动源共存。如果它只在一个 always 块或者一个 assign 语句里出现那按赋值方式选类型就行。这个判断法在大多数场景下都能帮你快速做出正确决定。Verilog 的学习就是这样很多概念单看语法是死的放到实际项目里就活起来了。wire 和 reg 只是起点后面还有状态机、FIFO 跨时钟域、时序收敛这些大山等着翻。但基础打牢了后面每一步都会顺畅很多。
返回列表