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
任务调用
端口连接信号的顺序必须与任务定义时的端口顺序一致。由于任务内的语句是顺序执行,所以输出信号的类型必须是寄存器类型。
函数
函数只能返回一个信号,即只有一个输出信号(函数名就是输出信号);且函数不能调用任务;也不能带时序控制。
系统任务函数
七 其它语法
阻塞与非阻塞
- 两者都能实现组合电路建模
- 如果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
事件
上升沿,下降沿;电平事件
来源:https://www.cnblogs.com/friedCoder/p/12287675.html