verilog HDL入门

|▌冷眼眸甩不掉的悲伤 提交于 2020-02-09 17:17:28

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

1580474384367

注意

  • 没考虑时延问题
  • 没有说明如果输入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)

1580475482543

进位输出:当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

任务调用

端口连接信号的顺序必须与任务定义时的端口顺序一致。由于任务内的语句是顺序执行,所以输出信号的类型必须是寄存器类型。

函数

函数只能返回一个信号,即只有一个输出信号(函数名就是输出信号);且函数不能调用任务;也不能带时序控制。

系统任务函数

七 其它语法

阻塞与非阻塞

  1. 两者都能实现组合电路建模
  2. 如果always进程中的敏感信号列表包括了所有赋值操作符右边的信号,则两者没区别。
  3. 如果always进程中的敏感信号列表没有包括所有赋值操作符右边的信号,则两者有差别,至少在ModelSim中的仿真有差别。
  4. always进程中的敏感信号列表没有包括所有赋值操作符右边的信号时,多条阻塞赋值语句书写顺序是有先后讲究的。
  5. 因此,推荐组合电路使用阻塞赋值,并且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

事件

上升沿,下降沿;电平事件



易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!