Verilog

一、数字集成电路的发展与演变

1.1 数字集成电路复杂度趋势与设计方法的演变

​ 随着半导体工艺的进步,数字集成电路的复杂度呈指数级增长,如今集成度已经达到数十亿个晶体管。这种硬件规模的爆发推动了设计方法的不断演变。在70年代,设计主要以元件为基础,对应多块印刷版系统;80年代发展为以单元为基础的单片系统;到了90年代,以RTL综合为基础的设计方法成为了主流。当前,设计方法已经全面演变为以IP为基础的复用设计。

​ RTL(Register Transfer Level,寄存器传输级)是指通过描述数据在寄存器之间的流动和逻辑操作来建模电路的抽象级别;
​ IP(Intellectual Property)是指在IC设计中那些预先设计好、经过验证且可重复利用的成熟电路功能模块;
片上系统(SoC,System on Chip)则是指将微处理器、模拟IP、数字逻辑等复杂系统集成在单一芯片上的高级架构。

1.2 硬件描述语言(HDL)的产生与功能

​ 传统的原理图输入方式在面对32位总线或复杂状态机时会变得极其繁琐且难以维护。
​ HDL产生的基础是借鉴了C、FORTRAN、Pascal等程序化设计语言的理念,极大地提高了集成电路设计的效率和可靠性。硬件描述语言(HDL : Hardware Description Language)是一种高级程序语言,通过对数字电路和系统的语言描述,可以对数字集成电路进行设计和验证。 其主要功能是让设计工程师能够根据电路结构特点,采用层次化的设计结构,将抽象的逻辑功能用电路的方式进行实现。使用HDL,设计者可以通过参数化(如改变总线位宽)轻松实现电路规模的扩展,而不需要像原理图那样逐根连线。

1.3 Verilog HDL的发展历程

​ Verilog诞生于80年代的Verilog-XL模拟器。在1990年公开发表并移交OVI组织后,它逐渐成为了行业标准。面试中偶尔会提及的标准节点包括:1995年发布的Verilog IEEE 1364-1995标准以及2001年发布的Verilog IEEE 1364-2001标准(后者引入了更方便的端口声明等重要特性)。随着系统级设计需求的增加,2005年进一步推出了System Verilog (IEEE 1800-2005),极大增强了语言的验证能力和系统级抽象能力。

1.4 Verilog HDL与VHDL的区别

​ 在描述和抽象能力上,这两种主流HDL语言有着显著的差异,这也是面试中的高频考点。VHDL的描述能力自上而下涵盖了系统级、算法级、寄存器传输级一直到逻辑门级,它常常被用作建立ASIC(Application-Specific Integrated Circuit,专用集成电路,即为特定用途定制的芯片)模型库的基准(例如VITAL)。相比之下,Verilog HDL的建模能力不仅涵盖了上述所有高级别抽象,还在底层向下延伸到了开关电路级(Switch Level)。这意味着Verilog在底层晶体管级别的网络模拟和门级延迟描述上具有更直接的表达能力。

image-20260312215025740

​ 在硬件描述语言的建模能力中,自上而下分为五个主要的抽象层级。面试中常要求解释这些层级的具体含义以及它们在设计验证流程中的作用。

​ 系统级(System Level)位于最高抽象层,它主要用于描述整个系统的行为和整体性能,而不涉及任何底层的硬件结构细节。系统级的主要功能是进行前期的架构探索与系统级的行为验证。从图中可以看出,System Verilog在这一层级进行了大量的能力扩展。

​ 算法级(Algorithm Level)紧随其后,它侧重于实现系统功能的具体数学模型或逻辑算法。算法级的重点在于纯逻辑与数学功能的描述与验证,帮助设计者在将其映射到具体硬件结构之前,确认核心算法的正确性。

​ 寄存器传输级(RTL级,Register Transfer Level)是目前数字IC前端设计最核心、最常用的层级。它通过描述数据在各个寄存器之间的流动路径,以及组合逻辑对这些数据的处理来构建电路。RTL级不仅用于编写可综合的硬件逻辑描述,也是进行前端功能验证(前仿)的绝对主力

​ 逻辑门级(Logic Gate Level)描述了电路由哪些基本的逻辑门(如与门、或门、非门)以及触发器互连构成。在实际开发中,门级代码通常不是由工程师手动编写的,而是由逻辑综合工具将RTL代码映射生成的门级网表,其主要功能是用于包含实际电路延迟的时序验证(后仿)和后续的物理布线实现。

​ 开关电路级(Switch Level)是图中展示的最底层抽象,它直接描述MOS晶体管的开关行为及其物理连接关系。Verilog HDL能够向下延伸到开关电路级,这意味着它能在最底层的晶体管级别进行精确的网络模拟和门级延迟描述,这也是VHDL所不具备的能力。此外,图中特别标注的VITAL(VHDL Initiative Towards ASIC Libraries),则是VHDL专门用来建立ASIC底层标准单元模型库的基准规范。

1.5 Verilog HDL的应用范围

数字集成电路的完整开发流程包含了总体方案、系统建模、RTL编码、功能验证、综合、时序验证、物理布局布线以及最后的工艺实现。这里的综合(Synthesis)是指将高级的RTL代码映射转换成底层由标准单元组成的门级网表的过程。在这个流程中,Verilog HDL的应用范围极其广泛,主要覆盖前端设计与验证阶段:包括系统建模、RTL编码、功能验证(前仿)和时序验证(后仿)。此外,在后期的原型建立和测试阶段,也需要大量使用Verilog来编写测试激励(Testbench)以驱动硬件平台进行验证。

image-20260312220311439

数字集成电路的完整设计流程是一个严谨的自顶向下的过程。首先是总体方案阶段,主要根据需求制定芯片的规格书,确定系统架构、核心性能指标和功耗面积预算。紧接着进入系统建模阶段,工程师会搭建高层次的算法或行为级模型,以验证前期架构方案的数学和理论可行性。系统建模是整个芯片设计的地基,通常也会部分使用到Verilog HDL或更高级的System Verilog来进行高层抽象描述。

在架构确立后,项目便正式进入前端设计阶段的核心:RTL编码。设计人员需要根据系统模型,使用Verilog HDL将抽象的算法转化为具体的寄存器传输级硬件逻辑代码。完成编码后,必须无缝衔接功能验证(即前仿)。在这个阶段,工程师同样会大量使用Verilog HDL编写复杂的测试激励(Testbench),用来给RTL设计施加输入并观察输出,以确保逻辑功能完全符合预期。对于面试而言,如何保证RTL代码的完备性以及如何搭建高效的验证环境是极具区分度的考点。

当纯逻辑功能验证通过后,设计就进入了连接前端与后端的桥梁阶段:综合。逻辑综合工具会将高级的RTL代码转化为由基础门电路构成的门级网表(Netlist)。因为综合器引入了真实物理门电路的延迟模型,所以接下来必须进行严格的时序验证(后仿或静态时序分析STA)。时序验证的目的是检查电路在加入了实际延迟后,是否仍然满足建立时间和保持时间的要求,这是防止芯片出现时序违例和亚稳态的关键防线。

​ 时序闭合后,网表将移交后端进行物理综合与布局布线。工程师需要把逻辑门映射到实际的硅片版图上,并完成复杂的金属层连线。随后进行物理验证(包括DRC设计规则检查和LVS版图与原理图一致性检查),确保最终版图符合晶圆厂的制造规范。在投入高昂的流片成本之前,团队通常还会进行原型建立和测试,比如将代码下载到FPGA中进行真实环境下的硬件级验证。确认万无一失后,最终生成GDSII文件交付代工厂完成最后的工艺实现。纵观全局,Verilog HDL的应用不仅贯穿了从系统建模到时序验证的整个前端流程,也在后期的原型测试中充当着不可或缺的驱动和验证角色。

二、Verilog基础知识

2.1 词法规定(空白符、注释符、标识符与关键字)

​ 空白符主要包括空格符(\b)、制表符(\t)、换行符和换页符。在Verilog代码编写中,合理使用空白符可以极大地提高代码的可读性与排版美观度。需要重点记住的是,在编译和综合阶段,所有的空白符都会被工具自动忽略,它们绝对不会对最终生成的硬件电路逻辑产生任何影响。

image-20260312225033658

Verilog提供了两种注释方式。单行注释以双斜杠“//”开头,编译器会直接忽略从该符号起到当前行尾的所有内容。多行注释以“/”开始,直到遇到“/”结束。这里在笔试中常考的一个语法陷阱是:多行注释内部绝对不允许再次嵌套多行注释,否则会导致编译错误;但是多行注释内部是允许嵌套单行注释的。

image-20260312225010881

标识符被用来给模块、端口、信号或参数命名,它可以是字母、数字、“$”符号和下划线“_”的自由组合。这里的核心规则是,Verilog的标识符严格区分大小写,且首字符必须是字母或者下划线,绝不能以数字开头。此外,为了能使用特殊符号命名,语言还引入了转义标识符。转义标识符必须以反斜线“\”作为开头,并且严格要求以空白符(空格、制表符或换行符)作为结尾来界定作用范围,通过这种方式,标识符内部就能包含任何可打印的特殊字符了。

image-20260312225104086

image-20260312225151243

关键字(也称保留字)是Verilog语言内部事先定义好的专用词汇,用于组织核心的语言结构,设计者不能将它们挪作他用。对于关键字,最关键的记忆点是:Verilog HDL中所有的关键字强制要求必须全部小写。例如,小写的“always”是系统识别的关键字,但如果你写成大写的“ALWAYS”,系统只会把它当成一个普通的标识符来处理。

