想象你是一名城市设计师,要在空地上快速盖出一片小区。你不会把每栋楼从地基开始重新设计,而是调用“标准卫生间单元”“标准楼梯间”这类预制构件。每张构件图纸都画好了边界,标注了水管接口、电线接口、门窗位置——工厂生产一次,就能在工地上反复拼接。Verilog硬件描述语言的模块化设计,和这套装配式建筑思路惊人相似:module就像一张预制构件图纸,端口就是图纸上留出的水电接口,而把一堆模块连接成完整芯片的过程,就是按图拼接预制件的过程。 本文会带你以“预制构件”的眼光理解Verilog模块与端口。我们会看到:一张图纸(模块定义)怎么画,接口(端口)怎么分类,参数(尺寸)怎么调节,构件(实例)怎么拼接,以及设计师之间如何依靠规范协作。读完你就能看懂最简单的Verilog模块,并建立“模块化、可复用”的硬件设计直觉。

岗位关联

掌握模块与端口设计,是FPGA开发者从“会写代码”走向“会做工程”的分水岭。理解它,你才能在芯片世界里搭建出可复用、可维护的数字系统,这也是数字IC与FPGA岗位面试的高频考点。

目录
  1. 可视化演示
  2. 模块定义语法
  3. 端口类型声明
  4. 数据类型与位宽
  5. 参数化设计
  6. 端口连接方式
  7. 层次化实例化
  8. 模块设计规范
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示Verilog模块结构(input/output端口)、端口方向类型、层次化实例化(U1/U2/U3)及参数化设计(#(.W(n)))覆盖机制。

本节配有交互动效(进入视口后自动加载,约 3KB)。

Verilog模块结构与端口 module-endmodule封装硬件逻辑 input/output端口定义外部接口 module MOD(input clk, output [7:0] dout); 模块是Verilog硬件描述的基本构建单元

模块定义语法

在预制装配式建筑里,一张“标准卫生间图纸”会画清边界线,标明构件名称和所有接缝接口。Verilog中这张图纸就是模块(module)。它的范围由module和endmodule两条语句框定:module是图纸的起笔,endmodule是图纸的落款。两者必须成对出现,就像右括号离不开左括号。

模块是Verilog最基本的构建单元。一个最小的模块定义是这样的:module adder (a, b, sum); 端口列表写在模块名之后的圆括号内,相当于构件图纸上的接口清单。模块名要能表达功能,如adder表示加法器、mux2to1表示二选一多路选择器。内部可以写端口声明、变量声明和逻辑描述语句。

从抽象层面看,模块本质上是一个把输入信号变换为输出信号的硬件单元,就像预制卫生间把“冷水、热水”变成“混水”输出:

一张图纸定边界,一个module定功能。后续所有设计——端口、参数、例化——都是在这张图纸上添加细节。endmodule配对了,图纸就闭合了,构件才能出厂。

(y0,y1,…,ym−1)=fmodule(x0,x1,…,xn−1)\displaystyle (y_0, y_1, \dots, y_{m-1}) = f_{\text{module}}(x_0, x_1, \dots, x_{n-1})
这个公式表示:模块是一个映射函数,外部输入(x系列)经过模块内部逻辑(f_module)后,产生输出(y系列)。括号里逗号分隔的内容,就是端口列表。
  1. 输出信号集合左侧y_0, y_1, \dots表示该模块对外输出的端口信号,如同一张图纸上标注好的废水管和排气管。
    y0,y1,…,ym−1y_0, y_1, \dots, y_{m-1}
  2. 模块功能标识f_{\text{module}}表示模块内部实现的功能,相当于图纸上注明“内部施工说明”。
    fmodulef_{\text{module}}
  3. 输入信号集合右侧括号里的x_0, x_1, \dots表示从外部输入模块的端口信号,如同接入预制卫生间的自来水管。
    x0,x1,…,xn−1x_0, x_1, \dots, x_{n-1}
  4. 映射关系等号表明“经过模块处理后,输入集合被映射为输出集合”,这正是硬件模块存在的意义。
    (y)=f(x)(y) = f(x)

端口类型声明

构件接口是有方向的:进水管只能进水,出水管只能出水,而阳台门则允许人双向通行。Verilog端口对应三种方向——input是进货口,信号只能从外部进入模块;output是出货口,信号只能由模块送向外部;inout是双向门,信号可以双向通行,常用于数据总线这样的共享通道。

在模块内部声明端口时,形式是input [3:0] a;,表示一个4位宽的输入端口;output reg [3:0] sum;则声明一个4位宽的reg型输出端口。方向声明决定了信号流动的“单向箭头”还是“双向箭头”,编译器会据此检查连接关系,就像物业规定哪个门是入口、哪个门是出口。

将三类端口放在一起看,一个模块的端口集合是有清晰结构的:

门窗各有方向,端口声明就是告诉编译器“这里的信号走哪个方向”。方向标错了,就像把进水管接到了出水管上——整个装配管道体系都会乱套,综合工具也会报出端口方向不匹配的错误。

ports=inputs∪outputs∪inouts,inputs∩outputs=∅\displaystyle \text{ports} = \text{inputs} \cup \text{outputs} \cup \text{inouts}, \quad \text{inputs} \cap \text{outputs} = \emptyset
这个公式说明:模块全部端口由输入、输出、双向三类组成,而且输入与输出互不重叠——一个物理端口不会既是纯入口又是纯出口,正如进货口与出货口不会共用一个门洞。
  1. 完整端口集合\text{ports}表示模块对外全部接口的集合,是构件图纸上的所有管线口汇总。
    ports\text{ports}
  2. 输入端口子集\text{inputs}表示只允许信号流入的端口集合,对应只进不出的进货口。
    inputs\text{inputs}
  3. 输出与双向端口子集\text{outputs}与\text{inouts}分别表示只出不进和可进可出的端口集合,如同出货口与双向门。
    outputs∪inouts\text{outputs} \cup \text{inouts}
  4. 互斥性\text{inputs} \cap \text{outputs} = \emptyset说明输入与输出集合不相交,一个端口只能有一个明确的主方向。
    inputs∩outputs=∅\text{inputs} \cap \text{outputs} = \emptyset

数据类型与位宽

预制构件内部有两类设施:一类是“临时通道”,一根管子通到底,水流随时可以从这头流到那头——这是wire线网;另一类是“储物柜”,门一关就能把东西锁在里面,直到下次开门才会更新——这是reg寄存器。wire由连续赋值语句(如assign)驱动,只负责连接和组合逻辑;reg在always块中被赋值,可以保存状态,是时序逻辑的载体。

位宽则像接口的口径。[3:0] a;表示a是一个4位向量,占电路上4根物理连线。端口声明常与数据类型配合,例如output reg [3:0] sum;定义了一个4位reg型输出。n位信号能表示多少种取值?答案由这个公式给出:

进料口要标清口径,端口声明要标清位宽。如果两个模块对接时位宽不一致,就好像粗水管接到细水龙头上——要么截断数据,要么多出的引脚悬空,信号传递就会失真。

Nvalue=2n\displaystyle N_{\text{value}} = 2^{n}
这个公式说明:n位宽的信号,可以表达2的n次方种不同的二进制取值,就像n位门牌号能编出2的n次方个房间号。
  1. 取值数量标识N_{\text{value}}表示n位二进制信号所有可能取值的总个数,是接口“容量”的度量。
    NvalueN_{\text{value}}
  2. 二进制基数底数2表示每一位只有0或1两种状态,如同每个门牌号位只有“开”或“关”两种牌子。
    22
  3. 位宽指数指数n表示信号的位宽,例如[3:0]时n等于4,表示有4根并行连线。
    nn
  4. 整体关系位宽每增加1,可表示的取值种类就翻一倍——4位能表示16种,8位能表示256种。这就是为什么位宽声明直接决定电路的能力边界。
    2n2^{n}

参数化设计

标准图纸上常有“尺寸按需调整”的标注:卫生间长度可以是2.4米,也可以是2.7米,由一个参数决定。Verilog提供parameter实现同样的灵活性。在模块头部用#(parameter WIDTH = 4)声明参数后,模块内部的端口位宽和逻辑都会跟着这个参数走。

实例化时可以覆盖默认参数:adder #(.WIDTH(8)) u1(...),同一个加法器模板立刻变成8位版本的构件。localparam则是模块内部的局部常量,不能从外部覆盖,相当于图纸上“不可改动的固定尺寸”。参数传递建立了如下联动关系:

一个模板,N种尺寸,一次编写,处处复用。这正是参数化让FPGA工程师“一图多建”的魔法:IP核写一次,项目里需要4位、8位、32位的加法器,全部由同一个模块实例化而来。

WIDTH=N⇒端口位宽变为 [N−1:0]\displaystyle \text{WIDTH} = N \quad \Rightarrow \quad \text{端口位宽变为}\ [N-1:0]
这个公式说明:当你给参数WIDTH赋值为N时,模块输入输出端口的位宽会自动变成N位,如同把图纸上的“尺寸”一改,所有接口标注自动缩放。
  1. 参数名称\text{WIDTH}是这里定义的可调参数,相当于图纸上印着的“净尺寸”变量。
    WIDTH\text{WIDTH}
  2. 具体数值N是实例化时赋给WIDTH的数值,比如8或16,是用户对构件的规格要求。
    NN
  3. 联动触发箭头\Rightarrow表示参数赋值会触发端口声明重算,是“一改全改”的连锁反应。
    ⇒\Rightarrow
  4. 位宽结果[N-1:0]表示当WIDTH为N时,端口位宽自动变成N位——从bit N-1到bit 0。
    [N−1:0][N-1:0]
verilog6 行
module adder #(parameter WIDTH = 4) (  input      [WIDTH-1:0] a, b,  output reg [WIDTH-1:0] sum);  always @(*) sum = a + b;endmodule

端口连接方式

预制构件运到工地后,管道工要把管线接到接口上。怎么保证不接错?一种方法是按图纸顺序一个个对着接——第1根管接第1个口,第2根管接第2个口;另一种方法是在每根管子上贴标签,写明“这根接进水口”“那根接排水口”。Verilog实例化时也对应两种方式:位置连接和名称连接。

位置连接的写法是adder u1(x, y, cin, s, cout);,信号顺序必须和端口声明顺序完全一致,一旦中间增减端口,后续全部错位,如同接口顺序变了而管工还按旧顺序硬接。名称连接则显式写明映射关系:adder u1(.a(x), .b(y), .cin(cin), .sum(s), .cout(cout));,每个信号标签都指向明确的端口。悬空端口可以省略不写,或在名称连接中留空,表示该接口暂不接线。名称连接的本质是一个映射:

标签比顺序更可靠。在大规模工程中,名称连接虽然代码略长,但可读性和安全性远高过位置连接——这也是绝大多数公司要求使用名称连接的原因。

porti↦signalj\displaystyle \text{port}_i \mapsto \text{signal}_j
这个公式表示:名称连接建立了一条从“模块端口”到“外部信号”的映射关系,每个端口名后面跟一个信号名,就像标签标明“某管接某口”。
  1. 模块端口左侧\text{port}_i表示模块声明中的第i个端口,比如加法器的a、b端口,相当于接口图纸上的名字。
    porti\text{port}_i
  2. 映射符号箭头\mapsto读作“映射到”,在这里就是“连接到”的意思。
    ↦\mapsto
  3. 外部信号右侧\text{signal}_j表示顶层模块中的连线信号,如x、y、s,相当于工地上的真实管线。
    signalj\text{signal}_j
  4. 映射关系整体含义是“端口i的信号来自或去向信号j”,一个端口只能映射到一个信号,正如一根管只接一个口。
    porti↦signalj\text{port}_i \mapsto \text{signal}_j

层次化实例化

小区由楼栋组成,楼栋由户型组成,户型又由房间组成。这就是层次化。Verilog的顶层模块就像总平图,内部可以例化多个子模块;子模块内部还可以再例化孙子模块,形成一棵树。每个被例化的模块都要起一个实例名,如同每栋楼要有楼号。

一个典型的两级层次如下:

这里的adder被实例化了两次,就像同一户型在1栋和2栋各建一处。信号通过wire在模块之间传递。从数学视角看,顶层模块的内容就是一组子模块的集合:

积木一层层搭,例化一句句写。层次化让复杂系统被拆解成可管理的小模块:每个工程师只看自己负责的那张图纸,最后由顶层把所有人的作品拼成完整的芯片。

Ttop={M1,M2,…,Mk}\displaystyle T_{\text{top}} = \{ M_1, M_2, \dots, M_k \}
这个公式说明:顶层模块定义本质是一个子模块集合,集合里的每个元素都是独立实例化出来的构件,它们协同完成整体功能。
  1. 顶层模块T_{\text{top}}表示顶层模块,是整个系统的总装配图,定义了小区的整体结构。
    TtopT_{\text{top}}
  2. 子模块元素M_1, M_2, \dots表示顶层内部例化的一个个子模块,如加法器、选择器,相当于楼栋中的户型单元。
    M1,M2,…,MkM_1, M_2, \dots, M_k
  3. 集合大括号花括号\{ \}把子模块聚合成一个整体,说明顶层是由多个构件组合而成的集合。
    {M1,M2,…,Mk}\{ M_1, M_2, \dots, M_k \}
  4. 规模下标k表示子模块数量,k越大系统越复杂,就像小区楼栋越多、管理越复杂。
    kk
verilog5 行
module top (input [3:0] x, y, z, output [4:0] result);  wire [4:0] s1;  adder u1 (.a(x),   .b(y),   .sum(s1));  adder u2 (.a(s1),  .b(z),   .sum(result));endmodule

模块设计规范

预制构件行业有国家标准:接口位置统一、编号清晰、尺寸标准化。模块设计同样需要规范,因为芯片设计从来不是一个人的独角戏——你的模块要交到他人手里集成,他人要能快速读懂并正确调用。

常见的规范有三条。命名规范:模块名用功能英文单词(如data_aligner),信号名用小写加下划线(如data_valid),让大家一看到名字就知道含义。注释规范:每个端口、每段关键逻辑都加注释,说明接口方向、位宽和功能。规模控制:一个模块只做一件事,内容过多就拆分,保持模块的可读性和可维护性。评审模块复杂度时,端口数量是最直观的度量:

标准规范让“你做的构件别人能装”,规范的模块让“你写的RTL同事能改”。命名、注释、规模这三条基本法,是每一个FPGA工程师走向职业化的必修课。

#ports=#inputs+#outputs+#inouts\displaystyle \#\text{ports} = \#\text{inputs} + \#\text{outputs} + \#\text{inouts}
这个公式说明:模块的端口总数等于三类方向端口数量之和,是设计师评审接口复杂度时用的第一把标尺。
  1. 端口总数左侧\#\text{ports}中,#表示“数量”,整体读作“端口总数”,即构件所有接口的个数。
    #ports\#\text{ports}
  2. 输入端口计数\#\text{inputs}表示输入方向端口个数,是接进模块的管线数量。
    #inputs\#\text{inputs}
  3. 输出与双向端口计数\#\text{outputs} + \#\text{inouts}表示输出与双向端口个数,是模块对外输出和双向交互的通道数量。
    #outputs+#inouts\#\text{outputs} + \#\text{inouts}
  4. 加和关系三种数量相加得到总端口数,这个数越大,模块与外部世界的交互越复杂,设计评审时需要重点关注。
    #ports=#inputs+#outputs+#inouts\#\text{ports} = \#\text{inputs} + \#\text{outputs} + \#\text{inouts}

AI 应用场景

场景一:FPGA可复用IP核封装

阶段:硬件加速器设计(RTL前端设计)

解决的问题:传统做法里每个项目从零编写加法器、FIFO、乘法器等基础电路,重复劳动多、Bug风险高。利用参数化模块,就像把预制构件标准化,封装一次、到处调用,项目间复用率大幅提升。

具体案例:某AI推理芯片团队把卷积计算中的乘累加单元封装成参数化Verilog模块:mac #(DATA_WIDTH=8, ACC_WIDTH=32)。FPGA工程师在搭建CNN加速器时,只需按层配置数据位宽和累加位宽,即可复用同一份模块代码,快速生成不同精度的运算单元。

Python 演示

python46 行
class Port:    def __init__(self, name, direction, width=1):        self.name = name        self.direction = direction        self.width = width  class VerilogModule:    def __init__(self, name, ports):        self.name = name        self.ports = {p.name: p for p in ports}     def port_list(self):        return list(self.ports.keys())     def check_instance(self, conns):        errors = []        connected = set()        for sig, pname in conns.items():            if pname not in self.ports:                errors.append("未知端口: " + pname)            else:                connected.add(pname)        for pname, port in self.ports.items():            if port.direction == "input" and pname not in connected:                errors.append("输入端口未连接: " + pname)        return errors  if __name__ == "__main__":    adder = VerilogModule("adder", [        Port("a", "input", 4),        Port("b", "input", 4),        Port("cin", "input", 1),        Port("sum", "output", 4),        Port("cout", "output", 1),    ])    conns = {"x": "a", "y": "b", "c_in": "cin",             "s": "sum", "c_out": "cout"}    problems = adder.check_instance(conns)    if not problems:        print("连接检查通过")    else:        print("连接检查发现 %d 个问题:" % len(problems))        for p in problems:            print(" -", p)
目的:在正式综合前用Python快速校验模块实例化时端口连接是否正确,防止“端口名写错”“输入悬空”这类低级错误。
  1. class Port:定义了一个端口类,存储名称、方向和位宽,就像给接口贴标签。
  2. self.ports = {p.name: p for p in ports}把端口列表转成字典,端口名是键、端口对象是值,方便后续按名字查找。
  3. for sig, pname in conns.items():遍历实例化时的连接字典(外部信号名 → 端口名),检查每个端口名是否真实存在。
  4. connected.add(pname)记录已经合法连接的端口名,用于最后找出遗漏的输入端口。
  5. if port.direction == "input" and pname not in connected:如果某个输入端口没有出现在连接中,就报“输入端口未连接”,像物业点名发现进货口没接进水管。

常见误区

误区以为写了`adder u1(...)`就像写C语言函数,程序执行到这里才“运行”一次加法,然后再继续往下执行下一行。

正解Verilog是一门硬件描述语言,模块实例化描述的是“物理上存在这个电路”,而不是“调用一次函数”。所有实例化出来的模块在上电后是并发同时工作的,正如小区里每户人家都同时生活,没有先后顺序。

为何常见:因为绝大多数初学者先学C/Python,习惯了程序按行顺序执行,很难立刻切换到“空间并发”的硬件思维。

误区在模块内部写了`input reg [3:0] a;`,或者试图在always块里给input端口整体赋值,以为这样能改变输入值。

正解input端口本质是外部驱动的线,只能由外部信号驱动,声明时要省略reg,实际隐式为wire型。在模块内部给input赋值,就像想在房子内部往进水管里灌水——水管是外部水源控制的,里面的水方向反了。

为何常见:写RTL时把“C语言引用传参”的习惯带进来,总觉得可以在函数体里改参数值。

误区以为inout端口就是个“万能口”,既能读又能写,模块里直接给它赋值就行,不需要其他控制。

正解inout在硬件上通常对应三态缓冲,必须用三态控制信号决定当前是驱动总线还是释放总线(高阻态)。如果多个模块同时驱动同一根inout线,就会总线冲突,轻则信号错误,重则烧毁驱动管脚。这就像双向门虽然能进出,但两个方向同时猛推,门就卡死了。

为何常见:初学者只看到inout“既能进又能出”的便利,忽略了硬件上“同一时刻只能有一个方向驱动”的物理约束。

误区把一个4位加法器的输出接到8位端口上,以为Verilog会自动做符号扩展或补零扩展,结果数据不对。

正解位宽不匹配时,综合工具会补位或截断,但规则要根据两端是wire还是reg、有无符号而定。最常见的结果是高位被截断丢掉,或者低位对齐补零。这就像4英寸粗的管子硬接到6英寸的管口,多出来的2英寸既不是自动密封也不是自动扩张。

为何常见:C语言中的整数类型有隐式提升规则,给了“类型会自动适配”的错误预期,而硬件位宽是物理连线的数量,不会魔法般变化。

误区写了很多内部逻辑,最后忘了写`endmodule`,或者随手写了个`end`,还疑惑为什么编译报错。

正解endmodule是模块的强制收尾,end只用于always、case、fork等块结构,两者不能互换。漏掉endmodule就像图纸没有落款和封边,工厂不知道构件在哪结束,整个设计文件都无法编译。

为何常见:Verilog关键字众多,end和endmodule写法相似,初学者在多种块结构混合书写时容易混淆。

面试题

Q1. 在Verilog中,一个模块的结束关键字是什么?

单选题 · easy

  • A.用end作结尾
  • B.用endmodule结尾← 正确答案
  • C.用final结束
  • D.用stop停住

答案:B。endmodule是模块的强制收尾关键字,与module配对。end只用于always、case等块结构,final与stop是仿真控制语句,不用于模块结束。这就像一张图纸必须落款“图纸完”,而不是写“盖章”或“停笔”。

Q2. 以下哪种端口声明表示双向端口?

单选题 · easy

  • A.input作为双向口
  • B.output作为输出口
  • C.inout作为双向口← 正确答案
  • D.wire作为存储口

答案:C。inout是双向端口声明,允许信号既进又出,常用于总线接口。input和output都是单向,wire是数据类型而非方向。如同阳台门可双向通行,而进料口只能入库。

Q3. 关于wire和reg,下列说法正确的是?

单选题 · mid

  • A.wire型可以保存状态
  • B.reg型由连续赋值驱动
  • C.wire用于组合逻辑连线← 正确答案
  • D.reg型只能作为输入

答案:C。wire是线网类型,适合组合逻辑连线,不能保存状态;reg可在always块中赋值并保存状态,通常用于时序逻辑。C正确。这如同临时通道直通但没门,储物柜能锁东西但需要触发开门。

Q4. 关于parameter与localparam,正确的是?

单选题 · mid

  • A.参数parameter可被实例化覆盖← 正确答案
  • B.localparam可被外部覆盖
  • C.parameter只能声明位宽
  • D.define宏属于端口声明

答案:A。parameter可以在实例化时通过#()覆盖默认值,这是实现参数化设计的关键;localparam是局部常量,不能被外部覆盖。C不全面,define是编译宏而非端口声明。如同图纸上的“可调尺寸”与“固定承重墙”的区别。

Q5. 以下哪种实例化方式更推荐在大规模设计中使用?

单选题 · mid

  • A.名称连接写法代码更短
  • B.名称连接与端口顺序无关更安全← 正确答案
  • C.名称连接使综合速度更快
  • D.名称连接占用的硬件资源更少

答案:B。名称连接通过.端口名(信号)显式建立映射,与端口声明顺序无关,增加或删除端口时不易错位,因此更安全、可读性更好。它并不会让综合更快或更省资源。标签对接比按顺序硬接可靠得多。

Q6. 以下哪些是合法的Verilog端口方向声明?

多选题 · mid

  • A.input是输入方向声明← 正确答案
  • B.output是输出方向声明← 正确答案
  • C.inout是双向方向声明← 正确答案
  • D.regout是双向寄存器口

答案:ABC。Verilog端口方向只有input、output、inout三种。regout不是合法的方向关键字。A、B、C分别对应单向输入、单向输出和双向通行,如同进料口、出货口和双向门三类接口。

Q7. 关于层次化实例化,正确的说法是?

多选题 · mid

  • A.顶层模块可以例化多个子模块← 正确答案
  • B.同一模块可以被多次实例化← 正确答案
  • C.子模块内部不能再进行例化
  • D.每个实例化实例名必须唯一← 正确答案

答案:ABD。顶层可以例化任意多个子模块,同一模块也可重复例化(实例名不同即可),而且子模块内部还能继续嵌套例化,形成树状层次。C错误。实例名是构件在工地上的编号,必须唯一,否则设计混淆。

Q8. 采用参数名称方式传递参数的正确写法是?

单选题 · hard

  • A.位置参数写法#(8)传给参数
  • B.按名称传参#(.WIDTH(8))赋值← 正确答案
  • C.端口表达.WIDTH(8)传参
  • D.普通实例化#8位数传参

答案:B。按名称传递参数使用#(.参数名(值))形式,如adder #(.WIDTH(8)) u1(...),可读性强且与参数顺序无关。A和D是位置传参,C的语法用于端口连接而非参数传递。如同在图纸上写“宽度=8”,而不是站在门口报顺序。

Q9. 使用inout端口时,最重要的注意点是什么?

单选题 · hard

  • A.可由两个模块同时自由驱动
  • B.需要三态控制避免总线冲突← 正确答案
  • C.仅可用于双向内存总线
  • D.只允许连接一位宽的信号

答案:B。inout端口是双向的,同一时刻只能有一个驱动源,必须配合三态缓冲和高阻态控制,否则多个模块同时驱动同一根线会造成总线冲突。C过于局限,D错误,inout可以多比特。双向门不能两头同时猛推。

Q10. 以下哪些做法符合模块设计规范?

多选题 · hard

  • A.模块名用英文功能词表达← 正确答案
  • B.给每个端口补充注释说明← 正确答案
  • C.把全部逻辑塞进一个模块
  • D.每个module配一个endmodule← 正确答案

答案:ABD。规范设计要求命名清晰、注释完整、模块规模适中、关键字成对。把全部逻辑塞进一个模块违背了“单一功能、可复用”的模块化思想,C错误。好的模块如同标准图纸——名字清楚、接口有注解、规模可控、边界完整。