草庐IT

Verilog基础入门

呜呼啦呼_l 2023-04-19 原文

Verilog简介

本文撰写的参考书目是陈彦辉老师的《数字逻辑电路基础》

一.Verilog语法知识简介

1.模块结构

   Verilog程序的最基本设计单元是“模块”,模块从关键字**module**开始,到**endmodule**结束,其中每条语句以";"分隔。

一个完整的模块由以下四个部分组成:

模块定义行:定义模块名称、输入输出参数列表;

说明部分:定义不同的项,其中包括端口类型(input输入、output输出和inout双向端口)、寄存器reg)、连线(wire)、参数(parameter)、函数(function)和任务(task);

描述体部分:这一部分描述模块的行为和功能;

结束行:以endmodule结束

下面以一个例子进行一个大致的认知,暂且先不管语句是什么意思,只需要理解结构即可。

//模块定义行,test为模块名,括号内为参数列表(这里可以不说明其输入输出类型)
module test(A,B,C,D,F1,F2); 

//说明部分,将A,B,C,D定义为输入,F1、F2为输出,wire和reg类型暂且先不管
  input  A,B,C,D;
  output F1,F2;
  wire   F1;
  reg    F2;

//描述体部分,F2是F1经过一个D触发器,将数据延迟一拍(在D的上升沿才将F1赋值给F2)
always@(posedge D)
  F2 <= F1;
  
//描述体部分,F1= AB + (~A)(~C)
assign F1 = (A & B)|(~A & ~C);

//结束行
endmodule
  

(1)模块声明

格式如下:

module 模块名端口名1端口名2,…,端口名n);

●注意模块名只能下划线字母****开头!!!

(2)端口定义

输入端口定义为

input 端口名1端口名2,…,端口名n

输出端口定义为

output 端口名1端口名2,…,端口名n;

●双向端口(不常用)

inout 端口名1,端口名2,…,端口名n

注意:定义完要有分号;

(3)信号类型声明

最常用的类型有wire(连线)和reg(寄存器);

●wire类型表示直通,一条连线,只要输入有变化输出马上无条件反映;

●reg类型表示一定要有触发,输出才会反应输入;

声明类型时也可以声明其位宽,如reg [2:0] A,其表示A为3bit位宽的寄存器。

不声明类型时默认为wire类型。

其实初学的时候比较难分清什么时候用wire,什么时候用reg,在这里先给大家说一下不严谨的判别方法,在设计文件里:

●输入一般都作为wire类型,因为他是模块外部驱动的传入到模块内部;

●输出既可以为wire也可以为reg,当希望某一输入或变量一变化输出立刻变化则采用assign语句赋值时,如assign A = B & C;这时将A定义为wire类型;当这个变量在过程块语句中被赋值时,将其定义为reg类型,可参考上面给出的例子。

可以这么理解,**assign(持续赋值语句)**赋值,该数据会一直被驱动,输入一变输出立刻变化,而过程块语句赋值(always过程块)往往需要等到时钟或某一变量的跳变等才能变化,但他需要把上一时刻的状态保存起来,因此需要存放在寄存器里。(但是这样理解不够严谨)

(4)逻辑功能定义

通常采用assign持续赋值语句always过程赋值块调用元件(元件例化,可以理解成C语言中调用函数,但不一样,verilog自身也有function)等方式构成逻辑功能。

●assign是持续赋值语句,只能用于对wire(连线)类型变量的赋值;

2.行为语句

(1)过程语句

always语句为过程语句,其表达式为

always@(<触发条件列表>)

触发条件列表又称为敏感信号表达式,触发条件写在敏感信号表达式之中,当触发条件满足时,其后的语句才能被执行,触发条件列表中的多个条件之间采用“or”来连接。

●当初发条件列表为“*****”时,只要有输入变量发生变化就触发条件,如下,只要A,B,C发生变化,就会执行always块中的语句。

input A,B,C;
output D;
reg D;
always@(*)
begin
  D <= A & B & C;
end

例子如下:

