侧边栏壁纸
博主头像
Zengyq's Blog博主等级

但行好事,莫问前程!

  • 累计撰写 4 篇文章
  • 累计创建 10 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

verilog

zengyq
2024-09-04 / 0 评论 / 0 点赞 / 96 阅读 / 20878 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2024-09-12,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

跑仿真

VCS 用于编译和放在,Verdi 用于查看波形。

  1. 编辑 test.v 和 tb_test.v 文件

  2. 编译、仿真

    module load vcs
    vcs-R-full64 -sverilog -l vcs_comile.log -kdb -lca tb_test.v test.v
  1. 查看波形

    module load verdi
    verdi

用 Verdi 软件打开波形文件(fsdb 文件)

verilog-01.png

verilog-02.png

基本语法

  • 每个模块的内容都是嵌在 module 和 endmodule 两个语句之间;

  • 每个模块实现特定的功能,模块可以进行层次嵌套;

  • 除了 endmodule 语句外,每个语句和数据定义的最后必须有分号;

  • 用 /**/ 和 // 进行注释。

模块

每个 Verilog 程序包括四个部分:端口定义、I/O说明、内部信号声明、功能定义。

verilog-03.png

  • 内部信号声明

    reg  [1:0] R变量1, R变量2...;
    wire [3:0] W变量1, W变量2...;
  • 功能定义

    • 用“assign”声明语句

    • 用实例元件

      and and_inst( q, a, b );

      像调库元件一样,输入元件的名字和相连的引脚即可,表示在设计中用到一个跟与门(and)一样的名为 and_inst 的与门,其输入端为 a, b,输出为 q。要求每个实例元件的名字必须唯一,并避免与其他调用与门的实例混淆。

    • 用“always”块

      "always" 块中的语句顺序执行,多个 "always" 之间是同时执行的。

      always @(posedge clk or posedge clr)
        begin
          if(clr) 	q<=0;
          else if(en) q<=d;
        end
      end

整数

  • 二进制整数(b 或 B)

  • 十进制整数(d 或 D)

  • 十六进制整数(h 或 H)

  • 八进制整数(o 或 O)

8'b10101100  // 位宽为 8 的二进制表示,'b 表示二进制
8'ha2				 // 位宽为 8 的十六进制,'h 表示十六进制

x和z值

'x' 表示不定值,'z' 表示高阻值,z 的另一种表达方式是写作 '?'

4'b10x0 // 位宽为4的二进制数从低位数起第二位为不定值 
4'b101z // 位宽为4的二进制数从低位数起第一位为高阻值 
12'dz   // 位宽为12的十进制数其值为高阻值(第一种表达方式) 
12'd?   // 位宽为12的十进制数其值为高阻值(第二种表达方式) 
8'h4x   // 位宽为8的十六进制数其低四位值为不定值 

负数

在位宽表达式前加一个减号即可:

-8'd5 // 这个表达式代表5的补数(用八位二进制数表示) 

参数(Parameter)类型

parameter msb=7; // 定义参数 msb 为常量 7
parameter e=25, f=29; // 定义两个常量参数
parameter average_dely = d+f; // 用常数表达式赋值
  • 参数型常数经常用于定义延迟时间和变量宽度。

  • 在模块或实例引用时可通过参数传递改变在被引用模 块或实例中已定义的参数,如:

    module Decode(A, F);
      parameter Width=1, Polarity=1;
      ......
    endmodule
    
    module Top;
      wire[3:0] A4;
      wire[4:0] A5;
      wire[15:0] F16;
      wire[31:0] F32;
      Decode #(4,0) D1(A4,F16); // 实例 D1 中 width = 4, Polarity = 0
      Decode #(5)   D2(A5,F32); // 实例 D2 中 width = 5, Polarity = 1
    Endmodule

wire和reg

wire 型数据常用来表示用于以 assign 关键字指定的组合逻辑信号。Verilog 程序模块中输入输出信号类型缺省时自动定义为 wire 型。wire 型信号可以用作任何方程式的输入,也可以用作 "assign" 语句或实例元件的输出。

wire [7:0] a, b; // 定义了两个八位的 wire 型数据。共有 2 条总线,每条总线内有 8 条线路

