Verilog基础入门-程序员宅基地

技术标签: fpga开发  数字IC入门  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.数据选择器

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

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)
  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译码器

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

对于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
	  if(enable)
	  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;
	      default: Y_n<=8'b11111111;
	    endcase 
	  end
	  else
	  	Y_n<=8'b11111111;
	  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

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

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

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)奇分频

这里以3分频为例:

module fenpin(
    input clk,
    input rst_n,
    output reg  clk_3

    );

reg [2:0] count;

always @(posedge clk or negedge clk or negedge rst_n) 
begin 
    if(~rst_n) 
        count <= 0;
    else if(count==2)
         count <= 0;
     else 
        count <= count+1;
end

always @(posedge clk or negedge  clk or negedge rst_n) 
begin 
    if(~rst_n) 
        clk_3 <= 0;
     else if(count==2)
        clk_3 <= ~clk_3;
end
endmodule

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
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_43433724/article/details/127508892

智能推荐

使用nginx解决浏览器跨域问题_nginx不停的xhr-程序员宅基地

文章浏览阅读1k次。通过使用ajax方法跨域请求是浏览器所不允许的,浏览器出于安全考虑是禁止的。警告信息如下:不过jQuery对跨域问题也有解决方案,使用jsonp的方式解决,方法如下:$.ajax({ async:false, url: 'http://www.mysite.com/demo.do', // 跨域URL ty..._nginx不停的xhr

在 Oracle 中配置 extproc 以访问 ST_Geometry-程序员宅基地

文章浏览阅读2k次。关于在 Oracle 中配置 extproc 以访问 ST_Geometry,也就是我们所说的 使用空间SQL 的方法,官方文档链接如下。http://desktop.arcgis.com/zh-cn/arcmap/latest/manage-data/gdbs-in-oracle/configure-oracle-extproc.htm其实简单总结一下,主要就分为以下几个步骤。..._extproc

Linux C++ gbk转为utf-8_linux c++ gbk->utf8-程序员宅基地

文章浏览阅读1.5w次。linux下没有上面的两个函数,需要使用函数 mbstowcs和wcstombsmbstowcs将多字节编码转换为宽字节编码wcstombs将宽字节编码转换为多字节编码这两个函数,转换过程中受到系统编码类型的影响,需要通过设置来设定转换前和转换后的编码类型。通过函数setlocale进行系统编码的设置。linux下输入命名locale -a查看系统支持的编码_linux c++ gbk->utf8

IMP-00009: 导出文件异常结束-程序员宅基地

文章浏览阅读750次。今天准备从生产库向测试库进行数据导入,结果在imp导入的时候遇到“ IMP-00009:导出文件异常结束” 错误,google一下,发现可能有如下原因导致imp的数据太大,没有写buffer和commit两个数据库字符集不同从低版本exp的dmp文件,向高版本imp导出的dmp文件出错传输dmp文件时,文件损坏解决办法:imp时指定..._imp-00009导出文件异常结束

python程序员需要深入掌握的技能_Python用数据说明程序员需要掌握的技能-程序员宅基地

文章浏览阅读143次。当下是一个大数据的时代,各个行业都离不开数据的支持。因此,网络爬虫就应运而生。网络爬虫当下最为火热的是Python,Python开发爬虫相对简单,而且功能库相当完善,力压众多开发语言。本次教程我们爬取前程无忧的招聘信息来分析Python程序员需要掌握那些编程技术。首先在谷歌浏览器打开前程无忧的首页,按F12打开浏览器的开发者工具。浏览器开发者工具是用于捕捉网站的请求信息,通过分析请求信息可以了解请..._初级python程序员能力要求

Spring @Service生成bean名称的规则(当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致)_@service beanname-程序员宅基地

文章浏览阅读7.6k次,点赞2次,收藏6次。@Service标注的bean,类名:ABDemoService查看源码后发现,原来是经过一个特殊处理:当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致public class AnnotationBeanNameGenerator implements BeanNameGenerator { private static final String C..._@service beanname

随便推点

二叉树的各种创建方法_二叉树的建立-程序员宅基地

文章浏览阅读6.9w次,点赞73次,收藏463次。1.前序创建#include&lt;stdio.h&gt;#include&lt;string.h&gt;#include&lt;stdlib.h&gt;#include&lt;malloc.h&gt;#include&lt;iostream&gt;#include&lt;stack&gt;#include&lt;queue&gt;using namespace std;typed_二叉树的建立

解决asp.net导出excel时中文文件名乱码_asp.net utf8 导出中文字符乱码-程序员宅基地

文章浏览阅读7.1k次。在Asp.net上使用Excel导出功能,如果文件名出现中文,便会以乱码视之。 解决方法: fileName = HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8);_asp.net utf8 导出中文字符乱码

笔记-编译原理-实验一-词法分析器设计_对pl/0作以下修改扩充。增加单词-程序员宅基地

文章浏览阅读2.1k次,点赞4次,收藏23次。第一次实验 词法分析实验报告设计思想词法分析的主要任务是根据文法的词汇表以及对应约定的编码进行一定的识别,找出文件中所有的合法的单词,并给出一定的信息作为最后的结果,用于后续语法分析程序的使用;本实验针对 PL/0 语言 的文法、词汇表编写一个词法分析程序,对于每个单词根据词汇表输出: (单词种类, 单词的值) 二元对。词汇表:种别编码单词符号助记符0beginb..._对pl/0作以下修改扩充。增加单词

android adb shell 权限,android adb shell权限被拒绝-程序员宅基地

文章浏览阅读773次。我在使用adb.exe时遇到了麻烦.我想使用与bash相同的adb.exe shell提示符,所以我决定更改默认的bash二进制文件(当然二进制文件是交叉编译的,一切都很完美)更改bash二进制文件遵循以下顺序> adb remount> adb push bash / system / bin /> adb shell> cd / system / bin> chm..._adb shell mv 权限

投影仪-相机标定_相机-投影仪标定-程序员宅基地

文章浏览阅读6.8k次,点赞12次,收藏125次。1. 单目相机标定引言相机标定已经研究多年,标定的算法可以分为基于摄影测量的标定和自标定。其中,应用最为广泛的还是张正友标定法。这是一种简单灵活、高鲁棒性、低成本的相机标定算法。仅需要一台相机和一块平面标定板构建相机标定系统,在标定过程中,相机拍摄多个角度下(至少两个角度,推荐10~20个角度)的标定板图像(相机和标定板都可以移动),即可对相机的内外参数进行标定。下面介绍张氏标定法(以下也这么称呼)的原理。原理相机模型和单应矩阵相机标定,就是对相机的内外参数进行计算的过程,从而得到物体到图像的投影_相机-投影仪标定

Wayland架构、渲染、硬件支持-程序员宅基地

文章浏览阅读2.2k次。文章目录Wayland 架构Wayland 渲染Wayland的 硬件支持简 述: 翻译一篇关于和 wayland 有关的技术文章, 其英文标题为Wayland Architecture .Wayland 架构若是想要更好的理解 Wayland 架构及其与 X (X11 or X Window System) 结构;一种很好的方法是将事件从输入设备就开始跟踪, 查看期间所有的屏幕上出现的变化。这就是我们现在对 X 的理解。 内核是从一个输入设备中获取一个事件,并通过 evdev 输入_wayland

推荐文章

热门文章

相关标签