2.2 四种基本逻辑数值

​ Verilog HDL中定义了四种基本的逻辑数值状态,用来精确模拟真实的物理硬件电路。其中,逻辑“0”代表低电平、逻辑0或条件为“假”,通常对应电路中的接地(GND);而逻辑“1”代表高电平、逻辑1或条件为“真”,通常对应电路中的电源电压(VCC)。这两种是数字电路中最基础的确定电平状态。

除了确定的0和1,Verilog还引入了另外两种在硬件仿真中极其重要的特殊状态。逻辑“x”或“X”表示不确定或未知的逻辑状态。在仿真过程中,如果寄存器上电后未被复位初始化,或者出现了多驱动源逻辑冲突(例如两个信号同时向同一条线输出0和1),信号就会呈现“X”态。这也是面试中常考的调试问题:仿真波形图中出现红线(X态)通常就是由于未复位初始化或多驱动冲突引起的

最后一种是逻辑“z”或“Z”,它表示高阻态。高阻态意味着该节点处于悬空状态,与电路的其他部分电气断开,没有受到任何外部驱动源的拉高或拉低。在实际的芯片设计和面试考核中,高阻态“Z”常被大量用于设计三态缓冲器和双向数据总线(inout端口),这是实现多设备共享通信总线的核心基础。

2.3 常量的表示方式(整数与实数)

​ 在Verilog中,整数的完整表示格式为 +/-<size>'<base_format><number>。其中size代表数值的位宽,base_format代表进制基数(b/B表示二进制,o/O表示八进制,d/D表示十进制,h/H表示十六进制)。需要特别注意的是,位宽<size>必须是一个确定的十进制常数,绝对不能是表达式(例如(4+4)'b11是非法的)。此外,数值部分除了可以包含数字外,在二进制、八进制和十六进制中,还可以合法地包含未知态x、高阻态z(在数值中也可以用?代替z),以及用于提高代码可读性的下划线“_”

在笔试的语法改错题中,有几个常见的整数表示陷阱极其容易丢分。首先,如果想要表示一个负数,负号“-”必须严格放在整个表达式的最左边,也就是位宽的最前面(例如-4'd4是合法的,而4'd-4则是数值部分带负号,属于非法格式)。其次,单引号(位宽标示符)和后面的基数符号(如b、d)之间绝对不允许出现任何空格,否则会导致编译报错。

image-20260312225739369

除了整数,Verilog还支持实数(浮点数)的表示,第一种常用方法是十进制表示法。这种格式对小数点的使用有非常严格的语法规定:小数点的两侧都必须有数字存在,哪怕是0也不能省略,否则就是非法的表示形式。例如,3.00.2都是正确的规范写法,但是像6.或者.3这种省略了小数点某侧数字的写法在编译时会直接报错。

实数的第二种表示方法是科学计数法,它采用大小写均可的“e”或“E”来表示10的幂次方,例如564.2e2的值就是56420.0,而3E-3的值是0.003。与十进制表示法一样,科学计数法前面的基数也不能省略小数点两侧的数字(如.3e5是非法的)。最后补充一个重要的工程规范:无论是很长的整数还是实数,合理使用下划线“_”来分隔数字(例如5_4582.2158_5896)不会改变数值大小,却能极大提高代码的辨识度,这在面试手撕代码时能很好地体现你的专业素养

image-20260312225819800

2.4 数据类型分类(连线型与寄存器型)

在Verilog中,物理数据类型主要分为连线型、寄存器型和存储器型。为了解决多驱动源带来的赋值冲突,语言定义了不同的信号强度(如最强的supply、较弱的pull/weak,直至高阻态highz)。理解这些数据类型是进行硬件逻辑建模的绝对核心。

2.4.1、 连线型数据(Net Type)

image-20260312230524234

连线型代表物理连线,最常用的是wire和tri(系统默认缺省类型为wire)。此外还包括具有线或特性的wor/trior、线与特性的wand/trand,以及电源地建模的supply1/supply0等。连线型变量的声明格式可以依次包含:网络类型、驱动强度、位宽和仿真延迟。
  面试中常考的一个重点是wire和tri的区别:虽然它们在语法树上功能相似,但wire类型通常用于表示单个驱动器驱动的信号线(如简单的输入输出端口或内部连线),不支持三态行为;而tri类型专门用于多个驱动器驱动的信号线,支持三态逻辑(0、1、Z之间切换),因此tri类型常被用于允许多个设备共享的总线驱动器设计中。

2.4.2、 寄存器型数据(Register Type)与存储器
  reg型是数据储存单元的抽象类型,对应触发器、锁存器等具有状态保持作用的硬件元件。在编写代码时,reg型变量常用于行为级描述(如always过程块),并且必须由过程赋值语句对其进行赋值。需要特别注意的是,reg型变量一般缺省为无符号数,如果将一个负数赋给无符号的reg变量,它会自动转换成对应位宽的二进制补码形式(例如给4位reg赋值-2,实际存储的值是4’b1110,即14)。
  由多个reg型变量可以构成存储器(Memory)。在笔试题中经常需要严格区分位宽和深度的定义顺序:例如reg [7:0] mem1 [255:0];,这里的[7:0]代表单个寄存器的位宽是8位,而
[255:0]代表这个存储器具有256个这样的寄存器(即存储深度为256)

image-20260312230632302

  • wire 类型: * 用于 assign 关键字引导的连续赋值语句。 * 用于模块实例化时的端口连接线(作为输入或输出的物理连接)。 * 用于描述纯粹的组合逻辑电路,且该逻辑不位于 always 块内
  • reg 类型: * 必须用于 initialalways 过程块内部的所有赋值目标。 * 在时序逻辑中,用于触发器、计数器、状态机状态寄存器等。 * 在 always @(*) 块中,用于描述复杂的组合逻辑(例如为了使用 if-elsecase 语句而声明为 reg,但最终生成的是组合电路)。

2.4.3、 抽象数据类型
  除了物理类型,Verilog还提供用于辅助建模和测试的抽象数据类型。整型(integer)是简单的32位有符号整数,通常在Testbench的for循环中用作计数器。时间型(time)则是64位的无符号数,主要用于对模拟时间的存储与计算处理,常与$time系统函数配合使用。实型(real)表示浮点数值,多用于复杂的延迟时间计算。

image-20260312231114338

image-20260312231125758

image-20260312231220009

最后是极其重要的参数型(parameter)。**parameter属于常量,在仿真开始前就被赋值,且在整个仿真过程中保持不变。**在实际的集成电路设计(如状态机编码、模块参数化调用)中,大量使用parameter可以极大地提高代码的可读性、可维护性和IP模块的复用性。

image-20260312231239342

2.4.4、 Verilog运算符

在Verilog中,运算符的优先级决定了表达式的计算顺序。整体优先级从高到低大致为:单目运算符(如按位取反~、逻辑非!)优先级最高,接着是算术运算符(先乘除取模,后加减)、移位运算符、关系运算符、等式运算符,然后是按位运算符、逻辑运算符,最后是条件运算符(三目运算符?:)。在实际手撕代码时,强烈建议在复杂的表达式中直接使用括号()来明确计算顺序,这不仅能避免优先级记错导致的逻辑Bug,也是良好的工程代码规范。

image-20260312231656322

算术运算符与位宽保留规则:算术运算符包括加(+)、减(-)、乘(*)、除(/)和取模(%)。这里面试最常挖坑的点在于计算结果的位宽判定:算术表达式本身结果的位宽由参与运算的最长操作数决定;但在赋值语句中,最终保留的结果位宽严格由等号左侧目标变量的位宽决定。例如,两个4位数相乘,如果赋值给一个4位的变量,那么结果的高位将被直接舍弃截断。

关系运算符与相等关系运算符(核心考点):关系运算符(>、<、>=、<=)用于比较大小,遇到未知态时结果为x。紧接着是面试中的超高频必考点——双等号==(二值逻辑相等)与三等号===(四值逻辑全等)的区别==在比较时,只要操作数中包含任何不定位x或高阻态z,比较的输出结果就立刻变为不定值x。而===进行的是严格的状态匹配,它将xz也视作可以比较的具体状态,因此**===的比较结果永远只有确定的0或1,它常用于测试激励(Testbench)中严格检查信号状态**。

image-20260312231908106

逻辑运算符与按位运算符的区别:逻辑运算符(&&、||、!)是将整个操作数视作一个整体进行真假判断,其运算结果永远只有1位的1(真)、0(假)或x(不定态)。需要记住的细节是,对于不定态进行逻辑取反(!x),其结果依然是不定态x。按位运算符(~、&、|、^异或、^~同或)则是对每一比特位进行逐一运算。这里的常考陷阱在于位宽不匹配的处理:当按位运算的两个操作数位宽不一致时,Verilog会自动在较短操作数的左侧高位无条件补0,补齐至相同位宽后再进行逐位运算

2.5 模块与端口的基础结构

​ 模块(module)是Verilog HDL语言的基本单元,代表一个实现特定逻辑结构的基础功能块。一个完整的模块由四个核心部分组成:模块的开始与结束、端口定义、数据类型说明以及逻辑功能描述。在语法结构上,模块必须以关键字module开始,并且包含模块名和端口列表的定义行必须以分号结束,最后使用关键字endmodule来标志整个模块代码的结束。这是所有Verilog设计的标准外层骨架。

image-20260312232353527

