Verilog学习笔记 Created by Chen Lekai 2019/12/29
1 基本语法(可综合Part) 1.1 基本元件 1 2 3 4 5 6 7 input x1,x2,x3;output answer;and (answer,x1,x2,x3);or (answer,x1,~x2,x3);not (answer,x1);xor (answer, x1, x2);xnor (answer, x1, x2);
1.2 modeul 1 2 3 module modeulname(var1,var2);endmodule
modeulname的括号内应包含所有input和output变量。
1.3 assign 1 2 3 assign g=(x1&x2)|(~x3&x4);assign h=(~x1&x2)|(~x3&x4);assign f=g&h;
主要用于简化表达式。
1.4 always 1 2 3 4 5 6 7 8 9 10 module xxx(x1,x2,s,f); input x1,x2,s; output f; reg f; always @(x1 or x2 or s) if (s==0 ) f=x1; else f=x2; endmodule
always的@后称为敏感事件列表 ,只要其中的任何一个变量改变,便会执行语句块内的内容,可以简化为如下形式:
1 2 3 4 5 6 7 8 9 module xxx(x1,x2,s,f); input x1,x2,s; output reg f; always @(x1,x2,s) if (s==0 ) f=x1; else f=x2; endmodule
进一步简化:
1 2 3 4 5 6 7 8 9 module xxx(x1,x2,s,f); input x1,x2,s; output reg f; always @* if (s==0 ) f=x1; else f=x2; endmodule
使用always@*替代主要是为了防止敏感变量太多,防止漏写。@*包含一切可能改变结果变量的敏感变量,在本例中,x1、x2会改变f的值,因此属于敏感变量列表。s在判断语句中,也会改变结果,所以也属于敏感变量列表。
1 2 3 4 5 always @(posedge clk, negedge clear) if (clear) qout=0 ; else qout=in;
always也可以不加@,就相当于C++中的while(true)循环。如果加了就是有判断条件的while循环。
注意事项:
不要在不同的always块内为同一个变量赋值。
不要在同一个always块内同时使用阻塞赋值(=)和非阻塞赋值(<=)。
使用always块描述组合逻辑时使用阻塞赋值(=),在使用always块描述时序逻辑时使用非阻塞赋值(<=)。简单理解就是,在电平敏感的always块内使用阻塞赋值,在边沿敏感的always块内使用非阻塞赋值。
任何在always块内“被赋值的变量”都必须是寄存器型(reg)。即<=或=左边的信号,必须是reg型。
always的敏感列表中可以同时包括多个电平敏感事件,也可以同时包括多个边沿敏感事件,但不能同时有电平和边沿敏感事件。另外,敏感列表中,同时包括一个信号的上升沿和它的下降沿敏感事件也是不允许的,因为这两个事件可以合并为一个电平事件。电平触发比时钟沿触发更容易受到干扰,在高速时容易受干扰,不够好(解决方法见:https://zhidao.baidu.com/question/1512023017981552420.html)。
always不可嵌套使用。
一个语句块内多个非阻塞赋值是并行执行的(顾名思义),而阻塞赋值则是顺序执行。
1.5 reg&wire reg 使用过程赋值语句,wire 使用连续赋值语句,reg 和wire 在赋值时实质上表现相同,只不过reg 只能在always 和initial 中赋值,而wire 只能被assign 连续赋值。即一个直接赋值,另一个需要条件 。
输入端口可以用wire/reg 驱动(连信号),但是输入端口只能是wire ;输出端口只能驱动wire ,但可以是wire/reg 。端口相当于元件的引脚,信号相当于连线,即一种数值容器。取值为0,1,X(不确定值),Z(高阻)。
reg 相当于储存单元,可以储存上一次的值,wire 相当于物理连线,需要持续驱动。
1.6 数组 1.6.1 一维数组和向量 reg [7:0] count 表示一个八维向量。reg count [7:0] 表示一个八位的一维数组。reg [7:0] count [3:0] 表示一个含有四个八维向量的数组。
如果要读取/写入一个储存单元的数据,则跟C语言一样:
1 2 3 reg [4 :0 ] count [8 :0 ];x1=count[1 ]; count[4 ]=x2;
但如果只读取一位数字,则需要经过变量转换,不然会编译错误。
还可以通过文件初始化:
1 2 3 4 5 module memory (); reg [7 :0 ] my_memory [0 :255 ]; initial $readmemh ("memory.list" , my_memory); endmodule
也可以用$readmemb函数,b代表二进制文件,h代表十六进制文件。$readmemh函数原型为$readmemh(“file_name”, mem_array, start_addr, stop_addr);
xxxx.list文件范例:
注意事项:
//可以使用注释。
@表示跳到新地址。
只初始化一部分数组可以用两种方法,一种是用@跳转地址,另一种是设定start_addr和stop_addr。
若q为一维数组,&q表示q[0]*q[1]*q[2]*q[3]*…*q[N]。
1.6.2 二维数组 二维数组声明:
1 reg [4 :0 ] count [8 :0 ] [8 :0 ];
1.7 =和<= “=”为阻塞赋值 ,“<=”为非阻塞赋值 。遵循以下使用原则:
时序逻辑一定用非阻塞赋值<=,一旦看到always 敏感列表中有posedge(or negedge,下同) 就用<=。组合逻辑一定用阻塞赋值=,一旦没有posedge ,或者看到assign ,就用=。虽然语法上支持你在always 里用=,或者在assign里用<=,但这是一种非常愚蠢的做法。
时序逻辑和组合逻辑分成不同模块,即一个always 模块内只能出现<=或=。
1 2 3 4 5 6 7 8 9 module non_block(a,b,c,clk); output reg b,c; input a,clk; always @(posedge clk) begin b<=a; c<=b; end endmodule
1.8 case 类似于C中的switch语句。
1 2 3 4 5 6 7 8 9 always @(cs_state) begin case (cs_state) 2 ’b00: next_state = 2 ’b01; 2 ’b01: next_state = 2 ’b00; 2 ’b10: next_state = 2 ’b10; default : next_state = 2 ’b00; endcase end
只写了if没写else,或者case没有写全所有条件分支,编译器认为条件不满足时,会引进锁存器保存原值。
时序逻辑可利用上述特性来保持状态。
组合逻辑必须列出所有条件分支,否则会产生隐含锁存器。
在设计电路时,一个很重要的原则是尽量少的出现锁存器 。锁存器具备下列三个缺点:
对毛刺敏感(使能信号有效时,输出状态可能随输入多次变化,产生空翻) ,不能异步复位,因此在上电后处于不确定的状态,这对于下一级电路是极其危险的。
锁存器会使静态时序分析变得非常复杂,不具备可重用性。 (首先, 锁存器没有时钟参与信号传递,无法做 STA;其次,综合工具会将 latch 优化掉,造成前后仿真结果不一致)
在 PLD 芯片中,基本的单元是由查找表和触发器组成的,若生成锁存器反而需要更多的资源。根据锁存器的特点可以看出,在电路设计中,要对锁存器特别谨慎,如果设计经过综合后产生出和设计意图不一致的锁存器,则将导致设计错误,包括仿真和综合。因此,在设计中需要避免产生意想不到的锁存器。
1.9 function 1 2 3 4 5 6 7 8 9 function [x:0 ] funcname; input a,b,cin; reg s,cout; begin s=a^b^cin; cout=(A&B) | (A&CIN) | (B&CIN); funcname={cout,s}; end endfunction
函数调用不可做为单独语句出现,必须是一个等式。
2 基本语法(不可综合Part) 2.1 initial 1 2 3 4 5 6 initial begin reg1=0 ; for (addr=0 ;addr<size;addr=addr+1 ) memory[addr]=0 ; end
主要面向功能模拟,通常不具有可综合性,只用于testbench。
模拟0时刻开始执行,只执行一次。
同一模块含有多个initial模块,模拟开始时并行执行,initial模块内的语句顺序执行。
initial不能嵌套使用。
2.2 forever&repeat&while while用法和C语言类似。
forever可以用于仿真时生成时钟:
1 2 3 4 begin clk = 0 ; forever #10 clk = !clk;end
repeat语法:
1 2 3 4 repeat (次数): begin ... end
2.3 task task主要用于调试,不可综合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 module top; reg clk, a, b; DUT u1(out,a ,b, clk); always #5 clk=!clk; task neg_clocks; input [31 :0 ] number_of_edges; repeat (number_of_edges) @(negedge clk); endtask initial begin clk=0 ;a=1 ;b=1 ; neg_clocks(3 ); a=0 ; neg_clocks(5 ); b=0 ; end endmodule
task可以有多个或者没有输入(输出)端口,调用task时只能用输出端口传递“返回值”。task内可以出现时间控制语句,task执行可以占用非零时间单位,其执行可以由disable中断。
2.4 系统任务和系统函数 2.4.1 标准输出任务 $display和$write在仿真执行时输出,区别是$write不会自动换行。$strobe在仿真结束时进行输出,自动换行。$monitor在信号列表中发生变化时进行输出,自动换行。
1 2 3 $display ("The binary value of A is: %b" , A);$write ("The register values are: " , Reg1, Reg2, Reg3, "\n" );$monitor ("x=%b,y=%b" ,x,y);
2.4.2 文件管理任务 1 2 3 4 5 6 7 8 9 10 11 module disp; integer handle1. handle2, handle3; initial begin handle1=$fopen ("file1.dat" ) handle2=$fopen ("file2.dat" ) handle3=$fopen ("file3.dat" ) $display ("%h %h %h" ,handle1, handle2, handle3); end endmodule
$readmemb&$readmemh
1 2 3 4 5 6 'timescale 10 ns/1 ns module test; reg set; reg [15 :0 ] memory[0 :7 ]
2.4.3 仿真控制任务 2.4.4 时间函数 2.4.5 时间显示函数 2.4.6 其他 2.5 testbench 2.5.1 #和‘ 1 2 'timescale 1 ns/1 ps #200
3 基本器件设计 3.1 选择器&分配器 3.1.1 MUX 2选1选择器
1 2 3 4 5 module mux2to1 (w0, w1, s, f); input w0, w1, s; output f; assign f = s ? w0 : w1; endmodule
4选1选择器
1 2 3 4 5 6 module mux4to1 (w0, w1, w2, w3, s, f); input w0, w1, w2, w3; input [1 :0 ] s; output f; assign f = s[1 ] ? (s[0 ] ? w0 : w1) : (s[0 ] ? w2 : w3); endmodule
16选1选择器
1 2 3 4 5 6 7 8 9 10 11 module mux4to1 (w, s, f); input [15 :0 ]w input [3 :0 ]s; output f; wire [3 :0 ] m; mux4to1 MUX0 (w[3 :0 ], s[1 :0 ], m[0 ]); mux4to1 MUX1 (w[7 :4 ], s[1 :0 ], m[1 ]); mux4to1 MUX2 (w[11 :8 ], s[1 :0 ], m[2 ]); mux4to1 MUX3 (w[15 :12 ], s[1 :0 ], m[3 ]); mux4to1 MUX4 (m[3 :0 ], s[3 :2 ], f); endmodule
3.1.2 交叉开关(Mux应用) 1 2 3 4 5 6 7 8 9 10 module cross_switch(in0, in1, sel0, sel1, out0, out1); parameter WIDTH = 16 ; input [WIDTH:0 ] in0, in1; input sel0, sel1; output [WIDTH-1 :0 ] out0, out1; assign out0 = sel0 ? in1 : in0; assign out1 = sel1 ? in1 : in0; endmodule
1 2 3 4 5 6 7 8 9 10 11 12 module cross_switch(in0, in1, sel0, sel1, out0, out1); parameter WIDTH = 16 ; input [WIDTH:0 ] in0, in1, in2, in3; input [1 :0 ]sel0, sel1, sel2, sel3; output [WIDTH-1 :0 ] out0, out1, out2, out3; assign out0 = sel0[1 ] ? (sel0[0 ] ? out1 : out1) : (sel0[0 ] ? out2 : out3); assign out1 = sel1[1 ] ? (sel1[0 ] ? out1 : out1) : (sel1[0 ] ? out2 : out3); assign out1 = sel2[1 ] ? (sel2[0 ] ? out1 : out1) : (sel2[0 ] ? out2 : out3); assign out1 = sel3[1 ] ? (sel3[0 ] ? out1 : out1) : (sel3[0 ] ? out2 : out3); endmodule
3.1.3 DEMUX 1to4数据分配器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 module dmux (y0, y1, y2, y3, din, sel); output y0, y1, y2, y3; input [1 :0 ] sel; input din; reg y0, y1, y2, y3; always @ (din, sel) begin y0 = 0 ; y1 = 0 ; y2 = 0 ; y3 = 0 ; case (sel[1 :0 ]) 2 ’b00: y0 = din; 2 ’b01: y1 = din; 2 ’b10: y2 = din; 2 ’b11: y3 = din; default :; endcase end endmodule
3.2 编码器&解码器 3.2.1 普通编码器 4to2编码器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 module encoder4_2(q, d); input [3 :0 ] d; output [1 :0 ] q; reg [1 :0 ] q; always @(d) begin case (d) 4'b0001 : q<=2'b11 ; 4'b0010 : q<=2'b10 ; 4'b0100 : q<=2'b01 ; 4'b1000 : q<=2'b00 ; default : q<=2'bxx ; endcase end endmodule
8to3编码器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 module encoder8_3(i, y); input [7 :0 ] i; output [2 :0 ] y; reg [2 :0 ] y; always @ (i) begin case (i[7 :0 ]) 8 ’b00000001: y[2 :0 ] = 3'b000 ; 8 ’b00000010: y[2 :0 ] = 3'b001 ; 8 ’b00000100: y[2 :0 ] = 3'b010 ; 8 ’b00001000: y[2 :0 ] = 3'b011 ; 8 ’b00010000: y[2 :0 ] = 3'b100 ; 8 ’b00100000: y[2 :0 ] = 3'b101 ; 8 ’b01000000: y[2 :0 ] = 3'b110 ; 8 ’b10000000: y[2 :0 ] = 3'b111 ; default : y[2 :0 ] = 3'bxxx ; endcase end endmodule
3.2.2 优先编码器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 module priority_encoder(w, y, z); input [3 :0 ]w; output reg [1 :0 ] Y; output reg z; always @ (w) begin z = 1 ; casex (w) 4'b1xxx : y = 3 ; 4'b01xx : y = 2 ; 4'b001x : y = 1 ; 4'b0001 : y = 0 ; default : begin z = 0 ; y = 2'bxx ; end endcase end endmodule
PS:
也可以通过if-else嵌套实现,但是那样代码比较繁琐,二者生成的电路几乎是一样的(vivado综合时内部有优化)
关于casex和casez使用哪个的问题,网上的普遍论调是casez更加常用,但具体哪个效果更好没有定论,有待深究,可以确定的是二者真值表是不一样的。
3.3 ALU74381 ALU(算术逻辑单元)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 module ALU(s, a, b, f); input [2 :0 ] s; input [3 :0 ] a, b; output reg [3 :0 ] f; always @ (s, a, b) begin case (s) 0 : f = 4'b0000 ; 1 : f = b - a; 2 : f = a - b; 3 : f = a + b; 4 : f = a ^ b; 5 : f = a | b; 6 : f = a & b; 7 : f = 4'b1111 ; default : f = 4 '0000 ; endcase end endmodule
3.4 数值比较器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 module comparator (y1, y2, y3, a, b); output y1, y2, y3; input [3 :0 ] a, b; reg y1, y2, y3; always @ (a, b) begin if (a > b) begin y1 = 1 ; y2 = 0 ; y3 = 0 ; end else if (a == b) begin y1 = 0 ; y2 = 1 ; y3 = 0 ; end else begin y1 = 0 ; y2 = 0 ; y3 = 1 ; end end endmodule
带reset的版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 module comp(CLK,RST,A,B,AGTB,ALTB,AEQB); input CLK,RST; input [1 :0 ] A,B; output AGTB,ALTB,AEQB; reg AGTB,ALTB,AEQB; always @(posedge CLK or negedge RST) begin if (!RST) begin AGTB<=0 ; AEQB<=0 ; ALTB<=0 ; end else begin if (A>B) begin AGTB<=1 ; AEQB<=0 ; ALTB<=0 ; end else if (A==B) begin AGTB<=0 ; AEQB<=1 ; ALTB<=0 ; end else begin AGTB<=0 ; AEQB<=0 ; ALTB<=1 ; end end end endmodule
3.5 加法器 3.5.1 半加器 若不考虑有来自低位的进位,将两个1 位二进制数相加,称为半加。实现半加运算的电路叫做半加器。
1 2 3 4 5 6 7 8 module half_add1(input a,b,output cout,sum); xor (sum,a,b); and (cout,a,b); endmodule
1 2 3 4 5 6 7 module half_add (sum, cout, a, b); output sum, cout; input a, b; assign cout = a & b; assign sum = a ^ b; endmodule
1 2 3 4 5 6 7 8 9 10 11 12 13 module half_add (sum, cout, a, b); output sum, cout; input a, b; reg sum,cout; always @ (a, b) begin sum = a ^ b; cout = a & b; end endmodule
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 module half_add (sum, cout, a, b); output sum, cout; input a, b; reg [2 :0 ] sum,cout; always @ (a, b) begin case [{a, b}] 2 ’b00: begin cout = 0 , sum = 0 ;end 2 ’b01: begin cout = 0 , sum = 1 ;end 2 ’b10: begin cout = 0 , sum = 1 ;end 2 ’b11: begin cout = 1 , sum = 0 ;end endcase end endmodule
3.5.2 全加器 全加器其实就是考虑到进位的加法器。
1 2 3 4 5 6 7 module ful_adder(cout,sum,a,b,cin); input a,b; input cin; output reg sum, cout; always @(a or b or cin) {cout, sum} = a+b+cin; endmodule
1 2 3 4 5 6 7 8 module add1 (cin, sum, cout, a, b); output sum, cout; input a, b; assign cout = (a & b)|(a & cin)|(cin & b); assign sum = a ^ b ^ cin; endmodule
1 2 3 4 5 6 7 8 9 10 11 12 13 module add1 (cin, sum, cout, a, b); output sum, cout; input a, b, cin; reg sum,cout; always @ (a, b, cin) begin sum = (a ^ b) ^ c; cout = (a & b)|(a & cin)|(cin & b); end endmodule
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 module add1 (cin, sum, cout, a, b); output sum, cout; input a, b; reg cout, m1, m2, m3; wire s1; xor (s1, a, b); always @ (a, b, cin) begin m1 = a & b; m2 = a & cin; m3 = cin & b; cout = (m1 | m2) | m3; end assign sum = s1 ^ cin; endmodule
3.5.3 多位加法器 1 2 3 4 5 6 7 8 9 module full_add (cin, sum, cout, a, b); parameter add_size = 4 ; output cout; output [add_size -1 :0 ] sum; input [add_size -1 :0 ] a, b; input cin; assign {cout, sum} = a + b + cin; endmodule
1 2 3 4 5 6 7 8 9 10 11 12 module add4 (cin, sum, cout, a, b); output [3 :0 ] sum; output cout; input [3 :0 ] a, b; input cin; wire c1, c2, c3; add1 f0(cin, sum[0 ], c1, a[0 ], b[0 ]); add1 f1(c1, sum[0 ], c2, a[1 ], b[1 ]); add1 f2(c2, sum[0 ], c3, a[2 ], b[2 ]); add1 f3(c3, sum[0 ], cout, a[3 ], b[3 ]); endmodule
1 2 3 4 5 6 7 8 9 10 11 12 13 module add4 (cin, sum, cout, a, b); output [3 :0 ] sum; output cout; input [3 :0 ] a, b; input cin; reg cout; reg [3 :0 ] sum; always @ (*) begin {cout, sum} = a + b + cin; end endmodule
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 module fulladd4(sum, c_out, a, b, cin); output [3 :0 ] sum; output c_out; input [3 :0 ] a, b; input cin; wire p0, g0, p1, g1, p2, g2, p3, g3; wire c4, c3, c2, c1; assign p0 = a[0 ] ^ b[0 ], p1 = a[1 ] ^ b[1 ], p2 = a[2 ] ^ b[2 ], p3 = a[3 ] ^ b[3 ]; assign g0 = a[0 ] & b[0 ], g1 = a[0 ] & b[1 ], g2 = a[0 ] & b[2 ], g3 = a[0 ] & b[3 ]; assign c1 = g0 | (p0 & cin), c2 = g1 | (p1 & c1), c3 = g2 | (p2 & c2), c4 = g3 | (p3 & c2); assign sum[0 ] = p0 ^ cin, sum[1 ] = p1 ^ c1, sum[2 ] = p2 ^ c2, sum[3 ] = p3 ^ c3; assign cout = c4; endmodule
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 module adder8pip(cout,sum,cin,ina,inb,clk); input cin,clk; input [7 :0 ] ina,inb; output reg cout; output reg [7 :0 ] sum; reg firstco,secondco,thirdco; reg [1 :0 ] firstsum,thirdina,thirdinb; reg [3 :0 ] secondsum,secondina,secondinb; reg [5 :0 ] thirdsum,firstina,firstinb; always @ (posedge clk) begin {firstco,firstsum} <= ina[1 :0 ]+inb[1 :0 ]+cin; firstina <= ina[7 :2 ];firstinb <= inb[7 :2 ]; end always @ (posedge clk) begin secondsum[1 :0 ] <= firstsum; {secondco,secondsum[3 :2 ]} <= firstina[1 :0 ]+firstinb[1 :0 ]+firstco; secondina <= firstina[5 :2 ];secondinb <= firstinb[5 :2 ]; end always @ (posedge clk) begin thirdsum[3 :0 ] <= secondsum; {thirdco,thirdsum[5 :4 ]} <= secondina[1 :0 ]+secondinb[1 :0 ]+secondco; thirdina <= secondina[3 :2 ];thirdinb <= secondinb[3 :2 ]; end always @ (posedge clk) begin sum[5 :0 ] <= thirdsum; {cout,sum[7 :6 ]} <= thirdina[1 :0 ]+thirdinb[1 :0 ]+thirdco; end endmodule
3.6 减法器 3.6.1 半减器 1 2 3 4 5 6 7 8 9 10 11 12 13 module half_sub (dout, cout, a, b); output dout, cout; input a, b; reg dout, cout; always @ (a, b) begin dout = a ^ b; cout = (~a) & b; end endmodule
3.6.2 全减器 1 2 3 4 5 6 7 8 9 10 module sub1 (cin, dout, cout, a, b); output dout, cout; input a, b; reg dout, cout; always @ (a, b) begin {cout, dout} = a – b – cin; end endmodule
3.6.3 多位减法器 1 2 3 4 5 6 7 8 9 10 11 12 13 module sub4 (cin, dout, cout, a, b); output [3 :0 ] dout; output cout; input [3 :0 ] a, b; input cin; reg [3 ;0 ] dout, reg cout; always @ (a, b) begin {cout, dout} = a – b – cin; end endmodule
3.7 锁存器 锁存器(latch):对脉冲电平敏感,在时钟脉冲的电平作用下改变状态
锁存器是电平触发的存储单元,数据存储的动作取决于输入时钟(或者使能)信号的电平值,当锁存器处于使能状态时,输出才会随着数据输入发生变化。(简单地说,它有两个输入,分别是一个有效信号EN,一个输入数据信号DATA_IN,它有一个输出Q,它的功能就是在EN有效的时候把DATA_IN的值传给Q,也就是锁存的过程)。
不用锁存器的原因:
锁存器容易产生毛刺。
锁存器在ASIC设计中应该说比FF要简单,消耗的资源少,但是在FPGA的资源中,大部分器件没有锁存器这个东西,所以需要用一个逻辑门和FF来组成锁存器,这样就浪费了资源。
3.8 触发器 触发器(Flip-Flop,简写为 FF):对脉冲边沿敏感,其状态只在时钟脉冲的上升沿或下降沿的瞬间改变
触发器也叫双稳态门,又称双稳态触发器,是一种可以在两种状态下运行的数字逻辑电路。触发器一直保持它们的状态,直到它们收到输入脉冲,又称为触发。当收到输入脉冲时,触发器输出就会根据规则改变状态,然后保持这种状态直到收到另一个触发。
3.8.1 D触发器 基本D触发器
1 2 3 4 5 6 7 8 module async_rddf(clk, d,q,qb); input clk, d; output reg q,qb; always @(posedge clk) begin q<=d; qb<=~d; end endmodule
同步复位的 D 触发器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 module sync_rddf(clk,reset,d,q,qb); input clk,reset,d; output reg q,qb; always @(posedge clk) begin if (!reset) begin q<=0 ; qb<=1 ; end else begin q<=d; qb<=~d; end end endmodule
异步复位的 D 触发器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 module async_rddf(clk,reset,d,q,qb); input clk,reset,d; output q,qb; reg q,qb; always @(posedge clk or negedge reset) begin if (!reset) begin q<=0 ; qb<=1 ; end else begin q<=d; qb<=~d; end end endmodule
同步置位/复位的 D 触发器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 module sync_rsddf(clk,reset,set,d,q,qb); input clk,reset,set; input d; output q,qb; reg q,qb; always @(posedge clk) begin if (!set && reset) begin q<=1 ; qb<=0 ; end else if (set && !reset) begin q<=0 ; qb<=1 ; end else begin q<=d; qb<=~d; end end endmodule
异步置位/复位的 D 触发器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 module async_rsddf(clk,reset,set,d,q,qb); input clk,reset,set; input d; output q,qb; reg q,qb; always @(posedge clk or negedge set or negedge reset) begin if (!set && reset) begin q<=1 ; qb<=0 ; end else if (set && !reset) begin q<=0 ; qb<=1 ; end else begin q<=d; qb<=~d; end end endmodule
3.8.2 JK触发器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 module syn_jk_ff(clk, rst_n, set_n, j, k, q ); input clk; input rst_n; input set_n; input j; input k; output reg q; always @(posedge clk) begin if (!rst_n) q <= 1'b0 ; else if (!set_n) q <= 1'b1 ; else begin case ({j,k}) 2'b00 : q <= q; 2'b01 : q <= 0 ; 2'b10 : q <= 1 ; default : q <= ~q; endcase end end endmodule
异步JK触发器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 module asyn_jk_ff(clk, rst_n, set_n, j, k, q ); input clk; input rst_n; input set_n; input j; input k; output reg q; always @(posedge clk or negedge rst_n or negedge set_n) begin if (!rst_n) q <= 1'b0 ; else if (!set_n) q <= 1'b1 ; else begin case ({j,k}) 2'b00 : q <= q; 2'b01 : q <= 0 ; 2'b10 : q <= 1 ; default : q <= ~q; endcase end end endmodule
3.8.3 T触发器 T触发器,带有同步复位信号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 module syn_t_trigger(clk,t,rst,q); input clk, t, rst; output reg q, qn; always @(posedge clk) begin if (!rst) q <= 0 ; qn <= 0 ; else if (t == 1 ) q <= ~q; qn <= ~qn; else q <= q; qn <= qn; end endmodule
T触发器,带有异步复位信号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 module asyn_t_trigger(clk,t,rst,q); input clk, t, rst; output reg q, qn; always @(posedge clk or negedge rst) begin if (!rst) q <= 0 ; qn <= 0 ; else if (t == 1 ) q <= ~q; qn <= ~qn; else q <= q; qn <= qn; end endmodule
3.8.4 SR触发器 1 2 3 4 5 6 7 8 9 10 11 12 module SY_RS_FF ( R, S, CLK, Q, QB ); input R, S, CLK; output Q, QB; reg Q; assign QB = ~Q; always @( posedge CLK ) case ({ R, S }) 1 : Q <= 1 ; 2 : Q <= 0 ; 3 : Q <= 1'bx ; endcase endmodule
带有R端D触发器
1 2 3 4 5 6 7 8 9 10 module R_SY_D_FF ( RB, D, CLK, Q, QB ); input RB, D, CLK; output Q, QB ; reg Q; assign QB = ~Q; always @( posedge CLK or negedge RB ) Q <= ( !RB )? 0 : D; endmodule
3.9 寄存器 通常由触发器构成寄存器,把多个D触发器的时钟端连接起来就可以构成一个存储多位二进制代码的寄存器。
3.9.1 普通寄存器 1 2 3 4 module Register (input [3 :0 ] D, input Clk, output reg [3 :0 ] Q); always @(posedge Clk) Q <= D; endmodule
同步清零
1 2 3 4 5 6 7 8 9 10 11 module reg8 (reset, CLK, D, Q); input reset; input CLK; input [7 :0 ] D; output reg [7 :0 ] Q; always @(posedge CLK) if (reset) Q <= 0 ; else Q <= D; endmodule
异步清零
1 2 3 4 5 6 7 8 9 10 11 12 13 module regN (reset, CLK, D, Q); input reset; input CLK; parameter N = 8 ; input [N-1 :0 ] D; output [N-1 :0 ] Q; reg [N-1 :0 ] Q; always @(posedge CLK or posedge reset) if (reset) Q <= 0 ; else if (CLK == 1 ) Q <= D; endmodule
3.9.2 移位寄存器 右移
1 2 3 4 5 6 7 8 9 10 11 module register_right(clk, din, dout); input clk; input din; reg [15 :0 ] d; output dout; always @(posedge clk) begin d <= {din, d[15 :1 ]}; dout <= d[0 ]; end endmodule
左移
1 2 3 4 5 6 7 8 9 module register_left(clk, din, dout); input clk; input din; output reg [15 :0 ] dout; always @(posedge clk) dout <= {din[14 :0 ], din}; endmodule
串行输入并行输出寄存器
1 2 3 4 5 6 7 8 9 10 11 12 13 module left_shifter_reg(clk, din, dout); input clk; input din; output [7 :0 ] dout; wire [7 :0 ] dout; reg [7 :0 ] qtemp; always @(posedge clk) qtemp <= {qtemp[6 :0 ], din}; assign dout = qtemp; endmodule
并行输入串行输出移位寄存器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 module right_shifter_reg(clk, en, din, dout); input [7 :0 ] din; input en,clk; output reg dout; reg [7 :0 ] qtemp; always @(posedge clk) if (en == 1 ) qtemp <= din; else begin dout <= qtemp[0 ]; qtemp <= {qtemp[0 ], qtemp[7 :1 ]}; end endmodule
并行输入并行输出移位寄存器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 module right_shifter_reg(clk, en, din, dout); input [7 :0 ] din; input en,clk; output reg [7 :0 ] dout; reg [7 :0 ] qtemp; always @(posedge clk) if (en == 1 ) begin dout <= 0 b'zzzzzzzz; qtemp <= din; end else begin dout <= qtemp; qtemp <= 0 b'xxxxxxxx; end endmodule
4 中级器件设计 4.1 计数器 计数器的逻辑功能是用与记忆时钟脉冲的具体个数,通常计数器最多能记忆时钟的最大数目m 称为计数器的模 。基本原理是将几个触发器按照一定的顺序连接起来,然后根据触发器的组合状态,按照一定的技术规律随着时钟脉冲变化来记忆时钟脉冲的个数。
按照计数方向分为加法,减法和可逆计数器 。
按照其中触发器是否与时钟同步又分为同步计数器 和异步计数器 。
4.1.1 同步计数器 同步4位计数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 module cnt16 (cout, q, clk, clr, load, en, d); output [3 :0 ] q; output cout; input clk, clr, load, en; input [3 :0 ] d; reg [3 :0 ] q; reg cout; always @ (posedge clk) begin if (clr) begin q <= 0 ; end else if (load) begin q <= d; end else if (en) begin q <= q + 1 ; if (q == 4 ’b1111) begin cout <= 1 ; end else begin cout <= 0 ; end end else begin q <= q; end end endmodule
同步24进制计数器,就套了一个十进制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 module cnt24 (ten, one, cout, clk, clr); output [3 :0 ] ten, one; output cout; input clk, clr; reg [3 ;0 ] ten, one; reg cout; always @ (posedge clk) begin if (clr) begin ten <= 0 ; one <= 0 ; end else begin if ({ten, one}) == 8 ’b0010_0011) begin ten <= 0 ; one <= 0 ; cout <= 1 ; end else if (one==4 ’b1001) begin one <= 0 ; ten<=ten+1 ; cout <= 0 ; end else begin one <= one + 1 ; cout <=0 ; end end end endmodule
模为60的BCD码加法计数器。可以去复习一下BCD码,BCD码就是用四位二进制数表示一个十进制数,但是每一位代表的权值不同,最常见的是8421BCD码,也就是普通的二进制数,下面这个也就是套了一个BCD码形式的计数器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 module count60(qout, cout, data, load, cin, reset, clk); output [7 :0 ] qout; output cout; input [7 :0 ] data; input load, cin, clk, reset; reg [7 :0 ] qout; always @ (posedge clk) begin if (reset) qout <= 0 ; else if (load) qout <= data; else if (cin) begin if (qout[3 :0 ] == 9 ) begin qout[3 :0 ] <= 0 ; if (qout[7 :4 ] == 5 ) qout[7 :4 ] <= 0 ; else qout[7 :4 ] <= qout[7 :4 ]+1 ; end else qout[3 :0 ] <= qout[3 :0 ]+1 ; end end assign cout = ((qout == 8 ’h59)&cin)?1 :0 ; endmodule
4.1.2 异步计数器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 module asyn_cnt4(q, clk, rst); output [3 :0 ] q; input clk, rst; reg [3 :0 ] q; reg [3 :0 ] qn; always @ (posedge clk) begin if (!rst) begin q[0 ] = 0 ; qn[0 ] = 1 ; end else begin q[0 ] = ~q[0 ]; qn[0 ] =~q[0 ]; end end always @ (posedge qn[0 ]) begin if (!rst) begin q[1 ] = 0 ; qn[1 ] = 1 ; end else begin q[1 ] = ~q[1 ]; qn[1 ] =~q[1 ]; end end always @ (posedge qn[1 ]) begin if (!rst) begin q[2 ] = 0 ; qn[2 ] = 1 ; end else begin q[2 ] = ~q[2 ]; qn[2 ] =~q[2 ]; end end always @ (posedge qn[2 ]) begin if (!rst) begin q[3 ] = 0 ; qn[3 ] = 1 ; end else begin q[3 ] = ~q[3 ]; qn[3 ] =~q[3 ]; end end endmodule
4.1.3 可变模计数器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 module cnt(q, clk, clr, load, m); output [6 :0 ] q; input clk, clr, load; input [6 :0 ] m; reg [6 :0 ] q; reg [6 :0 ] md; always @ (posedge clk) begin md <= m-1 ; if (!clr) q <= 0 ; else begin if (load) q <= md; else begin if (q == md) q <= 0 ; else q <= q + 1 ; end end end endmodule
4.2 分频器 4.2.1 偶分频器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 module clk_div #( parameter WIDTH = 3 , parameter N = 6 ) (clk,reset, clk_out); input clk; input reset; output clk_out; reg [WIDTH-1 :0 ] r_reg; always @(posedge clk or posedge reset)begin if (reset) begin r_reg <= 0 ; clk_out <= 1'b0 ; end else if (r_nxt == N) begin r_reg <= 0 ; clk_out <= ~clk_out; end else r_reg <= r_reg + 1 ; end endmodule
4.2.2 奇分频器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 module clk_divn #(parameter WIDTH = 3 ,parameter N = 5 )(clk,reset, clk_out); input clk; input reset; output clk_out; reg [WIDTH-1 :0 ] pos_count, neg_count; always @(posedge clk) if (reset) pos_count <=0 ; else if (pos_count ==N-1 ) pos_count <= 0 ; else pos_count<= pos_count +1 ; always @(negedge clk) if (reset) neg_count <=0 ; else if (neg_count ==N-1 ) neg_count <= 0 ; else neg_count<= neg_count +1 ; assign clk_out = ((pos_count > (N>>1 )) | (neg_count > (N>>1 ))); endmodule
4.3 RAM 4.3.1 单端口RAM 带同步和异步两种功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 module SinglePortRAM( clk , address , data_in , cs , we , oe , data ); input clk, cs, we, oe, address; input [7 :0 ] data_in; output [7 :0 ] data; reg [7 :0 ] data_temp_0; reg [7 :0 ] data_temp_1; reg [7 :0 ] mem [0 :1 ]; assign data = (cs) ? ((oe && !we) ? data_temp_1 : 8'bzzzzzzzz ) : ((oe && !we) ? data_temp_0 : 8'bzzzzzzzz ); always @ (posedge clk) begin if (we) mem[address] <= data_in; else mem[address] <= mem[address]; end always @ (posedge clk) begin if (!cs && !we && oe) data_temp_0 <= mem[address]; else data_temp_0 <= data_temp_0; end always @ (address or cs or we or oe) begin if (cs && !we && oe) data_temp_1 = mem[address]; else data_temp_1 = data_temp_1; end endmodule
4.3.2 双端口RAM 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 module double_port_RAM( input clk, input [7 :0 ]address_0, inout [7 :0 ]data_0, input cs_0, input we_0, input oe_0, input [7 :0 ]address_1, inout [7 :0 ]data_1, input cs_1, input we_1, input oe_1 ); reg [7 :0 ] data_temp_0; reg [7 :0 ] data_temp_1; reg [7 :0 ] adata_temp_0; reg [7 :0 ] adata_temp_1; reg [7 :0 ] mem[0 : 1 << 8 - 1 ]; assign data_0 = (cs_0) ? ((oe_0 && !we_0) ? adata_temp_0 : 8'bzzzzzzzz ) : ((oe_0 && !we_0) ? data_temp_0 : 8'bzzzzzzzz ); assign data_1 = (cs_1) ? ((oe_1 && !we_1) ? adata_temp_1 : 8'bzzzzzzzz ) : ((oe_1 && !we_1) ? data_temp_1 : 8'bzzzzzzzz ); always @(posedge clk) begin if (we_0) mem[address_0] <= data_0; else if (we_1) mem[address_1] <= data_1; end always @(posedge clk)begin if (!cs_0 && !we_0 && oe_0) data_temp_0 <= mem[address_0]; else data_temp_0 <= 0 ; end always @(posedge clk)begin if (!cs_1 && !we_1 && oe_1) data_temp_1 <= mem[address_1]; else data_temp_1 <= 0 ; end always @(address_0 or cs_0 or we_0 or oe_0)begin if (cs_0 && !we_0 && oe_0) adata_temp_0 = mem[address_0]; else adata_temp_0 = 0 ; end always @(address_1 or cs_1 or we_1 or oe_1)begin if (cs_1 && !we_1 && oe_1) adata_temp_1 = mem[address_1]; else adata_temp_1 = 0 ; end endmodule
4.3.3 FIFO FIFO(First In First Out)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 module double_port_RAM( input clk, input [7 :0 ]address_0, inout [7 :0 ]data_0, input cs_0, input we_0, input oe_0, input [7 :0 ]address_1, inout [7 :0 ]data_1, input cs_1, input we_1, input oe_1 ); reg [7 :0 ] data_temp_0; reg [7 :0 ] data_temp_1; reg [7 :0 ] adata_temp_0; reg [7 :0 ] adata_temp_1; reg [7 :0 ] mem[0 : 1 << 8 - 1 ]; assign data_0 = (cs_0) ? ((oe_0 && !we_0) ? adata_temp_0 : 8'bzzzzzzzz ) : ((oe_0 && !we_0) ? data_temp_0 : 8'bzzzzzzzz ); assign data_1 = (cs_1) ? ((oe_1 && !we_1) ? adata_temp_1 : 8'bzzzzzzzz ) : ((oe_1 && !we_1) ? data_temp_1 : 8'bzzzzzzzz ); always @(posedge clk) begin if (we_0) mem[address_0] <= data_0; else if (we_1) mem[address_1] <= data_1; end always @(posedge clk)begin if (!cs_0 && !we_0 && oe_0) data_temp_0 <= mem[address_0]; else data_temp_0 <= 0 ; end always @(posedge clk)begin if (!cs_1 && !we_1 && oe_1) data_temp_1 <= mem[address_1]; else data_temp_1 <= 0 ; end always @(address_0 or cs_0 or we_0 or oe_0)begin if (cs_0 && !we_0 && oe_0) adata_temp_0 = mem[address_0]; else adata_temp_0 = 0 ; end always @(address_1 or cs_1 or we_1 or oe_1)begin if (cs_1 && !we_1 && oe_1) adata_temp_1 = mem[address_1]; else adata_temp_1 = 0 ; end endmodule
4.4 有限状态机 4.4.1 Moore型&Mealy型
Moore型状态机输出仅和状态机的当前状态有关,与外部输入无关。Mealy型状态机的输出与当前状态和输入都有关。
体现在状态转移图上就是,Moore机的输出在状态圆圈内,Mealy机的输出在转移曲线上。
Moore完全描述状态转移图会比Mealy机多一个状态。
体现在verilog代码中就是,Moore机的最后输出逻辑只判断state,Mealy机的输出逻辑中判断。
4.4.2 有限状态机的设计
分析设计要求,列出全部可能状态
画出状态转移图
用HDL语言描述状态机
4.4.3 有限状态机的verilog设计流程 以10010序列检测机为例。
定义输入输出端口,并用parameter 类型定义各个状态。
1 2 3 4 5 6 7 8 9 10 11 12 module moorefsm(clk,rst,a,z); input clk,rst; input a; output z; reg z; reg [3 :0 ] currentstate, nextstate; parameter S0 = 4'b0000 ; parameter S1 = 4'b0001 ; parameter S2 = 4'b0010 ; parameter S3 = 4'b0011 ; parameter S4 = 4'b0100 ; parameter S5 = 4'b0101 ;
用两个reg 变量定义当前状态state和下一个状态state_next,然后用单独的一个always语句块完成状态转移,使用时序逻辑驱动。
1 2 3 4 5 6 always @(posedge clk or negedge rst) begin if (!rst) currentstate <= S0; else currentstate <= nextstate; end
用单独一个always语句块完成下一个状态state_next的确定,使用组合逻辑驱动。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 always @(currentstate or a or rst) begin if (!rst) nextstate = S0; else case (currentstate) S0: nextstate = (a==1 )?S1:S0; S1: nextstate = (a==0 )?S2:S1; S2: nextstate = (a==0 )?S3:S1; S3: nextstate = (a==1 )?S4:S0; S4: nextstate = (a==0 )?S5:S1; S5: nextstate = (a==0 )?S3:S1; default : nextstate = S0; endcase end
Moore型状态机用当前状态确定输出,Mealy型状态机用输入和当前状态确定输出,使用组合逻辑驱动输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 always @(rst or currentstate) begin if (!rst) z = 0 ; else case (currentstate) S0: z = 0 ; S1: z = 0 ; S2: z = 0 ; S3: z = 0 ; S4: z = 0 ; S5: z = 1 ; default : z = 0 ; endcase end endmodule
若用Mealy型实现,则代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 module mealyfsm(clk, rst, a, z); input clk; input rst; input a; output reg z; reg [3 :0 ] state, state_next; parameter S0 = 4'b0000 ; parameter S1 = 4'b0001 ; parameter S2 = 4'b0010 ; parameter S3 = 4'b0011 ; parameter S4 = 4'b0100 ; always @(posedge clk or negedge rst) begin if (!rst) state <= S0; else state <= state_next; end always @(state or a or rst) begin if (!rst) state_next = S0; else case (state) S0: state_next = (a == 1 )? S1 : S0; S1: state_next = (a == 0 )? S2 : S1; S2: state_next = (a == 0 )? S3 : S1; S3: state_next = (a == 1 )? S4 : S0; S4: state_next = (a == 0 )? S2 : S0; default :state_next = S0; endcase end always @(a or rst or state or state_next) begin if (!rst) z = 0 ; else case (state) S0: z = 0 ; S1: z = 0 ; S2: z = 0 ; S3: z = 0 ; S4: z = a == 0 ? 1 : 0 ; default :z = 0 ; endcase end endmodule
5 分析与设计 5.1 组合逻辑分析 5.1.1 分析步骤
分别用符号标注各级门的输出
从输入端到输出端逐级写出输入变量到输出变量的逻辑表达式,最后得到输入变量表示的输出函数表达式。需要时用卡诺图或公式化简法化简。
列真值表
根据真值表和函数表达式确定电路的逻辑功能。难以用语言描述时列真值表即可。
5.1.2 设计步骤
进行逻辑抽象:
1. 分析因果关系,确定输入输出变量。
2. 定义逻辑状态含义
3. 根据因果关系列出真值表
确定输出函数,并利用卡诺图或公式法化简
选定器件类型:SSI、MSI、PLD等
根据器件类型将逻辑函数化简或变换成适当形式
画出逻辑电路图
5.1.3 竞争与冒险 竞争 :当一个逻辑门的两个输入端信号同时向相反方向变化,而变化的时间有差异。冒险 :两个输入端信号取值的变化方向是相反时,如门电路输出端的逻辑表达式简化成两个互补信号相乘或相加,由竞争而可能产生输出干扰脉冲的现象。 由于竞争而在电路输出端可能产生尖峰脉冲的现象叫做竞争-冒险 。消除办法 : 一、接入滤波电容。优点 :简单。缺点 :增加了波形的上升和下降时间,波形变坏。 二、引入选通脉冲。优点 :简单。缺点 :正常的输出信号也会变成脉冲信号,宽度与选通脉冲相同,且此选通脉冲必须与输入信号同步。 三、修改设计(废话)。比如把$Y = AB + \overline{A}C$化成$Y = AB + \overline{A}C + BC$。