always@(A)                        //A发生变化就执行后面的语句
always@(A or B)                   //A或B发生变化就执行后面语句
always@(posedge A)                //在A的上升沿时执行后面语句
always@(negedge B)              //在B的下降沿时执行后面语句
always@(posedge A or negedge B)  //在A的上升沿或B的下降沿执行后面的语句

(2)块语句

在begin-end串行块中,语句按照串行方式顺序执行。

举例如下,想要在clk的上升沿处实现A=B,C=D,E=F功能时,

●若不采用begin-end需要如下代码

always@(posedge clk)
  A = B;
always@(posedge clk)
  C = D;
always@(posedge clk)
  E = F; 

●采用begin-end时

always@(posedge clk)
begin
  A = B;
  C = D;
  E = F;
end

需要注意的是,在一个always块语句中,各语句之间是串行的关系;但多个always块语句是并行的,那上面的例子来说,不采用begin-end时,A=B,C=D,E=F三条语句并行执行,A,C,E同时获得值;采用begin-end时,A=B执行完才执行C=D,以此类推。

(3)赋值语句

①用assign持续赋值

该语句一般用于组合逻辑的赋值,成为连续赋值;例如F=AB + AC;

assign F = (A & B) |(A & C);

②用always过程赋值

当过程赋值较多时,通常采用begin-end构成串行块,在该块中可以对多个变量进行赋值操作;在过程赋值中,只有寄存器类型的变量才能被赋值;

赋值有非阻塞赋值阻塞赋值

非阻塞赋值(**** < =****)

always@(posedge clk)
begin
  b <= 1'b1;//1'b表示一位二进制,再例如4’b 1111表示四位二进制数1111
  a <= b;
end

假设初始时b为0,当触发后b和a同时分别赋值1和0;也就是说a赋值的是b之前的值。

●阻塞赋值=

always@(posedge clk)
begin
  b = 1'b1;//1'b表示一位二进制,再例如4’b 1111表示四位二进制数1111
  a = b;
end

假设初始时b为0,当执行b=1后,b变为1,然后再执行a=b,a赋值为1。

(4)条件语句

两种条件语句if-else语句和case语句,他们都是顺序语句且只能****放在always块内。

①if-else语句

module test(A,B,C);
input A,B;
output C;
reg C;

always@(*)
begin
    if(A == B)
      C <= 1;
    else
      C <= 0;
endmodule

②case语句

语句格式:

case(条件表达式)
  值1 :语句1;
  值2 : 语句2;
  ...
  值n : 语句n;
  default : 语句n+1;
  endcase

例:当A为01时,输出B为0,A为00,10,11时为1。

module test(A,B);
input [1:0] A;
output B;
reg B;

always@(*)
begin
   case(A)
     2'b00 : B <= 1;
     2'b01 : B <= 0;
     2'b10 : B <= 1;
     2'b11 : B <= 1;
     default :B <= 0;
   endcase
end
endmodule

●关于casex和casez的用法可以参考课本P57以及P59下方的例题

3.运算量与运算符

这部分的知识建议参考课本P54,内容不多,只需要记住常用的几种即可,如parameter的使用、变量的声明、运算符(算数运算符、位运算符、逻辑运算符、关系运算符、移位运算符)即可。

●这里特别提一下逻辑运算符和位运算符,位运算符就是按位进行操作,如1011**** &**** 0111,就是对应位进行相与,结果就是0011;而采用逻辑运算符时,只要不为0即视作1,如1011 ****&& ****0101那结果就为1。

(1)条件运算符“ ?:”

举个例子,assign a = (b==3) ? 4 : 5;

意思就是如果b=3,那就将4赋值给a,如果不等于就将5赋值给a;

(2)拼接运算符“{ }”

假设a=3’b011,b=3’b101,执行如下语句

c = { a , b };

那c的结果就为011101,但前提是c这个变量的位宽必须要大于6,如果他只有4bit位宽,那结果就变为1101。

二、verilog实例

1.表决器电路

表决器的具体分析这里不再给出,功能就是对于三输入变量,当输入变量中至少有两个为1时输出就为1,

因此我们统计一下为1的个数,然后将它和2比较得到输出。

