当前位置: 首页 > article >正文

Verilog编程规范、示例

Verilog编程规范的详细介绍:

一、工程组织形式

工程的组织形式一般包括doc、par、rtl和sim四个部分:

  • doc:存放工程相关的文档,包括该项目用到的数据手册(datasheet)、设计方案等。
  • prj:存放工程文件和使用到的一些IP文件。
  • rtl:存放工程的rtl代码,这是工程的核心,文件名与module名称应当一致,建议按照模块的层次分开存放。
  • sim:存放工程的仿真代码,复杂的工程里仿真不可或缺,可以极大减少调试的工作量。

二、文件头声明

每个Verilog文件的开头,都必须有一段声明的文字,包括文件的版权、作者、创建日期以及内容介绍等。

三、输入输出定义

  • input:定义模块的输入信号,可以是 wire 类型或其他类型,表示外部输入的信号。
  • output:定义模块的输出信号,通常是 wire 或 reg 类型,表示模块输出到外部的信号。
  • inout:用于双向信号,可以同时作为输入和输出,根据外部控制信号的状态决定其行为。
示例1:逻辑功能:AND门
module and_gate (
    input wire a,  // 输入信号 a
    input wire b,  // 输入信号 b
    output wire y  // 输出信号 y
);

    // 逻辑功能:AND 门
    assign y = a & b;  // y = a AND b

endmodule

说明:

  • input wire a, b:声明了两个输入端口,类型为 wire,表示这两个信号是连接外部电路的信号。
  • output wire y:声明了一个输出端口,类型为 wire,表示它是一个由模块输出到外部电路的信号。
示例2:带时序逻辑的模块
module d_flip_flop (
    input wire clk,    // 时钟信号
    input wire reset,  // 异步复位信号
    input wire d,      // 数据输入
    output reg q       // 输出,使用 reg 类型,因为需要存储状态
);
    // 异步复位,并在时钟的上升沿更新输出
    always @(posedge clk or posedge reset) begin
        if (reset) begin
            q <= 0;  // 复位时输出为 0
        end else begin
            q <= d;  // 时钟信号有效时,输出跟随输入 d
        end
    end
endmodule
  • 输入端口(input)

    • clkresetd 都是输入端口,分别代表时钟信号、复位信号和数据输入。
  • 输出端口(output)

    • q 是一个时序输出,类型是 reg,因为它存储了时钟信号控制下的状态值。
示例3:加法
module adder (
    input wire [3:0] a,   // 4位输入端口 a
    input wire [3:0] b,   // 4位输入端口 b
    output wire [3:0] sum, // 4位输出端口 sum
    output wire carry_out  // 输出进位信号
);
    assign {carry_out, sum} = a + b;  // 执行加法操作并将进位和和输出
endmodule
  • 输入端口(input)

    • a 和 b 是4位宽度的输入端口,表示两个4位二进制数。
  • 输出端口(output)

    • sum 是一个4位宽度的输出端口,表示两个输入相加的结果。
    • carry_out 是一个单比特的输出端口,表示加法操作的进位输出。
示例4:双向端口inout
module bidirectional_example (
    inout wire data,  // 双向端口,数据线
    input wire enable // 启用信号
);
    // 当 enable 为 1 时,data 输出;当 enable 为 0 时,data 输入
    assign data = (enable) ? 1'b1 : 1'bz; // 1'bz 表示高阻态
endmodule
  • inout端口
    • data 是一个双向端口,可以在不同的条件下作为输入或输出使用。它的信号状态是三态(z)或由 assign 语句控制。
示例5:乘法
module multiplier (
    input wire [7:0] a,   // 8位输入端口 a
    input wire [7:0] b,   // 8位输入端口 b
    output wire [15:0] product  // 16位输出端口
);
    assign product = a * b;  // 执行乘法操作
endmodule
  • 输入端口(input)

    • a 和 b 都是8位宽度的输入端口,表示两个8位的二进制数。
  • 输出端口(output)

    • product 是一个16位宽度的输出端口,用来存储 a 和 b 相乘的结果。

四、parameter定义

parameter定义一个常量值,建议全部字母大写,并用来定义有实际意义的常数,如单位延时、版本号等。

