• 计算机基础(1)——Verilog语法入门


    最近在学计算机基础课程,硬核到不仅仅是汇编,而是直接开始写硬件相关代码了!为了能够跟上课程进度,提前了解一些Verilog语法是很有必要的。

    Verilog语法入门

    简单介绍一下Verilog的作用:Verilog HDL(简称 Verilog )是一种硬件描述语言,用于数字电路的系统设计。可对算法级、门级、开关级等多种抽象设计层次进行建模。

    其实Verilog和C语言很像,只不过在命名、使用上有一些特殊的地方,一开始写起来可能不太习惯。

    好吧,要不我们直接来看一段代码?下面将会贴出一段Verilog代码,它定义了一个**4位的十进制计数器,每当计数到10就会输出一个溢出位并清零重新开始计数,**它有两个输入端口(一个输入让计数器复位、另一个输入可以让计数器+1);两个输出端口(一个输出当前计数,另一个则当超过4位计数时输出溢出位)。

    知道了代码的作用之后,来看代码吧:

    //ref:https://time.geekbang.org/column/article/543867
    module counter(
      //端口定义
      input                   reset_n,  //复位端,低电平有效,让计数器清零
      input                   clk,       //输入时钟,每获得一个时钟信号,计数器+1
      output [3:0]            cnt,      //4位的输出端口,输出当前的计数值
      output                  cout     //1位溢出端口,如果溢出则输出高电平(1)
    );  
      
      reg [3:0]               cnt_r ;      //4位的计数器寄存器
      
      // 可以把下面这一段代码理解成while(条件)
      always@(posedge clk or negedge reset_n) begin 
        if(!reset_n) begin                  //复位时,计时归0
          cnt_r        <= 4'b0000 ; // 非阻塞赋值
        end
        else if (cnt_r==4'd9) begin      //计时10个cycle时,计时归0
          cnt_r        <=4'b0000;  // 非阻塞赋值
        end
        else begin                      
          cnt_r        <= cnt_r + 1'b1 ; //计数加1 // 非阻塞赋值
        end
      end
      
      assign  cout = (cnt_r==4'd9) ;       //输出周期位 // 阻塞赋值
      assign  cnt  = cnt_r ;               //输出实时计时器 // 阻塞赋值
      
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    其实上述代码可以看作一个“函数”,只不过在Verilog中被称为模块(module),这是最基本的设计单元。

    一个模块以module+模块名开始,到endmodule结束,它主要包含两个部分:

    端口IO定义、内部逻辑

    端口IO定义

    “端口”指的是模块的输入信号与输出信号,类似于函数参数。值得一提的是,与其他高级语言不同,这些参数是有方向性的,**需要注明输入和输出。**以上述代码为例,如:

    module counter(
      //端口定义
      input                   reset_n,  //复位端,低电平有效,让计数器清零
      input                   clk,       //输入时钟,每获得一个时钟信号,计数器+1
      output [3:0]            cnt,      //4位的输出端口,输出当前的计数值
      output                  cout     //1位溢出端口,如果溢出则输出高电平(1)
    );  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    上述的reset_n、clk、cnt以及cout即是端口IO定义,既定义了端口的名称,又定义了它们的输入输出属性。其中input标注的参数是输入信号,output标明的是输出信号。

    顺带一提,Verilog主要有四种数据类型:线网型、寄存器型、常量、参数。

    线网型(Wire)

    线网型代表元器件之间的连线,是Verilog的缺省类型,上文代码中的reset_n、clk、cout、cnt均为线网型参数。其中上cnt的命名方式类似于数组,[3:0]表示该信号有4条线路。

    在二进制计数中,单比特逻辑值只有“0”和“1”两种状态,在 Verilog 语言中,为了对电路了进行精确的建模又增加了两种逻辑状态(“X”和“Z”)。

    逻辑0:表示低电平,表示GND(接地)。
    逻辑1:表示高电平,表示VCC(接电源)。
    逻辑X:表示未知,有可能是高电平,也有可能是低电平。
    逻辑Z:表示高阻态,没有激励信号,悬空状态。

    寄存器型(Reg)

    寄存器型的信号(变量)是一个抽象的数据存储单元,它只能够在always和initial中赋值(后文再表)。上文代码中的cnt_r就表示一个4位的寄存器。注意,这里所说的位数都是对于二进制而言。

    常量(Const)

    常量又分为了整数型、实数型和字符串型。

    整数型的命名方式为**+/- 位宽 进制 数字,位宽表示对应的二进制宽度,进制表示后跟数字的进制。**

    例如,4’b1010是二进制写法等同于十进制写法的 4’d10。

    实数型可以用科学表示法和十进制写法来表示,例如5.0,2.11e2。

    字符串型实质上还是无符号整数,例如:

    output  [8*4-1:0] ca //定义了一个为输出的32位线网型变量ca
    assign ca = "Andy"; // 给它赋值"Andy"
    
    • 1
    • 2

    其实ca中的值就是32’h416E6479,即Andy字符串的ASCII码。

    参数(param)

    参数用关键词parameter表示,有点儿像宏定义,如下:

    parameter      width = 10'd48 ;
    parameter      mem_size = width * 10 ;
    
    • 1
    • 2

    参数只能被赋值一次(如果采用实例化可以更改它的值,这里暂时不涉及)。局部变量用localparam关键词表示,它的作用域尽在本模块中使用,且值无法改变。

    内部逻辑

    内部功能逻辑是指模块中对输入输出信号(变量)的各种操作,下面将介绍一些基础的内部逻辑。

    always

    之前也提到,always可以简单的看作while(event),即当事件发生的时候,就执行always语句。根据事件event中是否包含时序事件,always分为两种逻辑:时序逻辑和组合逻辑

    时序逻辑表示always只会在对应的信号出现边沿事件时,才会执行对应的代码。如果有多个信号以or连接,这些对应的事件被称为敏感事件列表,如下:

    always @ (edge eventa or edge eventb) begin
    	[multiple statements]
    end
    
    • 1
    • 2
    • 3

    其中edge可以是negedge(下降沿)和posedge(上升沿)。

    组合逻辑通常用来监听信号水平事件的发生。当敏感信号出现电平的变化时就会执行always语句。例如always @(a or b or c),a、b、c均为变量,当其中一个发生变化时都会执行后续代码。例如:

    always@(a,b) begin
    	out = a&b;
    end
    
    • 1
    • 2
    • 3

    上述代码中always的敏感事件列表为a b发生电平变化,即当ab其中任何一个发生变化,都会赋予out新值。

    有的时候敏感事件变量较多,一个一个写比较麻烦,还有一种特殊的用法,always @(*),此时的敏感列表是module中所有具有形参的信号,其中任何信号发生变化都会触发always语句。上述代码就可以写成:

    always@(*) begin
    	out = a&b;
    end
    
    • 1
    • 2
    • 3

    initial

    initial语句只执行一次,常用于产生仿真测试信号,或对某些变量赋初值。

    如果module中有多个initial语句,这些语句之间是相互独立的,都是从0时刻开始并行执行,并没有顺序之分。

    阻塞/非阻塞赋值

    如果现在跳回去看之前的代码,会看到这样的注释:

    ……                   
      cnt_r        <= cnt_r + 1'b1 ; //计数加1 // 非阻塞赋值
    ……  
      assign  cout = (cnt_r==4'd9) ;       //输出周期位 // 阻塞赋值
      assign  cnt  = cnt_r ;               //输出实时计时器 // 阻塞赋值
    ……
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    可以发现对变量进行赋值时有两种方法:=(阻塞赋值)和<+(非阻塞赋值)。

    两者的区别就在于阻塞赋值会立即计算右手语句值(RHS)并立即赋值给左手语句(LHS), 而且这行代码之后的语句会被阻塞,只有它执行完毕之后才能够继续进行。

    而非阻塞赋值将赋值分为两个步骤:计算右边RHS,更新左边(LHS),并且其他语句不会被其所阻塞。非阻塞赋值只能用于对寄存器信号进行赋值。

    需要注意的是,阻塞赋值可能会造成数据竞争,例如实现在上升沿时交换两个寄存器的值:

    always @(posedge clk) begin
        a = b ;
    end
     
    always @(posedge clk) begin
        b = a;
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    如果采用上述阻塞赋值,最后a b的值都会相等,而不是我们想要的交换。但是如果使用非阻塞赋值就没有这个顾虑:

    always @(posedge clk) begin
        a <= b ;
    end
     
    always @(posedge clk) begin
        b <= a;
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    算术运算与逻辑运算

    Verilog语法支持常规的算术运算和逻辑运算,更详细的介绍和例子可以参考这篇博客:Verilog 表达式

    在线学习网站

    要记住,学习一门语言的最佳方式是实践!实践!实践!

    换句话说,你只需要不停地敲代码——遇到问题——百度/谷歌——解决问题——总结归纳,你的语言水平自然而然就会得到提升。

    这里推荐大家两个网站,一个是菜鸟教程的中文Verilog教程,对于新手来说,花几个小时看完这些就差不多入门了。

    菜鸟教程——Verilog教程

    **另外一个就是Verilog在线执行网站+英文教程:**https://hdlbits.01xz.net/wiki/Step_one

    img

    免去了部署Verilog HDL环境的繁杂步骤,就像刷Leetcode那样学习Verilog语法,还自带详细的英文教程!

    img

    总结

    回答两个问题。

    为什么很多特定算法,用 Verilog 设计并且硬件化之后,要比用软件实现的运算速度快很多?

    简单的说越是底层的东西越快,效率越高,因为中间步骤少!从另外一个方面来说,硬件化是一种定制化,在某一方面具有特别突出的能力,例如CPU软解码和GPU硬解码,两者的效率天差地别。

    推荐阅读,如果你想进一步了解高级语言是如何一步一步到具体门电路的过程,可参阅:https://zhuanlan.zhihu.com/p/362950660

    既然用 Verilog 很容易就可以设计出芯片的数字电路,为什么我们国家还没有完全自主可控的高端 CPU 呢?

    CPU设计我们是世界顶尖水平,但是没有机器造不出来,西方国家根本不敢给我们,因为我们如果拿到了,以后CPU基本没有他们的事情了。这就是所谓的技术壁垒,另一种形式的“铁幕”。

    module alu(a, b, cin, sel, y);
      input [7:0] a, b;
      input cin;
      input [3:0] sel;
      output [7:0] y;
    
      reg [7:0] y;
      reg [7:0] arithval;
      reg [7:0] logicval;
    
      // 算术执行单元
      always @(a or b or cin or sel) begin
        case (sel[2:0])
          3'b000  : arithval = a;
          3'b001  : arithval = a + 1;
          3'b010  : arithval = a - 1;
          3'b011  : arithval = b;
          3'b100  : arithval = b + 1;
          3'b101  : arithval = b - 1;
          3'b110  : arithval = a + b;
          default : arithval = a + b + cin;
        endcase
      end
    
      // 逻辑处理单元
      always @(a or b or sel) begin
        case (sel[2:0])
          3'b000  : logicval =  ~a;
          3'b001  : logicval =  ~b;
          3'b010  : logicval = a & b;
          3'b011  : logicval = a | b;
          3'b100  : logicval =  ~((a & b));
          3'b101  : logicval =  ~((a | b));
          3'b110  : logicval = a ^ b;
          default : logicval =  ~(a ^ b);
        endcase
      end
    
      // 输出选择单元
      always @(arithval or logicval or sel) begin
        case (sel[3])
          1'b0    : y = arithval;
          default : y = logicval;
        endcase
      end
    
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    参考链接

    https://time.geekbang.org/column/article/543867

    https://hdlbits.01xz.net/wiki/Step_one

    https://blog.csdn.net/weixin_42705678/article/details/120904738

    https://blog.csdn.net/Reborn_Lee/article/details/107052261

    efault : y = logicval;
    endcase
    end

    endmodule

    
    
    
    # 参考链接
    
    https://time.geekbang.org/column/article/543867
    
    https://hdlbits.01xz.net/wiki/Step_one
    
    https://blog.csdn.net/weixin_42705678/article/details/120904738
    
    https://blog.csdn.net/Reborn_Lee/article/details/107052261
    
    https://blog.csdn.net/Lee_tr/article/details/122488970
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
  • 相关阅读:
    正向代理(流量代理)
    uniapp-vue3-微信小程序-标签选择器wo-tag
    卷积神经网络Inception Net
    centos8.5安装mysql8.0时出现GPG检查失败
    按关键字采集淘特商品列表API接口H5
    老卫带你学---leetcode刷题(39. 组合总和)
    操作系统安装在哪里?
    H2创建表带注释的语法
    【✨十五天搞定电工基础】电阻电路的分析方法
    C/C++ 深入浅出C++模板(上)
  • 原文地址:https://blog.csdn.net/ll15982534415/article/details/126202327