module biaojueqi(A,B,C,F);
  input A,B,C;
  output F;
  reg F;
  //定义一个变量,统计1的个数,因为1的个数最大值为3所以定义位宽为2bit
  wire[1:0]count;
  assign count = A + B +C;//统计三个变量中1的个数
  
  always@(*)
  begin
    if(count >= 2)
      F <= 1;
    else
      F <= 0;
  end
 endmodule

2.数据选择器

这里我们设计一个四选一选择器,对应地址与数据选择关系如下:

addrdata_out
2’b00D0
2’b01D1
2’b10D2
2’b11D3

显然采用case语句很方便,假设输入数据位宽是8bit,verilog代码如下:

module mux_4_1(addr,D0,D1,D2,D3,data_out);
  input [1:0] addr;
  input [7:0] D0;
  input [7:0] D1;
  input [7:0] D2;
  input [7:0] D3;
  output [7:0] data_out;

  reg [7:0] data_out;//因为要使用case,在always语句中赋值所以定义为reg类型

  always@(addr)
  begin
    case(addr)
      2'b00 : data_out <= D0;
      2'b01 : data_out <= D1;
      2'b10 : data_out <= D2;
      2'b11 : data_out <= D3;
    endcase 
  end
endmodule


3.3-8译码器

对于3-8译码器,显然也是采用case语句很方便,观察真值表我们可以发现首先只有在E1、E2、E3均为1时才能工作,然后经过译码后的输出为0,其余输出为1,verilog代码如下:

module decoder_8(E1,E2_n,E3_n,A,Y_n);
	 input E1,E2_n,E3_n;
	  input [2:0] A;
	  output [7:0] Y_n;
	
	  reg [7:0] Y_n;
	
	  //首先判断是否使能,只有当E1,E2_n,E3_n为1,0,0时才使能
	  wire enable;
	  assign enable = E1 & (~E2_n) & (~E3_n);
	
	  always@(enable or A)
	  begin
	    case(A)
	      3'b000 : Y_n<=8'b11111110;
	      3'b001 : Y_n<=8'b11111101;
	      3'b010 : Y_n<=8'b11111011;
	      3'b011 : Y_n<=8'b11110111;
	      3'b100 : Y_n<=8'b11101111;
	      3'b101 : Y_n<=8'b11011111;
	      3'b110 : Y_n<=8'b10111111;
	      3'b111 : Y_n<=8'b01111111;
	    endcase 
	  end

endmodule 

4.加法器

对于一位加法器来说,有两个三个输入数据,分别是两个加数和一个低位对本位的进位,有两个输出,分别为本位和以及本位对高位的进位。

module add(A,B,Cin,S,Cout);
  input A,B,Cin;
  output S,Cout;
  assign {S,Cout} = A+B+Cin;//S和cout拼接后,cout代表1bitS代表0bit,如果有进位那么会给Cout赋1
endmodule

那对于两位数的相加呢?其实我们可以把他拆为两个一位数相加,然后用上面的模块直接实现一位数相加。