示例1:定义常量宽度
module register (
    input wire clk,
    input wire reset,
    input wire [WIDTH-1:0] data_in,  // 输入数据,宽度由 parameter 决定
    output reg [WIDTH-1:0] data_out  // 输出数据,宽度由 parameter 决定
);
    // 定义 parameter,用于指定寄存器宽度
    parameter WIDTH = 8;  // 默认宽度为 8 位

    always @(posedge clk or posedge reset) begin
        if (reset)
            data_out <= 0;
        else
            data_out <= data_in;
    end
endmodule
  • parameter WIDTH = 8:定义了一个名为 WIDTH 的参数,默认值为 8。这个参数决定了 data_in 和 data_out 的位宽。
  • 如果在实例化时没有传递 WIDTH 参数,默认值 8 将会被使用。
示例2:实例化 register 模块传递parameter值
module top_module (
    input wire clk,
    input wire reset,
    input wire [15:0] data_in_16,  // 16位输入
    input wire [31:0] data_in_32,  // 32位输入
    output wire [15:0] data_out_16,
    output wire [31:0] data_out_32
);
    // 实例化 register 模块,使用不同的 WIDTH 参数
    register #(.WIDTH(16)) reg_16 (
        .clk(clk),
        .reset(reset),
        .data_in(data_in_16),
        .data_out(data_out_16)
    );

    register #(.WIDTH(32)) reg_32 (
        .clk(clk),
        .reset(reset),
        .data_in(data_in_32),
        .data_out(data_out_32)
    );
  • register #(.WIDTH(16)):在实例化 register 模块时,通过 #(.WIDTH(16)) 为 WIDTH 参数指定了 16,这样 data_in 和 data_out 的宽度将是 16 位。
  • register #(.WIDTH(32)):在另一个实例化中,指定 WIDTH 为 32,从而使得 data_in 和 data_out 的宽度为 32 位。

五、wire/reg定义

  • wire:代表逻辑单元的物理连线,用于描述信号连接,无存储功能,需要外部驱动源。
  • reg:一种存储型变量,能保持数据,常用于行为级描述。在过程语句中,被赋值信号通常定义为reg类型。
特性wirereg
用途用于连接信号(组合逻辑的输入输出)用于存储值(时序逻辑、触发器)
驱动方式由其他信号(如 assign 或模块输出)驱动由 always 块或时钟边沿驱动
更新时机由外部模块或逻辑驱动,不能在 always 中更新可以在 always 块中进行赋值更新
适用场景组合逻辑电路、模块间的信号传递寄存器、触发器、时序逻辑
赋值方式使用 assign 语句赋值使用 = 或 <= 赋值
示例1:使用wire连接多个模块的输入和输出
module and_gate (
    input wire a,  // 输入信号
    input wire b,  // 输入信号
    output wire y  // 输出信号
);
    assign y = a & b;  // 计算 a 和 b 的与操作
endmodule

module top_module (
    input wire a,     // 输入信号
    input wire b,     // 输入信号
    output wire result  // 输出信号
);
    wire and_output;  // 定义一个 wire 用于连接 AND 门的输出

    // 实例化 and_gate 模块
    and_gate u1 (
        .a(a),         // 连接输入信号 a
        .b(b),         // 连接输入信号 b
        .y(and_output) // 将 AND 门输出连接到 wire and_output
    );

    // 将 and_output 传递给最终的结果
    assign result = and_output;
endmodule
  • 在上述例子中,wire and_output; 定义了一个 wire 类型的信号,它用于连接 and_gate 模块的输出。
  • wire 信号必须由某个逻辑源(如 assign 语句、组合逻辑门等)驱动。
示例2:使用reg存储值
module flip_flop (
    input wire clk,       // 时钟信号
    input wire reset,     // 复位信号
    input wire d,         // 数据输入
    output reg q          // 数据输出(寄存器类型)
);
    always @(posedge clk or posedge reset) begin
        if (reset) 
            q <= 0;  // 复位时,q 输出为 0
        else 
            q <= d;  // 在时钟上升沿时,将 d 的值存入 q
    end
endmodule
  • 在上述例子中,q 被定义为 reg 类型,因为它存储数据值,并且在时钟的上升沿时更新其值。
  • reg 信号能够在 always 块中通过赋值语句(<= 或 =)进行更新。
