verilog语法进阶


前言

  本文是针对verilog基础语法做进一步的学,通过网站HDLbits中的代码和例子来展开本文要讨论的内容。HDLbits是一个多伦多大学的学生做的网站,该学生把课程中的习题汇总起来,一共有178道题目,其中包括verilog的组合逻辑、时序逻辑、状态机、testbench等等。对于FPGA初学者,很有必要借助该网站来学习verilog。


素材来源:HDLbits

一、always块(always block)

  在always块中,可以是时序逻辑,也可以是组合逻辑。时序逻辑和组合逻辑的区别在于always敏感列表中有没有时钟信号参与,有时钟信号参与的是时序逻辑,没有时钟信号参与的是组合逻辑。wire使用assign赋值,reg型适用于过程语句赋值,在always过程块中要求被赋值变量必须为reg型。

在这里插入图片描述

module top_module(
    input a, 
    input b,
    output wire out_assign,
    output reg out_alwaysblock
);
assign out_assign = a & b;
always@(*)begin
    out_alwaysblock = a & b;
end 
endmodule

  在always中使用组合逻辑,与assign连续赋值作用是一样的,如下图1所示中out_assign,out_alwaysblock输出的波形是同步并且一致的。由此说明,always中使用组合逻辑,实现的功能和assign连续赋值实现的功能是一样的。
  注意:如上代码和如下图1仿真所示,always和assign代码之间是并行运行的,assign与always放置位置不会影响程序最终的结果。

在这里插入图片描述

图1. assign_always_block仿真

  在下面模块中加入有时钟信号的always模块,从图2仿真中out_assign,out_always_comb输出信号,可以发现always的敏感列表(*)没有时钟,最终实现的功能和assign是一致的。但是在加入时钟信号的always块中,设置信号对时钟上升沿有效(posedge clk),输出的out_always_ff信号是有延迟的,要等到时钟上升沿开始才进行非阻塞赋值<=,并不是和assign与always(*)一样马上给出结果。

在这里插入图片描述

图2. assign_always_clock仿真
module top_module(
    input clk,
    input a,
    input b,
    output wire out_assign,
    output reg out_always_comb,
    output reg out_always_ff   );

always@(*)begin
    out_always_comb = a ^ b;  
end 

always@(posedge clk)begin
    out_always_ff <= a ^ b;
end 

assign out_assign = a ^ b;
endmodule

在这里插入图片描述

图3. assign_always_sequential仿真

二、if语句

  if语句实现的电路是选择器,out输出受条件condition控制,condition为真的时候out=x,condition为假的时候out=y。

在这里插入图片描述

图4. 2-1选择器

  以下两种写法,always(*)组合逻辑与三目运算实现的功能是一样的。

always @(*) begin
    if (condition) begin
        out = x;
    end
    else begin
        out = y;
    end
end
assign out = (condition) ? x : y;

  问题:构建一个2-1选择器,在a和b之间进行选择。如果sel_b1和sel_b2都为true,则选择b。否则,选择a。分别使用assgign和always(*)实现。

在这里插入图片描述

module top_module(
    input a,
    input b,
    input sel_b1,
    input sel_b2,
    output wire out_assign,
    output reg out_always   ); 

always@(*)begin
    if(sel_b1 & sel_b2)begin
        out_always = b;
    end 
    else begin
        out_always = a;
    end 
end 

assign out_assign = (sel_b1 & sel_b2)? b:a;
endmodule

在这里插入图片描述

图5. 2-1选择器仿真

  在编写代码之前,要提前思考一下,你需要一个什么电路,verilog最终的目的是将代码综合成门级网表。在编写代码的时候,使用if或者case语句一定要把所有条件考虑周全,什么条件输出什么结果,如果条件考虑不周全就会产生毛刺(latch)。在if和case中,要使用else和default把其他条件包括进来,这是同学们在编写代码的时候容易忽略的地方,这与编写c、c++、python、java软件代码不一样的地方。

在这里插入图片描述

图6. 2-1选择器应用

  以下是一个考虑不周全的代码示范。

always @(*) begin
    if (cpu_overheated)
       shut_off_computer = 1;