那这里涉及到模块的例化(简单理解成函数的调用),下面给出例化的例子。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SOms0pEy-1666665526782)(https://secure2.wostatic.cn/static/nKErGZKTjiS5eMGPyj57RG/image.png?auth_key=1666665513-ra5Kb96x54eAtVLtKikBbX-0-c069a4096267d1977d04e8df29c09136)]

add为要例化的模块名,inst_add为例化给他指定的名字(自己随便定义,只要符合起名字的规则),

.Cout为使用模块的信号名,括号里的Cout为调用时传递给它的信号名,两个可以不一样。

两位数加法verilog

module add_2(A,B,Cin,Cout,S);
  input [1:0] A;
  input [1:0] B;
  input Cin;
  output Cout;
  output [2:0] S;

  wire cout1;

  add inst_add1 (.A(A[0]), .B(B[0]), .Cin(Cin), .S(S[0]), .Cout(cout1));
  add inst_add2 (.A(A[1]), .B(B[1]), .Cin(cout1), .S(S[1]), .Cout(Cout));
endmodule

第一次调用模块的输出Cout作为第二次调用模块的输入Cin

5.边沿D触发器

同步复位是指,复位信号只有在时钟上升沿或下降沿处才进行判断,如该D触发器是低电平复为有效,在复位信号变低后复位信号不会立刻发挥作用,而是等到下一个时钟CP的上升沿或下降沿才起作用。

而异步复位是指,当复位信号变低时,在复位信号的下降沿复位信号便开始发挥作用(这里说下降沿时默认复位信号低电平有效,若是高电平有效那就是在复位信号的上升沿判断)。

可以参考如下文章:
从零开始的FPGA学习5-同步复位D触发器、异步复位D触发器

(1)同步复位的D触发器

always@(posedge clk)
begin
  if(!rst_n)    //rst_n信号为0时进行复位
    Q <= 0;
  else
    Q <= D;
end

(2)异步复位的D触发器

always@(posedge clk or negedge rst_n)  //在rst_n的下降沿也可以触发
begin
  if(!rst_n)    //rst_n信号为0时进行复位
    Q <= 0;
  else
    Q <= D;
end

6.计数器

实现一个计数器,计数16次再从0开始(这其实就是模16计数器)

module count(clk,count);
	input clk;
	output [3:0] count;
	reg [3:0] count;     //也可以将上下两行用一行语句完成 output reg [3:0] count;
	
	always@(posedge clk)
	begin
	  if(count == 4'b1111)
	    count <= 0;
	  else
	    count <= count + 1;
	end

endmodule

其实这里也可以不需要加if-else判断,因为count为4bit最大为4’b1111,如果直接+1会变为0,因为不考虑尾款情况下加1的结果为5’b10000,但因为只有4bit所以只取低4位。

7.分频器

分频器有奇分频和偶分频,先说偶分频

(1)偶分频

首先我们考虑两分频,两分频意思即是将时钟周期变为原来的二倍,也就是说我们可以将原来一个时钟周期内高电平和低电平的时间全部变为高电平或者低电平,那我们可以采取每次在原时钟上升沿到来的同时,对新时钟的值取一个反即可。

always@(posedge clk)
  clk_new = ~clk;

那对于六分频八分频这样高分频的情况呢,我们可以利用计数器来实现,比如对于6分频,我们需要将三个时钟周期变为新时钟的半个周期(为高电平或者为低电平),那我们采用计数器。

module div(clk,clk_new);
	input clk;
	output clk_new;
	
	reg [1:0] count;
	wire clk_new;
	
	always@(posedge clk)
	begin
	  if(count == 2)
	    count <= 0;
	  else
	    count <= count + 1;
	end
	
	assign clk_new = (count == 2)? (~count):count;
endmodule


(2)奇分频

奇分频的实现稍微多一点步骤,例如实现三分频,由上面可知,如果直接采用计数器实现只能实现偶分频,因为每两个posedge是一个整周期,而3分频意味着一个半时钟周期为新时钟的高电平或低电平,那我们采取一种方法获得那“半个周期”,假设我们先实现两个二分频,第一个二分频采用上升沿计数,第二个采用下降沿计数,这样两个波形时间就会相差半个周期,我们再将两次二分频得到的时钟进行或便得到了三分频。

reg [1:0] count1;
reg [1:0] count2;
always@(posedge clk)
begin
  clk1 = ~clk1;
end

8.序列检测器

采用移位寄存器实现“11010110”序列检测

module detect(clk,x,y);
	input clk;
	input x;
	output y;
	
	reg[7:0] ram;
	always@(posedge)
	  ram <= {ram[6:0],x};
	
	assign y = (ram == 8'b11010110)? 1 : 0;
endmodule

有关Verilog基础入门的更多相关文章

  1. postman接口测试工具-基础使用教程 - 2

    1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,

  2. 软件测试基础 - 2

    Ⅰ软件测试基础一、软件测试基础理论1、软件测试的必要性所有的产品或者服务上线都需要测试2、测试的发展过程3、什么是软件测试找bug,发现缺陷4、测试的定义使用人工或自动的手段来运行或者测试某个系统的过程。目的在于检测它是否满足规定的需求。弄清预期结果和实际结果的差别。5、测试的目的以最小的人力、物力和时间找出软件中潜在的错误和缺陷6、测试的原则28原则:20%的主要功能要重点测(eg:支付宝的支付功能,其他功能都是次要的)80%的错误存在于20%的代码中7、测试标准8、测试的基本要求功能测试性能测试安全性测试兼容性测试易用性测试外观界面测试可靠性测试二、质量模型衡量一个优秀软件的维度①功能性功

  3. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  4. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  5. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  6. ES基础入门 - 2

    ES一、简介1、ElasticStackES技术栈:ElasticSearch:存数据+搜索;QL;Kibana:Web可视化平台,分析。LogStash:日志收集,Log4j:产生日志;log.info(xxx)。。。。使用场景:metrics:指标监控…2、基本概念Index(索引)动词:保存(插入)名词:类似MySQL数据库,给数据Type(类型)已废弃,以前类似MySQL的表现在用索引对数据分类Document(文档)真正要保存的一个JSON数据{name:"tcx"}二、入门实战{"name":"DESKTOP-1TSVGKG","cluster_name":"elasticsear

  7. 【网络】-- 网络基础 - 2

    (本文是网络的宏观的概念铺垫)目录计算机网络背景网络发展认识"协议"网络协议初识协议分层OSI七层模型TCP/IP五层(或四层)模型报头以太网碰撞路由器IP地址和MAC地址IP地址与MAC地址总结IP地址MAC地址计算机网络背景网络发展        是最开始先有的计算机,计算机后来因为多项技术的水平升高,逐渐的计算机变的小型化、高效化。后来因为计算机其本身的计算能力比较的快速:独立模式:计算机之间相互独立。    如:有三个人,每个人做的不同的事物,但是是需要协作的完成。    而这三个人所做的事是需要进行协作的,然而刚开始因为每一台计算机之间都是互相独立的。所以前面的人处理完了就需要将数据

  8. 区块链入门教程(6)--WeBASE-Front节点前置服务安装 - 2

    文章目录1.任务背景2.任务目标3.相关知识点4.任务实操4.1安装配置JDK4.2启动FISCOBCOS4.3下载解压WeBASE-Front4.4拷贝sdk证书文件4.5启动节点4.6访问节点4.7检查运行状态5.任务总结1.任务背景FISCOBCOS其实是有控制台管理工具,用来对区块链系统进行各种管理操作。但是对于初学者来说,还是可视化界面更友好,本节就来介绍WeBASE管理平台,这是一款微众银行开源的自研区块链中间件平台,可以降低区块链使用的门槛,大幅提高区块链应用的开发效率。微众银行是腾讯牵头设立的民营银行,在国内民营银行里还是比较出名的。微众银行参与FISCOBCOS生态建设,一定

  9. Verilog使用inout信号的方法 - 2

    目录一、inout在设计文件中的使用方法1.1、inout的第一种使用方法1.2、inout实现的第二种使用方法1.3、inout使用总结 二、inout在仿真测试中的使用方法一、inout在设计文件中的使用方法在FPGA的设计过程中,有时候会遇到双向信号(既能作为输出,也能作为输入的信号叫双向信号)。比如,IIC总线中的SDA信号就是一个双向信号,QSPIFlash的四线操作的时候四根信号线均为双向信号。在Verilog中用关键字inout定义双向信号,这里总结一下双向信号的处理方法。1.1、inout的第一种使用方法  实际上,双向信号的本质是由一个三态门组成的,三态门可以输出高电平,低电

  10. Tcl脚本入门笔记详解(一) - 2

    TCL脚本语言简介•TCL(ToolCommandLanguage)是一种解释执行的脚本语言(ScriptingLanguage),它提供了通用的编程能力:支持变量、过程和控制结构;同时TCL还拥有一个功能强大的固有的核心命令集。TCL经常被用于快速原型开发,脚本编程,GUI和测试等方面。•实际上包含了两个部分:一个语言和一个库。首先,Tcl是一种简单的脚本语言,主要使用于发布命令给一些互交程序如文本编辑器、调试器和shell。由于TCL的解释器是用C\C++语言的过程库实现的,因此在某种意义上我们又可以把TCL看作C库,这个库中有丰富的用于扩展TCL命令的C\C++过程和函数,所以,Tcl是

随机推荐