示例3:组合使用wire、reg
module counter (
    input wire clk,      // 时钟信号
    input wire reset,    // 复位信号
    output wire [3:0] count  // 4位计数器输出
);
    reg [3:0] count_reg;  // 使用 reg 类型存储计数值
    wire reset_signal;     // 使用 wire 类型作为复位信号

    assign reset_signal = reset;  // 将复位信号传递给 wire 类型信号

    always @(posedge clk or posedge reset_signal) begin
        if (reset_signal)
            count_reg <= 4'b0000;  // 复位时将计数器值清零
        else
            count_reg <= count_reg + 1;  // 否则计数加 1
    end

    assign count = count_reg;  // 将寄存器值传递给输出
endmodule
  • count_reg 是一个 reg 类型的信号,用于存储计数值。
  • reset_signal 是一个 wire 类型的信号,用于传递外部复位信号。
  • count 是输出信号,通过 assign 将 count_reg 的值传递出去。

六、信号命名

  • 每个文件只包含一个module,module名要小写,并且与文件名保持一致。
  • 除parameter外,信号名全部小写,名字中的两个词之间用下划线连接。
  • 信号名长度建议不超过15至20个字符,并且避免使用Verilog和VHDL的保留字命令。
  • 不允许两个连续的下划线出现在命名字符串中。
  • 不允许使用大小写来区分模块名称、变量、信号。

七、always块描述方式

  • begin/end要单独另起一行,配对的begin/end列对齐。
  • 在时序逻辑语句块(always)中统一采用非阻塞型赋值。
  • 同一always块中,非阻塞赋值和阻塞赋值不能混用。
  • 避免使用latch,如组合逻辑里面的if不带else分支。
  • always有且仅有一个的敏感事件列表,敏感事件列表要完整,否则可能会造成前后仿真的结果不一致。
示例1:时钟驱动
module d_flip_flop (
    input wire clk,    // 时钟信号
    input wire reset,  // 复位信号
    input wire d,      // 数据输入
    output reg q       // 输出
);

    always @(posedge clk or posedge reset) begin
        if (reset)
            q <= 0;  // 复位时将 q 输出置为 0
        else
            q <= d;  // 时钟上升沿时,将 d 的值存入 q
    end
endmodule
  • 描述方式always @(posedge clk or posedge reset) 表示当 clk 上升沿或 reset 变为高电平时触发该块。
  • 行为:当 reset 信号为高时,输出 q 置为 0;否则,在 clk 上升沿时将输入 d 的值赋给输出 q

1. always 语句

always 语句块用于描述硬件行为的变化。always 后面的括号内是触发条件,指定了何时该块的代码会被执行。always 语句块会在触发条件满足时执行其中的代码。

2. @ 符号

@ 符号是事件控制符号,用来指定触发条件。例如,@posedge clk 表示在 clk 信号的上升沿(从 0 到 1)时触发该语句块。

3. posedge clk 的含义

  • posedge 是 "positive edge"(上升沿)的缩写,表示信号由低电平(0)变为高电平(1)的瞬间。

  • clk 是时钟信号,通常用来驱动时序电路的操作。

因此,posedge clk 表示当时钟信号 clk 由低电平变为高电平时,触发该 always 语句块的执行。

4. posedge reset 的含义

  • reset 通常是一个复位信号,当该信号有效时(通常为高电平),可以强制模块进入一个初始状态。

  • posedge reset 表示当 reset 信号从低电平变为高电平时,触发该 always 语句块的执行。

5. always @(posedge clk or posedge reset) 的含义

结合起来,always @(posedge clk or posedge reset) 表示该 always 块会在两种情况下被触发:

  • 时钟上升沿触发:当时钟 clk 由低电平变为高电平时(posedge clk),触发该块的执行。

  • 复位信号上升沿触发:当复位信号 reset 由低电平变为高电平时(posedge reset),触发该块的执行。

这种触发条件常用于设计时序电路中的触发器(如 D 触发器)或者带有复位功能的计数器等。

6. 复位和时钟的优先级

在这种触发条件下,复位信号和时钟信号是“或”关系。这意味着:

  • 优先级:如果复位信号 reset 在时钟的上升沿之前变为高电平,那么复位信号会优先生效,因为复位通常是硬件电路中最优先的操作。即使时钟信号也在变化,复位信号一旦有效,模块会立即进入复位状态。

  • 复位条件:当 reset 为高电平时,always 语句块的逻辑会优先处理复位操作。只有当复位信号无效(低电平)时,时钟的上升沿才会触发后续的逻辑操作。