reg 表示寄存器。reg 型数据常用来表示用于"always"模块内的指定信号,常代表触发器。通常,在设计中要由"always"块通过使用行为描述语句来表达逻辑关系。"always"块内被赋值的每一个信号都必须定义成 reg 型。

reg [4:0] rega; // 定义了一个四位的名为 rega 的reg型数据 

reg 型只表示被定义的信号将用在"always"块内,并不是说 reg 型信号一定是寄存器或触发器的输出。虽然 reg 型信号常常是寄存器或触发器的输出,但并不一定总是这样。

  • 设计代码仅用 reg 和 wire

  • 设计代码:由本模块产生且是用 always 产生的信号,用 reg 类型

  • 测试代码:用 initial 产生的信号(一般是对测试模块的输入),用 reg 类型

  • 其他都要 wire

  • reg 类型的信号,不一定产生寄存器

运算符

  1. 算术运算符(+,-,*,/)

  2. 赋值运算符(=,<=)

    时序逻辑用 "<="

    组合逻辑用 "="

    1. <=(非阻塞赋值方式,如 b<=a;)

      ① 块结束后才完成赋值操作;

      ② b 的值不是立刻就改变的。

    2. =(阻塞赋值方式,如 b=a;)

      ① 赋值语句执行完后,块才结束;

      ② b的值在赋值语句执行完后立刻就改变的。

  3. 关系运算符(>,<,>=,<=)

  4. 逻辑运算符(&&,||,!)

    【为避免歧义,逻辑运算符两边必须为 1 bit 信号】

  5. 条件运算符(?:)

    r = s ? t : u

  6. 位运算符(~,|,^,&,^~)

    ~:取反(按位取反)

    &:按位与

    |:按位或

    ^:按位异或

    ^~:按位同或

  7. 移位运算符(<<,>>)

  8. 位拼接运算符({ })

    用这个运算符可以把两个或多个信号的某些位拼接起来进行运算操作:

    {信号 1 的某几位, 信号 2 的某几位, ..., ..., 信号 n 的某几位}

    例如:

    {4{w}} // 这等同于 {w,w,w,w}

    {b,{3{a,b}}} // 这等同于 {b,a,b,a,b,a,b}

  9. 等式运算符

    ==:等于

    !=:不等于

    ===:等于

    !===:不等于

    "==" 和 "!=" 又称为逻辑等式运算符。其结果由两个操作数的值决定。由于操作数中某些位可能是不定值 x 和高阻值 z,结果可能为不定值 x。而 "===" 和 "!==" 运算符则不同,它在对操作数进行比较时对某些位的不定值 x 和高阻值 z 也进行比较,两个操作数必需完全一致,其结果才是 1,否则为 0。"===" 和 "!==" 运算符常用于 case 表达式的判别。

并行块

块内语句是同时执行的,即程序流程控制一进入到该并行块,块内语句则开始同时并行地执行。

块内每条语句的延迟时间是相对于程序流程控制进入到块内时的仿真时间的。

当按时间时序排序在最后的语句执行完后或一个 disable 语句执行时,程序流程控制跳出该程序块。

// 用并行块产生一个时序波形
fork
  #50		r='h35;          // '#'表示延时
  #100  r='hE2;
  #150  r='h00;
  #200  r='hF;
  #250   -> end_wave;   // 触发事件 end_wave
join

产生的波形是和下面这个顺序块相同的:

parameter		d=50;		// 声明 d 是一个参数
reg [7:0]		r;			// 声明 r 是一个 8 位的寄存器变量
begin
  #d	r='h35;
  #d	r='hE2;
  #d	r='h00;
  #d	r='hF;
  #d	 -> end_wave;
end

if

if(a>b)
  begin
    out1<=int1;
    out2<=int2;
  end
else
  begin
    out1<=int2;
    out2<=int1;
  end

使用条件语句不当在设计中会生成原本没想到有的锁存器:

verilog-04.png

另一种偶然生成锁存器是在使用 case 语句时缺少 default 项的情况下发生的:

verilog-05.png

为了避免偶然生成锁存器的错误。如果用到 if 语句,最好写上 else 项。如果用 case 语句, 最好写上 default 项。遵循上面两条原则,就可以避免发生这种错误,使设计者更加明确设计目标, 同时也增强了 Verilog 程序的可读性。