端口是模块与外部环境进行数据通信的物理接口,主要分为输入端口(input)、输出端口(output)和双向端口(inout)。在定义完端口后,需要进行数据类型说明(如wireregparameter等),用来声明模块内部使用的信号和常量。在面试中常被问到的一个关键点是:inputinout端口在模块内部默认且只能是wire(连线)类型;而output端口则根据内部驱动方式的不同,既可以是wire类型(用assign驱动),也可以声明为reg类型(在always块中驱动)

逻辑功能描述是模块的核心灵魂,用于具体实现各种组合逻辑和时序逻辑。在Verilog中,描述逻辑功能主要依赖于以下几种语句:用于测试环境初始化的initial语句(综合工具通常会忽略)、用于行为级建模的always语句、用于组合逻辑的连续赋值语句(assign),以及底层子模块的实例化语句、函数(function)和任务(task。需要牢记的是,在同一个模块内部,这些语句模块之间都是相互独立、并发执行的。

当我们在顶层设计中引用(实例化)底层子模块时,端口的连接对应主要有两种方式。第一种是位置映射,即严格按照原模块定义的端口顺序依次填入连接信号,这种方式一旦底层端口顺序修改,顶层极易发生致命错误。第二种是命名映射,格式为.底层端口名(外接信号名)。在实际的企业工程规范和面试手撕代码中,强烈要求且只推荐使用命名映射方式。因为这种方式将端口名与外接信号强绑定,不必理会底层定义的顺序,极大地提高了代码的可读性、可移植性和容错率。

image-20260312232744030

image-20260312232851248

三、Verilog HDL程序设计语句和描述方式

3.1 数据流建模

​ 数据流建模侧重于描述系统中数据的流向和处理过程,而不直接涉及底层物理门电路的具体结构。在实际的数字电路设计中,数据流建模最核心的应用是通过连续赋值语句(assign)来实现纯组合逻辑电路的设计。这是因为连续赋值语句能够完美模拟组合逻辑中“只要输入有变化,输出就会立刻响应”的硬件物理特性。

image-20260313153027440

在使用连续赋值语句时,最基本也是最重要的语法规则是:赋值目标(即等号左边的变量)必须且只能是线网类型(wire),包括标量和向量,绝对不能是寄存器类型(reg。在代码编写上,它分为显式和隐式两种格式。显式赋值是先声明wire变量,再用assign关键字独立完成赋值(如 wire y; assign y = a & b;);隐式赋值则是在声明wire变量的同时直接赋初值(如 wire y = a & b;)。在更复杂的场景下,这两者都可以附加驱动强度和延时量进行精细化建模。

针对面试和笔试的选择改错题,连续赋值语句有几个极其容易挖坑的特征需要死记硬背。第一,连续赋值语句绝对不能出现在过程块(如always或initial)内部。第二,多个assign语句之间在硬件上是完全并行的,它们在代码中的书写先后顺序对最终生成的逻辑电路没有任何影响。第三,它具有“连续驱动”的特性,只要等号右边任何一个操作数发生变化,等式立刻重新计算并更新左侧线网。第四,也是常考的时序概念:assign语句中设置的延时具有硬件电路的“惯性延时”特性,这意味着任何持续时间小于该延时量的毛刺或脉冲都会被物理滤除,根本不会在输出端口上体现出来。

重点复习**连续赋值语句(assign)**的使用场景与规则。

3.2 行为级建模(核心重点)

3.2.1 过程语句

​ 行为级建模侧重于描述数字电路的逻辑功能和算法行为,而不直接涉及底层的门级硬件结构。在行为级建模中,最核心的载体就是过程语句(initialalways)。这里有一个绝对不能犯的语法铁律:在任何过程语句(initialalways)中,被赋值的目标信号(等号左边的变量)必须严格定义为寄存器类型(reg,绝不能是wire类型。

过程语句主要分为initialalwaysinitial语句块从仿真0时刻开始执行,且在整个仿真过程中只执行一次,因此initial语句是不可综合的,它只能存在于Testbench中用于产生测试激励或赋初值。相对而言,always语句块则是不断循环执行的,只要满足其后面的“敏感事件列表”,就会触发执行。always语句是可综合的(只要代码风格规范),它是我们在实际RTL设计中构建组合逻辑和时序逻辑的绝对主力

敏感事件列表的编写是面试考察基本功的重灾区。当使用always描述纯组合电路时,必须将所有的输入信号全部列入敏感事件列表(或者直接使用always @(*),否则会产生意想不到的锁存器(Latch)。当描述时序电路时,敏感列表必须包含时钟的边沿(如posedge clk。这里常考同步复位与异步复位的区别:同步复位的敏感列表中只有时钟沿(如always @(posedge clk)),复位操作在时钟沿到来时才生效;而异步复位的敏感列表中必须同时包含时钟沿和复位信号沿(如always @(posedge clk or negedge clear)),复位信号一旦跳变,无论有没有此时钟,都会立刻清零

image-20260313154154871

在过程语句内部,我们可以使用语句块来组织多行代码。主要分为串行语句块(begin-end)和并行语句块(fork-join)。begin-end内部的语句是按照从上到下的顺序依次执行的,语句前面的延时是相对于上一条语句结束的相对时间,这是实际电路中最常用的描述方式。而**fork-join内部的所有语句是在同一时刻并发执行的,其延时是相对于语句块启动时的绝对时间,最重要的是,fork-join只能用于仿真测试程序,它是绝对不可综合的**。

3.2.2 过程赋值语句

​ 过程赋值语句分为阻塞赋值(=)和非阻塞赋值(<=)两种极其重要的形式。阻塞赋值(=)在串行语句块中严格按照先后顺序依次执行,其执行逻辑是:先计算等号右端表达式的值,然后立刻将新值赋给左边的变量。正因为赋值是立刻生效的,它会“阻塞”后面的语句,直到当前语句执行完毕。相比之下,非阻塞赋值(<=)在串行语句块中没有先后顺序之分,各条语句是并行执行的。它的执行逻辑是:先统一计算所有语句右端表达式的值,然后等到延时时间结束时(或者当前时间步的末尾),才将计算好的值统一赋给左边的变量。

为了在面试手撕代码时不出错,这里有一个极其好用的快速记忆技巧:把阻塞赋值的“=”想象成一根导线,直接连通,立刻得到新值;把非阻塞赋值的“<=”想象成一个触发器(寄存器),不仅符号像,而且它传递的是当前时刻的旧值,要在下一个时钟沿(或者时间步完结)才更新。如果前面一句使用了阻塞赋值,后面一句引用该变量时用的是刚更新的新值;如果使用了非阻塞赋值,后面一句引用该变量时用的依然是原来的旧值。

image-20260313155334315

这种机制的差异直接决定了综合后生成的硬件电路结构,这是面试最爱考的看代码画电路题。例如,针对 out1 = din; out2 = out1;(阻塞赋值),out2 立刻获取了 din 的最新值,综合出来的是两个并联的触发器(甚至只有一个触发器,两条线连在一起);而如果是 out1 <= din; out2 <= out1;(非阻塞赋值),out2 获取的是 out1 在时钟上升沿到来那一瞬间的旧值,这会综合出两个级联的D触发器,也就是典型的移位寄存器。因此,业界有一条死规矩:设计时序逻辑电路(含有posedge/negedge)时必须使用非阻塞赋值(<=),设计组合逻辑电路时必须使用阻塞赋值(=

最后,Verilog还提供了一类特殊的“过程连续赋值语句”,用于在过程块内部对信号进行强制覆盖。主要包括 assign/deassign(只能对 reg 型变量操作)和 force/release(可对 regwire 型变量操作)。面试中常考它们的优先级与使用场景:force/release 的优先级高于 assign/deassign,且 force 语句通常只用于仿真测试阶段的强行灌入信号打补丁或抓取波形,绝对不推荐用于可综合的RTL代码设计中。assign/deassign 用于仿真中强制修改 reg 的值,而 force/release 威力更大,能强制覆盖 regwire 的值,它们如同“上帝之手”仅用于 Testbench 调试(如强行拉低某个信号看系统反应),在实际硬件电路中无法实现,因此绝对不可综合

3.2.3 条件分支语句

​ 条件分支语句主要包括if-elsecase语句,它们在行为级建模中用来实现数据流的选择和路由。if-else语句具有从上到下的顺序判断特性,在面试中常考的一个知识点是:带有else if的多分支结构在逻辑综合后,会隐式地生成带有优先级(Priority)的级联硬件电路(例如优先编码器)。写在最前面的if条件拥有最高的优先级,这意味着在关键路径设计中,我们应该把最晚到达或最关键的信号放在最前面的判断条件里。

相对而言,case语句提供了一种多路分支选择的机制,它的结构比嵌套的if-else更加直观,综合出来的通常是没有优先级的、并行的多路选择器(MUX)或译码器。在使用case语句时,有几个非常关键的语法细节:可以通过逗号,将多个值放在同一个分支里进行多值匹配;它的匹配必须是精确相等的。

这是本节最大的面试必考点:如何避免产生意外的锁存器(Latch)?在设计组合逻辑(使用always @(*))时,如果if语句缺少了else分支,或者case语句没有列举出所有可能的状态且漏写了default缺省项,综合工具就会认为在这些未定义的情况下,输出需要“保持原值”。在物理硬件上,“保持原值”就必须生成一个电平敏感的锁存器。**在现代同步时序电路设计中,意外产生的锁存器会导致极难排查的时序违例(Timing Violation),是绝对不被允许的。因此,写组合逻辑的case必须带default,写if必须带完整的else,这是一条铁律。产生 Latch 会导致组合逻辑产生意外的“记忆”功能,使输出锁死在旧值导致逻辑错误,同时它对毛刺(Glitch)极度敏感,且由于其电平透明特性会使时序分析(STA)**变得极其复杂,极易引发时序违例,严重威胁同步电路的稳定性。

除了分支语句,Verilog还提供了四种循环语句:foreverrepeatwhileforforever表示永久循环,通常结合延时(如#50 clock=~clock;)在测试激励(Testbench)中用来生成系统时钟信号repeat用于执行固定次数的循环;whilefor则是条件循环。在实际的芯片前端设计(RTL编码)中,这四种循环语句绝大多数情况只用于仿真验证代码(Testbench)中,通常是不可综合的(除了部分边界恒定、用于代码复制展开的for循环外)。因此在手撕可综合代码时,一定要慎用循环语句。

3.3 结构化建模

3.3.1 模块型建模

​ 模块级建模是通过调用已经设计好的底层模块(module)来构建更复杂的硬件电路结构。在这个层级树中,如果一个模块不再被其他任何模块调用,那么它就是整个硬件系统的顶层模块(Top Module),一个纯粹的硬件系统必定有且只有一个顶层模块。模块调用的基本语法格式为:模块名 <参数值列表> 实例名 (端口连接表);。这里必须要有唯一的实例名,用来在硬件实体中区分不同的物理模块。

image-20260313163905239

当我们需要多次调用同一个底层子模块时,除了逐行写出,还有两种便捷写法。第一种是在一条语句中写出多个实例名并用逗号隔开。第二种在面试读代码题中常考的是阵列调用(Array Instantiation)。它的语法格式为:<被调用模块名> <实例阵列名>[阵列左边界:阵列右边界](<端口连接表>);。例如 AND AND_ARREY[15:0](out, a, b); 这一句代码,在综合时会直接生成16个并行的基本与门物理电路,这在处理多位数据总线逻辑时极其高效。

image-20260313163933224

接下来是模块例化中最重要的考点:端口对应方式。第一种是端口位置对应方式(顺序映射),即严格按照底层模块定义时的端口顺序依次填入连接信号。这种方式极度脆弱,一旦底层模块偷偷修改了端口顺序,顶层连线就会全盘崩溃且很难排查。第二种是端口名对应方式(命名映射),语法为 模块名 实例名 (.底层端口名1(外接信号1), .底层端口名2(外接信号2));在所有的企业级IC设计规范和面试要求中,强制要求且仅推荐使用端口名对应方式(命名映射)。因为这种方式将外接信号与底层端口强绑定,不仅无视顺序,而且悬空的端口也一目了然,极大地提高了代码的健壮性和可读性。

image-20260313164122797

最后是笔试中容易被坑的细节:不同端口位宽的匹配问题。在模块例化连线时,底层端口和外接信号表达式之间实际上存在着一种隐含的连续赋值(assign)关系。当两者的位宽不一致时,Verilog会采用右对齐(从最低位LSB开始对齐)的原则进行匹配。如果外接信号位宽更宽,那么多出的高位会被直接截断;如果外接信号更窄,那么多出的高位会自动补零。虽然语法允许,但在严谨的数字设计中,一定要确保位宽精确匹配,避免产生意想不到的截断错误。

3.3.2 门级建模

​ 门级建模是指直接调用Verilog HDL内部事先定义好的基本逻辑门元件来构建电路。Verilog内置了26个基本元件,其中包含14个门级元件和12个开关级元件。对于数字逻辑设计而言,我们重点掌握三大类门级元件:多输入门andnandornorxorxnor)、多输出门bufnot)以及三态门bufif1bufif0notif1notif0)。

image-20260313164417605

在调用这些内置的门级元件时,端口的排列顺序是笔试题中最爱挖坑的地方,也是必须死记硬背的规则
  第一,对于多输入门(如与门、或门),它只能有一个输出,但可以有无限个输入。其端口排列规则是:输出端口必须放在第一位,后面紧跟所有的输入端口。例如 and A1 (out, in1, in2, in3);
  第二,对于多输出门(如非门、缓冲器),它可以驱动多个输出,但只能接受一个输入。其端口排列规则正好相反:所有的输出端口排在前面,而唯一的输入端口必须放在最后一位。例如 not NOT_1 (out1, out2, out3, in);
  第三,对于三态门,它由数据、控制端和输出端组成。其排列规则是:输出端口排在第一位,数据输入端口排在第二位,控制输入端口排在最后一位。例如高电平使能的三态缓冲器 bufif1 BF1 (data_out, data_in, enable);

image-20260313164544141

在实际的工程应用中,像上图里展示的用底层门级元件去搭建2-4译码器(先实例化内部连线wire,再把notnand一个个连起来)是非常低效的。现代的开发流程是:设计工程师使用行为级或数据流建模写出RTL代码,然后由逻辑综合工具(如Design Compiler)将RTL代码自动映射、转换为这样由基础门构成的“门级网表(Netlist)”。因此,面试官考察门级建模,往往是为了确认你是否具备阅读和分析底层网表的能力,以及在极端情况下手动修改门级电路的功底。

3.3.3 开关级建模

​ 开关级建模是Verilog HDL中最低抽象层级的建模方式,它直接模拟了底层物理MOS晶体管的开关行为。虽然在常规的数字前端RTL编码中几乎不会用到开关级语句,但在面试数字集成电路(特别是涉及全定制设计、标准单元库开发或后端知识)时,如何用纯MOS管搭建基本的逻辑门(如与非门、或非门)是绝对的必考手撕题。

开关级基元主要分为单向的MOS开关和双向开关。MOS开关(nmospmos)在Verilog中模拟了单向的数据驱动能力。它们的端口实例化排列规则是:实例名 (输出端, 数据输入端, 控制端);。这里必须死记硬背它们的物理导通特性:nmos当控制端为高电平(1)时导通,pmos当控制端为低电平(0)时导通。此外,语言还提供了cmos开关,相当于传输门中一对互补的NMOS和PMOS并联,其格式多了一个控制端:(输出, 数据, n管控制, p管控制)

image-20260313165617493

在真实物理世界中,MOS管的源极和漏极往往是对称且双向的。为了模拟这种具有双向传输能力的物理传输门,Verilog提供了双向开关(trantranif0tranif1)。双向开关的特点是其两端都可以作为输入去驱动另一端(端口必须定义为inout类型)。无条件双向开关tran的两端随时保持连通一致;而有条件双向开关tranif1tranif0则分别在控制端为高电平或低电平时才将两端双向连通。

PPT最后展示的“两输入CMOS与非门(NAND2)”电路是面试中的超级高频考点!在这个例子中,使用了两个非常特殊的线网变量:supply1(代表电源Vdd,恒驱动高电平)和supply0(代表地Gnd,恒驱动低电平)。构建任何CMOS反相逻辑的核心法则都在这里体现了:上拉网络(Pull-up Network)由PMOS组成,对于与非门是并联接Vdd;下拉网络(Pull-down Network)由NMOS组成,对于与非门是串联接Gnd。这四条MOS开关例化语句完美对应了底层晶体管的物理拓扑,只要吃透这个例子,面试让你手画或手写或非门、反相器的底层电路,你都能秒杀。

四、Verilog HDL数字逻辑电路设计方法

4.1 组合逻辑电路

4.1.1 组合逻辑电路设计基本知识

​ 组合逻辑电路是数字系统设计的基石。它的核心物理特点是:电路中任意时刻的稳态输出仅仅取决于该时刻的输入,而与电路原来的状态完全无关(即内部没有记忆功能)。在实际的IC设计和面试考核中,优秀的组合逻辑设计必须追求“最小化”:即使用的逻辑器件数目最少、种类最少、连线最简单,同时要尽量减少级数以降低物理门电路的延迟(Delay)和功耗。

Verilog HDL为代码编写提供了极大的灵活性。对于同一个组合逻辑功能(例如PPT中展示的多路选择器MUX和3人表决器),通常有四种完全等效的描述方式,这也是面试中常考的代码转换能力。

​ 第一种是真值表方式(通常对应行为级描述),它主要使用always过程块配合case语句,直接把真值表的输入输出映射关系一一列举出来。这种方式最为直观简单,但在面试手撕代码时一定要注意:必须写全所有可能的状态或加上default缺省分支,绝对不能产生意料之外的锁存器(Latch)

第二种是逻辑代数方式(对应数据流描述)。设计者需要先通过卡诺图将真值表化简,得出最简的布尔代数表达式,然后使用连续赋值语句(assign)和逻辑运算符(如&|)来实现。例如表决器化简后的核心表达就是assign OUT = (A&B) | (B&C) | (A&C);

​ 第三种是结构描述方式(对应门级建模),即像画硬件原理图一样,直接实例化底层的andor等基本门级元件并进行连线。这种方式代码较为冗长,但在阅读和修改底层网表时非常基础且关键。

第四种是抽象描述方式(高级行为级描述),这是现代IC前端RTL设计中最推崇的编写方式。它抛弃了底层的门级细节,直接从系统的数学功能意图出发。例如对于3人表决器,可以直接把三个输入当作数值相加,用if(sum>1)来判断是否通过。

本节最核心的面试认知考点在于:无论你采用上述四种方式中的哪一种,只要代码符合可综合规范,经过现代EDA综合工具优化后,最终映射出的底层物理电路网表通常是完全相同且最简化的。熟练掌握并能自如切换这四种描述方式,是体现你扎实数字逻辑功底的关键。

4.1.2 组合逻辑电路经典例子

​ 这部分通过加法器、比较器、选择器、编码器、译码器和奇偶校验器这六个经典电路,完美展示了Verilog抽象行为建模的强大威力。对于面试而言,我们不需要死记硬背它们底层的门级网表连线,而是要掌握如何用最简洁的高级语法去映射这些硬件功能

加法器与比较器:在早期的结构化设计中,全加器需要用异或门和与门硬连线拼凑;但在现代RTL设计中,设计一个多位加法器只需一句进位位拼接赋值 assign {C_OUT, SUM} = A + B + C_IN; 即可,综合工具会自动将其优化为超前进位等高性能物理电路。同理,多位数值比较器直接使用关系运算符(如 A > B)就能让代码既清晰又高效,完全没必要手动从高位到低位去级联逻辑门。

多路选择器(MUX)与译码器:对于像8选1这样的大型数据选择器,如果用逻辑表达式(与或非)去写会极其臃肿且容易出错。在面试手撕代码中,实现多路选择器和译码器最标准、最受推崇的写法是使用带有 default 分支的 case 语句。它不仅在视觉上高度契合真值表,而且综合出的MUX电路完全并行,没有任何冗余的串行优先级延迟。

优先编码器(面试高频考点):普通编码器严格要求同一时刻只能有一个有效输入,而优先编码器允许同时有多个输入,电路只响应优先级最高的那一个。实现优先编码器有两种最经典的硬件映射代码:第一种是利用 if...else if...else 语句自身从上到下顺序判断所隐式生成的硬件优先级特性;第二种是使用 casex 语句,配合包含无关项(如 ?x)的真值表来实现匹配。这两种写法都会被综合器准确识别为优先编码级联电路。

奇偶校验器(语法秀技点):奇偶校验的核心是对所有数据位进行连续的级联异或运算。在笔试或面试中,千万不要傻傻地实例化一堆 xor 门去两两相异或。面试官希望看到的满分操作是使用Verilog的缩减运算符(Reduction Operator),仅仅一句 assign Fod = ^b; 就能解决战斗。这个单目运算符 ^ 会直接对向量 b 内部的所有比特位进行按位异或相加,得出最终的校验位,极大地体现了你对语言特性的熟练度。

4.2 时序逻辑电路

​ 时序逻辑电路的核心本质在于其输出不仅取决于当前的外部输入,还与电路原有的状态(即历史输入记录)密切相关。它在结构上必须包含组合逻辑电路和具有记忆功能的存储电路(通常为触发器),并且存储电路的状态会形成反馈回路连接到组合逻辑的输入端。这种电路的通用理论模型即为有限同步状态机(FSM),其一切状态转移均在时钟沿的触发下严格同步进行。在面试中,考官经常会问到状态机的分类,你需要明确指出:Mealy型状态机的输出由当前状态和当前外部输入共同决定,而Moore型状态机的输出则仅仅依赖于当前状态,与此时的外部输入完全无关

image-20260313181154006

​ 设计一个标准的时序电路或状态机需要遵循一套严谨的流程,关键步骤包括根据设计要求建立原始状态图/表、进行状态化简以消除冗余、进行状态分配(即状态编码)、选择合适的触发器、推导输出函数与激励函数,以及至关重要的自启动检查。在状态分配环节,不同的编码方式直接决定了电路的性能与面积:二进制编码使用的触发器数量最少(最节省资源),但状态跳转时可能出现多位同时跳变,极易产生毛刺从而导致逻辑错误格雷码由于相邻状态跳转时只有一位发生变化,能够有效抑制毛刺的产生;而独热码(One-hot)采用N个寄存器表示N个状态(仅有一位为1),虽然最消耗触发器资源,但它能极大地方便译码并有效化简后续的组合逻辑电路,这三种编码的优缺点对比是复试中必考的极其关键的知识点。

image-20260313181735325

同一个时序功能(如序列检测或发生)可以有多种截然不同的架构实现,例如既可以使用标准的状态机,也可以巧妙利用移位寄存器配合组合逻辑,或者用计数器作为状态发生器配合输出网络。在实际工程和面试回答中,带异步复位的D触发器 always @(posedge clk or negedge rst_n) 是构建一切时序电路的绝对基石。对于序列发生器,你需要根据给定序列的循环特性,在移位寄存器法(可能存在多余状态需处理)和计数器型(状态设置与输出序列无直接关系,修改方便)之间权衡资源与速度。

​ 在所有Verilog编码风格中,三段式状态机(Three-segment FSM)是工业界最规范、面试官最看重的描述方式。它的本质是将时序控制与组合逻辑严格物理隔离,分为三个独立的 always 块。第一段必须是同步时序逻辑,使用非阻塞赋值 current_state <= next_state; 来完成状态的寄存器翻转第二段必须是纯组合逻辑,敏感列表为 always @(*) 或包含所有输入及现态,使用阻塞赋值 = 来推导 next_state 的跳转条件,此处极其关键的考点是必须写全所有的 if-elsecasedefault 分支,以防止综合出意料之外的锁存器(Latch)第三段专门负责输出,强烈建议采用同步时序逻辑(即在时钟上升沿触发)并使用非阻塞赋值来产生输出信号。采用寄存器输出的第三段不仅能彻底消除组合逻辑输出所带来的毛刺(Glitches),保证信号的纯净度,还能有效切断关键路径,优化电路的总体时序(Timing)。相较于两段式,三段式虽然代码行数略多,但逻辑极为清晰,综合后的电路性能最稳定,是你必须刻在脑子里的黄金模板。

五、仿真验证与Testbench编写

5.1 Verilog HDL电路仿真和编写

电路仿真(Simulation)的本质是利用EDA工具,向设计好的电路模型注入特定的测试激励信号,并观察、提取其产生的输出响应(如波形图、文本日志或VCD波形数据),通过将实际输出与理论期望值进行严格比对,从而确认设计结果的正确性。在真实的数字芯片开发周期中,验证(Verification)是一个系统性地证明设计意图被无误转化为逻辑功能的宏观过程,它往往占据了整个项目绝大部分的时间与工程精力。在面试时考官常会考察你的工程视野,你需要明确表达出:仿真是一种依赖工具验证功能的具体技术手段,而验证则是一个包含仿真在内的、旨在保证芯片最终能完美工作的完整质量保证闭环。

image-20260313185659076

​ 在标准的Verilog HDL开发流程中,验证工作必须严谨地划分为四个递进的阶段:首先是纯粹考察RTL代码逻辑是否符合设计规格的功能验证(也称前仿真),其次是验证逻辑综合生成的门级网表是否等效的综合后验证,紧接着是必须加入门延迟和线延迟信息、以严格检查建立时间与保持时间等物理约束的时序验证(也称后仿真),最后则是将芯片或配置好的FPGA放入实际硬件系统环境中的板级验证。这四个阶段环环相扣,越往后发现设计缺陷的修复成本就呈指数级飙升,因此复试导师通常会极其看重你是否清楚前仿真与后仿真的本质区别,以及你是否具备在第一阶段(功能验证)就通过严谨的测试用例把Bug扼杀在摇篮里的意识

5.2 Verilog HDL测试程序设计

5.2.1 Testbench及其结构

Testbench 是一个顶层的仿真封装模块,其最显著的特征是模块声明时没有输入输出端口列表,因为它本身就是一个封闭的激励产生与响应观测系统。在 Testbench 内部,核心任务是正确实例化待测设计(DUT/DUV),并将激励信号(定义为 reg 型以在 initialalways 块中赋值)连接到 DUT 的输入端,同时将 DUT 的输出端连接到观测信号(定义为 wire 型)。一个标准的 Testbench 功能闭环包括:为 DUT 提供精准的时钟、复位及数据激励,自动化比对仿真结果与理想期望值,并将关键信息通过终端显示或存储为波形文件(如 VCD)以供后续分析。

DUT/DUV(Design Under Test / Design Under Verification),即“待测设计”,指的就是你写的那个需要被验证的寄存器传输级(RTL)代码模块。其次是 Stimulus(激励),它模拟了电路在真实环境中接收到的输入信号,如复位脉冲或数据流。最后是 Response(响应),即 DUT 在激励作用下产生的输出结果。在面试中,理解这些术语能让你在讨论验证方案时显得更加专业。为了实现高效的仿真,Verilog 提供了几种描述层次,Testbench 通常运行在算法级(Algorithm Level)或系统级(System Level),这意味着你可以使用类似 C 语言的逻辑来编写复杂的测试向量,而无需担心这些代码是否能转换成真实的逻辑门。结构化描述(Structured Description)要求我们将不同功能的测试代码分开存放,例如用一个单独的 initial 块负责产生全局复位,另一个 always 块专门负责产生时钟。这种解耦方式不仅让代码更易读,还能有效避免在同一个过程块中处理过多信号时产生的竞争冒险(Race Condition,即多个赋值操作在同一时刻发生,导致结果不确定)

image-20260313190458048

​ 在编写 Testbench 时,必须牢记其代码是不需要可综合的,因为它本质上是对硬件行为的软件化描述,而非实际硬件设计,这使得我们可以自由使用 initial#delay$monitor 等不可综合语句。为了提高验证效率和代码可维护性,强烈建议采用结构化、程式化的描述方式,利用多个独立的 initialalways 块将时钟产生、复位初始化、测试数据注入及响应收集逻辑彻底解耦

​ 面试中的常考点在于信号类型的选择:激励信号必须使用 reg 类型,因为它们需要在过程块中保持并更新状态;而接收 DUT 输出的信号必须使用 wire 类型,因为它们只是被动承载来自实例电路的连续赋值信号

5.2.2 测试平台举例

测试平台举例的核心在于针对不同性质的电路构建差异化的仿真向量(Simulation Vector,即一组随时间变化的测试输入信号)。对于全加器这种组合逻辑电路,测试重点在于遍历真值表的所有输入组合,通过 initial 块配合延迟语句(如 #20)依次改变输入信号 a, b, ci 的值,从而验证输出 so, co 是否符合逻辑预期。而对于计数器等时序逻辑电路,仿真的灵魂在于时钟信号(Clock)的产生,通常使用 always #50 clk = ~clk; 语句来生成占空比为 50% 的方波。此外,时序仿真还必须包含全局复位(Reset)操作和使能控制(Enable)验证,确保电路在初始状态明确的前提下,能够按照时钟边沿正确跳转。

5.2.3 仿真结果确认

仿真结果的确认是验证环节的最后一步,主要通过直接观察波形、打印文本输出、自动检查以及使用 VCD 文件这四种手段来实现

​ 直接观察波形是最直观的方法,通过 EDA 工具界面比对输入输出的逻辑时序是否符合设计预期;而打印文本输出则利用 $display(立即打印)和 $monitor(值变即打印) 等系统任务,将仿真过程中的信号数值实时输出到控制台。对于大型设计,自动检查通过在代码中嵌入断言(Assertion)监控器来提高验证效率,它能自动捕捉逻辑违例并报警。最后,VCD(Value Change Dump)文件作为一种标准格式的波形记录数据库,能够详细存储信号在每一个时刻的变化,是进行后期离线分析和功耗仿真的重要数据来源

​ 在实际面试和工程应用中,理解 $display$monitor 的执行机制差异是区分验证水平的关键考点$display 类似于 C 语言的 printf,只在执行到该行语句的瞬时打印一次信息;而 $monitor 则具备敏感触发特性,只要其监控列表中的任何一个信号发生跳变,就会自动重新打印整行信息。此外,引入断言机制能够显著增强设计的可观察性,使得原本难以发现的深层逻辑错误在仿真阶段就能被自动定位。VCD 文件的优势在于其“只记录变化”的特性,这极大地压缩了存储空间,并使其成为不同仿真工具之间交换波形数据的通用语言

5.2.4 Verilog HDL仿真效率

Verilog HDL 仿真效率的核心瓶颈在于:仿真软件需要在串行执行的 CPU 上,通过调度算法来模拟硬件电路的并行行为,这导致复杂的行为级代码执行时间较长。为了优化这一过程,首先应当减小电路的层次结构并尽量减少门级代码(Gate-level)的使用,因为越抽象的行为级描述(如算法级)对仿真器越友好,其执行效率远高于底层结构描述。此外,进程(Process,即 alwaysinitial 块)的数量也直接影响速度,块越少,仿真器在不同任务间切换的调度开销就越小,从而能显著缩短模拟耗时。

​ 在面试或实际工程中,仿真精度与效率的权衡(Trade-off)是体现资深水平的考点。当 timescale 定义的计时精度值(Precision)与计时单位值(Unit)差距过大时,仿真器必须处理更细微的时间步长,这会拖慢整体模拟速度。同时,频繁的系统任务输出(如大量的 $display$monitor)会占用大量的 I/O 资源,是降低仿真器执行效率的罪魁祸首之一。因此,在进行大规模验证时,通常建议只在关键节点保留打印信息,并在确保功能正确后,尽量降低仿真精度以换取更短的回归测试时间。

5.3 与仿真相关的系统任务

5.3.1 $display$write

$display$write 是 Verilog 中最常用的信息显示任务,它们的主要功能是将特定的文本或变量值输出到仿真控制台,以便设计者观察电路运行状态。两者的语法结构完全一致,由格式控制字符串(用双引号括起)和输出信号列表组成。它们唯一的核心区别在于:$display 在输出信息后会自动执行换行操作,而 $write 则不会自动换行。如果需要手动控制格式,可以使用转义字符,例如 \n 代表换行,\t 代表横向跳格。

​ 在面试和实际调试中,熟练掌握输出格式符至关重要,因为这直接决定了你观察数据的效率。常用的输出格式如下表所示:

格式符 说明 格式符 说明
%h / %H 十六进制输出 %o / %O 八进制输出
%d / %D 十进制输出 %b / %B 二进制输出
%c / %C ASCII 字符输出 %v / %V 网络型信号强度
%t / %T 当前仿真时间格式 %m / %M 模块层次名字
%s / %S 字符串形式输出 %f / %F 十进制实数输出

​ 此外,对于一些无法直接输入的特殊符号,需要使用换码序列(转义字符)\\ 输出反斜杠,\" 输出双引号,%% 输出百分比符号本身。需要特别注意的是,$display 在显示数据时,其显示宽度会自动按照表达式的最大可能值所占的位数进行调整,确保数据对齐且不丢失精度。在调试复杂的 FSM(状态机)或数据流时,巧妙配合 %t%h 能让你快速定位逻辑错误发生的时间点与具体数值。

5.3.2 $monitor$strobe

$monitor 是一个具备“自动触发”特性的监控任务,它通常在 initial 块中调用一次,便会自动对目标信号进行全时段监视。其核心机制是:只要参数列表中的任何一个变量或表达式的值发生变化,整个列表的内容都会被自动打印输出。为了灵活控制监控过程,可以使用 $monitoron$monitoroff 来随时启动或停止监控。在调试如串行通信(RXD/TXD)等信号时,使用 $monitor 可以省去大量重复的打印语句,只需关注数据变化的瞬间即可。

$strobe 被称为选通显示任务,它解决了仿真中因赋值顺序导致的“数据假象”问题。与立即执行的 $display 不同,$strobe 会在当前仿真时间步(Time Step)的所有赋值语句(包括非阻塞赋值)全部处理完毕后的最后时刻,才执行打印操作。这意味着它抓取到的是该时刻电路稳定后的最终值,能够有效避免由于竞争冒险或非阻塞赋值延迟更新导致的输出不一致。在面试中,$display 打印的是执行瞬间的值,而 $strobe 打印的是当前时间步结尾处稳定的值”,这是体现你深谙 Verilog 仿真调度机制的黄金考点。

5.3.3 $time$realtime

$time$realtime 系统函数的主要功能是返回当前的仿真时刻,它们是观察 Testbench 运行进度和测量逻辑延迟的关键手段。两者的核心区别在于返回数值的数据类型:$time 返回一个 64 位的整数值,它会根据 `timescale 定义的时间精度对当前仿真时间进行四舍五入取整;而 $realtime 则返回一个实数(实型数),能够保留更精确的小数部分信息。在编写测试脚本时,通常将它们配合 $monitor$display 使用,通过在输出列表中加入时间戳,可以精准地记录信号发生跳变的绝对时间点。

​ 在面试和工程实践中,理解时间函数与 `timescale 指令的联动效应是高频考点。当你在代码中使用延迟语句(如 #delay)时,仿真时刻的递增量等于延迟常数乘以时间单位,而输出显示的数值则受时间精度的约束。例如,如果你的仿真精度设置得非常细,使用 $realtime 可以让你观察到比整数时刻更细微的逻辑触发过程。熟练使用这两个函数不仅能帮你验证电路的功能对错,更是排查时序违例、分析关键路径延迟以及确保多模块同步运行的必备调试技能。

5.3.4 $finish$stop

$finish$stop 是 Verilog 中用于精确控制仿真生命周期的两个核心系统任务,它们分别对应“退出”与“暂停”两种逻辑状态。其中,$finish 的作用是彻底结束仿真过程,并直接退出仿真器返回操作系统控制台,常用于自动化测试脚本的末尾,以确保仿真完成后自动关闭任务。而 $stop 则会将仿真器置于暂停模式(Interactive Mode),此时仿真时间停止走动,但仿真环境依然保留,将控制权交给用户进行交互式调试,例如查看当前波形或检查寄存器状态。

​ 这两个任务都可以携带可选参数 n(取值 0、1 或 2),用于控制退出或暂停时打印出的统计信息详略程度。若不带参数,默认值通常为 1,此时会输出当前的仿真时刻以及在源代码中的具体位置;若设为 0,则不打印任何信息;若设为 2,则会给出最详尽的统计,包括仿真时间、位置以及所占用的内存和 CPU 时间。在面试中,$finish 是终止(退出程序),$stop 是中断(停下检查)”,这是一个反映你是否具备实际动手调试经验的小考点。

5.3.5 $random

$random 是 Verilog 中用于产生随机数的系统函数,每次调用都会返回一个 32 位的带符号整数。在编写 Testbench 时,它常用于模拟真实电路中不确定的输入数据或随机的响应延迟。最常用的语法格式是 $random % b(其中 b > 0),它会产生一个范围在 (-b+1)(b-1) 之间的随机数;如果需要产生纯正整数,通常采用位拼接运算符或取模后加绝对值的处理方式,例如 {$random} % b 产生的范围则是 0b-1

​ 在高级验证方法学中,随机化测试(Constrained Random Verification)是发现隐藏 Bug 的核心手段。通过 $random 配合取模运算,可以轻松实现诸如“随机脉冲宽度”或“随机发送间隔”的仿真环境。面试中需要注意的一个细节是:$random 产生的随机序列在相同的初始种子(Seed)下是确定的(即伪随机),这保证了当你在仿真中发现错误时,可以通过固定的种子来重现(Repeat)该错误,从而进行故障定位与修复。

5.4 信号时间赋值语句

5.4.1 时间延迟语法说明

时间延迟(Time Delay)在 Verilog 中使用关键字 # 来表示,它允许设计者在仿真过程中精确控制语句的执行时间点,以满足特定的时序规格要求。根据 # 符号在赋值语句中出现的位置,可以将其分为外部时间控制内部时间控制两种截然不同的方式。

​ 外部时间控制(如 #5 a = b;)表现为一种“阻塞等待”机制:仿真器会先停下来等待 5 个时间单位,直到延迟结束才去读取右值 b 并赋给左值 a。这意味着如果在等待期间 b 的值发生了变化,最终赋给 a 的将是延迟结束那一刻的最新值。

​ 相比之下,内部时间控制(如 a = #5 b;)则表现出一种“先捕获、再延迟”的特性,这在模拟硬件传输延迟时极其有用。在这种方式下,仿真器会立即读取(捕获)当前时刻右值 b 的值并将其暂存在中间变量中,然后等待 5 个时间单位,最后再将那个暂存的旧值赋给左值 a

​ 在面试中,这两者的行为差异是核心考点:外部延迟是“等完再看(读取)”,内部延迟是“看完(读取)再等”。这种细微的差别在处理高速信号采样或模拟逻辑门延迟时,会直接导致仿真结果产生巨大的偏差。

5.4.2 时间延迟描述形式

时间延迟的描述形式直接影响仿真进程的推进方式,主要分为串行延迟、并行延迟、阻塞式延迟和非阻塞式延迟四种

​ 在 begin-end 块中的串行延迟控制遵循累加原则,每一条延迟语句都会阻塞后续语句的执行,导致仿真时间呈阶梯状向后推进;而在 fork-join 块中的并行延迟控制则以当前时间时刻为基准同时启动,所有延迟语句并发执行,互不干扰,其最终的仿真时间由延迟最大的那条语句决定。这两种结构在产生复杂的非周期性测试波形时非常常用,面试考点在于区分“时间累加”与“起始点对齐”的逻辑差异。

阻塞式延迟控制(=)与非阻塞式延迟控制(<=)在 begin-end 块中的行为对比,是 Verilog 仿真机制中最核心的知识点。使用阻塞赋值带延迟时,语句依次执行,总延迟是各条延迟之和,这会导致仿真器产生串行的波形脉冲;而使用非阻塞赋值带内部延迟时,尽管代码写在串行块内,但所有赋值操作会在仿真起始时刻同时“计划”好(Schedule),各条语句的延迟时间是相对于初始 0 时刻的绝对偏移,表现出并行的特征。掌握这一特性的工程意义在于,非阻塞延迟常被用来模拟触发器的 clock-to-output 延迟,而串行延迟则更多用于编写 Testbench 中的行为激励序列

5.4.3 边沿触发事件控制

边沿触发事件控制通过符号 @ 来实现,其本质是让仿真进程在遇到该语句时进入挂起(等待)状态,直到指定的信号事件发生后才继续向下执行。事件表达式主要分为三种形式:直接信号名(代表电平任意跳变触发)、posedge(仅正跳变触发)以及 negedge(仅负跳变触发)。在 Verilog 的严谨定义中,正跳变不仅包括 0->1,还包括 0->x/zx/z->1;同理,负跳变也涵盖了从高电平或不确定态向低电平转化的多种路径。这种机制是建模时序逻辑(如时钟驱动的触发器)以及在 Testbench 中进行精确时序测量的绝对核心。

​ 在复杂的逻辑描述中,可以使用关键字 or(或逗号 ,)将多个事件表达式组合成敏感列表(Sensitivity List)。只要列表中任何一个信号满足触发条件,仿真进程就会被激活。这种语法衍生出四种常见的形式:带行为语句的单/多事件触发,以及不带行为语句的独立等待控制。独立等待控制(如 @(posedge clk); 后直接跟分号)在仿真验证中非常实用,它常被用来作为一种“阻塞路障”,确保后续的采样或赋值操作严格发生在时钟边沿之后。熟练掌握事件控制的嵌套与组合,能够帮助你轻松实现如时钟周期测量、脉冲宽度检测等复杂的仿真验证任务

5.4.4 电平敏感事件控制

电平敏感事件控制使用关键字 wait 来实现,其核心逻辑是检查一个条件表达式的真假状态:如果表达式为“真(逻辑1)”,则后续语句立即执行;如果为“假”,则仿真进程进入挂起状态,直到该条件变为“真”为止。与边沿触发不同,wait 不关注信号跳变的瞬间,而是关注信号所处的稳定电平状态。这种机制在 Testbench 中常用于实现握手协议(Handshaking)或等待使能信号(Enable)生效。例如,wait(enable == 1); 会确保只有在使能信号拉高后,相关的测试激励或数据处理逻辑才会启动。

​ 在语法结构上,wait 既可以直接引导单条语句或 begin-end 块,也可以作为独立的同步控制点存在。wait 作为独立语句使用时(形式 2),它起到的是一种“软路障”的作用:只有当指定条件满足,仿真进程才会越过这一行继续向下推进。这种写法在控制串行块(Sequential Block)的执行时序时非常高效。

​ 面试中的关键考点在于区分 wait@@ 是由信号的动态跳变(Edge)激活的,而 wait 是由信号的静态电平(Level)状态激活的,理解这一点对于编写精确的验证环境逻辑至关重要。

5.5 任务和函数

5.5.1 任务

任务(Task)是 Verilog 中用于封装较大行为级设计段的一种结构,主要通过 taskendtask 关键字进行定义,能够显著提高代码的可读性与可维护性。与函数不同,任务可以包含时间控制语句(如 # 延迟、@ 边沿触发或 wait 电平敏感语句),这使得它在编写测试激励(Stimulus)时具有得天独厚的优势。任务定义时不需要在第一行列出端口名列表,而是通过在内部声明 inputoutputinout 端口来与外部交换数据。调用任务时,只需给出任务名并按定义的顺序传入对应变量即可。

​ 在面试中,掌握任务的底层限制和特殊行为是区分“课本水平”与“工程水平”的关键。你需要牢记:任务可以没有返回值,也可以通过输出/双向端口返回多个值;任务内部允许调用其他任务或函数,甚至可以递归调用自身。特别需要注意的是,任务定义结构内绝对不允许出现 initialalways 过程块,且任务执行过程中可以通过 disable 语句强行中止。对于验证而言,任务最典型的应用场景是模拟总线时序(如读写存储器数据),通过封装复杂的时序逻辑,让主仿真流程变得简洁明了。

5.5.2 函数

函数(Function)主要用于将常用的计算逻辑或转换操作提取出来,其定义嵌在 functionendfunction 关键字之间,核心目标是返回一个单一的计算结果。在定义函数时,必须明确指定返回值的位宽或类型(如 [7:0]integerreal),如果省略则默认为 1 位寄存器类型。函数名本身在内部代表一个隐含的寄存器变量,函数执行完毕后的最终返回值正是通过给这个“函数名变量”赋值来传递的。与模块定义类似,函数内部通过 input 声明输入端口,但函数必须至少有一个输入端口,且绝对不允许有 outputinout 端口

​ 在面试和工程实践中,函数与任务的本质区别是最高频的考点:函数内部严禁出现任何形式的时间控制描述(如 #@wait),这意味着函数必须在瞬时完成,只能描述纯组合逻辑。此外,函数不能调用任务,但可以调用其他函数;它不能单独作为一条语句执行,而必须作为表达式中的操作数出现(类似于 C 语言中的右值调用)。需要特别注意的是,函数内声明的所有局部寄存器都是静态存储的,这意味着即使函数调用结束,这些局部变量的值也会被保留。这种特性使得函数在实现阶乘计算、奇偶校验或数据转换等不含时序开销的算法逻辑时非常高效。

5.5.3 任务和函数的区别

image-20260313201141203

任务与函数最本质的区别在于对“时间”的处理能力:函数必须在瞬时执行完毕,严禁包含任何时间控制(如 #@wait),而任务则可以包含丰富的时延和事件控制,能够模拟具有时序特征的行为。在数据交互方面,函数通过函数名返回且仅能返回一个值,至少需要一个输入端口,且不允许有输出或双向端口;而任务则灵活得多,它可以没有返回值,也可以通过多个 outputinout 端口返回多个结果。调用方式上,函数必须作为表达式的一部分(如右值)出现,支持在 assign 和过程块中调用;任务则作为一条独立的语句被调用,且只能出现在过程块(如 initialalways)中。

​ 在面试中,你还需要展示出对两者嵌套关系和执行特性的深刻理解:任务可以调用其他任务或函数,但函数只能调用其他函数,绝对不能调用任务。此外,任务的执行可以通过 disable 语句强行中断并返回,而函数则必须完整执行,不允被中断

​ 从工程应用角度来看,函数主要用于纯组合逻辑的计算和转换,其执行时间在仿真坐标轴上为 0;任务则主要用于构建 Testbench 中的复杂激励模型,能够完美描述跨越多个时钟周期的总线协议或交互流程。理解这些差异,能帮助你在设计 RTL 代码和编写验证环境时,做出最合理的语法选择。

六、常见问题

6.1 数字集成电路的发展及设计方法的演变

image-20260312232714922

image-20260312232927138

image-20260312232935142

image-20260312232943003

image-20260312232949996

image-20260312232956172

image-20260312233005764

image-20260312233015498

image-20260312233024267

6.2 程序设计语句和描述方式

image-20260313165801266

image-20260313165811422

image-20260313165824572

image-20260313165848048

image-20260313165857088

image-20260313165905985

image-20260313165914053

image-20260313165922236

image-20260313165930994

image-20260313165939056

image-20260313165947569

image-20260313165957216

image-20260313170006716

image-20260313170027164

6.3 数字逻辑电路设计方法

image-20260313183940941

image-20260313184005399

image-20260313184013713

image-20260313184120260

image-20260313184103450

七、面试问题

7.1 选择题

image-20260313201920557

image-20260313201929758

7.2 简答题

image-20260313202023951

image-20260313202051673

image-20260313202213319

image-20260313202220522

image-20260313202259550

image-20260313202402871

image-20260313202411667

image-20260313202504191

image-20260313202514612

image-20260313202533040

image-20260313202555379

image-20260313202605781

image-20260313202646278

image-20260313202656465

image-20260313202704855

image-20260313202712536

image-20260313202720156

image-20260313202727605

image-20260313202739400

image-20260313202747673

image-20260313202755480

image-20260313202803126

image-20260313202810303

image-20260313202818312

image-20260313202825375

image-20260313202832380

image-20260313202838948

image-20260313202855680

image-20260313202903253

image-20260313202911774

image-20260313202923300

image-20260313202931415

八、数字集成电路Verilog HDL的学科探索

8.1 溯源与终极目标

​ 数字集成电路与 Verilog HDL 的诞生源于 20 世纪 70 至 80 年代的**“复杂度危机”。当时摩尔定律推动晶体管数量成倍增长,传统的底层手工布线和逻辑门搭建已无法应对数以万计的逻辑需求,设计与验证的物理成本与时间成本呈现指数级飙升。Verilog 诞生的初衷并非为了“写代码”,而是为了解决“如何在不制造出实物电路的前提下,精准描述并验证复杂硬件逻辑”这一工程痛点。它通过高层次的抽象,将物理硬件转化为计算机可识别、可模拟的语言描述,从而实现了“设计与物理实现的彻底解耦”**,这是芯片设计效率实现飞跃的关键转折点。

​ 如果用一句话概括这门学科的终极目标,那就是:在确保 100% 功能正确性的前提下,实现从人类设计意图到最优物理实现(即性能 PPA:功耗 Power、性能 Performance、面积 Area)的全自动映射与闭环验证

​ 这门学科研究的核心“对象”是基于时间与事件驱动的离散事件系统(Discrete Event System)。在数学上,这个对象被严格抽象为受限布尔代数空间上的有限状态机(Finite State Machine, FSM)及其映射关系。其中,组合逻辑被抽象为布尔函数映射,而时序逻辑则被抽象为状态转移矩阵。这种数学抽象的伟大之处在于,它忽略了电压和电流等复杂的连续物理过程,将电子运动简化为在离散时间点(时钟边沿)上发生的逻辑状态转移,从而使我们能够利用计算机算法来自动求解、优化和验证极大规模的硬件系统。

8.2 边界与极限

​ 数字集成电路的理论极限主要受限于热力学与量子力学的双重边界。其中最核心的定理是兰道尔原理(Landauer’s Principle),由物理学家罗夫·兰道尔在1961年提出,他证明了擦除 1 bit 信息所释放的最小能量为 E=kBTln2E = k_B T \ln 2。这一极限确立了计算过程在能量消耗上的绝对红线,意味着只要电路在处理逻辑状态的切换与重置,就必然伴随能量流失。从信息处理速度上看,**布雷默曼极限(Bremermann’s Limit)**则根据量子力学和能量守恒,定下了每克质量每秒钟处理信息的最大比特数。这些定律共同构成了数字 IC 的理论天花板:计算不是无代价的,信息的处理能力最终会撞上能量密度与散热效率的物理围墙。

​ 在现实物理世界中,阻碍我们逼近这一极限的核心干扰源主要来自于热噪声(Thermal Noise)与互连延迟(Interconnect Delay)。随着工艺制程向埃米级迈进,晶体管的特征尺寸已经接近原子尺度,**量子隧穿效应(Quantum Tunneling)导致漏电流指数级上升,使得电路即便不进行翻转也会消耗巨大的静止功耗,这被称为“功耗墙”。同时,随着逻辑单元翻转速度的提升,信号在金属导线上的寄生电阻和电容(RC 延迟)成为了全局时序的瓶颈,信号传输的速度远慢于逻辑门开启的速度。此外,物理制造中的工艺波动(PVT Variations)**作为一种随机干扰,迫使设计者必须预留大量的“时序裕量”,这种由于不确定性带来的资源浪费,是人类工程实践与完美理论模型之间最难以逾越的鸿沟。

8.3 核心方法和工具箱

为了实现 PPA(功耗、性能、面积)的最优闭环,数字集成电路演化出了前端设计与验证、后端物理实现这两大核心分支前端分支主要负责逻辑抽象与功能完备性,通过 RTL 建模与仿真验证解决“如何正确实现人类设计意图”的问题;而后端分支则聚焦于物理落地,通过逻辑综合、布局布线(P&R)以及静态时序分析(STA)解决“如何将抽象逻辑转化为具有真实物理约束的电路网表”的问题。这种分而治之(Divide and Conquer)的工程方法论,使得我们可以通过自动化的 EDA 工具链,在有限的时间内处理具备数十亿晶体管规模的超大规模集成电路(VLSI)。

布尔代数(Boolean Algebra)与离散数学的状态机理论是这门学科最核心、且不可替代的数学工具非使用布尔代数不可的原因在于其物理意义完美契合了 MOS 管的开关特性:逻辑上的“真”与“假”直接对应了半导体器件的“导通”与“截止”,这使得复杂的硬件行为可以用严谨的逻辑代数方程进行化简与优化。同时,离散时间序列的抽象将连续的电流运动演变为离散的同步状态跳转,这种抽象屏蔽了底层模拟电路的非线性干扰,使得我们可以利用确定性的数学模型去预测并控制大规模系统的动态行为,从而将不可控的物理世界约束在可计算的逻辑框架之内。

8.4 工程妥协和核心矛盾

​ 数字集成电路设计中最核心的“跷跷板”博弈是 PPA(Power-Performance-Area)三要素的动态平衡,即功耗、性能(速度)与面积的互相制约。为了追求极致的吞吐率(Throughput),设计者通常会采用流水线(Pipelining)技术,通过插入寄存器来缩短组合逻辑路径,从而提升时钟频率;但这必然会导致面积增加(由于寄存器开销)以及延迟(Latency)的增大。在现代移动端芯片中,这种博弈演变为能效比(Performance per Watt)的博弈:我们往往愿意牺牲一部分峰值性能,通过动态电压频率调整(DVFS)或多电压域设计,来换取更长的待机时间。在面试中,当你能意识到**“高性能往往意味着高面积成本和高热耗散”**时,你就具备了从芯片架构师视角审视工程的能力。

​ 为了让复杂的电路逻辑在工程上可计算,课本模型做出了最核心的假设——数字抽象假设(Digital Abstraction)与同步时序假设(Synchronous Timing Model)。我们大胆地假设电压信号在阈值之外是非零即一的,完全忽略了晶体管作为模拟器件时的跨导、电流波动以及复杂的非线性过渡过程。同时,我们通过时钟信号(Clock)强制让全系统在离散的时间点进行同步,假设时钟沿在同一瞬间到达所有触发器(忽略了实际存在的时钟偏斜 Clock Skew)。这种简化使得我们可以利用**静态时序分析(STA)**来预测电路行为,而无需进行耗时巨大的全电路瞬态仿真。这种从“连续模拟世界”向“离散数字世界”的粗暴切分,正是 EDA 工具能自动处理数十亿晶体管逻辑的基础前提,但也定下了逻辑电路在遭遇电磁干扰、工艺噪声和极端电压波动时显得异常脆弱的底层宿命。

8.5 学科纵横与前沿

数字集成电路(DIC)与 Verilog HDL 处于电子信息学科承上启下的核心枢纽位置,向上支撑体系结构,向下扎根固体物理。逻辑关系上,它的上游基础是《电路分析》与《模拟电子技术》(提供物理层的电压电流模型)以及**《数字逻辑设计》(提供布尔代数与状态机理论);DIC 本身则是将数学逻辑转化为物理实体的关键转换层。它的下游应用则延伸至《计算机组成原理》与《微处理器架构》,通过 Verilog 构建的算术逻辑单元(ALU)和寄存器组,支撑起现代通用计算的基石。在面试中,如果你能意识到“Verilog 不是编程,而是用硬件语言去描述一套符合物理规律的逻辑互连”**,你便跳出了“代码码农”的范畴,进入了“系统设计者”的视角。

当前工业界与学术界共同面临的最大技术瓶颈是“冯·诺依曼瓶颈(冯氏墙)”以及“后摩尔时代”的能效危机。随着 AI 大模型的需求爆发,传统架构中存储与计算的分离导致的频繁数据搬运(存算墙),其能耗与延迟已占据系统总消耗的 80% 以上。因此,未来的演进方向正在发生范式转移:一是**“存算一体(Processing-in-Memory)”架构**,试图在存储单元内直接完成计算以彻底打破数据搬运瓶颈;二是**“领域特定架构(DSA)”,即不再追求万能 CPU,而是通过专用集成电路(ASIC)实现特定任务下的能效巅峰;三是“Chiplet(芯粒)”技术**,通过异质集成手段在物理层面延续摩尔定律。作为未来的研究生,你需要明白:未来的 IC 工程师不仅要会写代码,更要具备在系统架构层面进行软硬件协同优化的能力