示例2:计数器
module counter (
    input wire clk,    // 时钟信号
    input wire reset,  // 复位信号
    output reg [3:0] count  // 计数器输出
);

    always @(posedge clk or posedge reset) begin
        if (reset)
            count <= 4'b0000;  // 复位时将计数器清零
        else
            count <= count + 1;  // 时钟上升沿时,计数加 1
    end
endmodule
  • 描述方式always @(posedge clk or posedge reset),计数器在 clk 上升沿时增加计数,或者在 reset 信号为高时清零。
  • 行为:计数器在每个时钟周期增加 1,或者在复位信号有效时将计数值清零。
示例3:组合逻辑:与门
module and_gate (
    input wire a,  // 输入信号
    input wire b,  // 输入信号
    output reg y   // 输出信号
);

    always @(*) begin
        y = a & b;  // 计算 a 和 b 的与操作
    end
endmodule
  • 描述方式always @(*) 表示该块在任何输入信号变化时都会触发,适用于组合逻辑。
  • 行为:当输入信号 a 或 b 改变时,y 的值将立即更新为 a & b
示例4:多路选择器MUX
module mux (
    input wire [1:0] sel,  // 选择信号
    input wire a,          // 输入信号 a
    input wire b,          // 输入信号 b
    input wire c,          // 输入信号 c
    input wire d,          // 输入信号 d
    output reg y           // 输出信号
);

    always @(*) begin
        case (sel)
            2'b00: y = a;  // 选择 a
            2'b01: y = b;  // 选择 b
            2'b10: y = c;  // 选择 c
            2'b11: y = d;  // 选择 d
            default: y = 0;  // 默认输出 0
        endcase
    end
endmodule
  • 描述方式always @(*) 表示当输入信号 selabcd 改变时,y 的值会根据 sel 的值选择不同的输入信号。
  • 行为sel 信号决定了输出 y 的值是 abc 还是 d,这是一个典型的多路选择器。