end

always @(*) begin
    if (~arrived)
       keep_driving = ~gas_tank_empty;
end

  从仿真中可以发现,cpu_overheated信号不管是高电平还是低电平,shut_off_ computer的信号始终为高电平。我们想实现的功能是,如果cpu的温度过高,那么就关掉电脑,如果cpu的温度是正常,就不关电脑。仿真给我们的结果是不管cpu温度是否正常都关掉电脑,这是不符合现实情况的。arrived信号和gas_tank_empty的信号共同影响keep_driving信号,实现的功能是,如果没有到达目的地,并且车的油箱还有油,那么就可以继续保持开车这个状态。如果没有到达目的,并且油箱里面没有油,也会停止保持开车这个状态。如果到达目的地,不管油箱里面是否有油,停止保持开车这个状态。仿真结果显示并没有实现这一功能,造成这样的情况主要是我们没有把所有的情况考虑周全。

在这里插入图片描述

图6. 条件考虑不周全功能仿真

  以下是上述考虑不周全代码修改过后的代码。

module top_module (
    input      cpu_overheated,
    output reg shut_off_computer,
    input      arrived,
    input      gas_tank_empty,
    output reg keep_driving); 

always @(*) begin
     if (cpu_overheated)begin
        shut_off_computer = 1;
     end 
     else if(~cpu_overheated)begin
         shut_off_computer = 0;
     end 
     else begin
         shut_off_computer = shut_off_computer;
     end      
 end

 always @(*) begin
     if (~arrived)begin
        keep_driving = ~gas_tank_empty;
     end 
     else if(arrived)begin
         keep_driving = 0;
     end
     else begin
         keep_driving = keep_driving;
     end
 end

endmodule

  我们在条件if语句中添加了其他条件,并且也把else的情况考虑进来,最后实现了我们想要得结果,如下图7功能仿真所示。

在这里插入图片描述

图7. 条件考虑周全功能仿真

三、case语句

  case语句实现的功能和if是一致的,在case语句中的default和if语句中的else是一样的。要注意一点,如果冒号(:)后面有多条语句,需要用begin和end将代码块包裹起来。

always @(*) begin    
    case (in)
      1'b1: begin 
               out = 1'b1;  
            end
      1'b0:    out = 1'b0;
      default: out = 1'bx;
    endcase
end

  问题:在有大量条件的情况下,case语句比if语句更方便。所以,在这个练习中,创建一个6-1的多路选择器。当sel在0到5之间时,选择相应的数据输入,否则默认条件输出0。数据输入和输出都是4位宽。编写程序时注意毛刺。

module top_module ( 
    input [2:0] sel, 
    input [3:0] data0,
    input [3:0] data1,
    input [3:0] data2,
    input [3:0] data3,
    input [3:0] data4,
    input [3:0] data5,
    output reg [3:0] out);

always@(*) begin  
     case(sel)
         3'd0:	 out = data0;
         3'd1:	 out = data1;
         3'd2:	 out = data2;
         3'd3: 	 out = data3;
         3'd4:	 out = data4;
         3'd5:	 out = data5;
         default:out = 1'd0 ;
     endcase
 end

endmodule

在这里插入图片描述

图8. 6-1选择器case仿真

  问题:在这个问题中,我们要实现优先编码器,需要找出信号比特为1的位置,例如,给定输入8’b10010000的8位优先级编码器将输出3’d4,因为[4]位是第一个高的位。注意比特的位置是从0开始编号。如果信号中没有比特1,默认输出的位置为0。

module top_module (
    input [3:0] in,
    output reg [1:0] pos);
    
always@(*)begin
   case(in)
       4'b0000:	pos = 2'd0;
       4'b0001:	pos = 2'd0;
       4'b0010:	pos = 2'd1;
       4'b0011:	pos = 2'd0;
       4'b0100:	pos = 2'd2;
       4'b0101:	pos = 2'd0;
       4'b0110:	pos = 2'd1;
       4'b0111:	pos = 2'd0;
       4'b1000:	pos = 2'd3;
       4'b1001:	pos = 2'd0;
       4'b1010:	pos = 2'd1;
       4'b1011:	pos = 2'd0;
       4'b1100:	pos = 2'd2;
       4'b1101:	pos = 2'd0;
       4'b1110:	pos = 2'd1;
       4'b1111:	pos = 2'd0;
       default:	pos = 2'd0;
   endcase