while

下面例子用 while 循环语句对 rega 这个八位二进制数中值为 1 的位进行计数:

begin:	countls   // 给这个 begin 块命名为 countls
  reg[7:0] tempreg;
  count=0;
  tempreg=rega;
  while(tempreg)
    begin
      if(tempreg[0]) count=count1;
      tempreg = tempreg>>1;
    end
end

for

下面例子用 while 循环语句对 rega 这个八位二进制数中值为 1 的位进行计数:

begin:init_men
  reg[7:0] tempi;
  for(tempi=0;tempi<memsize;tempi=tempi+1)
    memory[tempi]=0;
end

结构说明语句

initial 和 always 说明语句在仿真的一开始即开始执行。initial 语句只执行一次。相反,always 语句则是不断地重复执行,直到仿真过程结束。

inital

例 1:

用 inital 语句在仿真开始时对各变量进行初始化。

initial
  begin
    areg=0;		// 初始化寄存器 areg
    for(index=0; index<size; index=index+1)
      memory[index]=0;
  end

例 2:

用 initial 语句来生成激励波形作为电路的 测试仿真信号。一个模块中可以有多个 initial 块,它们都是并行运行的。initial 块常用于测试文件 和虚拟模块的编写,用来产生仿真测试信号和设置信号记录等仿真环境。

initial
  begin
    inputs='b00000;		// 初始时刻为 0
    #10 inputs='b011001;
    #10 inputs='b011011;
    #10 inputs='b011000;
    #10 inputs='b001000;
  end

always

一个模块中的多个 always 块是并行运行的。

always 语句在仿真过程中是不断重复执行的,只有和一定的时序控制结合在一起才有用。下面这个例子中,每当 areg 信号的上升沿出现时把 tick 信号反相,并且把 counter 增加 1。

reg[7:0] counter:
  reg tick;
  always @(posedge areg)
    begin
      tick=~tick;
      counter=counter+1;
    end

always 的时间控制可以是沿触发也可以是电平触发的,可以单个信号也可以多个信号,中间需要用关键字 or 连接,如:

always @(posedge clock or posedge reset)	// 由两个沿触发的 always 块
  begin
    ...
  end

always @(a or b or c)		// 由多个电平触发的 always 块
  begin
    ...
  end

门级结构描述

  • 门类型

    • and 与门

    • nand 与非门

    • nor 或非门

    • or 或门

    • xor 异或门

    • xnor 异或非门

    • buf 缓冲器

    • not 非门

    • ......

// 描述了一个名为 nd1 的与非门(nand),输入为 data、clock 和 clear,输出为 a,
// 输出与输入的延时为 10 个单位时间
nand #10 nd1(a, data, clock, clear);

用门级结构描述 D 触发器

module flop(data, clock, clear, q, qb);
  input data, clock, clear;
  output q, qb;

  nand #10 nd1(a, data, clock, clear),
           nd2(b, ndata, clock),
           nd4(d, c, b, clear),
           nd5(e, c, nclock),
           nd6(f, d, nclock),
           nd8(qb, q, f, clear);
  nand #9  nd3(c, a, d),
           nd7(q, e, qb);
  not	 #10 iv1(ndata, data),
           iv2(nclock, clock);
endmodule

在这个 Verilog HDL 结构描述的模块中,flop 定义了模块名,设计上层模块时可以用这个名(flop)调用这个模块;module、input、output、endmodule 等都是关键字;nand 表示与非门;#10 表示 10 个单 位时间的延时;nd1、nd2、......、nd8、iv1、iv2 分别为下中的各个基本部件。

verilog-06.png

用已有模块来组成其他模块

如果已经编制了一个模块,如上面的 flop,可以在另外的模块中引用这个模块,引用的方法与门类型的实例引用非常类似。

module hardreg(d, clk, clrb, q);
  input				clk, clrb;
  input[3:0] 	d;
  output[3:0]	q;

  flop		f1(.data(d[0]), .clock(clk), .clear(clrb), .q(q0),),
          f2(.data[d1], .clock(clk), .clear(clrb), .q(q[1]),),
          f3(.data[d2], .clock(clk), .clear(clrb), .q(q[2]),),
          f4(.data[d3], .clock(clk), .clear(clrb), .q(q[3]),);
