Verilog 语言基础


Verilog 语言基础

三种描述方式

1.数据流描述
通常指采用assign语句进行连续赋值(continous assignment),连续赋值意味着输入的变化会立即导致输出的变化,这正是组合逻辑电路的特点。注意:assgin是不能用在always或者initial语句块中的,且通常搭配wire或者tri变量

	assign #3 result = (Sel_in)? B_in:A_in;//when Sel_in,B_in or A_in changed, result changed immediately

2.行为描述
行为描述通常用于描述电路中的行为,行为描述适用于实现顺序执行,从而实现时序逻辑电路,通常通过always或者initial代码块的方式来写。注意:begin end块只能出现在行为描述的代码块中,硬件描述和C语言程序的显著区别在于C语言是默认顺序执行的,而硬件描述语言则是默认并行执行的。通常搭配reg变量,reg即寄存器只有执行相应代码块时才能改变,其余时候无论输入变化都不改变。

//一个2-4译码器的代码块
always@(result)
begin
	case(result)
	2'b00 : begin
				{eq3,eq2,eq1,eq0} = #2 4'b0001;
				$display("At time %t -",$time,"eq0=1");
			end
	2'b01 : begin
				{eq3,eq2,eq1,eq0} = #2 4'b0010;
				$display("At time %t -",$time,"eq1=1");
			end
    2'b10 : begin
				{eq3,eq2,eq1,eq0} = #2 4'b0100;
				$display("At time %t -",$time,"eq2=1");
			end
    2'b11 : begin
				{eq3,eq2,eq1,eq0} = #2 4'b1000;
				$display("At time %t -",$time,"eq3=1");
			end       
    default:;
    endcase
end

3.结构化描述
结构化描述指的是通过实例化已有的功能模块进行硬件描述,这其实很像C语言中的函数调用,同样结构化地描述也为多人合作开发大规模硬件实现了可能。需要注意的是,如果在描述中反复使用了同一个模块,需要分别命名各个模块,因为在实际电路中对应着若干个实际模块而非一个,module的使用称为实例化(Instantiate)。

//假设已经存在了一个一位加法器模块 module add1(input a,input b,cin,cout,sum)
//两位加法器的扩展
module add2(input[1:0] in1,input[1:0] in2,output[1:0] out)
	wire cout;
	add1 ADD1(in1[0],in2[0],0,cout,out[0]);
	add1 ADD2(in1[1],in2[1],cout, ,out[1]);//两个一位加法器可以扩展为两位加法器
endmodule

4.一些简单的规范
Verilog同C一样对大小写敏感,因而在信号命名上也应尽量规范。一般而言:推荐所有信号和线网名用小写,单词间使用下划线连接,而宏变量则应该全部用大写。
在一个Verilog文件推荐指存放一个module定义,并且使得module名字与文件名相同;

模块与端口

1.module通常是具有输入和输出的(有一些特殊的用于仿真的module是没有的),从语法上说也接近于C语言中的函数。
2.端口列表类似于C语言函数中的函数参数列表,也是由端口类型于名字组成,例如:input[1:0] in1,output[1:0] out, inout[1:0] io1 等等。其中[1:0]表示的是对应端口的位宽,即两位分别为in[0],in[1],类似于数组或者向量vector,也可以通过in[i]调用特定的位进行计算
3.通常来说输入是wire类型,输出是reg类型,inout双向信号多用于总线管理是tri类型(即三态门,含有高阻态以阻断电气连接)
4.特别要强调的是:module中的内容在没有特殊说明时是并行的,就好像原理图是先画任意一个门一样,不影响结果。

module 模块名称(端口列表)
//声明:
	reg,wire,parameter,
	input,output,inout,
	function,task
//语句:
	initial 语句
	always 语句
	module 实例化
	门实例化
	用户定义原语实例化(UPD)
	连续赋值 assign ...

编译指令

类似于C语言中的 #include<stdio.h>等的预指令,Verilog中也存在一些编译指令来指导编译器工作,对应于C中的”#“,Verilog的编译指令前需要加反引号“`"以下介绍几个常见的编译指令:
1."`timescale"用于表示时间延迟的单位和精度,在module文件前加上该指令可以保证该模块中的延迟信息采用这个单位,否则则是使用缺省值或者沿用上一个模块的延迟值;举例:

`timescale 1ns/100ps //1ns为延迟时间,100ps为时间精度,即处理时1.23ns≈1.2ns

2.”`define"同C语言中一样用于定义宏与之相对的有“`undef”,遇到之后取消前一个定义的宏

`define BUS_WIDTH 16 //定义总线宽度
module(...);
	reg[BUS_WIDTH:0] data;
	...
endmodule

3.“`ifdef, `else, `endif”同C语言中一样是条件编译符,条件发生时执行编译指令

`ifdef NARROW	//若已经定义了NARROW宏则执行
	parameter BUSWIDTH=16;
`else
	parameter BUSWIDTH=32;