end 
endmodule

在这里插入图片描述

图9. 优先编码器仿真

四、casez语句

  在章节三中种使用case,实现4位的优先编码器,需要17个情况,包括默认条件default。如果信号是8位,信号的组合情况是2的8次方256种,要把每种情况都考虑,需要编写256个情况。如果我们使用casez可以将256中情况减少至9种情况,其中包括默认default项。casez它将值为z的位视为不关心的项,如下面的代码所示。
  注意:如果把8’bzzzzzz10换成8’bzzzzzz1z是不行的,因为8’bzzzzzz11、8’bzzzzzz10都会匹配到8’bzzzzzz1z项。

module top_module (
    input [7:0] in,
    output reg [2:0] pos);
    
always@(*)
    begin
        casez(in[7:0])
            8'bzzzzzzz1: pos = 3'b000;
            8'bzzzzzz10: pos = 3'b001;
            8'bzzzzz100: pos = 3'b010;
            8'bzzzz1000: pos = 3'b011;
            8'bzzz10000: pos = 3'b100;
            8'bzz100000: pos = 3'b101;
            8'bz1000000: pos = 3'b110;
            8'b10000000: pos = 3'b111;
            default: pos = 3'b000;
        endcase
    end
endmodule

  经过测试,casex和casez的用法是一致的,只是要把z改为x。

module top_module (
    input [7:0] in,
    output reg [2:0] pos);
    
always@(*)
    begin
        casex(in[7:0])
            8'bxxxxxxx1: pos = 3'b000;
            8'bxxxxxx10: pos = 3'b001;
            8'bxxxxx100: pos = 3'b010;
            8'bxxxx1000: pos = 3'b011;
            8'bxxx10000: pos = 3'b100;
            8'bxx100000: pos = 3'b101;
            8'bx1000000: pos = 3'b110;
            8'b10000000: pos = 3'b111;
            default: pos = 3'b000;
        endcase
    end
endmodule

  也可以在casez和casex中用?代替z或者x,如下代码所示。

module top_module (
    input [7:0] in,
    output reg [2:0] pos);
    
always@(*)
    begin
        casex(in[7:0])
            8'b???????1: pos = 3'b000;
            8'b??????10: pos = 3'b001;
            8'b?????100: pos = 3'b010;
            8'b????1000: pos = 3'b011;
            8'b???10000: pos = 3'b100;
            8'b??100000: pos = 3'b101;
            8'b?1000000: pos = 3'b110;
            8'b10000000: pos = 3'b111;
            default: pos = 3'b000;
        endcase
    end
endmodule

在这里插入图片描述

图10. casez仿真

  问题:为游戏构建一个电路来处理来自PS/2键盘的扫描代码。给定接收到的最后两个字节的扫描码,您需要指示是否按下了键盘上的哪一个方向键。

在这里插入图片描述

表1. 按键code对应表
module top_module (
    input [15:0] scancode,
    output reg left,
    output reg down,
    output reg right,
    output reg up); 
    
always @(*) begin
    up = 1'b0   ; 
    down = 1'b0 ; 
    left = 1'b0 ; 
    right = 1'b0;
    case (scancode)
        16'he06b: left  = 1'b1;
        16'he072: down  = 1'b1;
        16'he074: right = 1'b1;
        16'he075: up    = 1'b1;
        default:;
    endcase
end
endmodule

在这里插入图片描述

图11. PS/2按键仿真

五、三目运算(ternary conditional operator)

  问题:求出四个输入中最小的值,将最小值赋值给输出。
  注意:输入的信号位宽是8位。

module top_module (
    input [7:0] a, b, c, d,
    output [7:0] min);

wire [7:0]	inter1;
wire [7:0]	inter2;

assign inter1 = a > b ? b : a					 ;
assign inter2 = c > d ? d : c					 ;
assign min 	  = inter1 > inter2 ? inter2 : inter1;
        
