Verilog 学习之路二——基础学习总结(摘取自菜鸟教程)

目录

      • [1 Verilog 设计方法](#1 Verilog 设计方法)
      • [2. 基础语法](#2. 基础语法)
        • [2.1 格式](#2.1 格式)
        • [2.2 数值表示](#2.2 数值表示)
        • [2.3 数据类型](#2.3 数据类型)
        • [2.4 表达式](#2.4 表达式)
      • [3. 编译指令](#3. 编译指令)
      • [4. 连续赋值](#4. 连续赋值)
      • [5. 过程结构](#5. 过程结构)
      • [6 过程赋值](#6 过程赋值)
      • [7 时序控制](#7 时序控制)
      • [8 语句块](#8 语句块)
      • [9 循环](#9 循环)
      • [10 函数](#10 函数)

1 Verilog 设计方法

Verilog 的设计多采用自上而下的设计方法(top-down)。即先定义顶层模块功能,进而分析要构成顶层模块的必要子模块;然后进一步对各个模块进行分解、设计,直到到达无法进一步分解的底层功能块。这样,可以把一个较大的系统,细化成多个小系统,从时间、工作量上分配给更多的人员去设计,从而提高了设计速度,缩短了开发周期。

2. 基础语法

2.1 格式

Verilog 是区分大小写的。

格式自由,可以在一行内编写,也可跨多行编写。

每个语句必须以分号为结束符。空白符(换行、制表、空格)都没有实际的意义,在编译阶段可忽略。例如下面两中编程方式都是等效的。

2.2 数值表示

数值种类

0 1 x z

表示方法

合法的基数格式:4'b1011,32'h3022_c0de,'d100,'O332

负数-------6d15,-15

实数------20.123,1.2e4【12000】

  • 字符串表示方法
    字符串是由双引号包起来的字符队列。字符串不能多行书写,即字符串中不能包含回车符。Verilog 将字符串当做一系列的单字节 ASCII 字符队列。例如,为存储字符串 "www.runoob.com", 需要 14*8bit 的存储单元。例如:

reg [0: 14*8-1] str ;

initial begin

str = "www.runoob.com";

end

2.3 数据类型

wire 类型表示硬件单元之间的物理连线,由其连接的器件输出端连续驱动。如果没有驱动元件连接到 wire 型变量,缺省值一般为 "Z"。

寄存器(reg)用来表示存储单元,它会保持数据原有的值,直到被改写。

整数,实数,时间寄存器变量等数据类型实际也属于寄存器类型。

  1. 整数类型用关键字 integer 来声明。声明时不用指明位宽,位宽和编译器有关,一般为32 bit。reg 型变量为无符号数,而 integer 型变量为有符号数。
  2. 实数用关键字 real 来声明,可用十进制或科学计数法来表示。实数声明不能带有范围,默认值为 0。如果将一个实数赋值给一个整数,则只有实数的整数部分会赋值给整数。
  3. Verilog 使用特殊的时间寄存器 time 型变量,对仿真时间进行保存。其宽度一般为 64 bit,通过调用系统函数 $time 获取当前仿真时间。
  • 数组
    在 Verilog 中允许声明 reg, wire, integer, time, real 及其向量类型的数组。

    integer flag [7:0] ; //8个整数组成的数组
    reg [3:0] counter [3:0] ; //由4个4bit计数器组成的数组
    wire [7:0] addr_bus [3:0] ; //由4个8bit wire型变量组成的数组
    wire data_bit[7:0][5:0] ; //声明1bit wire型变量的二维数组
    reg [31:0] data_4d[11:0][3:0][3:0][255:0] ; //声明4维的32bit数据变量数组

存储器变量就是一种寄存器数组,可用来描述 RAM 或 ROM 的行为。

参数用来表示常量,用关键字 parameter 声明,只能赋值一次。

  • 字符串
    字符串保存在 reg 类型的变量中,每个字符占用一个字节(8bit)。因此寄存器变量的宽度应该足够大,以保证不会溢出。
    字符串不能多行书写,即字符串中不能包含回车符。如果寄存器变量的宽度大于字符串的大小,则使用 0 来填充左边的空余位;如果寄存器变量的宽度小于字符串大小,则会截去字符串左边多余的数据。
    有一些特殊字符在显示字符串中有特殊意义,例如换行符,制表符等。如果需要在字符串中显示这些特殊的字符,则需要在前面加前缀转义字符 \ 。

2.4 表达式

操作符------

3. 编译指令

以反引号 ` 开始的某些标识符是 Verilog 系统编译指令。

编译指令 含义
`define 宏定义,替换文本
`undef 取消宏定义
`ifdef, `ifndef, `elsif, `else, `endif 条件编译指令
复制代码
`ifdef       MCU51
    parameter DATA_DW = 8   ;
`elsif       WINDOW
    parameter DATA_DW = 64  ;
`else
    parameter DATA_DW = 32  ;
`endif

例子中,如果定义了 MCU51,则使用第一种参数说明;如果没有定义 MCU、定义了 WINDOW,则使用第二种参数说明;如果 2 个都没有定义,则使用第三种参数说明。

编译指令 含义
`include 使用 `include 可以在编译时将一个 Verilog 文件内嵌到另一个 Verilog 文件中,作用类似于 C 语言中的 #include 结构。该指令通常用于将全局或公用的头文件包含在设计文件里。文件路径既可以使用相对路径,也可以使用绝对路径。
`timescale 在 Verilog 模型中,时延有具体的单位时间表述,并用 `timescale 编译指令将时间单位与实际时间相关联。`timescale 的时间精度设置是会影响仿真时间的。时间精度越小,仿真时占用内存越多,实际使用的仿真时间就越长。所以如果没有必要,应尽量将时间精度设置的大一些。
  • `timescale
    该指令用于定义时延、仿真的单位和精度,格式为:

`timescale time_unit / time_precision

time_unit 表示时间单位,time_precision 表示时间精度,它们均是由数字以及单位 s(秒),ms(毫秒),us(微妙),ns(纳秒),ps(皮秒)和 fs(飞秒)组成。时间精度可以和时间单位一样,但是时间精度大小不能超过时间单位大小,例如下面例子中,输出端 Z 会延迟 5.21ns 输出 A&B 的结果。

在编译过程中,`timescale 指令会影响后面所有模块中的时延值,直至遇到另一个 `timescale 指令或 `resetall 指令。

编译指令 含义
`default_nettype 该指令用于为隐式的线网变量指定为线网类型,即将没有被声明的连线定义为线网类型。
`resetall 该编译器指令将所有的编译指令重新设置为缺省值。`resetall 可以使得缺省连线类型为线网类型。当 `resetall 加到模块最后时,可以将当前的 `timescale 取消防止进一步传递,只保证当前的 `timescale 在局部有效,避免 `timescale 的错误继承。
`celldefine, `endcelldefine 这两个程序指令用于将模块标记为单元模块,他们包含模块的定义。例如一些与、或、非门,一些 PLL 单元,PAD 模型,以及一些 Analog IP 等。
`unconnected_drive, `nounconnected_drive 在模块实例化中,出现在这两个编译指令间的任何未连接的输入端口,为正偏电路状态或者为反偏电路状态。
复制代码
`celldefine
module (
    input      clk,
    input      rst,
    output     clk_pll,
    output     flag);
        ......
endmodule
`endcelldefine

4. 连续赋值

复制代码
assign     LHS_target = RHS_expression  ;
  • LHS_target 必须是一个标量或者线型向量,而不能是寄存器类型。

  • RHS_expression 的类型没有要求,可以是标量或线型或存器向量,也可以是函数调用。

  • 只要 RHS_expression 表达式的操作数有事件发生(值的变化)时,RHS_expression 就会立刻重新计算,同时赋值给 LHS_target。

    wire A, B ;
    wire Cout = A & B ;

5. 过程结构

一个模块中可以包含多个 initial 和 always 语句,但 2 种语句不能嵌套使用

这些语句在模块间并行执行 ,与其在模块的前后顺序没有关系。

但是 initial 语句或 always 语句内部可以理解为是顺序执行的(非阻塞赋值除外)。

每个 initial 语句或 always 语句都会产生一个独立的控制流,执行时间都是从 0 时刻开始。

  • initial

initial 语句从 0 时刻开始执行,只执行一次,多个 initial 块之间是相互独立的。

initial 理论上来讲是不可综合的,多用于初始化、信号检测等。

  • always

与 initial 语句相反,always 语句是重复执行的。always 语句块从 0 时刻开始执行其中的行为语句;当执行完最后一条语句后,便再次执行语句块中的第一条语句,如此循环反复。

由于循环执行的特点,always 语句多用于仿真时钟的产生,信号行为的检测等。

6 过程赋值

  • 阻塞赋值
    阻塞赋值属于顺序执行,即下一条语句执行前,当前语句一定会执行完毕。
    阻塞赋值语句使用等号 = 作为赋值符。
    前面的仿真中,initial 里面的赋值语句都是用的阻塞赋值。
  • 非阻塞赋值
    非阻塞赋值属于并行执行语句,即下一条语句的执行和当前语句的执行是同时进行的,它不会阻塞位于同一个语句块中后面语句的执行。
    非阻塞赋值语句使用小于等于号 <= 作为赋值符。

7 时序控制

基于时延的时序控制出现在表达式中,它指定了语句从开始执行到执行完毕之间的时间间隔。

时延类型 解释
常规时延 遇到常规延时时,该语句需要等待一定时间,然后将计算结果赋值给目标信号。
内嵌时延 遇到内嵌延时时,该语句先将计算结果保存,然后等待一定的时间后赋值给目标信号。

当延时语句的赋值符号右端是常量时,2 种时延控制都能达到相同的延时赋值效果。

当延时语句的赋值符号右端是变量时,2 种时延控制可能会产生不同的延时赋值效果。

一般时延赋值方式:遇到延迟语句后先延迟一定的时间,然后将当前操作数赋值给目标信号,并没有"惯性延迟"的特点,不会漏掉相对较窄的脉冲。

内嵌时延赋值方式:遇到延迟语句后,先计算出表达式右端的结果,然后再延迟一定的时间,赋值给目标信号。

8 语句块

类型 内容
顺序块 顺序块用关键字 begin 和 end 来表示。顺序块中的语句是一条条执行的。当然,非阻塞赋值除外。顺序块中每条语句的时延总是与其前面语句执行的时间相关。
并行块 并行块有关键字 fork 和 join 来表示。并行块中的语句是并行执行的,即便是阻塞形式的赋值。并行块中每条语句的时延都是与块语句开始执行的时间相关。
嵌套块 顺序块和并行块还可以嵌套使用。
命名块 我们可以给块语句结构命名。命名的块中可以声明局部变量,通过层次名引用的方法对变量进行访问。

9 循环

  • while 循环

    while (condition) begin
    ...
    end

  • for 循环

    for(initial_assignment; condition ; step_assignment) begin
    ...
    end

  • repeat 循环

    repeat (loop_times) begin
    ...
    end

repeat 的功能是执行固定次数的循环,它不能像 while 循环那样用一个逻辑表达式来确定循环是否继续执行。repeat 循环的次数必须是一个常量、变量或信号。如果循环次数是变量信号,则循环次数是开始执行 repeat 循环时变量信号的值。即便执行期间,循环次数代表的变量信号值发生了变化,repeat 执行次数也不会改变。

  • forever 循环

    forever begin
    ...
    end

forever 语句表示永久循环,不包含任何条件表达式,一旦执行便无限的执行下去,系统函数 $finish 可退出 forever。

forever 相当于 while(1) 。

通常,forever 循环是和时序控制结构配合使用的。

复制代码
// 产生一个时钟
reg          clk ;
initial begin
    clk       = 0 ;
    forever begin
        clk = ~clk ;
        #5 ;
    end
end

10 函数

函数只能在模块中定义,位置任意,并在模块的任何地方引用,作用范围也局限于此模块。函数主要有以下几个特点:

1)不含有任何延迟、时序或时序控制逻辑

2)至少有一个输入变量

3)只有一个返回值,且没有输出

4)不含有非阻塞赋值语句

5)函数可以调用其他函数,但是不能调用任务

  • 声明格式

function [range-1:0] function_id ;

input_declaration ;

other_declaration ;

procedural_statement ;

endfunction

函数在声明时,会隐式的声明一个宽度为 range、 名字为 function_id 的寄存器变量,函数的返回值通过这个变量进行传递。当该寄存器变量没有指定位宽时,默认位宽为 1。

函数通过指明函数名与输入变量进行调用。函数结束时,返回值被传递到调用处。

  • 函数调用格式如下:

    function_id(input1, input2, ...);

下面用函数实现一个数据大小端转换的功能。

复制代码
module endian_rvs
    #(parameter N = 4)
        (
            input             en,     //enable control
            input [N-1:0]     a ,
            output [N-1:0]    b
    );
         
        reg [N-1:0]          b_temp ;
        always @(*) begin
        if (en) begin
                b_temp =  data_rvs(a);
            end
            else begin
                b_temp = 0 ;
            end
    end
        assign b = b_temp ;
         
    //function entity
        function [N-1:0]     data_rvs ;
            input     [N-1:0] data_in ;
            parameter         MASK = 32'h3 ; 
            integer           k ;
            begin
                for(k=0; k<N; k=k+1) begin
                    data_rvs[N-k-1]  = data_in[k] ;  
                end
            end
    endfunction
         
endmodule
  • 常数函数

常数函数是指在仿真开始之前,在编译期间就计算出结果为常数的函数。常数函数不允许访问全局变量或者调用系统函数,但是可以调用另一个常数函数。

这种函数能够用来引用复杂的值,因此可用来代替常量。

  • automatic 函数

在 Verilog 中,一般函数的局部变量是静态的,即函数的每次调用,函数的局部变量都会使用同一个存储空间。若某个函数在两个不同的地方同时并发的调用,那么两个函数调用行为同时对同一块地址进行操作,会导致不确定的函数结果。

Verilog 用关键字 automatic 来对函数进行说明,此类函数在调用时是可以自动分配新的内存空间的,也可以理解为是可递归的。因此,automatic 函数中声明的局部变量不能通过层次命名进行访问,但是 automatic 函数本身可以通过层次名进行调用。

例子-数码管译码

复制代码
module digital_tube
     (
      input             clk ,
      input             rstn ,
      input             en ,
 
      input [3:0]       single_digit ,
      input [3:0]       ten_digit ,
      input [3:0]       hundred_digit ,
      input [3:0]       kilo_digit ,
 
      output reg [3:0]  csn , //chip select, low-available
      output reg [6:0]  abcdefg        //light control
      );
 
    reg [1:0]            scan_r ;  //scan_ctrl
    always @ (posedge clk or negedge rstn) begin
        if(!rstn)begin
            csn            <= 4'b1111;
            abcdefg        <= 'd0;
            scan_r         <= 3'd0;
        end
        else if (en) begin
            case(scan_r)
            2'd0:begin
                scan_r    <= 3'd1;
                csn       <= 4'b0111;     //select single digit
                abcdefg   <= dt_translate(single_digit);
            end
            2'd1:begin
                scan_r    <= 3'd2;
                csn       <= 4'b1011;     //select ten digit
                abcdefg   <= dt_translate(ten_digit);
            end
            2'd2:begin
                scan_r    <= 3'd3;
                csn       <= 4'b1101;     //select hundred digit
                abcdefg   <= dt_translate(hundred_digit);
            end
            2'd3:begin
                scan_r    <= 3'd0;
                csn       <= 4'b1110;     //select kilo digit
                abcdefg   <= dt_translate(kilo_digit);
            end
            endcase
        end
    end
 
    /*------------ translate function -------*/
    function [6:0] dt_translate;
        input [3:0]   data;
        begin
        case(data)
            4'd0: dt_translate = 7'b1111110;     //number 0 -> 0x7e
            4'd1: dt_translate = 7'b0110000;     //number 1 -> 0x30
            4'd2: dt_translate = 7'b1101101;     //number 2 -> 0x6d
            4'd3: dt_translate = 7'b1111001;     //number 3 -> 0x79
            4'd4: dt_translate = 7'b0110011;     //number 4 -> 0x33
            4'd5: dt_translate = 7'b1011011;     //number 5 -> 0x5b
            4'd6: dt_translate = 7'b1011111;     //number 6 -> 0x5f
            4'd7: dt_translate = 7'b1110000;     //number 7 -> 0x70
            4'd8: dt_translate = 7'b1111111;     //number 8 -> 0x7f
            4'd9: dt_translate = 7'b1111011;     //number 9 -> 0x7b
        endcase
        end
    endfunction
 
endmodule
相关推荐
西岸行者4 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
ZPC82104 天前
docker 镜像备份
人工智能·算法·fpga开发·机器人
ZPC82104 天前
docker 使用GUI ROS2
人工智能·算法·fpga开发·机器人
悠哉悠哉愿意4 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码4 天前
嵌入式学习路线
学习
毛小茛4 天前
计算机系统概论——校验码
学习
babe小鑫4 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms4 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下4 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。4 天前
2026.2.25监控学习
学习