`endif

4."`include"表示嵌入某个文件内容,同C语言中也是一样的,编译时使用相应文件的内容替换该行代码

`include "HEADFILE.h"

5."`resetall"将所有的编译指令的值设置为缺省值

逻辑值与常量的表示

1.逻辑值:主要有0,1,Z(高阻态),X(不定态,对应CMOS中上下均导通时)
2.常量:注意常量的表示方法,b,d,o,h分别代表二进制,十进制,八进制,十六进制
常量包含整数型,实数型,字符串型

	//[位宽]'进制 数字
	4'b1011//数值等于十进制下的11
	5'd0011//十进制11,但是占5位宽
	13_2.18e2//表示13218
	reg[1:8*11] message = "Hello World";//需要11个字节存储

变量类型

Verilog中主要有线网类型和寄存器类型两大变量类型
凡是用assgin赋值的一定是线网类型,凡是在always或者initial代码块中赋值的一定是寄存器类型

线网型:表示物理连线,仿真时也不占用仿真内存

主要有几种子类
1.wire、tri:电路间联线,tri用于多驱动源
2.wor、trior、wireand、triand:实现线与、线或
3.trireg:表示该联线有总线保持功能

准确的说,线网是被驱动(drive)而非赋值,驱动是指在每一个仿真步进上都要重新计算,即连续驱动(continuous assignment)

寄存器类型:一个抽象的数据存储单元,仿真时占用内存

寄存器类型即对应了实际的寄存器器件需要满足相应条件才能改变其中的值,不会随着输入变化而持续地变化。
reg:常用的寄存器类型数据
integer:整型数据,至少32位
time:整型时间,至少64位
realtime,real:实数时间与实数寄存器

寄存器是可以以寄存器组的形式定义和使用的

	reg[3:0] Mem[0:7];//定义一个存储单元地址为0到7,每个单元四位
	always@(eq1 or eq0)
		reg xor = #1 eq0^eq1;//实际上就是一个异或门
	initial
		begin 
		integer i;//integer可以辅助循环,同C语言是一样的
		for(i=0;i<7;i++)
			begin
				Mem[i]=i;
			end
		end

注意:代码中的reg不一定总是对应着实际中的寄存器

参数

参数也是一种常量,但是通常有一些特殊的用途,比如状态机的状态,总线的带宽等等。同时参数也可以在编译时就通过编译指令进行改变。特性即类似于C语言中的宏

`define BUS_WIDTH 16;
module();
	parameter BUS_WIDTH;
endmodule

并发与顺序

这是硬件描述与程序设计语言差别最大的地方,硬件描述总是默认是并行执行的,但是在always和initial块中可以产生顺序或者并发执行。
begin... ...end:顺序执行语句
fork... ...join:并发执行语句

操作数、操作符与表达式

1.操作符
这里说几个需要注意的算符:
&m:只对m数按位作归约与的结果(每一位相与,1111结果为1,1110结果为0)或,而m&n则是两变量之间按位与(11&10为10,01&10结果为00,注意位数)
sel?m:n:条件运算符,与C语言相同,可以代替一个if操作
{}:连接运算符,{n,m}结果为二者连接即更长的一个数,{n{m}}结果为n个m相连接,可以代替一些循环操作。

2.操作数
操作数的类型前文全部都介绍过了,这里要说明两个问题:
多位操作数是可以类似于数组和向量按位操作的(wire[3:0] in ,可以直接操作 in[0]),也可以对模块返回值进行操作,类似于C语言中函数的返回值

特别注意,操作数的极性(正负):
线网变量,一般寄存器变量,基数格式表示的整数常数是没有正负的;
整型寄存器变量于十进制整型常量是有正负的(补码表示前面全1)。

系统任务与系统函数

1.显示任务
$display使用即类似于C语言中的printf

$display("At time %t -",$time,"eq0=1");//%t表示时间,通过time系统函数产生时间
$display("The value of ABC is %d",ABC);

2.文件输入输出任务
$fopen 打开文件并返回该文件指针
$fdisplay可以使用文件指针写入信息
$fclose关闭文件

integer Write_Out_File;
Write_out_File=$fopen("Write_out_File.txt");
$fdisplay(Write_Out_File,"@%h\n%h",Mpi_Add,Data_in);
//向指定文件中的指定地址(@的内容是地址Mpi_Add),后面的是内容(Data_in)
$fclose(Write_Out_File);//关闭指定文件

3.仿真控制任务
$finish仿真器退出
$stop仿真器挂起
2.时序验证任务与时间仿真函数
$setup检查建立时间
$hold检查保持时间
$time返回一个64位的模拟时间
3.概率分布函数
$random产生一个32位的有符号整型随机数

主要内容和示例来源:《轻松称为设计高手 Verilog HDL实用精解》;北京航空航天大学出版社;
推荐练习网站:https://hdlbits.01xz.net/wiki


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM