课程主页:

http://www.eng.biu.ac.il/temanad/digital-vlsi-design/

课程视频:

https://www.youtube.com/watch?reload=9&v=RbZ3BXbd6_k&list=PLZU5hLL_713x0_AV_rVbay0pWmED7992G

https://www.bilibili.com/video/BV1EA411n7VQ?from=search&seid=14545899898143497364

这次回顾第二讲,这一讲介绍了Verilog。

Verilog语法

基本结构

  • 原语:

    • not,and,or,etc
  • 信号:

    • $4$种状态:$\text{0,1,X,Z}$

    • 电线:不保持状态

    • 寄存器:保持状态

    • 可以表示总线或一组信号

    • ```vhdl
      wire in1,in2;
      reg out;
      wire [7:0] data;
      reg [31:0] mem [0:7];//width (bits)=32,depth (words)=8

      
      - 操作符
      
        - 类似原语:
        - $\&,  \sim, \& \&,| \mid, \text { etc }$
        - ![](https://github.com/Doraemonzzz/md-photo/blob/master/Digital%20VLSI%20Design/Lecture%202/2020082302.jpg?raw=true)
      
      - 常量:
      
        - 标准形式:$\text{W'Bval}$
        - 例子:
          - 1'b0
          - 8'hff
          - 8'd255
      
      
      
      #### 过程块
      
      - Initial块
      
        - 将只执行一次,第一次调用该单元(仅在测试中)
      
        - ```vhdl
          initial begin
          	a = 1’b0;
          	b = 1’b0;
          end
  • always块

    • 当敏感列表发生更改时,将对语句进行执行

      • 同步复位与异步复位:

        • ```vhdl
          //同步复位
          always @(posedge clock)

          if (!nreset)
              q <= 1’b0;
            else
              q <= d;
          

          //异步复位
          always @(posedge clock or negedge nreset)

          if (!nreset)
              q <= 1’b0;
          else if (load_enable)
              q <= d;
          
          
          - always块有两种
          
            - 时序
          
              - 由灵敏度列表中的时钟确定。
              
              - ```vhdl
            	  always @(posedge clock or negedge nreset)
                	if (!nreset)
                		q <= 1’b0;
                	else if (load_enable)
                		q <= d;
    • 组合

      • 描述纯组合逻辑,敏感列表没有时钟信号。

      • ```vhdl
        always @(a or b or c)

        out = a & b & c
        
            
        
        
        
        #### 赋值
        
        Verilog有三种类型的赋值:
        
        - 连续赋值
          - 在always块之外。
          
          - ```vhdl
            assign muxout = (sel&in1) l (~sel&ine);
            assign muxout = sel ? in1 : ine;
  • 阻塞过程赋值”=”

    • 时序执行。

    • ```vhdl
      // assume initially a=1;
      a = 2;
      b = a;
      // a=2; b=2

      - 非阻塞过程赋值"<="
        - 并行执行。
        
        - ```vhdl
          // assume initially a=1; 
          a <= 2;
          b <= a;
          // a=2; b=2
  • 要消除错误,请遵循以下规则:
    • 组合always块:使用阻塞赋值(=)
    • 时序always块:使用非阻止赋值(<=)
    • 不要将阻塞和非阻塞混合在同一always块
    • 不要在多个always块中为同一个变量赋值

层次化

  • 模块

    • 用来定义硬件模块

  • 实例

    • 引用不同级别的块

    • ```vhdl
      mux4 M0 (.out(outa),.in(a),.sel(sel));
      mux4 M1 (.out(outb),.in(b),.sel(sel));

        
          
      
      #### 系统任务
      
      - 系统任务用于提供模拟数据的接口
      
      - 以$\$\text{name}$语法标识
      
      - 打印任务:
      
        - $\text{\$display, \$strobe}$:语句执行后立即打印
      
        - $\text{\$monitor}$:每当其中一个参数发生更改时打印一次
      
        - 全部采用c语言的printf格式
      
          - ```vhdl
            $display(At %t Value of out is %b \n”,$time,out);

简单例子

Hello World

