技术标签: fpga开发 数字IC入门 verilog初学习
本文撰写的参考书目是陈彦辉老师的《数字逻辑电路基础》
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
格式如下:
module 模块名(端口名1,端口名2,…,端口名n);
●注意模块名只能以下划线和字母****开头!!!
●输入端口定义为
input 端口名1,端口名2,…,端口名n;
●输出端口定义为
output 端口名1,端口名2,…,端口名n;
●双向端口(不常用)
inout 端口名1,端口名2,…,端口名n
注意:定义完要有分号;
最常用的类型有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过程块)往往需要等到时钟或某一变量的跳变等才能变化,但他需要把上一时刻的状态保存起来,因此需要存放在寄存器里。(但是这样理解不够严谨)
通常采用assign持续赋值语句、always过程赋值块和调用元件(元件例化,可以理解成C语言中调用函数,但不一样,verilog自身也有function)等方式构成逻辑功能。
●assign是持续赋值语句,只能用于对wire(连线)类型变量的赋值;
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的下降沿执行后面的语句
在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,以此类推。
①用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。
两种条件语句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下方的例题
这部分的知识建议参考课本P54,内容不多,只需要记住常用的几种即可,如parameter的使用、变量的声明、运算符(算数运算符、位运算符、逻辑运算符、关系运算符、移位运算符)即可。
●这里特别提一下逻辑运算符和位运算符,位运算符就是按位进行操作,如1011**** &**** 0111,就是对应位进行相与,结果就是0011;而采用逻辑运算符时,只要不为0即视作1,如1011 ****&& ****0101那结果就为1。
举个例子,assign a = (b==3) ? 4 : 5;
意思就是如果b=3,那就将4赋值给a,如果不等于就将5赋值给a;
假设a=3’b011,b=3’b101,执行如下语句
c = { a , b };
那c的结果就为011101,但前提是c这个变量的位宽必须要大于6,如果他只有4bit位宽,那结果就变为1101。
表决器的具体分析这里不再给出,功能就是对于三输入变量,当输入变量中至少有两个为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
这里我们设计一个四选一选择器,对应地址与数据选择关系如下:
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-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
对于一位加法器来说,有两个三个输入数据,分别是两个加数和一个低位对本位的进位,有两个输出,分别为本位和以及本位对高位的进位。
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
同步复位是指,复位信号只有在时钟上升沿或下降沿处才进行判断,如该D触发器是低电平复为有效,在复位信号变低后复位信号不会立刻发挥作用,而是等到下一个时钟CP的上升沿或下降沿才起作用。
而异步复位是指,当复位信号变低时,在复位信号的下降沿复位信号便开始发挥作用(这里说下降沿时默认复位信号低电平有效,若是高电平有效那就是在复位信号的上升沿判断)。
可以参考如下文章:
从零开始的FPGA学习5-同步复位D触发器、异步复位D触发器
always@(posedge clk)
begin
if(!rst_n) //rst_n信号为0时进行复位
Q <= 0;
else
Q <= D;
end
always@(posedge clk or negedge rst_n) //在rst_n的下降沿也可以触发
begin
if(!rst_n) //rst_n信号为0时进行复位
Q <= 0;
else
Q <= D;
end
实现一个计数器,计数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位。
分频器有奇分频和偶分频,先说偶分频
首先我们考虑两分频,两分频意思即是将时钟周期变为原来的二倍,也就是说我们可以将原来一个时钟周期内高电平和低电平的时间全部变为高电平或者低电平,那我们可以采取每次在原时钟上升沿到来的同时,对新时钟的值取一个反即可。
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
这里以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
采用移位寄存器实现“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
本篇文章介绍了十进制与其他进制之间的相互转换方法,包括十进制转换为二进制、八进制、十六进制,以及其他进制转换为十进制的方法。同时还提供了一些具体的转换示例。
文章浏览阅读4k次,点赞11次,收藏26次。1.概念:数据:Data,是客观事物的符号表示,是所有能输入到计算机中并被计算机程序处理的符号的总称。数据元素:Data Element,是数据的基本单位,在计算机中常作为一个整体进行考虑和处理,用于完整的描述一个对象。数据项:Data Item,是组成数据元素的、有独立含义的、不可分割的最小单位。数据对象:Data Object,是性质相同的数据元素的集合,是数据的一个子集。数据结构:Data Structure,是相互之间存在一种或多种特定关系的数据元素的集合。逻辑结构:从具体问题抽象出来的_简述逻辑结构的四种基本关系并画出它们的关系图
文章浏览阅读887次。///////////////////////////////////////变量语法使用以下语法规则声明 HLSL 变量。[Storage_Class] [Type_Modifier]Type Name[Index] [: Semantic] [: Packoffset] [: Register]; [Annotations] [= Initial_Value]参数存储 _班级可选的存储类修饰符,它们为编译器提示指定变量范围和生存期;可以按任意顺序指定修饰符。值 说明._hlsl compute shader
文章浏览阅读4.3k次,点赞4次,收藏16次。一文彻底搞懂 Alertmanager 的告警抑制与静默。_alertmanager告警
文章浏览阅读5.2k次,点赞8次,收藏8次。什么是性能调优?性能调优就是对计算机硬件、操作系统和应用有相当深入的了解,调节三者之间的关系,实现整个系统(包括硬件、操作系统、应用)的性能最大化,并能不断的满足现有的业务需求。性能优化的目的流畅(解决:卡顿)稳定(解决:内存溢出、崩溃)低耗损(解决:耗电快、流量大、网络慢)小安装包(解决:APK过大)性能优化原则:先优化瓶颈问题;方案简单,尽量不引入更多复杂性,尽量不降低业务体验;满足系统性能要求即可,不引入新的bug。为什么需要性能调优?为了获得更好的系统性能(就是你现_论软件的性能优化设计
文章浏览阅读1.3w次,点赞2次,收藏26次。例1: 批量 查询部门号为 "10" 号的并把它们打印出来 . DECLARE TYPE emp_table_type IS TABLE OF my_emp%ROWTYPE INDEX BY BINARY_INTEGER; v_emp_table emp_table_type; BEGIN SELECT * BULK COLLECT INTO v_emp__bulk collect into 写表
文章浏览阅读2.9k次,点赞8次,收藏17次。本文是对上一篇文章《逻辑地址、线性地址、物理地址的关系以及段寄存器在不同位数CPU中的用途演变以及GDT LDT PGD PT的关系》的补充。一. 寻址方式:实地址模式和保护地址模式我们知道,内存寻址模式在早期是采用的实地址模式(intel 80286之前),后面发展到了保护模式(80286开始)。在8086的时候,也就是16位cpu的时候,CPU配备了4个16位段寄存器(CS代码段寄存器..._ldtr
文章浏览阅读1.9k次。1、报错场景:在jsp中使用el表达式时,出现JasperException异常。2、报错信息:org.apache.jasper.JasperException: 在 [45] 行处理 [/register.jsp] 时发生异常42: <td style="width:40%">43: <input type="text" clas..._org.apache.jasper.jasperexception: 在 [31] 行处理 [/register1.jsp] 时发生异
文章浏览阅读9.9k次,点赞14次,收藏94次。html零基础01_html5教程
文章浏览阅读2.1w次,点赞5次,收藏6次。1、第一种方式/**function layer_show(title,url,data,w,h){if (title == null || title == ‘’) {title=false;};if (url == null || url == ‘’) {url=“404.html”;};if (w == null || w == ‘’) {w=800;};if (h ..._layer.open data
文章浏览阅读2.1w次,点赞9次,收藏7次。这是来自一位学长的 (业务主管综合面)4.29下午2:10分,全程20min学长的视角:主管也是真的很nice!我每次回答问题后都给我说谢谢,搞得我都不好意思了,整个过程非常随和,完全没有架子,很耐心的给我解释问题,最后退出还说非常感谢面试华为,体验超好,面完五分钟官网刷新通过,十分钟短信通过。总结一下吧,总的来说,我这次华子的面试准备了很多东西,但是基本没问…整个过程体验非常好,不会让你尴尬的,面试官都大赞!给大家分享面筋,希望对还没面试的小伙伴提供参考,不过目前进了池子,得等很久才能出结果,许愿offe_华为实习业务主管面试
文章浏览阅读674次。对文本进行提取,利用结巴分词进行分词,然后进行word2vec训练(维度设置为100),得到每个词的词向量.对于每一个用户,通过其发表的内容,得到用户所使用的词汇,然后求得用户的平均词向量.(词向量和除以词的数量)通过训练集,分别对用户地区,年龄,性别进行建模2017CSDN用户画像竞赛用户内容主题词生成:给定若干用户文档(博客或帖子),为每一篇文档生成3个最合适的主题词。要求生成的主题..._word2vec文本相关性