verilog HDL入门
特点
- 类C语言
- 并行执行
- 硬件描述
设计流程: 自顶向下
前提:懂C语言和简单的数电知识
简单体验
语法很类似C语言,同时不难看出描述的是一个多路选择器
module muxtwo (out, a, b, s1);
input a,b,s1;
output out;
reg out;
always @(s1 or a or b)
if(!s1) out = a;
else out = b;
endmodule
注意
- 没考虑时延问题
- 没有说明如果输入a或b是三态的(高阻时)输出应该是什么。
一 入门例子
例1.
多路选择器
描述一个多路选择器,控制信号sel,输入信号in0、in1, 输出信号out
module mux(out, in0, in1, sel);
parameter N=8;
output[N:1] out;
input[N:1] in0, in1;
input sel;
assign out=sel?in1:in0;
endmodule
例2.
4位二进制加法计数器(带同步清0)
进位输出:当q为最大值(15)且cin=1时,cout=1;否则cout=0
module counter(q, cout, reset, cin, clk);
parameter N=4;
input reset, cin, clk;
output cout, q;
reg[N:1] q; //寄存器型有保持功能
//逻辑功能
always @(posedge clk) //时钟上升沿执行
begin
if (reset) q<=0;
else
q<=q+cin
end
assign cout=&q && cin; //&q是语法糖,相当于q[1]&...q[N]
endmodule
二 数据类型
空白符
忽略
注释符
// 和 /**/
注释最好是英文,EDA对中文不友好
数值
0/1/x/z
x : 不确定
z : 高阻态
整数
/*
format : +/-<size>'<base_format><number>
size的确定是一个好的编程习惯
*/
8'b1001101 //位宽为8位的二进制数
8'ha6 //位宽为8位的十六进制数
4'b1x_01 //下划线只是方便阅读
//wrong eg.
4'd-4 //补码表示 或 在位宽表达式前面加一个减号
3' b001 //空格
(4+4)'b11
//高级建模使用
integer idata; //一个整型数据对象
//idata 位宽由机器和编译器决定, 是有符号数
idata = -12;
idata[7:0] = 8'ha2
integer iram[7:0]; //数组
实数
/*
虽然parameter可以定义常量,parameter pi=3.14
但是verilog本身不能识别小数,不能直接运算小数(需要技巧)
*/
//小数点两边必须有数字
2.7
5.2e8 //520.0
//wrong eg.
6.
.3e5
//建模使用
real idata;
物理数据类型
物理数据类型:连线型、寄存器型和存储器类型
标记符 | 名称 | 类型 |
---|---|---|
supply | 电源级驱动 | 驱动 |
strong | 强驱动 | 驱动 |
pull | 上拉级驱动 | 驱动 |
large | 大容性 | 存储 |
weak | 弱驱动 | 驱动 |
mediam | 中性驱动 | 存储 |
small | 小容性 | 存储 |
highz | 高容性 | 高阻 |
信号强度:表示数字电路中不同强度的驱动源,解决不同驱动强度存在下的赋值冲突。
简单理解就是,表格里的驱动源,从上到下强度依次减弱,supply相当于短路,highz相当于断路。
连线型
连线型数据类型 | 功能说明 |
---|---|
wire, tri | 标准连续(缺省默认类型) |
tri1 | 上拉电阻 |
tri0 | 下拉电阻 |
supply1 | 电源线,高电平1 |
supply0 | 电源线,低电平0 |
... ... | |
寄存器型
与连线型数据的区别:reg型数据保存最后一次赋值,而wire型数据需要持续的驱动。reg默认初始值为不定值x, 且默认无符号。
//有符号写法
reg signed[3:0] rega;
rega = -2; //rega值为1110(14)
存储器类型
可以理解为寄存器的组合(二维寄存器)
reg[7:0] memory[63:0]; //宽度为8,深度64
memory[0] = 8'h12;
memory = 12; //error
向量与标量
//vector 默认
wire vectored[7:0] v_data;
assign a = v_data[0];
assign b = v_data[5:3];
assign v_data[1] = 1'b1;
//scalar
wire scalared[7:0] s_data;
assign a = v_data[0]; //error
字符串
字符串变量其实就是寄存器变量,由""括起。
module string_test;
reg[19*8:1] str;
initial
begin
str = "I love verilog HDL!";
$display("str is :%H",str);
end
endmodule
时间型数据对象
time idata;
time iram[7:0];
参数
parameter H=1;
defparam H=2;
//仅限于当前模块的参数定义
localparam real PI = 3.14;
三 操作符
赋值
= 与 <= : https://www.cnblogs.com/friedCoder/articles/12257385.html
等式
逻辑相等‘’与逻辑全等‘=’
(1)、逻辑相等:两个操作数逐位比较,如果两个进行比较的位是不定态‘x’或者高阻态’z’,则输出x
$displayb ( 4’b0011 == 4’b1010 ); // 0
$displayb ( 4’b0011 != 4’b1x10 ); // 1
$displayb ( 4’b1010 == 4’b1x10 ); // x
$displayb ( 4’b1x10 == 4’b1x10 ); // x
$displayb ( 4’b1z10 == 4’b1z10 ); // x
(2)、逻辑全等
$displayb ( 4’b01zx === 4’b01zx ); // 1
$displayb ( 4’b01zx !== 4’b01zx ); // 0
$displayb ( 4’b01zx === 4’b00zx ); // 0
$displayb ( 4’b01zx !== 4’b11zx ); // 1
在进行全等运算时,对不定态与高阻态也要进行比较,当两个操作数完全一致时,其结果才为1,否则为0
比较
对于<、>之类的操作符,操作数中只要有一位为x或z,结果都为x
拼接
起拼接的作用 如 a = {b[5],b[4:0]}
意思为 b的最高位和b的低五位拼接起来,组成的a为6位
缩位
~^ : 位取同或操作
四 顺序语句
//下面2个并行赋值
assign p_sum_one = data1 + data2;
assign p_sum_two = data1 + data3;
always @(data1, data2, data3);
begin
//下面2个顺序赋值
assign s_sum_one = data1 + data2;
assign s_sum_one = data1 + data2;
end
//但是实际生成电路后,会发现这4个加法器是并行执行
if 条件语句
if 条件
顺序语句1;
elsif 条件
顺序语句2;
end if
case 条件语句
//要求匹配完全一致,包括x和z
casez语句:忽略值为z的位
casex语句:忽略值为z或x位
repeat语句
repeat(8)
begin
顺序语句;
end
forever
永远执行
五 自定义原语 UDP
原语只能有一个标量输出(0,1或x),可以有多个标量输入(0,1,z或x,这里x和z同等看待)。
六 任务和函数
verilog的任务 = C语言的函数
task cal_num_one;
input[15:0] data;
output[4:0] num;
integer i, j;
begin
i = 0;
for(j=0; j<16; j++)
if(data[j])
i = i + 1;
num = i[4:0];
end
endtask
若在函数定义中没有指定函数值得取值范围和类型,则函数默认返回1位二进制数。返回值的类型可以是real,integer,time或者realtime之一。通过关键词signed可以把返回值声明为带符号值。
function [automatic] [signed]
[range or type] function_id;
input_declaration
other_declaration
statements
endfunction
//eg.
function [7:0] expected_led;
input [7:0] swt;
begin
expected_led[0] = ~swt[0];
expected_led[1] = swt[1] & ~swt[2];
expected_led[3] = swt[2] & swt[3];
expected_led[2] = expected_led[1] | expected_led[3];
expected_led[7:4] = swt[7:4];
end
endfunction
任务调用
端口连接信号的顺序必须与任务定义时的端口顺序一致。由于任务内的语句是顺序执行,所以输出信号的类型必须是寄存器类型。
函数
函数只能返回一个信号,即只有一个输出信号(函数名就是输出信号);且函数不能调用任务;也不能带时序控制。
系统任务函数
七 其它语法
阻塞与非阻塞
- 两者都能实现组合电路建模
- 如果always进程中的敏感信号列表包括了所有赋值操作符右边的信号,则两者没区别。
- 如果always进程中的敏感信号列表没有包括所有赋值操作符右边的信号,则两者有差别,至少在ModelSim中的仿真有差别。
- always进程中的敏感信号列表没有包括所有赋值操作符右边的信号时,多条阻塞赋值语句书写顺序是有先后讲究的。
- 因此,推荐组合电路使用阻塞赋值,并且always敏感列表包括所有赋值操作符右边的信号,即使用always@(*)。时序电路推荐非阻塞语句。
//3个寄存器
always@(posedge clk)
begin
temp_one <= a;
temp_two <= temp_one;
y_r <= temp_two;
end
//1个寄存器
always@(posedge clk)
begin
temp_one = a;
temp_two = temp_one;
y_r = temp_two; //最后相当于y_r = a
end
//3个寄存器
always@(posedge clk)
begin
y_r = temp_two;
temp_two = temp_one;
temp_one = a;
end
预编译指令
类似`define
时延
assign #2 a = b;
//b计算好后,延时2个时间单位送给a
事件
上升沿,下降沿;电平事件