示例5:时序逻辑与组合逻辑混合使用
module counter_with_reset (
    input wire clk,    // 时钟信号
    input wire reset,  // 复位信号
    output reg [3:0] count  // 计数器输出
);

    always @(posedge clk) begin
        if (reset)
            count <= 4'b0000;  // 复位时清零
        else
            count <= count + 1;  // 时钟上升沿时计数
    end

    always @(*) begin
        if (count == 4'b1000)
            $display("Count reached 8");  // 当计数达到 8 时输出信息
    end
endmodule
  • 描述方式:第一个 always @(posedge clk) 块描述了时序逻辑,计数器在时钟上升沿时增加。第二个 always @(*) 块是组合逻辑,当计数器值为 8 时输出信息。

八、assign块描述方式

  • 使用assign关键字用于指定输出信号与输入信号之间的逻辑关系。
  • 在组合逻辑语句块(always和assign)中统一采用阻塞型赋值。
  • 在逻辑代码中,除了三态控制逻辑接口允许使用高阻Z状态进行信号赋值外,在其他信号赋值、条件表达式等逻辑中都不允许使用高阻Z状态。
  • assign 用于描述组合逻辑,其赋值操作是连续的,即输入信号发生变化时,输出信号会立即反映出来。
  • always 语句用于描述时序逻辑,并且通常配合时钟信号(例如 posedge clk)进行触发。

基本语法:assign <信号名> = <逻辑表达式>;

示例1:基本语句
module simple_assign(
    input wire a,      // 输入信号a
    input wire b,      // 输入信号b
    output wire y      // 输出信号y
);

    // 组合逻辑:y = a AND b
    assign y = a & b;

endmodule
  • assign y = a & b; 表示 y 的值是 a 和 b 的逻辑与(AND)运算的结果。
  • 一旦 a 或 b 改变,y 会立即反映出新的结果。
  • 这是一个组合逻辑电路的简单示例。
示例2:多输入逻辑表达式
module logic_example(
    input wire a,      // 输入信号a
    input wire b,      // 输入信号b
    input wire c,      // 输入信号c
    output wire y      // 输出信号y
);

    // 组合逻辑:y = (a AND b) OR c
    assign y = (a & b) | c;

endmodule
  • assign y = (a & b) | c; 表示 y 是 (a AND b) 和 c 的逻辑或(OR)运算的结果。
  • 一旦 ab 或 c 中任何一个信号变化,y 的值会立即更新。
示例3:反转器
module inverter(
    input wire a,     // 输入信号a
    output wire y     // 输出信号y
);

    // 组合逻辑:y = NOT a
    assign y = ~a;

endmodule
  • assign y = ~a; 表示 y 是 a 的逻辑非(NOT)运算的结果。
  • 当输入信号 a 变化时,输出信号 y 会立即反映其反转值。
示例4:4位加法器
module adder(
    input wire [3:0] a,     // 4位输入a
    input wire [3:0] b,     // 4位输入b
    output wire [3:0] sum,  // 4位和
    output wire carry_out   // 进位输出
);

    // 组合逻辑:4位加法器
    assign {carry_out, sum} = a + b;

endmodule
  • assign {carry_out, sum} = a + b; 表示 sum 是 a 和 b 的和,carry_out 是进位输出。
  • 使用大括号 {} 表示将多个信号连接成一个信号。

九、空格和TAB

由于不同的解释器对于TAB翻译不一致,因此建议使用空格而不是TAB进行缩进,一般所有缩进以4个空格为单位。

十、注释

  • 注释描述需要清晰、简洁,避免冗余。
  • 核心代码和信号定义之间需要增加注释。
  • 用“//”做小于1行的注释,用“/* */”做多于1行的注释。

十一、模块例化

  • module例化名可以用“u_xx_x”标示。
  • 建议给每个模块加timescale。

十二、其他注意事项

  • 不使用repeat等循环语句(仿真代码除外)。
  • 避免使用太复杂和少见的语法,可能造成语法综合器优化力度较低。
  • 不使用include语句。
  • 不使用disable、initial等综合工具不支持的电路。
  • 不使用specify模块,不使用“===”、“!==”等不可综合的操作符。
  • 除仿真外,不使用fork-join、while、repeat、forever语句。
  • 除仿真外,不使用系统任务($)。
  • 除仿真外,不使用deassign、force、release语句。
  • 不在连续赋值语句中引入驱动强度和延时。
  • 设计中不使用macro_module。
  • 不在RTL代码中实例门级单元,尤其是CMOS、NMOS、PMOS、trans等。

遵循这些Verilog编程规范,有助于提高代码的可读性、可维护性和可移植性,也有助于逻辑工程师之间的交流与合作。


http://www.kler.cn/a/390080.html

相关文章:

  • Android OpenGL ES详解——立方体贴图
  • Kafka - 启用安全通信和认证机制_SSL + SASL
  • SciPy:Python 科学计算工具包的全面教程
  • SpringBoot(八)使用AES库对字符串进行加密解密
  • [CKS] K8S ServiceAccount Set Up
  • 10款PDF翻译工具的探索之旅:我的使用经历与工具特色!!
  • qt QUndoCommand 与 QUndoStack详解
  • 测试实项中的偶必现难测bug--一键登录失败
  • Vite环境下uniapp Vue 3项目添加和使用环境变量的完整指南
  • SystemVerilog学习笔记(四):用户自定义类型
  • Rust常量
  • 24GHz毫米波雷达探测器,办公室灯光照明控制,共筑节能减排风尚
  • 【开源社区】使用 ES 实现多种地理位置检索
  • 【系统配置】命令行配置麒麟安全中心应用程序来源检查
  • 深圳华为展厅:30寸OLED透明屏中控桌引领科技新风尚
  • UEditor(百度开源的在线编辑器,修改版)
  • PaddleYOLO目标检测训练(集成SwanLab可视化全过程)
  • 基于OpenCV的相机捕捉视频进行人脸检测--米尔NXP i.MX93开发板
  • 【前端学习笔记】JavaScript学习一【变量与数据类型】
  • 在vscode中开发运行uni-app项目
  • ‘conda‘ 不是内部或外部命令,也不是可运行的程序或批处理文件,Miniconda
  • Windows10/11开启卓越性能模式 windows开启卓越性能电源模式 工作电脑开启卓越性能模式 电脑开启性能模式
  • [Meachines] [Medium] Compiled Git-RCE+Visual Studio 2019权限提升
  • [ Linux 命令基础 2 ] Linux 命令详解-系统管理命令
  • Rust学习(二):rust基础语法Ⅰ
  • conda环境迁移,修改conda路径(附带脚本)