module main;
	initial
		begin
			$display("Hello world !”);
			$finish;
		end
	endmodule

组合逻辑

三种使用MUX的方法

  • 使用assign:

    • ```vhdl
      wire out;
      assign out = sel ? a : b;
      
      - 使用always块:
      
        - ```vhdl
          reg out;
          always  (a or b or sel)
          	if (sel)
          		out=a;
          	else
          		out=b;
  • 使用case语句:

    • ```vhdl
      reg out;
      always @ (a or b or sel)
      begin
          case (sel)
              1'b0: out=b;
              1'b1: out=a;
          endcase
      end
      
      
      
      
      
      #### 时序逻辑
      
      - D-Flip Flop:
      
        - ```vhdl
          reg q;
          always @(posedge clk)
          	q<= d;
  • 异步D-Flip Flop

    • ```vhdl
      reg q;
      always @(posedge clk or negedge reset_)
      if(~reset_)
          q<= 0;
      else
          q<= d;
      
      
      
      
      #### 算数运算
      
      - Verilog支持标准算数运算:
      
        - $+,-, *,<<($ shift left $),>>($ shift right $),$ etc
      
        - 使用$\{,\}$连接向量
      
          - ```vhdl
            assign a = 4'b1100;
            assign b = 4'b1010;
            assign c = {a,b}; //c=8'b11001010
  • 但是

    • Verilog将所有向量视为无符号二进制数
    • 为了做带符号操作,需要将reg/wire声明为signed:

      • ```vhdl
        wire signed [9:0] a,b;
        wire signed [19:0] result = a*b;
          - 为了使用带符号常熟,需要增加s:
        
        
        
        #### reg vs wire
        
        1.always块内部(时序和组合)只有reg可以用作LHS。
        2.对于assign语句,只能使用wire作为LHS。
        3.在initial块(Testbench)中,LHS上只能使用reg。
        4.实例化模块的输出只能连接到wire
        5.模块输入不能为reg
        
        正确案例:
        
        ```vhdl
        reg r;
        always @*
        	r = a & b;
wire w;
assign w = a & b;
reg r;
initial
	begin
		r=1'bo;
	   #1
		r =1'b1;
	end

错误案例:

module m1 (out)
	output out;
endmodule

reg r
m1_m1_instance(.out(r))
module m2(in)
	input in;
	reg in,
endmodule

Testbench构造

  • 定义clock:

    `define CLK_PERIOD 10
    initial
    	begin	//begins executing at time e
    		clk = 0;
    	end
    	
    always		//begins executing at time 0 and never stops
    	#(CLK_PERIOD/2) clk = ~clk;

Verilog FSM(有限状态自动机)实现

FSM

  • 4比特计数器

    • 4个输入:
      • clk:系统时钟
      • rst_n:低有效复位
      • act:激活信号
      • up_dwn_n:计数上移(正)或计数下移(负数)
    • 两个输出信号:
      • count:当前计数值
      • ovflw:溢出信号
  • 状态机的图示:

  • 定义每个状态:

    localparam IDLE  = 4'b0001;
    localparam CNTUP = 4'b0010;
    localparam CNTDN = 4'b0100;
    localparam OVFLW = 4'b1000;
  • 组合块

    • 计算下个状态:
    always @*
    	case (state)
    		IDLE: begin
    			if(act)
    				if(up_dwn_n)
    					next_state = CNTUP;
    				else
    					next_state = CNTDN;
    			else
    				next_state = IDLE;
    		end
    CNTUP: begin
    	if (act)
    		if(up_dwn_n)
    			if (count==(1<<COUNTER_WIDTH)-1)
    				next_state = OVFLW;
    			else
    				next_state = CNTUP;
    		else
    			if (count=='b0)
    				next_state=OVFLW;
    			else
    				next_state=CNTDN;
    			else
    				next_state=IDLE;
    	end

  • 时序块:

    • 定义状态寄存器

    • 定义计数寄存器

    • 最后给输出赋值

Testbench

  • 定义信号和参数:

  • 实例化状态机:

  • 设置初始值、值监视和时序重置:

  • 定义时钟:

  • 设置stimuli:

RTL编程风格

组织代码

  • 每个模块应位于单独的文件中

    • 将文件命名为$<\text{modulename}>.\text{v}$
    • 始终按名称(.dot()形式)连接模块。
  • 将每个输入/输出写在单独的一行上

  • 注释每个信号的作用。

  • 分开时序和组合逻辑

赋值

  • 在组合块($\text{always@}^\star$):
    • 始终使用阻塞(=)赋值。
    • 建议在敏感性列表中使用$\star$。
  • 在时序块($\text{always@posedge}$):
    • 始终使用非阻塞(<=)
    • 最好将每个触发器放在单独的always块中。
  • 永远不要从多个块中对同一个信号( LHS )赋值。

注意不要引起闩锁(latch)

  • 每个if后要跟else

  • 错误案例:

    module mux4to1 (out, a, b, c, d, sel);
    	output out;
    	input &b, c, d;
    	input [1:0] sel;
    	reg out;
    
    always @(sel or a ob or c or d)
    	case (sel)
    		2'b00: out = a;
    		2'b01: out = b;
    		2'b10: out = d;
    	endcase
    endmodule

坚持使用一种重置类型

  • 重置的目的是使您的设计进入一个众所周知的状态。

    • 无论是否需要,都希望每个触发器都可重置。

    • 我们通常使用异步重置:

      always @(posedge clock or negedge rst_)
      	if (!nreset)
      		state <= idle;
      	else
      		state <= next_state;
    • 但同步重置也没关系:

      always @(posedge clock)
      	if (!nreset)
      		state <= idle;
      	else
      		state <= next_state;
  • 只要确保不要在设计中混合使用

  • 在复位信号中不要使用逻辑(这一点没有完全理解)

    • 反例
    assignsomething = a && reset ;
    always@*
    	case (state)
    		1'b1011. if(b ll reset)
    			next_state = idle;

参数化设计

  • “Pretty code”是完全参数化的代码
  • 参数化的两种方法:’define, ‘include, and ‘ifdef
    • 编译器指令:
      • 将所有`define语句放在外部定义文件中。
    • 参数或局部参数
      • 参数可以通过实例化来重写
      • 局部参数更适合常量,例如FSM编码
  • 也可以使用generate语句,但要注意这些语句。
  • 始终使用硬编码值对FSM状态进行编码
    • 可以选择各种方法,如二进制码、灰度码、独热码等。

编写可读代码

一些有用的文档和参考资料

  • Chris Fletcher “Verilog: wire vs. reg
  • Greg Tumbush “Signed Arithmetic in Verilog 2001 Opportunities and Hazards”
  • NetFPGA wiki VerilogCodingGuidelines
  • MIT 6.111 Lectures http://web.mit.edu/6.111/www/f2007/
  • Stuart Sutherland, Don Mills “Standard Gotchas : Subtleties in the Verilog and SystemVerilog Standards That Every Engineer Should Know”