verilog语言学习-HDLBits

基本逻辑:

and  a&b  与  同为1时输出1
or   a|b  或  有一个为1输出1
xor   a^b  异或  ab不同时输出1
nand   !(a&b)  与非  not and  a与b再取反
nor   ~(a|b)  或非  a或b再取反
xnor  a~^b  同或  ab相同时输出1
anotb  a&(!b)  a与非b  and not    

 

假如有多个元件,并且使用的是多重逻辑判断,那就要注意运算的先后顺序:

比如有四个输入abcd,那么接4-1与非门时,就要先对四个输入做与,然后整体的结果再非。从上面的元件图中也可以直观地理解这个过程。

  verilog语言和c语言的编写逻辑比较接近,但在核心思想上有些区别:在不使用代码块的情况下,verilog代码描述的是模块内部的关系,并不是对运算过程的描述。这就意味着,代码顺序无关紧要,爱写什么顺序就写什么顺序。如果需要代码有顺序应该怎么办呢?用always initial这样的代码块,代码块内部代码有顺序关系。

 

 

向量的位拓展:

用数字进行拓展的时候,必须写清楚位宽。比如{5{1'b1}}是合法的,{5{1}}就不行。

 

注意!仔细看,定义里的形参就和C语言一样,中间是逗号,不是分号!为了避免误会,可以写成C的形式避免写错。

以下是一个一位全加器的例子,可以直接拿来使用:

module add1(
  input a,b,        <--------是逗号,不是分号
  input cin,
  output sum,cout);


    assign sum=a^b^cin;
    assign cout=(a&b)|(a&cin)|(b&cin);
endmodule

从代码里可以看到,一个一位全加器有a b两个输入变量和cin进位变量,输出则是cout,指示是否需要进位,sum决定当前位;

因为sum实际上只决定当前位,所以可以采用三个输入异或,三个变量里有奇数个1,sum为1,反之为0.

cout则是进位,只要有至少两个输入为1,cout就是1,所以用三种情况或运算。

 

 

 

 

减法器的设计和加法器其实一样,只是在b的输入上增加了一个xor异或门,以及一位的符号变量sub。

这里我一开始遗漏了一处,和加法器不一样,第一个add16的cin输出是sub,不是0.

sub是0的时候,同普通的32位加法器,sub是1的时候,第一个加法器需要考虑sub作为cin也要一并加上,此外b的每一位都与1进行了异或。异或是指不一样为1,反之为0.

 

正确的代码:

module top_module(
    input [31:0] a,
    input [31:0] b,
    input sub,
    output [31:0] sum
);
//module add16 ( input[15:0] a, input[15:0] b, input cin, output[15:0] sum, output cout );
    wire [31:0] real_b;
    wire in_out;
    assign real_b= b^{32{sub}};
    add16 test1(.a(a[15:0]), .b(real_b[15:0]), .cin(sub), .cout(in_out), .sum(sum[15:0]));
    add16 test2(.a(a[31:16]), .b(real_b[31:16]), .cin(in_out), .cout(), .sum(sum[31:16]));
    
endmodule

我在一开始尝试时,尝试了很多次,加法部分正常,减法部分一直有问题。查阅资料后才发现,问题出在real_b这一行,real_b是b和sub的异或,但是我一开始用的是b^sub,这种用法有问题.题目里也提到了类似的内容。

  • a异或{16{b}}是将b复制16次,然后与a进行按位异或运算,输出结果是一个16位的向量。例如,如果a是16’h1234,b是1’b1,那么a异或{16{b}}的结果是16’hEDCB。
  • a异或b是将b扩展为16位,然后与a进行按位异或运算,输出结果也是一个16位的向量。但是,b的扩展方式取决于b的类型,如果b是无符号的,那么b会在高位补零;如果b是有符号的,那么b会在高位补符号位。例如,如果a是16’h1234,b是1’b1,那么a异或b的结果是16’h1235(无符号扩展)或者16’h9235(有符号扩展)

 

 

三种赋值:

assign 连续赋值    右侧的值改变后,左侧立刻更新

x=y   阻塞赋值,只在initial和always里使用,这一句执行后,右边的表达式立刻赋值给左侧,然后执行下一句

x<=y  非阻塞赋值 ,只在initial和always里使用 直到当前块全部执行完,才统一更新左侧变量

参考:assign、always/阻塞赋值与阻塞赋值 

 

组合逻辑: @(*) *表示敏感列表自动维护   一般配合阻塞赋值

时序逻辑:   @(posedge clock)  上升沿为敏感列表        @(clock) 任意时钟变化为敏感列表   clock是变量  一般配合非阻塞赋值

因为非阻塞赋值在条件达成后才更新,这样可以确保时序相关的逻辑不会乱掉

  注意:在同一个always块里,不要混用阻塞与非阻塞赋值。(但是实际使用了混用也能通过编译,代码的执行顺序可能会变乱。但一定不要给一个变量同时混用两种赋值方式)

 

三种赋值的区别:

module top_module(
    input clk,
    input a,
    input b,
    output wire out_assign,
    output reg out_always_comb,
    output reg out_always_ff   );
assign out_assign=a^b; always @(*) out_always_comb=a^b; always @(posedge clk) out_always_ff <=a^b; //这里用的是上升沿触发,所以实际电路里会慢一点赋值 endmodule
//省略了always语句本来该有的begin和end

 

 

利用生成语句把1位全加器拼接出100位全加器:

module top_module( 
    input [99:0] a, b,
    input cin,
    output [99:0] cout,
    output [99:0] sum );

    genvar i; // 定义一个循环变量i
    generate
        for (i = 0; i < 100; i = i + 1) begin: ripple // 给begin...end块起一个名字ripple
            if (i == 0) // 如果是第一个全加器,cin就是模块的输入cin
                add1 adder0 (.a(a[0]), .b(b[0]), .cin(cin), .cout(cout[0]), .sum(sum[0]));
             else // 如果不是第一个全加器,cin就是上一个全加器的cout
                add1 adder1 (.a(a[i]), .b(b[i]), .cin(cout[i-1]), .cout(cout[i]), .sum(sum[i]));
        end
    endgenerate
endmodule

//  1位全加器的定义
module add1(
    input a,b,
    input cin,
    output sum,cout);
    assign sum=a^b^cin;
    assign cout=(a&b)|(a&cin)|(b&cin);
endmodule

 

利用生成语句生成100位BCD码的行波加法器:

单行加法器的定义:

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 [99:0]c;
    genvar i; // 定义一个循环变量i
    generate
        for (i = 0; i < 100; i = i + 1) begin: ripple // 给begin...end块起一个名字ripple
            if (i == 0) // 第一个全加器
                bcd_fadd  adder0 (.a(a[3:0]), .b(b[3:0]), .cin(cin), .cout(c[0]), .sum(sum[3:0]));
             else // 如果不是第一个全加器,cin就是上一个全加器的cout
                 bcd_fadd  adder1 (.a(a[4*i+3:4*i]), .b(b[4*i+3:4*i]), .cin(c[i-1]), .cout(c[i]), .sum(sum[4*i+3:4*i]));
        end
    endgenerate
    assign cout=c[99];
endmodule

值得注意的是,BCD码是用4个二进制来表示一个10进制数,所以他本质上还是十进制数,在加法器内,输入a、b和输出sum都是400位长。但是cout只需要1位。此外,每次输入的是4位,不是1位。

为了方便内部计算,增加了一个wire 变量c,用来存储中间的cout变量。记得最后把c[99]赋值给cout,这才是需要的cout输出。 

 

 

---------------------

补码加法器:

补码加法器

-----------

一个奇葩卡诺图:

 

module top_module(
    input a,
    input b,
    input c,
    input d,
    output out  ); 
    assign out=(a^b)&(c~^d) | (a~^b)&(c^d); //其实只需要这一句
endmodule

 ----------------

用二选一实现四选一:Bugs mux4 - HDLBits (01xz.net)

module top_module (
    input [1:0] sel,
    input [7:0] a,
    input [7:0] b,
    input [7:0] c,
    input [7:0] d,
    output [7:0] out  ); //

    //module mux2 (input sel,  input [7:0] a,  input [7:0] b,  output [7:0] out
    wire [7:0] test0,test1;
    reg [7:0] res;
    mux2 t1 (.sel(sel[0]),.a(a),.b(b),.out(test0) );
    mux2 t2 (.sel(sel[0]),.a(c),.b(d),.out(test1) );
    mux2 t3 (.sel(sel[1]),.a(test0),.b(test1),.out(res));
    assign out=res;
    
endmodule

给出了二选一,实现四选一,这个思路还是需要记住的,不难。注意的是第一次选择都用的是sel[0]不需要用sel[1]。我之前在纠结四选二时想分别用sel两位,导致总有一位不对。

 

---------------------

时序逻辑:

d触发器:只能在时序逻辑里赋值,每次达到条件时,d触发器的q更新值。

8位版的d触发器,单位的d触发器只需要1位。

上升沿:posedge  下降沿 negedge

module top_module (
    input clk,
    input [7:0] d,
    output [7:0] q
);
    always @(posedge clk)begin
        q<=d;
    end
endmodule

 

假如现在又多了一个reset信号,这两种代码会产生什么区别:

//代码1
always @(posedge clk or posedge reset)begin
        if(reset) q<=8'b00110100;
        else q<=d;
 end




//代码2
always @(posedge clk)begin
        if(reset) q<=8'b00110100;
        else q<=d;
 end

代码1中,reset信号直接连到复位端,只要reset有信号,不需要和时钟同步就可以改变信号,这是异步复位。

代码2中,reset信号产生后,要延迟一个时刻等到时钟触发有效,才会改变输出q,这是同步复位,因为它和d作为组合逻辑一块送入。当然,缺点就是需要额外的一个周期才能复位。

-----------

latch 即锁存器
特点:它是电平敏感。也就是说不依赖边沿,always里用电平敏感列表
但是它仍存在时序,所以赋值要用非阻塞赋值 <=
效果:禁用时锁住当前值

 D型锁存器,ena有效时,Q可变,否则Q锁定

但是锁存器不基于时钟信号,所以可以用assign直接定义:

module top_module (
    input d, 
    input ena,
    output q);
    assign q = ena ? d : q; 
endmodule

 

 

不是时序逻辑的部分就不要写在时序逻辑内

 

 

 如图所示,一个包含两个复用器和d触发器的子模块:

module top_module (
    input clk,
    input w, R, E, L,
    output Q
);
    reg l1;
    
    always @(*) begin
        l1=E?w:Q;
    end
    
    always @(posedge clk)begin
        if(L==0) Q<=l1;
        else Q<=R;
    end
endmodule

不是时序逻辑部分的定义就不要写在时序逻辑内。以上面这段代码为例,在输入d触发器前的这一段,需要用组合逻辑而不是时序逻辑,不要图方便把二者都写到时序逻辑里。

 ------------------

利用时序逻辑检测信号的上升沿和下降沿:

module top_module (
    input clk,
    input [7:0] in,
    output [7:0] anyedge
);
    reg [7:0]prev;
    always @(posedge clk)begin
        anyedge <= (in & ~prev)| (~in & prev); 
        prev <= in; 
    end
endmodule

 

 下降沿捕获:Edgecapture - HDLBits (01xz.net)

捕获是指输出收到输入信号的下降沿后一直为0,直到被reset复位

module top_module (
    input clk,
    input reset,
    input [31:0] in,
    output [31:0] out
);
    reg [31:0] out_reg; 
    reg [31:0] prev_in;

    always @(posedge clk) begin
        if (reset) begin
            out_reg <= 32'b0;
            prev_in <= in;  // 复位时记录当前输入值
        end 
        else begin
            prev_in <= in;
            out_reg <= out_reg | (prev_in & ~in);  // 捕获下降沿
        end
    end
    
    assign out=out_reg;
endmodule
//捕获下降沿并一直保存,除非reseet开着,reset关了以后,要继续保存值

 

verilog不支持同时在条件里写一个信号的上升沿和下降沿,也不支持在多个代码块里修改一个变量的值(比如assig和时序逻辑中都尝试赋值一个变量)。这两点要注意。

posedge clk or negedge clk是非法写法。

双边检测器:输入信号有上升沿和下降沿都起效,但是说实话,我自己都不清楚为什么这么写就对了。

module top_module (
    input clk,
    input d,
    output q
);
    reg q_pos,q_neg;
   /* always @(clk) begin
        delay1<=d;
    end*/
    //assign q=delay1;
    
    always @(posedge clk)begin
        q_pos<=d;
    end
    
    always @(negedge clk)begin
        q_neg<=d;
    end
    assign q=clk?q_pos:q_neg;
endmodule

 

 

Countbcd - HDLBits (01xz.net)  用bcd计数器做一个1000分频器,做了半天死活做不对

一开始我的错误想法:

module cacu1 (
    input clk,
    input reset,  
    input enable,
    output ena,// Synchronous active-high reset
    output [3:0] q);
    always @(posedge clk)begin
        if(reset) q<=0;
        else if(enable)begin
            if(q==4'b1001)begin
                ena<=1;
                q<=0;
            end
            else if(q<4'b1001) begin
                ena<=0;
                q<=q+4'b0001;
            end
        end    
    end
endmodule


module top_module (
    input clk,
    input reset,   // Synchronous active-high reset
    output [3:1] ena,
    output [15:0] q);  //15:12  11:8  7:4  3:0
    
    cacu1 test1(.clk(clk),.reset(reset),.enable(1),.q(q[3:0]),.ena(ena[1]));
    cacu1 test2(.clk(clk),.reset(reset),.enable(ena[1]),.q(q[7:4]),.ena(ena[2]));
    cacu1 test3(.clk(clk),.reset(reset),.enable(ena[2]),.q(q[11:8]),.ena(ena[3]));
    cacu1 test4(.clk(clk),.reset(reset),.enable(ena[3]),.q(q[15:12]),.ena());

endmodule 

//这段代码是错的

这段代码有个严重问题,就是如果用ena来作为进位使能,那么要保证和题目要求一样,输出9时同步输出ena进位,那么在输出8时就要准备输出ena,那么判断条件就变成了8,而不是9.这样一来,个位的问题解决了,后面的却全是问题:用阻塞输出会导致ena不能输出,用非阻塞赋值又会导致乱序。

所以之后我换了个思路:

    cacu1 test1(.clk(clk),.reset(reset),.enable(1),.q(q[3:0]),.ena());
    cacu1 test2(.clk(clk),.reset(reset),.enable(ena[1]),.q(q[7:4]),.ena());
    cacu1 test3(.clk(clk),.reset(reset),.enable(ena[2]),.q(q[11:8]),.ena());
    cacu1 test4(.clk(clk),.reset(reset),.enable(ena[3]),.q(q[15:12]),.ena());
    assign ena[1]=(q[3:0]==4'b1001)?1:0;
    assign ena[2]=(q[7:4]==4'b1001 && ena[1])?1:0;
    assign ena[3]=(q[11:8]==4'b1001 && ena[1] && ena[2])?1:0;

把ena的判断拆出来,直接用q的值来判断,bcd计数器内部的代码不管了。这样一来就不用再纠结进位的问题了。

需要注意的是,十位进位的条件是个位为9,百位进位的条件是十位和个位都要为9.。。以此类推。

 -------------------------

 

时钟:用bcd计数器做一个12小时制的时钟:

module sixty(
    input clk,
    input reset,
    input ena,
    output [7:0] q);
    always @(posedge clk)begin
        if(reset) q<=8'b00000000;  
        else if(ena) begin
            
            if(q[3:0]<4'b1001)begin
                q[3:0]<=q[3:0]+4'b0001;
            end
            
            else begin //有进位
                q[3:0]<=4'b0000;
                if(q[7:4]<4'b0101)begin
                    q[7:4]<=q[7:4]+4'b0001;
                end
                else begin
                    q[7:4]<=4'b0000;
                end
            end                                  
                   
        end
    end   
endmodule  //分和秒用这个

module hou(
    input clk,
    input reset,
    input ena,
    output reg up,
    output [7:0] q);
    always @(posedge clk)begin
        if(reset) q<=8'b00010010;      //最大值12,所以处理会不一样
        else if(ena) begin
            if(q[7:4]==4'b001 && q[3:0]==4'b0010)begin
                q[7:4]<=0;
                q[3:0]<=4'b0001;
                up<=1;
            end
            else if(q[7:4]==0 && q[3:0]<4'b1001)begin
                q[3:0]<=q[3:0]+4'b0001;
                up<=0;
            end
            else if(q[7:4]==0 && q[3:0]==4'b1001)begin
                q[7:4]<=4'b0001;
                q[3:0]<=0;
                up<=0;
            end
            else if(q[7:4]==4'b0001 && q[3:0]<=4'b0001)begin
                q[3:0]<=q[3:0]+4'b0001;
                up<=0;
            end
            
        end
    end   
endmodule

module top_module(
    input clk,
    input reset,
    input ena,
    output pm,
    output [7:0] hh,
    output [7:0] mm,
    output [7:0] ss); 
    
    wire sec2min,min2hou;
    wire hour2pm;
    sixty second(.clk(clk),.reset(reset),.ena(ena),.q(ss));
    sixty minutes(.clk(clk),.reset(reset),.ena(sec2min),.q(mm));
    hou hour(.clk(clk),.reset(reset),.ena(min2hou),.up(hour2pm),.q(hh)); //它的up也不能留下
    
    assign sec2min=(ss==8'b01011001)?1:0;
    assign min2hou=(mm==8'b01011001 && ss==8'b01011001)?1:0; //别忘了分针时针均为59才进位
    always @(posedge clk) begin
        if (hh==8'b00010001 && mm==8'b01011001 && ss==8'b01011001) 
        pm <= !pm;
    end //不再造成组合逻辑环路
   

endmodule

在一开始,我遇到了一个问题,就是选择pm的值时,我用的是

   //assign pm = (hh==8'b00010010 && mm==8'b01011001 && ss==8'b01011001)?(!pm):pm;

这条语句逻辑上没有问题,但电路设计上有明显问题:pm的值会依赖它的上一个值,导致在仿真过程中形成了逻辑环路,无限回环。

解决方法就是改成always 逻辑,或者用一个中间变量,避免环路的发生。

 --------------------------

 1-12计数器:Exams/ece241 2014 q7a - HDLBits (01xz.net)

这道题题干其实也很简单,给了一个4位的计数器子模块,外层再放一个总模块控制,每到12就重置。就是线接的比较奇怪。外层的Q和内层的Q连接,都是输出。c_enable三件套是输出,但是要接到内层的子模块上。enable可以让计数器正常工作,load则是强制让计数器的Q等于D。也就是说,外层在计数器值小于等于12时正常工作,等于12时,外层使能load信号,让计数器内部值重新变成c_d,即0001即可。

module top_module (
    input clk,
    input reset,
    input enable,
    output [3:0] Q,
    output c_enable,
    output c_load,
    output [3:0] c_d
); 

    reg [3:0] temp;

    //4-bit计数器的控制信号
    assign c_enable = enable;
    //带复位和置位,
    assign c_load   = reset | (Q == 4'd12 & enable == 1'b1);
    assign c_d      = 4'b1;

    count4 counter1
    (
        .clk(clk),
        .enable(c_enable),
        .load(c_load),
        .d(c_d),
        .Q(Q)
    );

endmodule

 

 

 -------------------------------------

对于有限状态机:

 状态转移的部分放在组合逻辑,外部的复位信号则放在时序逻辑。

根据要求的同步复位和异步复位不同,异步复位需要在时序逻辑的敏感条件里增加reset信号的上升沿,同步不需要。

 一个异步复位状态机,上面的out表示在该状态下应该输出什么。

module top_module(
    input clk,
    input areset,    // Asynchronous reset to state B
    input in,
    output out);//  

    parameter A=0, B=1; 
    reg state, next_state;

    always @(*) begin    // This is a combinational always block
        // State transition logic
        if(state==A)begin           
            if(in)begin
                next_state=A;
            end
            else begin
                next_state=B;
            end
        end
        
        else begin
            if(in)begin
                next_state=B;
            end
            else begin
                next_state=A;
            end
        end
    end

    always @(posedge clk, posedge areset) begin    // This is a sequential always block
        // State flip-flops with asynchronous reset //异步,立刻变
        if(areset) 
            state=B;
        else
            state=next_state;
    end
    
    assign out=(state==A)?0:1;  //根据提示,把out放在外面定义
    // Output logic
    // assign out = (state == ...);

endmodule

 -----------------------------------

Exams/2014 q3fsm - HDLBits (01xz.net) 这一道题,说实话花了我很长时间

我一开始的思路:

module top_module (
    input clk,
    input reset,   // Synchronous reset
    input s,
    input w,
    output z
);
    parameter a=1,b=2;
    reg [1:0] state,next_state;   
    reg [1:0] count,index;
    //int count,index;
    
    always @(*)begin
        if(state==a && s)
            next_state=b;
        else if(state==a &&s==0)
            next_state=a;
    end
    
    always @(posedge clk)begin
        if(reset)begin
            state=a;
            count=0;
            index=0;
            //z<=0;
        end
        else begin
            state=next_state;
            if(state==b)begin
                index=index+1;
                if(w==1)      count=count+1; 
            end
            if(index==2'b11)begin    
                count=0;
                index=0;
            end
           // if(index==2'b11 && count==2'b10)  z<=1;
            //else z<=0;  //写到里面延迟太大了 两个周期
        end
    end
    
    assign z=(state==b&&count==2'b10)?1:0;
    //assign z=((index==2'b10&&count==2'b01&&w)||(index==2'b10&&count==2'b10&&w==0))?1:0; //时长不对
endmodule

  一开始的思路就是跟着题目给出的状态机走,只设置AB两个状态,然后在时序里计数,每次循环记到3就清零,在外面设置w为1的计数到2就输出1.但是这么设计,输出总是慢一个周期。

  后来看到了别人的代码:

module top_module (
    input clk,
    input reset,   // Synchronous reset
    input s,
    input w,
    output z
);
    parameter A = 0, B = 1, C = 2, D = 3;
    reg [1:0] count;
    reg [2:0] state, next_state;

    always @ (*) begin
        case (state)
            A: next_state = s ? B : A;
            B: next_state = C;
            C: next_state = D;
            D: next_state = B;
            default: next_state = A;
        endcase
    end

    always @ (posedge clk) begin
        if (reset) state <= A;
        else state <= next_state;
    end

    always @ (posedge clk) begin
        case (state)
            B: count <= w;
            C: count <= w ? count + 1 : count;
            D: begin
                count <= w ? count + 1 : count;
                // count <= 1'b0;
            end
            default: count <= 0;
        endcase
    end

    assign z = ((state == B) && (count == 2));
endmodule

  思路改变其实不大。开始我也想过用更多状态,但是当时在纠结后面输入0 1时需要几个状态。这里的代码则是很干脆地就分了 B C D三个状态,不管输入什么都向下一个状态转移。然后在时序逻辑里再分别计算加几个w。在最后的状态判断里,也不再使用循环数==3这样的条件,直接用state==B即可。我感觉这里可能是我的代码有问题的原因之一。

 

 

参考内容: 

常见Verilog运算符(逻辑运算符、按位运算符、缩位运算符、迭代连接运算符、移位运算符)_verilog位运算符和逻辑运算符-CSDN博客

verilog语法2:assign、always/阻塞赋值与阻塞赋值 - 知乎 (zhihu.com)

 

posted @ 2024-03-29 11:35  namezhyp  阅读(94)  评论(0编辑  收藏  举报