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

简介: 计算机基础(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

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

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

端口IO定义、内部逻辑

端口IO定义

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

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

上述的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"

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

参数(param)

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

parameter      width = 10'd48 ;
parameter      mem_size = width * 10 ;

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

内部逻辑

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

always

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

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

always @ (edge eventa or edge eventb) begin
  [multiple statements]
end

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

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

always@(a,b) begin
  out = a&b;
end

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


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

always@(*) begin
  out = a&b;
end

initial

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

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

阻塞/非阻塞赋值

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

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

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

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

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

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

always @(posedge clk) begin
    a = b ;
end
 
always @(posedge clk) begin
    b = a;
end

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

always @(posedge clk) begin
    a <= b ;
end
 
always @(posedge clk) begin
    b <= a;
end

算术运算与逻辑运算

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

在线学习网站

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

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

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

菜鸟教程——Verilog教程

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

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

总结

回答两个问题。

为什么很多特定算法,用 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

参考链接

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

相关文章
|
7月前
|
人工智能 C语言
【C语言必知必会| 第七篇】循环结构入门,这一篇就够了
【C语言必知必会| 第七篇】循环结构入门,这一篇就够了
136 0
|
SQL 算法 Java
【Verilog刷题篇】硬件工程师从0到入门1|基础语法入门
硬件工程师近年来也开始慢慢吃香,校招进大厂年薪总包不下30-40w的人数一大把!而且大厂人数并没有饱和!
【Verilog刷题篇】硬件工程师从0到入门1|基础语法入门
灭霸打个响指的功夫,看懂Verilog多维数组【Verilog高级教程】
灭霸打个响指的功夫,看懂Verilog多维数组【Verilog高级教程】
灭霸打个响指的功夫,看懂Verilog多维数组【Verilog高级教程】
|
数据格式
Verilog语法入门(五)三态门
Verilog HDL是一种硬件描述语言(HDL:Hardware Description Language),以文本形式来描述数字系统硬件的结构和行为的语言,用它可以表示逻辑电路图、逻辑表达式,还可以表示数字逻辑系统所完成的逻辑功能。 Verilog HDL和VHDL是世界上最流行的两种硬件描述语言,都是在20世纪80年代中期开发出来的。前者由Gateway Design Automation公司(该公司于1989年被Cadence公司收购)开发。两种HDL均为IEEE标准。
740 0
|
算法
Verilog语法入门
Verilog HDL是一种硬件描述语言(HDL:Hardware Description Language),以文本形式来描述数字系统硬件的结构和行为的语言,用它可以表示逻辑电路图、逻辑表达式,还可以表示数字逻辑系统所完成的逻辑功能。 Verilog HDL和VHDL是世界上最流行的两种硬件描述语言,都是在20世纪80年代中期开发出来的。前者由Gateway Design Automation公司(该公司于1989年被Cadence公司收购)开发。两种HDL均为IEEE标准。
122 0
|
芯片
HDLBits练习汇总-01-Verilog语言--基础部分
HDLBits练习汇总-01-Verilog语言--基础部分
196 0
HDLBits练习汇总-01-Verilog语言--基础部分
|
存储 安全 编译器
【C++初阶学习】C++入门基础语法【总结】(3)
【C++初阶学习】C++入门基础语法【总结】(3)
【C++初阶学习】C++入门基础语法【总结】(3)
|
编译器 C语言 C++
【C++初阶学习】C++入门基础语法【总结】(1)
【C++初阶学习】C++入门基础语法【总结】(1)
【C++初阶学习】C++入门基础语法【总结】(1)
|
编译器 Linux C语言
【C++初阶学习】C++入门基础语法【总结】(2)
【C++初阶学习】C++入门基础语法【总结】(2)
【C++初阶学习】C++入门基础语法【总结】(2)
|
存储 编译器 C语言
《C语言深度剖析》第一章 关键字详解 p2 C语言从入门到入土(进阶篇)(二)
1.signed、unsigned 1.1整形在内存的存储 1.2signed(有符号数) 1.3unsigned(无符号数) 2 if else 组合 3 各种变量与“零值”进行比较 3.1 bool 变量与"零值"进行比较 3.2 float 变量与"零值"进行比较 3.3 指针变量与“零值”进行比较
《C语言深度剖析》第一章 关键字详解 p2 C语言从入门到入土(进阶篇)(二)