一般认为
Verilog HDL在系统级抽象方面比VHDL略差一些,而在门级开关电路描述方面比VHDL要强的多
写了第一个verilog程序,是一个加法器内容如下
module adder(count,sum,a,b,cin);
input[2:0] a,b;
input cin;
output count;
output [2:0] sum;
assign{count,sum}=a+b+cin;
endmodule
开始编译出现了几次错误,后来发现给实体的命名和程序中实体要一致而且大小写要一样,整个程序是嵌套再module和endmodule当中的
而其中的注释和C/C++类似,用//和来标明
module compare(equal,a,b);
output equal;
input [1:0] a,b;
assign equal=(a==b)?1:0;//和C语言中的相同
endmodule
verilog的基本设计单元是“模块(BLOCK)”。一个模块由两个部分组成,一部分描述端口,一部分描述逻辑功能,即定义输入是如何影响输出的。如下
module block(a,b,c,d);
input a,b;
output c,d;
assign c=a|b;
assign d=a&b;
endmodule
模块中最重要的部分是逻辑功能定义。有三种方法可以再模块中产生逻辑。
1、用“assign”声明语句
如:assign a=b&c;
2、用实例元件,如同调入库元件一样
如:and and_inst(q,a,b);
3、用“always”块
如:always @(posedge clk or posedge clr)
采用assign语句是最常用的方法之一。“always”块可以用于产生各种逻辑,常用于描述时序逻辑。
Verilog HDL中总共有19种数据类型
4 个最基本的数据类型:integer型、parameter型、reg型和wire型。其他的类型有large型、medium型、scalared型、 time型、small型、tri型、trio型、tril型、triand型、trior型、trireg型、vectored型、wand型和wor 型,以后会有介绍。
一、数字
整数
有二、八、十、十六进制
<位宽>’<进制><数字>,这是一种全面的描述方式
缺省的位宽由具体的机器系统决定,至少是32位
缺省的进制为十进制
x表示不定值,z代表高阻值,z还可以用?代替
4'b10x0 //位宽为4的二进制数从低位数起第二位为不定值
4'b101z //位宽为4的二进制数从低位数起第一位为高阻值
12'dz
12'd?
8'h4x
要表示负数的话只需在位宽表达式前加一个减号
4、下划线(underscore_)
可以用在数字之间提高数字的可读性如16'b1010_1011_1111_1010
二、参数(parameter)型
即用parameter来定义一个标识符代表一个常量,称为符号常量,类似于const和define pi一样。
网络数据类型表示结构实体(例如门)之间的物理连接,不能储存值,而且必须收到驱动器的驱动,如果没有驱动则该变量就是高阻的,值为z,常用的网络数据类型包括wire和tri型,wire型变量通常是用来表示单个门驱动或连续赋值语句驱动的网络型数据,tri型变量则用来表示多驱动器驱动的网络型数据
wire[7:0] b;//定义了一个8位的wire型数据
reg型
寄存器是数据存储单元的抽象,缺省初始值为不定值x
memory型
Verilog HDL通过对reg型变量建立数组来对存储器建模,可以描述RAM,ROM和reg文件。
如reg[7:0] mema[255:0];
定义了一个名为mema的存储器,有256个8位的寄存器,地址范围从0到255。
如果想对memory中的存储单元进行读写操作。必须指定该单元在存储器中的地址。·
2.3
(1)算数运算符(+,-,*,/,%);%为模运算,求余运算符
(2)赋值运算符(=,<=);
(3)关系运算符(>,<,>=,<=);
(4)逻辑运算符(&&,||,!);逻辑与,逻辑或,逻辑非
(5)条件运算符(?:);r=s?t:u;如果s为真,则r=t,否则r=u
(6)位运算符(~,|,^,&,^~);~取反,|位或,^按位异或,&按位与,^~按位同或(异或非)
(7)移位运算符(<<左移,>>右移);a>>n,n代表移几位,都用0来填补移出的空位
(8)拼接运算符({});把多个信号的某些位拼接起来
(9)其他。
单目运算缩减运算,如&b,是将b的每一位相与得出一位的结果
赋值语句和块语句
信号有两种赋值方式
1、非阻塞(non_blocking)赋值方式(如b<=a;)b的值不是立刻改变的,等到块结束后才完成赋值操作
2、阻塞(blocking)赋值方式(如b=a;)b的值立刻改变
顺序块
语句是顺序执行的
parameter d=50;
reg[7:0] r;
begin
#d r='h35;
#d r='hE2;
#d r='h00;
#d r='hF7;
#d ->end_wave;
end
用顺序块和延时控制组合来产生一个时序波形
并行块是语句同时执行的
在并行块和顺序块中都有一个起始时间和结束时间的概念。对于顺序块,起始时间就是第一条语句开始被执行的时间,结束时间就是最后一条语句执行结束的时间。而对于并行块来说,起始时间对于块内所有的语句是相同的,即程序植程控制进入该块的时间,其结束时间是按时间排序在最后的语句执行结束的时间。
if_else语句
case_endcase 语句
(1)case(表达式)<case分支项> endcase
(2)casez(表达式)<case分支项> endcase
(3)casex(表达式)<case分支项> endcase
Verilog HDL 针对电路的持性提供了case语句的其他两种形式,用来处理caee语句比较过程中的不必考虑的情况(don't care condition)。其中,casez语句用来处理不考虑高阻值z的比较过程,casex语句则将高阻值z和不定值x都视为不必关心的情况。所谓不必关心的情况,即在表达式进行比较时,不将该位的状态考虑在内。这样,在case语句表达式进行比较时,就可以灵活地设置以对信号的某些位进行比较。见下面的两个例子。
2.6
有4种类型的循环语句
(1)forever--连续执行的语句,常用于产生周期性的波形,作为仿真测试信号。
(2)repeat--连续执行一条语句n次
(3)while--执行一条语句直到某个条件不满足
(4)for循环语句,和C/C++中的循环语句类似
2.7
(1)initial;和always在仿真一开始即开始执行,initial语句只执行一次
(2)always;而always语句则不断重复执行
(3)task;和function语句可以在程序模块的一处或多处调用
(4)function;
always #half_period areg=~areg;//这条语句生成了一个周期为2*half_period的无限延续的信号波形。
reg[7:0] counter
reg tick;
always @(posedge areg)
begin
tick=~tick;
counter=counter+1;
end
这种时间控制是always语句最常用的
2.7.3
一、task和function说明语句的不同点
任务和函数有些不同,主要表现为以下4点:
(1)函数只能与主模块共用同一个仿真时间单位,而任务可以定义自己的仿真时间单位。
(2)函数不能启动任务,而任务能启动其他任务和函数。
(3)函数至少要有一个输入变量,而任务可以没有或有多个任何类型的变量。
(4)函数返回一个值,而任务则不返回值
例:这个例子描述了一个简单的交通灯的时序控制,并且该交通灯有它自己的时钟产生器。
module traffic_lights;
reg clock,red,amber,green;
parameter on=1,off=0,red_tics=350,amber_tics=30, green_tics=200;
//initialization
initial red=off;
initial amber=off;
initial green=off;
//clk control
always
begin
red=on;
light(red,red_tics);
green=on;
light(green,green_tics);
amber=on;
light(amber,amber_tics);
end
//define traffic light on mission
task light(color,tics);
output color;
input[31:0] tics;
begin
repeat(tics)@(posedge clock);//use repeat sentence to wait for posedge clock
color=off;
end
end task
//generate clock
always
begin
#100 clock=0;
#100 clock=1;
end
end module
三、function说明语句
函数的目的是返回一个用于表达式的值
例子 定义了一个叫factorial的函数,返回一个32位的寄存器类型的值,可后向调用自身,并打印出部分结果值。
module tryfact;
//function define
function[31:0] factorial;
input[3:0] operand;
reg[3:0] index;
begin
factorial=operand?1:0;
for(index=2;index<=operand;index=index+1)
factorial=index*factorial;
end
endfunction
//function test
reg[31:0] result;
reg[3:0] n;
initial
begin
result=1;
for(n=2;n<=9;n=n+1)
begin
$display("partial result n=%d result=%d",n,result);
result=n*factorial(n)/((n*2)+1);
end
$display("finalresult=%d",result);
end
endmodule
2.8
Verilog HDL语言中共有以下一些系统函数和任务:
$bitstoreal, $rtoi,$display,$setup,$finish,$skew,$hold,$setuphold,$itor,$strobe,$ period,$time,$printtimescale,$timefoemat,$realtime,$width,$real tobits,$ write,$recovery.
2.8.1 $display和$write任务
格式:
$display(p1,p2,…,pn);
$write(p1,p2,…,pn);
用来输出信息,将参数p2~pn按参数p1给定的格式输出。
$display("d=%0h a=%0h",data,addr);
这样显示输出数据的时候,经过格式转换以后总是以最少的位数来显示表达式的当前值。
2.8.2
格式:
$monitor(p1,p2,…,pn);
$monitor;
$monitoron;
$monitoroff;
具有监控和输出参数列表中的表达式或变量值的功能。使得每当参数列表中变量或表达式的值发生变化时,整个参数列表中变量或表达式的值都将输出显示。
在$monitor中,参数可以是$time系统函数,如下
$monitor($time,,"rxd=%b txd=%b",rxd,txd);
在$display中也可以这样使用。
$monitoron和$monitoroff这两个任务的作用是通过打开和关闭监控标志来控制、监控任务$monitor的启动和停止,这样使得程序员可以很容易的控制$monitor何时发生。
2.8.3
$time和$realtime,用这两个时间系统函数可以得到当前的仿真时刻。
cpu
5. 指令寄存器(INSTRUCTION REGISTER)
module register(r,clk,data,ena,rst);
output[7:0] r;
input [7:0] data;
input clk,ena,rst;
wire load;
and a1(load,clk,ena);
DFF d7(r[7],,load,data[7],rst);
DFF d6(r[6],,load,data[6],rst);
DFF d5(r[5],,load,data[5],rst);
DFF d4(r[4],,load,data[4],rst);
DFF d3(r[3],,load,data[3],rst);
DFF d2(r[2],,load,data[2],rst);
DFF d1(r[1],,load,data[1],rst);
DFF d0(r[0],,load,data[0],rst);
endmodule
6. 状态控制器(STATE CONTROLLER)
`timescale 1ns/1ns
module control(load_acc,mem_rd,mem_wr,inc_pc,load_pc,load_ir,halt,opcode,fetch,zero,clk,clk2,reset);
output load_acc,mem_rd,mem_wr,inc_pc,load_ir,halt;
reg load_acc,mem_rd,mem_wr,inc_pc,load_pc,load_ir,halt;
input[2:0] opcode;
input fetch,zero,clk,clk2,reset;
`define HLT 3'b000
`define SKZ 3'b001
`define ADD 3'b010
`define AND 3'b011
`define XOR 3'b100
`define LDA 3'b101
`define STO 3'b110
`define JMP 3'b111
always @(posedge fetch)
if(reset)
ctl_cycle;
always @(negedge reset)
begin
disable`ctl_cycle;
{inc_pc,load_acc,load_pc,mem_wr,mem_rd,load_ir,halt}=7'b0000000;
end
always @(posedge reset)
@(posedge fetch) ctl_cycle;
task ctl_cycle;
begin
//state0 --first address setup
{inc_pc,load_acc,load_pc,mem_wr,mem_rd,load_ir,halt}=7'b0000000;
//state1 --instruction fetch
@(posedge clk)
{inc_pc,load_acc,load_pc,mem_wr,mem_rd,load_ir,halt}=7'b0000100;
//state2 --instructionload
@(negedge clk)
{inc_pc,load_acc,load_pc,mem_wr,mem_rd,load_ir,halt}=7'b0000110;
//state3 --idle
@(posedge clk)
{inc_pc,load_acc,load_pc,mem_wr,mem_rd,load_ir,halt}=7'b0000110;
//state4 --second address setup
@(negedge clk)
if(opcode==`HLT)
{inc_pc,load_acc,load_pc,mem_wr,mem_rd,load_ir,halt)=7'b1000001;
else
{inc_pc,load_acc,load_pc,mem_wr,mem_rd,load_ir,halt)=7'b1000000;
//state5 --operand fetch
@(posedge clk)
if((opcode==`ADD||(opcode==`AND)||(opcode==`XOR)||(opcode==`LDA))
{inc_pc,load_acc,load_pc,mem_wr,mem_rd,load_ir,halt)=7'b0000100;
else
{inc_pc,load_acc,load_pc,mem_wr,mem_rd,load_ir,halt)=7'b0000000;
//state5 --operand fetch