endmodule

在这里插入图片描述

图12. 求最小值仿真

六、递减运算符(reduction)

  奇偶校验(Parity Check)是一种校验代码传输正确性的方法。根据被传输的一组二进制代码的数位中“1”的个数是奇数或偶数来进行校验。通过递逐位异或就可以判断出信号中“1”是奇数还是偶数。

module top_module (
    input [7:0] in,
    output parity); 
    
assign parity = ^in;
endmodule

  我们还可以使用&a判断信号a是不是最大,通过|a判断a信号是不是最小。
  问题:递减符&、|、^构建一个有100位宽的输入信号的组合电路。

module top_module( 
    input [99:0] in,
    output out_and,
    output out_or,
    output out_xor );
    
assign out_and = &in;
assign out_or  = |in;
assign out_xor = ^in;
endmodule

  1. Test AND gate
    在这里插入图片描述
图13. AND门仿真
  1. Test OR and XOR gates
    在这里插入图片描述
图14. OR and XOR门仿真

七、for循环语句

  问题:使用for循环语句对100位宽的向量位逆序排列,代码如下。

module top_module( 
    input [99:0] in,
    output [99:0] out);

    integer i;
    always@(*)begin
        for(i=0;i<100;i=i+1)begin
            out[99-i] = in[i] ;
        end
    end 
endmodule

  问题:统计信号中位为1的个数。

module top_module( 
    input [254:0] in,
    output [7:0] out);
    
    integer i;
    always@(*)begin
        out = 7'd0;
        for(i=0; i<255; i = i+1)begin
            if(in[i] == 1'b1)begin
               out = out + 1'd1;  
            end
        end  
    end 
endmodule

在这里插入图片描述

图15. for计数比特1仿真

  问题:使用for循环创建100位的多位加法器,有进位信号。在2位的全加器中,要考虑有没有进位,如表2所示。

  1. 当A、B为0时,和S为0,进位C为0(没有进位);
  2. 当A为0,B为1,和S为1,进位C为0(没有进位);
  3. 当A为1,B为0,和S为1,进位C为0(没有进位);
  4. 当A为1,B为1,和S为0,进位C为1(产生进位);

在这里插入图片描述

表2. 2位全加器真值表
module top_module( 
    input [99:0] a, b,
    input cin,
    output [99:0] cout,
    output [99:0] sum);
    
    integer i;    
    always @(*)begin
        for(i = 1;i<100;i++)begin
            sum[i]  = a[i] ^ b[i] ^ cout[i-1];
            cout[i] = a[i] & b[i] | a[i] & cout[i-1] | b[i] & cout[i-1];
        end
    end
    
    assign sum[0]  = a[0] ^ b[0] ^ cin					  ;
    assign cout[0] = a[0] & b[0] | a[0] & cin | b[0] & cin;
endmodule

八、实例化多个模块(generate)

  问题:实例化多个BCD模块,使用generate和for循环实现。
  注意:generate 需要使用genvar类型变量。在for循环中begin后需要加上标识符比如gen,其他标识符也可以,不然会报错。

module bcd_fadd (
    input [3:0] a,
    input [3:0] b,
    input     cin,
    output   cout,
    output [3:0] sum );
module top_module( 
    input [399:0] a, b,
    input cin,
    output cout,
    output [399:0] sum);
    
    wire [100:0] cin_inter;
    genvar i		      ;
    
    generate 
        for(i=1;i<=100;i=i+1)
            begin : gen
                bcd_fadd u_bcd_fadd (
                    .a		(a[(i*4-1)-:4])	,
                    .b		(b[(i*4-1)-:4])	,
                    .cin	(cin_inter[i-1]),
                    .cout	(cin_inter[i])	,
                    .sum	(sum[(i*4-1)-:4])); 
               end 
     endgenerate
     
     assign cin_inter[0]=cin		   ;
     assign cout		=cin_inter[100];
endmodule

总结

  以上就是verilog进阶使用的例子、代码、仿真。后期文章将延续使用HDLbits网站内容,做为课程的内容来源。后期将推出verilog中的组合电路,敬请期待。谢谢你的观看!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值