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里使用 直到当前块全部执行完,才统一更新左侧变量
组合逻辑: @(*) *表示敏感列表自动维护 一般配合阻塞赋值
时序逻辑: @(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)