Verilog基础入门
Verilog简介
- 一.Verilog语法知识简介
- 1.模块结构
- (1)模块声明
- (2)端口定义
- (3)信号类型声明
- (4)逻辑功能定义
- 2.行为语句
- (1)过程语句
- (2)块语句
- (3)赋值语句
- (4)条件语句
- 3.运算量与运算符
- (1)条件运算符“ ?:”
- (2)拼接运算符“{ }”
- 二、verilog实例
- 1.表决器电路
- 2.数据选择器
- 3.3-8译码器
- 4.加法器
- 5.边沿D触发器
- (1)同步复位的D触发器
- (2)异步复位的D触发器
- 6.计数器
- 7.分频器
- (1)偶分频
- (2)奇分频
- 8.序列检测器
本文撰写的参考书目是陈彦辉老师的《数字逻辑电路基础》
一.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@(*)
beginD <= 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)
beginA = 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)
beginb <= 1'b1;//1'b表示一位二进制,再例如4’b 1111表示四位二进制数1111a <= b;
end
假设初始时b为0,当触发后b和a同时分别赋值1和0;也就是说a赋值的是b之前的值。
●阻塞赋值(=)
always@(posedge clk)
beginb = 1'b1;//1'b表示一位二进制,再例如4’b 1111表示四位二进制数1111a = 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@(*)
beginif(A == B)C <= 1;elseC <= 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@(*)
begincase(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所以定义位宽为2bitwire[1:0]count;assign count = A + B +C;//统计三个变量中1的个数always@(*)beginif(count >= 2)F <= 1;elseF <= 0;endendmodule
2.数据选择器
这里我们设计一个四选一选择器,对应地址与数据选择关系如下:
addr | data_out |
---|---|
2’b00 | D0 |
2’b01 | D1 |
2’b10 | D2 |
2’b11 | D3 |
显然采用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)begincase(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)begincase(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 endendmodule
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)
beginif(!rst_n) //rst_n信号为0时进行复位Q <= 0;elseQ <= D;
end
(2)异步复位的D触发器
always@(posedge clk or negedge rst_n) //在rst_n的下降沿也可以触发
beginif(!rst_n) //rst_n信号为0时进行复位Q <= 0;elseQ <= 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)beginif(count == 4'b1111)count <= 0;elsecount <= count + 1;endendmodule
其实这里也可以不需要加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)beginif(count == 2)count <= 0;elsecount <= count + 1;endassign clk_new = (count == 2)? (~count):count;
endmodule
(2)奇分频
奇分频的实现稍微多一点步骤,例如实现三分频,由上面可知,如果直接采用计数器实现只能实现偶分频,因为每两个posedge是一个整周期,而3分频意味着一个半时钟周期为新时钟的高电平或低电平,那我们采取一种方法获得那“半个周期”,假设我们先实现两个二分频,第一个二分频采用上升沿计数,第二个采用下降沿计数,这样两个波形时间就会相差半个周期,我们再将两次二分频得到的时钟进行或便得到了三分频。
reg [1:0] count1;
reg [1:0] count2;
always@(posedge clk)
beginclk1 = ~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基础入门相关推荐
- 【Modelsim零基础入门】verilog仿真程序:1-bit A+B
关于如何新建项目,请移步:[Modelsim零基础入门]新建项目+运行第一个verilog仿真程序:一位加法器 ↓ 第一个自己写的verilog程序以及对应的测试程序 计算A+B 源程序 文件名称:s ...
- [SugerTangYL] Verilog 语言入门(零基础视角)
目录 前言 一.示例 1.一位全加器功能及电路图 2.一位全加器Verilog代码 (1)模块定义声明 (2)变量声明定义 (3)子模块调用 二.模块声明定义模板 总结 前言 hi guys,这是我第 ...
- 【Verilog零基础入门-边看边练】学习笔记——第三讲 组合逻辑代码设计和仿真(补码转换和七段译码逻辑设计)(二)
二.七段译码逻辑设计 所需软件 Verilog编程软件:Lattice Diamond(3.11.0.396.4_Diamond_x64) Verilog仿真软件:ModelSim SE-64 10. ...
- FPGA基础入门篇(四) 边沿检测电路
FPGA基础入门篇(四)--边沿检测电路 一.边沿检测 边沿检测,就是检测输入信号,或者FPGA内部逻辑信号的跳变,即上升沿或者下降沿的检测.在检测到所需要的边沿后产生一个高电平的脉冲.这在FPGA电 ...
- 《牛客刷verilog》Part I Verilog快速入门
前言 之前刷过HDLbits上面的题目,点击链接可以查看详细笔记:verilog练习:hdlbits网站系列完结! 最近又想刷一下牛客上面的题目,可以点击链接与小编一起刷题:牛客刷题 小编不才,文中如 ...
- FPGA基础入门【1】Vivado官方免费版安装
本人自本科大二开始接触FPGA相关知识,现已将近五年,从这篇开始将从比较基础的角度讲述如何一步步了解FPGA.我相信动手一步步做下去是从零开始学习知识的最快方法,因此不会从最基础开始讲,而是在碰到相应 ...
- FPGA基础入门【3】Blink逻辑及仿真
从这一篇开始正式介绍FPGA中的硬件逻辑,第一个目标就是从零开始在NEXYS 4开发板上实现闪烁LED. 软件编程中hello world是初学语言中实现的第一个功能,而硬件编程中blink是同等的地 ...
- FPGA基础入门【8】开发板外部存储器SPI flash访问
前两篇教程利用数码管project介绍了chipscope和各种烧写开发板的方式,这篇开始继续探索开发板,这次关注外置存储器的控制,外置指的是芯片外部,不是开发板外部.板子上的外置存储器有DDR2和S ...
- Verilog语言入门——边学边练
第一次写博客,记录北京交通大学李金城的Verilog语言入门 参考复制了一些代码,其他代码均为自敲 Verilog语言入门--边学边练 前言 一.基本逻辑门代码设计与仿真 1.1反相器 1.2与非门 ...
最新文章
- xaml中的布局面板
- Django+Vue前后端分离项目的部署
- 美国团购网站Groupon的盈利模式
- python读取两个csv文件后比较_python – 读取两个csv文件并比较每一行.如果行匹配打印两行,如果不相似则打印无效...
- 这种事情干不得!微信已动手处理4.5万个公众号
- javascript监测form是否提交
- this全面解析(二)
- C++二叉树遍历递归算法
- linux安装桌面键盘,如何在 Linux 中使用屏幕键盘
- 保存电脑上的屏保图片
- 手机声音同步到另一部手机_教你一招,手机耳机音量一键同步!
- 认识PASCAL VOC数据集(目标检测)
- 2022年系统架构设计师考试大纲
- HTML5期末大作业:在线电影网站设计——电影我不是药神响应式页播(4页) HTML+CSS+JavaScript HTML+CSS+JS网页设计期末课程大作业 web前端开发技术 web课程设计
- 华为 应用隐藏大师 计算机,【分享】应用隐藏大师v6.3.1~一键隐藏不想让别人看到的软件...
- 菜鸟学数据库——大话 char、varchar、 nchar、nvarchar之间剪不断理还乱的关系
- JS中定义函数的几种方法
- 将CSS文件转换为标准格式
- [附源码]Python计算机毕业设计东北鹿产品售卖网站Django(程序+LW)
- 检测特殊字符的正则表达式