endmodule

在上面这个结构描述的模块中,hardreg 定义了模块名;f1、f2、f3、f4 分别为下图中的各个基本部件,而其后面括号中的参数分别为下图中各基本部件的输入输出信号。请注意当 f1 到 f4 实例引用已编模块 flop 时,由于不需要 flop 端口中的 qb 口,故在引用时把它省去,但逗号仍需要留着。

verilog-07.png

电路设计的3种结构

  1. 组合逻辑

    always@(*)begin
      语句
    end
  2. 时序逻辑

    1. 同步复位的时序电路

      always@(posedge clk) begin
        if(rst_n==1'b0)begin
          语句
        else begin
          语句
        end
      end
    2. 异步复位的时序电路

      always@(posedge clk or negedge rst_n) begin
        if(rst_n==1'b0)begin
          语句
        else begin
          语句
        end
      end

电路设计要点

  1. 一个 always 只产生一个信号(方便分析调试、便于改代码)

    verilog-08.png

  2. 一个信号只能在一个 always 中产生

  3. 条件判断只使用 if else 和 case 即可完成设计

  4. 含义 posedge 或 negedge 的,一定是 D 触发器,是时序电路

  5. 设计时,想立即有结果,用组合逻辑;想延时一拍有结果,用时序逻辑

模块例化

一个模块由其他子模块组成,例化可以理解为模块的调用。

verilog-09.png

练习

例1

verilog-10.png

module mul_module(
  mul_a			,
  mul_b			,
  clk				,
  rst_n			,
  mul_result
  );

  parameter			A_W = 4;
  parameter 		B_W = 3;
  parameter			C_W = A_W + B_W;

  input[A_W-1:0] 		mul_a;
  input[B_W-1:0] 		mul_b;
  input			 		clk;
  input			 		rst_n;

  output[C_W-1:0] 	mul_result;
  reg		[C_W-1:0] 	mul_result;
  reg		[C_W-1:0]		mul_result_tmp;

  always@(*)begin
    mul_result_tmp = mul_a * mul_b;
  end

  always@(posedge clk or negedge rst_n) begin
    if(rst_n==1'b0)begin
      mul_result <= 0;
    end
    else begin
      mul_result <= mul_result_tmp;
    end
  end
  
endmodule

例2

verilog-11.png

verilog-12.png

verilog-13.png

module mul2port(
  din_a		,
  din_b		,
  din_c		,
  din_d		,
  sel_a		,
  sel_b		,
  clk			,
  rst_n		,
  result_a,
  result_b
);

  input[2:0] 	din_a;
  input[1:0] 	din_b;
  input[3:0] 	din_c;
  input[3:0] 	din_d;
  input 			sel_a;
  input 			sel_b;
  
  output[6:0] result_a;
  output[5:0] result_b;
  reg		[6:0] result_a;
  reg		[5:0]	result_b;
  reg		[3:0]	sel_dout;	
  wire	[6:0]	result_a_tmp;
  wire	[5:0]	result_b_tmp;
  reg					sel;
  
  always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      result_b <=0;
    else begin
      result_b <= result_b_tmp;
    end
  end

  always@(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
      result_a <= 0;
    else begin
      result_a <= result_a_tmp
    end
  end

  mul_module#(.A_W(3),.B_W(4)) mul_4_3(
    .mul_a  		(din_a),
    .mul_b  		(sel_dout),
    .clk    		(clk),	  
    .rst_n  		(rst_n),
    .mul_result (result_a_tmp)
  );

  mul_module#(.A_W(2),.B_W(4)) mul_4_2(
    .mul_a			(din_b),
    .mul_b			(sel_dout),
    .clk				(clk),
    .rst_n			(rst_n),
    .mul_result	(result_b_tmp)
  );

  always@(*)begin
    if(sel==0)
      sel_dout = sel_a;
    else
      sel_dout = sel_b;
  end
  
  always@(posedge cld or negedge rst_n)begin
    if(rst_n==1'b0)begin
      sel <= 0;
    end
    else begin
      sel <= sel_a & sel_b;
    end
  end
  
endmodule

0

评论区